HIGH ssrf server sidedjangodynamodb

Ssrf Server Side in Django with Dynamodb

Ssrf Server Side in Django with Dynamodb — how this specific combination creates or exposes the vulnerability

Server-side request forgery (SSRF) in a Django application that interacts with Amazon DynamoDB can arise when an attacker is able to influence an HTTP request initiated by the server-side code, and that request targets internal or external services whose responses are then used to construct or modify DynamoDB operations. For example, if a Django view accepts a URL parameter that is passed to an AWS SDK call such as boto3.client("dynamodb").get_item, and the URL or an associated metadata field (e.g., a DynamoDB stream ARN or a conditional check endpoint) is user-controlled, an attacker may force the server to make unintended requests to internal metadata services (169.254.169.254), cloud provider metadata endpoints, or other backend systems. Because DynamoDB operations often require precise input such as table names, keys, or condition expressions, an SSRF-induced request can be coerced into probing internal services that expose credentials, instance metadata, or misconfigured endpoints that subsequently affect DynamoDB behavior.

In this specific stack, the risk is compounded by how SDK clients are instantiated and credentials are resolved. Django applications commonly rely on IAM roles attached to the host (EC2, ECS, or EKS) or on environment variables for AWS credentials. If an SSRF vector allows an attacker to reach the metadata service, they may be able to enumerate the associated IAM role and its policies, potentially revealing DynamoDB permissions such as dynamodb:GetItem or dynamodb:UpdateItem. Moreover, malformed input that reaches DynamoDB condition checks can cause unexpected evaluation paths, especially when combined with SSRF-influenced data used to build expression attribute values. While the DynamoDB service itself is not directly exploitable over HTTP from an attacker, the interplay between Django’s request handling, boto3 client configuration, and attacker-controlled inputs can lead to unauthorized data access or changes in access patterns that violate the intended authorization boundaries.

An illustrative scenario: a Django endpoint exposes a feature to fetch item details from DynamoDB by accepting a table name and a key via query parameters, and also allows specifying a custom health-check URL for validation. If the health-check URL is not strictly validated, an attacker provides http://169.254.169.254/latest/meta-data/iam/security-credentials/ as the health-check target. The server-side code performs an HTTP GET to that URL as part of validation, leaks the IAM role, and the role is then used by the same process to make DynamoDB calls. This does not require DynamoDB to be directly reachable from the attacker; the exposure occurs through Django’s integration layer and the permissions tied to the runtime identity.

Dynamodb-Specific Remediation in Django — concrete code fixes

Remediation focuses on strict input validation, avoiding runtime reflection of attacker-controlled data into AWS SDK calls, and ensuring that credentials are not discoverable via internal endpoints. Below are concrete, safe patterns for Django projects using boto3 with DynamoDB.

1. Validate and whitelist table names and keys

Never pass raw user input into DynamoDB API calls. Use allowlists for table names and enforce strict key schemas.

import boto3
from django.core.exceptions import ValidationError
import re

TABLE_ALLOWLIST = {"users", "orders", "products"}
KEY_PATTERN = re.compile(r"^[A-Za-z0-9\-_]{1,100}$")

def get_dynamodb_item(table_name: str, partition_key: str, sort_key: str | None = None) -> dict:
    if table_name not in TABLE_ALLOWLIST:
        raise ValidationError("Invalid table name.")
    if not KEY_PATTERN.match(partition_key):
        raise ValidationError("Invalid partition key.")
    if sort_key is not None and not KEY_PATTERN.match(sort_key):
        raise ValidationError("Invalid sort key.")

    client = boto3.client("dynamodb")
    response = client.get_item(
        TableName=table_name,
        Key={
            "pk": {"S": partition_key},
            **({"sk": {"S": sort_key}} if sort_key else {}),
        },
    )
    return response.get("Item", {})

2. Avoid dynamic endpoint or metadata usage in request flow

Do not allow user input to influence URLs that your server will request, especially metadata endpoints. If you need to introspect the environment, perform it once at startup or via secure configuration, not per request.

from django.conf import settings
import boto3
import os

# Safe: credentials come from environment or instance metadata at startup, not per request.
AWS_REGION = os.getenv("AWS_REGION", "us-east-1")

def safe_dynamodb_client():
    # Do not build endpoint_url from user input.
    return boto3.client("dynamodb", region_name=AWS_REGION)

3. Use IAM roles and least privilege

Ensure the IAM role attached to the Django host (or task) has only the DynamoDB actions required for the specific tables and paths. Avoid wildcard permissions. Do not expose credentials via URLs or logs that SSRF could reach.

# Example policy snippet (JSON) for least privilege:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:Query"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/users"
        }
    ]
}

4. Sanitize inputs used in DynamoDB expressions

When building ConditionExpression or FilterExpression, use ExpressionAttributeNames and ExpressionAttributeValues rather than string interpolation to avoid injection and unintended evaluation paths that SSRF could exploit indirectly.

def query_user_orders(user_id: str, status_filter: str | None = None):
    client = boto3.client("dynamodb")
    expression = "pk = :uid"
    names = {"#s": "status"}
    values = {":uid": {"S": user_id}, ":st": {"S": status_filter}}
    if status_filter:
        expression += " AND #s = :st"
    response = client.scan(
        TableName="orders",
        FilterExpression=expression,
        ExpressionAttributeNames=names,
        ExpressionAttributeValues=values,
    )
    return response.get("Items", [])

Frequently Asked Questions

Can SSRF allow an attacker to directly read or modify DynamoDB data?
SSRF does not directly compromise DynamoDB because DynamoDB is not reachable over the public internet from an attacker. However, SSRF can expose metadata that reveals IAM role permissions, and it can manipulate server-side logic to cause unsafe DynamoDB operations, leading to unauthorized reads or writes.
Does scanning with middleBrick detect SSRF risks related to DynamoDB integrations?
middleBrick scans the unauthenticated attack surface and tests input validation and data exposure pathways. While it does not exploit internal AWS metadata services, it can identify SSRF-like indicators such as overly dynamic endpoint usage and missing input constraints that could lead to unsafe DynamoDB interactions.