How an S3 File Upload Trigger Can Lead to AWS Lambda Command Injection
In serverless applications, developers often wire up S3 file uploads to AWS Lambda functions for processing. It’s fast, scalable, and cost-effective. But this convenience can come at a steep cost if input validation and permission boundaries are overlooked.
In this article, I’ll walk through a real-world style scenario showing how a misconfigured Lambda function—triggered by S3 object uploads—can be exploited using nothing more than a malicious filename. This leads to full OS command injection, AWS credential exfiltration, and abuse of services like SES, all from a simple upload interface.
📌 This is a practical walkthrough of an actual vulnerability found in a demo serverless application. It highlights the risks of insecure input handling, over-permissive IAM roles, and outdated Lambda runtimes.
⚙️ Architecture: How S3 and Lambda Work Together
Let’s say you’re building a simple user feedback system. Users can upload files (e.g., screenshots or logs), which are stored in an S3 bucket. An AWS Lambda function is triggered automatically to process each upload.
The architecture looks like this:
[ User ] → [ Upload File ] → [ S3 Bucket ] → [ Lambda Function (triggered on PUT) ]
A pre-signed URL is generated to allow users to upload securely. Once the file lands in the S3 bucket, the Lambda function is invoked via an event trigger.
But this convenience hides a potential danger: if the filename of the uploaded file is not sanitized, and the Lambda handler uses it in a shell command, you’ve just created a vector for command injection.
🔓 The Vulnerability: Filename-Based OS Command Injection
Here’s a simplified version of the insecure validation function:
def is_safe(s):
# if s.find(";") > -1 or s.find("'") > -1 or s.find("|") > -1:
# return False
return True
This function is present in the codebase, but the actual validation is commented out. In effect, every filename is treated as safe.
Imagine your Lambda function contains a line like this:
os.system("touch /tmp/" + filename)
If a user uploads a file with a name like:
screenshot.png;uname -a
The command passed to the shell becomes:
touch /tmp/screenshot.png;uname -a
This executes both touch and uname, leaking system information into CloudWatch logs. From there, an attacker can test for other commands available in the Lambda runtime.
🧪 Exploiting the Runtime
Let’s go deeper. The attacker uploads a file named:
"cat.png;compgen -c | tr '\n' ' '"
This executes the compgen command, which lists all available shell commands. Since Lambda runs on Amazon Linux, the attacker now knows exactly what tooling is available.
The output ends up in the function’s logs, viewable in CloudWatch.
From here, the attacker can:
Install packages via pip install
Execute arbitrary scripts
Pivot to more sensitive operations
…
🔐 Leaking Environment Variables: The Real Jackpot
Environment variables in AWS Lambda often contain IAM credentials, like:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN
These are automatically injected to let Lambda interact with AWS services. If a malicious payload uses os.environ and exfiltrates those values via HTTP, the attacker now has short-term credentials that may have broad permissions.
✉️ Misusing SES: A Realistic Post-Exploitation Scenario
Let’s assume the IAM role attached to the Lambda function allows access to Amazon SES. Using the stolen credentials, an attacker can:
Enumerate verified identities:
aws ses list-identities
Send spoofed emails:
aws ses send-email --from attacker@yourdomain.com --to victim@example.com ...
This could be used to deliver phishing attacks or spam from a legitimate domain, causing reputational damage or worse.
🔍 Detection with Amazon Inspector
Security tools like Amazon Inspector can help detect issues like this. It scans your Lambda functions for known patterns and vulnerabilities.
In this case, Inspector was able to flag:
OS command injection via os.system()
Lack of input sanitization
Use of an outdated runtime (e.g., Python 3.8)
It recommended replacing os.system() with subprocess.run() and using proper argument escaping.
🧯 How to Mitigate This Risk
To prevent this class of vulnerability, apply the following principles:
✅ 1. Sanitize All Inputs
Do not assume filenames are safe. Use allow-lists for characters or patterns. Even better: generate your own safe names and ignore user input.
🔐 2. Use Least-Privilege IAM Roles
Don’t attach broad roles like AmazonS3FullAccess or AmazonSESFullAccess. Create fine-grained policies that allow access only to what’s needed.
🚀 3. Update Your Lambda Runtimes
Using deprecated runtimes increases your attack surface. They no longer receive security updates. Stick to supported versions and monitor AWS’s runtime support policy.
🛡️ 4. Use Inspector or Static Analysis
Run code through Amazon Inspector or tools like Bandit (for Python) or Semgrep to catch risky code before it goes live.
🔍 TL;DR Summary
Issue: Unsanitized filenames in an S3-triggered Lambda allowed OS command injection.
Exploit: File named example.png;uname -a caused shell command execution.
Impact: Credentials leaked, allowing abuse of other AWS services like SES.
Fix:
Validate inputs (especially filenames)
Apply least-privilege IAM roles
Keep Lambda runtimes current
Use Inspector/static analysis for security scanning
Serverless isn’t inherently safe. Every input—yes, even filenames—must be treated as untrusted and validated accordingly.