# PYTHON-LAMBDA-SEC-016: Lambda DynamoDB FilterExpression Injection

> **Severity:** HIGH | **CWE:** CWE-943 | **OWASP:** A03:2021

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

## Description

This rule detects injection vulnerabilities unique to serverless DynamoDB integrations
in AWS Lambda functions, where untrusted event data flows into a DynamoDB FilterExpression
string passed to table.scan() or table.query() calls.

DynamoDB is the most commonly used database for AWS Lambda functions due to its
serverless scaling model, IAM-based access control, and native AWS integration. When
Lambda functions use the low-level DynamoDB API with raw expression strings (rather
than the high-level boto3 Attr() and Key() condition builder), event data can be
injected into the FilterExpression string parameter.

Unlike SQL injection, DynamoDB FilterExpression injection does not enable arbitrary
data access (DynamoDB does not support cross-table operations or nested expression
execution). However, attackers can manipulate filter logic to bypass intended access
controls, cause denial-of-service by injecting always-false conditions that return
empty results, enumerate attribute names via error-based probing, or modify scan
behavior to expose data that should be filtered out.

This vulnerability is specific to the serverless ecosystem: Lambda functions commonly
skip the extra abstraction layer that would prevent it, and DynamoDB's expression
language is less widely understood than SQL, making developers less likely to recognize
injection patterns.


## Vulnerable Code

```python
import json

# SEC-016: DynamoDB filter injection
def handler_dynamodb(event, context):
    import boto3
    table = boto3.resource('dynamodb').Table('users')
    filter_expr = event.get('filter')
    result = table.scan(FilterExpression=filter_expr)
    return {"statusCode": 200, "body": json.dumps(result)}
```

## Secure Code

```python
import json
import boto3
from boto3.dynamodb.conditions import Key, Attr

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('orders')

def lambda_handler(event, context):
    params = event.get('queryStringParameters', {}) or {}
    user_id = params.get('user_id', '')
    status = params.get('status', '')

    # SECURE: Validate user_id format
    if not user_id or not user_id.isalnum():
        return {'statusCode': 400, 'body': 'Invalid user_id'}

    # SECURE: Use Key() for partition key conditions in query()
    # SECURE: Use Attr() for filter conditions — SDK handles value escaping
    ALLOWED_STATUSES = {'pending', 'shipped', 'delivered', 'cancelled'}
    if status and status not in ALLOWED_STATUSES:
        return {'statusCode': 400, 'body': 'Invalid status'}

    query_kwargs = {
        'KeyConditionExpression': Key('user_id').eq(user_id)
    }
    if status:
        query_kwargs['FilterExpression'] = Attr('status').eq(status)

    response = table.query(**query_kwargs)
    return {'statusCode': 200, 'body': json.dumps(response['Items'], default=str)}

```

## Detection Rule (Python SDK)

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

_LAMBDA_SOURCES = [
    calls("event.get"),
    calls("event.items"),
    calls("event.values"),
    calls("*.get"),
]


@python_rule(
    id="PYTHON-LAMBDA-SEC-016",
    name="Lambda DynamoDB Filter Injection",
    severity="HIGH",
    category="aws_lambda",
    cwe="CWE-943",
    tags="python,aws,lambda,dynamodb,nosql-injection,OWASP-A03,CWE-943",
    message="Lambda event data flows to DynamoDB scan/query filter. Validate input.",
    owasp="A03:2021",
)
def detect_lambda_dynamodb_injection():
    """Detects Lambda event data flowing to DynamoDB scan/query filters."""
    return flows(
        from_sources=_LAMBDA_SOURCES,
        to_sinks=[
            calls("*.scan"),
            calls("*.query"),
            calls("table.scan"),
            calls("table.query"),
        ],
        sanitized_by=[],
        propagates_through=PropagationPresets.standard(),
        scope="global",
    )
```

## How to Fix

- Use boto3's Attr() and Key() condition expression builders instead of raw FilterExpression strings; the SDK handles value escaping and expression syntax automatically.
- Always use ExpressionAttributeValues and ExpressionAttributeNames for substitution when FilterExpression strings must be constructed programmatically.
- Validate event fields against strict allowlists before using them in any DynamoDB expression, particularly for attribute names that cannot be parameterized by the SDK.
- Apply DynamoDB IAM policies that restrict the Lambda's access to specific tables and operations (e.g., GetItem, Query only; no Scan on production tables).
- Prefer table.query() with KeyConditionExpression over table.scan() with FilterExpression; queries are more efficient and inherently scope to a partition key value.

## Security Implications

- **Filter Logic Bypass via Expression Injection:** An attacker who controls part of the FilterExpression string can inject expression
syntax to bypass intended data access controls. For example, injecting OR
attribute_exists(id) into a filter intended to restrict results to a specific
user's records can cause the filter to return all items in the table rather than
only the intended subset.

- **Access Control Bypass in Multi-Tenant Data:** DynamoDB tables in Lambda applications often store multi-tenant data separated
by a tenant_id attribute. FilterExpression injection can bypass tenant isolation
by modifying the filter to include OR conditions that match items from other
tenants, exposing cross-tenant data to attackers.

- **Denial of Service via Always-False Filters:** Injecting always-false conditions (e.g., attribute_not_exists(id) when id always
exists) causes table.scan() to consume all DynamoDB read capacity units while
returning no results. For large tables with high read capacity costs, this can
generate significant AWS charges and degrade application performance.

- **Attribute Name Enumeration via Error Probing:** DynamoDB returns specific error messages for invalid expression attribute names.
By injecting probe expressions and observing error responses, an attacker can
enumerate the attribute names present in the DynamoDB table, revealing the
data schema for use in more targeted attacks.


## FAQ

**Q: Is DynamoDB FilterExpression injection as severe as SQL injection?**

DynamoDB FilterExpression injection is less severe than SQL injection because
DynamoDB's expression language does not support cross-table operations, subqueries,
OS commands, or file access. However, it can still enable data access control bypass
(exposing rows that should be filtered), multi-tenant data leakage, and denial-of-
service via always-false conditions that waste read capacity units. The impact is
scoped to the table being queried.


**Q: What is the difference between FilterExpression as a string versus Attr() conditions?**

FilterExpression as a raw string (e.g., "status = :s") requires manually specifying
ExpressionAttributeValues (:s mapping) and is vulnerable to injection when event
data is embedded in the expression string itself. Attr("status").eq(status) uses
the boto3 condition builder, which generates the expression string and attribute
value mapping internally. The value (status) is passed as a Python object and the
SDK ensures it is properly represented as an ExpressionAttributeValue, preventing
injection of expression syntax.


**Q: Why is this vulnerability unique to serverless DynamoDB Lambda integrations?**

DynamoDB is the default database for Lambda functions due to its native AWS
integration and serverless scaling. Unlike relational databases where ORMs are
the standard abstraction, DynamoDB is often accessed via the low-level boto3 API
directly in Lambda handlers, which requires developers to construct expression
strings manually. The expression language is less familiar than SQL, making
developers less likely to recognize injection risks when building filter strings
from event data.


**Q: Can I use ExpressionAttributeNames and ExpressionAttributeValues to safely inject event data into FilterExpression strings?**

Yes, if done correctly. ExpressionAttributeValues safely substitutes placeholder
values (e.g., :val) with actual values in the expression. ExpressionAttributeNames
safely substitutes placeholder names (e.g., #attr) with actual attribute names.
If both the expression structure and the values use this substitution mechanism
rather than direct string embedding, injection is prevented. However, the boto3
Attr() builder is simpler and less error-prone for most use cases.


**Q: How do I handle dynamic FilterExpression conditions where the number of filters varies?**

Build a list of Attr() conditions and combine them with the & operator:
filter_expr = Attr('status').eq(status) & Attr('region').eq(region). For
a variable number of conditions, start with the first Attr() condition and
use reduce() or a loop to & additional conditions. This approach builds a
complete parameterized expression using only boto3's condition builder and
never embeds event data in expression strings.


## References

- [CWE-943: Improper Neutralization of Special Elements in Data Query Logic](https://cwe.mitre.org/data/definitions/943.html)
- [AWS DynamoDB FilterExpression documentation](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.FilterExpression.html)
- [boto3 DynamoDB Conditions](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/customizations/dynamodb.html#boto3.dynamodb.conditions.Attr)
- [OWASP NoSQL Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05.6-Testing_for_NoSQL_Injection)
- [AWS Lambda Security Best Practices](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html)
- [DynamoDB Expression Attribute Names and Values](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeValues.html)

---

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