Lambda Command Injection via asyncio.create_subprocess_shell()

CRITICAL

Lambda event data flows to asyncio.create_subprocess_shell(), enabling OS command injection in async Lambda handlers.

Rule Information

Language
Python
Category
AWS Lambda
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonawslambdacommand-injectionasynciocreate-subprocess-shelltaint-analysisinter-proceduralCWE-78OWASP-A03
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-LAMBDA-SEC-004 --project .
1
2
3
4
5
6
7
8
9
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

About This Rule

Understanding the vulnerability and how it is detected

This rule detects OS command injection vulnerabilities in AWS Lambda functions where untrusted event data flows into asyncio.create_subprocess_shell() calls within async Lambda handlers.

Lambda functions increasingly use async/await patterns with asyncio for concurrent I/O operations. asyncio.create_subprocess_shell() is the async equivalent of subprocess.run(shell=True) and carries the same shell injection risk: the command string is passed to /bin/sh for interpretation, and any shell metacharacters in the event data will be executed as shell syntax.

Lambda functions receive input from the event dictionary populated by API Gateway, SQS, SNS, S3, DynamoDB Streams, and other triggers. Fields like event.get("body"), event.get("queryStringParameters"), and event["Records"] are attacker-controllable. There is no sanitization layer between the event payload and application code.

The async nature of the handler does not provide any additional security for shell commands. Injected commands run asynchronously but still complete within the invocation timeout, allowing exfiltration of the Lambda execution role's AWS credentials (available in environment variables) and other sensitive data before the invocation ends.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Shell Injection in Async Lambda Handlers

asyncio.create_subprocess_shell() passes the command string to /bin/sh for execution. Lambda event data embedded via f-strings or concatenation allows an attacker to inject shell metacharacters (;, |, $(), ``) that chain additional commands. The async execution model does not prevent injection; commands run concurrently and complete before the handler returns.

2

AWS Credential Exfiltration via Async Shell

The Lambda execution environment exposes AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN as environment variables. An async shell injection can spawn a background process that exfiltrates these credentials via curl or wget to attacker-controlled infrastructure, completing before the Lambda invocation ends.

3

Concurrent Exploitation Amplification

Async handlers may process multiple event records concurrently (e.g., SQS batch processing). A batch of malicious SQS messages could trigger multiple concurrent injections within a single invocation, amplifying the attack surface and potentially overwhelming logging or detection systems.

4

VPC Lateral Movement

Lambda functions in a VPC have network access to internal resources. An async shell injection can initiate non-blocking outbound connections to internal endpoints (RDS, ElastiCache, internal APIs) that are not accessible from the internet, enabling lateral movement through the VPC.

How to Fix

Recommended remediation steps

  • 1Replace asyncio.create_subprocess_shell() with asyncio.create_subprocess_exec() using individual arguments, which bypasses the shell entirely.
  • 2Never interpolate Lambda event data into shell command strings, even with asyncio async patterns.
  • 3Validate all event fields with strict allowlists or regular expressions before they appear in any subprocess or asyncio subprocess argument.
  • 4Apply least-privilege IAM policies to the Lambda execution role to limit the AWS APIs accessible if credentials are exfiltrated.
  • 5Enable AWS CloudTrail and VPC Flow Logs to detect unusual outbound connections from the Lambda execution environment.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule performs inter-procedural taint analysis with global scope. Sources are Lambda event dictionary access calls: calls("event.get"), calls("event.__getitem__"), including event.get("body"), event.get("queryStringParameters"), event.get("pathParameters"), and event["Records"]. The sink is calls("asyncio.create_subprocess_shell") with the tainted command string tracked via .tracks(0). Sanitizers include shlex.quote() applied per-argument. The analysis follows taint through async function calls, await expressions, f-string interpolation, and module boundaries.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

CWE Top 25
CWE-78 ranked #5 in 2023 Most Dangerous Software Weaknesses
OWASP Top 10
A03:2021 - Injection
PCI DSS v4.0
Requirement 6.2.4 - protect against injection attacks
NIST SP 800-53
SI-10: Information Input Validation
AWS Security Best Practices
Validate all inputs; apply least-privilege execution roles

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Lambda Command Injection via asyncio.create_subprocess_shell()

create_subprocess_shell() passes the command to the system shell (/bin/sh) for interpretation, exactly like subprocess.run(shell=True). This means shell metacharacters in user input are interpreted as shell syntax. create_subprocess_exec() takes individual arguments and passes them directly to execve() without shell interpretation, making it safe from shell injection when arguments are validated.
No. The async execution model allows non-blocking I/O but does not impose any security boundary on the shell commands that are spawned. The injected shell command runs as a subprocess of the Lambda process and completes before the invocation ends. Async concurrency may actually amplify the attack if multiple event records are processed concurrently and each contains an injection payload.
Use asyncio.create_subprocess_exec() with individual string arguments for each part of the command. This function bypasses the shell entirely and passes arguments directly to the process. Validate all arguments from event data before use and source executable names from a hardcoded allowlist rather than event data.
Yes. The rule performs inter-procedural analysis and follows taint from the Lambda handler's event parameter through await calls, async function arguments, and return values. If event data flows from lambda_handler() through an async helper to asyncio.create_subprocess_shell() in another module, the finding is still reported.
SQS batch processing often loops over event["Records"] and processes each record. If the loop body passes record data to create_subprocess_shell(), each record in the batch is a potential injection vector. Validate every record field individually before use. Use batch item failure reporting to handle individual malformed records without failing the entire batch.

New feature

Get these findings posted directly on your GitHub pull requests

The Lambda Command Injection via asyncio.create_subprocess_shell() rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works