Command Injection on Aws
How Command Injection Manifests in AWS
Command injection occurs when attacker‑controlled data is interpreted as part of a shell command. In AWS environments this most often happens inside Lambda functions, EC2 user‑data scripts, AWS Batch job definitions, or Systems Manager Run Command documents where developer code concatenates raw request parameters with a shell invocation.
Example – Node.js Lambda triggered by API Gateway:
const { exec } = require('child_process');
exports.handler = async (event) => {
const userInput = event.queryStringParameters?.cmd || '';
exec(`echo ${userInput}`, (err, stdout) => {
// …
});
};
If the query string contains something like ; rm -rf / or && aws s3 cp s3://bucket/secret ., the attacker can execute arbitrary commands under the Lambda’s execution role.
Example – Python Lambda using subprocess.call:
import subprocess
import json
def lambda_handler(event, context):
user_input = event.get('queryStringParameters', {}).get('text', '')
subprocess.call(['sh', '-c', f'echo {user_input}'])
return {'statusCode': 200}
Here the unsanitized user_input is placed directly inside a shell string, enabling injection.
AWS‑specific surfaces where this pattern appears:
- API Gateway → Lambda integration (proxy or custom) that passes
eventvalues tochild_process.execorsubprocess. - EC2 launch templates with user‑data that reads instance metadata or tags and feeds them into
bash -c. - AWS Batch job definitions where the
commandfield is templated from user‑provided parameters. - AWS Systems Manager Run Command documents that invoke shell scripts with parameters drawn from Parameter Store or invocation input without validation.
In each case the root cause is the same: trusting external data and handing it to a shell interpreter.
AWS‑Specific Detection
Detecting command injection starts with reviewing code paths that reach a shell. Look for calls to child_process.exec, child_process.spawn (when the shell option is true), subprocess.call, subprocess.run with shell=True, or any raw bash -c / sh -c strings that incorporate request data.
Static analysis tools can flag these patterns, but runtime validation is essential because the injection may only be reachable under specific execution roles or environment variables.
middleBrick helps by scanning the unauthenticated attack surface of any exposed API endpoint. Using the CLI you run:
middlebrick scan https://api.example.com/prod/resource --output json
The scanner performs the Input Validation check (one of its 12 parallel tests) and injects a set of command‑injection payloads such as:
; id&& cat /etc/passwd| wc -l`whoami`
If the endpoint reflects the output of those payloads in its response, middleBrick records a finding with:
- Severity (based on impact and exploitability)
- Location (URL, HTTP method, parameter)
- Remediation guidance (avoid shell, use SDK, validate input)
- Proof‑of‑concept request/response snippet
An example JSON excerpt from a middleBrick report:
{
"findings": [
{
"check": "Input Validation",
"severity": "high",
"parameter": "cmd",
"payload": "; id",
"evidence": "uid=1000(lsb) gid=1000(lsb) groups=1000(lsb)",
"remediation": "Replace shell calls with AWS SDK calls or use subprocess with an argument list and shell=False."
}
]
}
Because middleBrick does not require agents, credentials, or configuration, you can point it at any publicly reachable AWS API (e.g., an API Gateway endpoint) and receive a reliable signal within the advertised 5‑15 second window.
AWS‑Specific Remediation
The most reliable fix is to eliminate the shell entirely and let the AWS SDK perform the intended action. This removes the injection surface while preserving functionality.
Node.js – replace exec with SDK calls:
const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
const s3 = new S3Client({});
exports.handler = async (event) => {
const key = event.queryStringParameters?.key;
if (!/^[a-zA-Z0-9/_-]+$/.test(key)) {
return { statusCode: 400, body: 'Invalid key' };
}
const cmd = new GetObjectCommand({ Bucket: 'my-bucket', Key: key });
const data = await s3.send(cmd);
return { statusCode: 200, body: await data.Body.transformToString() };
};
The user‑supplied key is validated against an allowlist before being used in the SDK command; no shell is invoked.
Python – use boto3 with an argument list and avoid shell=True:
import boto3
import re
def lambda_handler(event, context):
bucket = event.get('queryStringParameters', {}).get('bucket')
key = event.get('queryStringParameters', {}).get('key')
if not bucket or not re.match(r'^[a-zA-Z0-9._-]+$', bucket):
return {'statusCode': 400, 'body': 'Invalid bucket'}
if not key or not re.match(r'^[a-zA-Z0-9/_-]+$', key):
return {'statusCode': 400, 'body': 'Invalid key'}
s3 = boto3.client('s3')
obj = s3.get_object(Bucket=bucket, Key=key)
return {'statusCode': 200, 'body': obj['Body'].read().decode()}
If a shell command is truly unavoidable (e.g., invoking a legacy binary), use the SDK’s child_process.spawn or subprocess.run with an explicit argument list and shell=False:
# Node.js
const { spawn } = require('child_process');
sync = spawn('ffmpeg', ['-i', inputFile, '-c:v', 'libx264', outputFile]);
# Python
import subprocess
subprocess.run(['ffmpeg', '-i', inputFile, '-c:v', 'libx264', outputFile], check=True)
AWS‑specific hardening steps further reduce risk:
- Apply the principle of least privilege to the Lambda/execution role – grant only the S3, DynamoDB, or other service permissions actually needed.
- Store configuration values and secrets in AWS Systems Manager Parameter Store or Secrets Manager and retrieve them via the SDK, never by echoing environment variables into a shell.
- Use API Gateway request validation (JSON schema) to reject malformed parameters before they reach your integration.
- For batch workloads, define the
commandfield as a static array in the job definition; do not interpolate user input. - Enable AWS CloudTrail logging and monitor for unexpected
InvokeorRunCommandAPI calls that could indicate post‑exploitation activity.
After applying these fixes, rescan the endpoint with middleBrick (CLI, GitHub Action, or the Dashboard) to verify that the Input Validation check no longer reports a command‑injection finding.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |