Command Injection in Django with Dynamodb
Command Injection in Django with Dynamodb — how this specific combination creates or exposes the vulnerability
Command Injection occurs when untrusted input is concatenated into system or shell commands. In a Django application that uses Amazon DynamoDB, the risk typically arises not from DynamoDB itself (it is a managed NoSQL service and does not execute shell commands), but from how developers construct dynamic shell commands to interact with AWS resources. For example, a developer might invoke the AWS CLI or an SDK wrapper in a subprocess, passing DynamoDB table names, scan filters, or key condition expressions built from request data. If these values are not strictly validated and sanitized, an attacker can inject additional shell operators such as &&, ||, or semicolons to execute arbitrary commands on the host.
Consider a Django view that accepts a table_name parameter from a query string and uses it to run an AWS CLI command describing the table. If the input is concatenated directly into the command string, an attacker can break out of the intended argument and execute additional shell commands. This becomes a viable attack vector when the Django process has AWS credentials with sufficient permissions and the runtime environment allows shell execution. DynamoDB is involved because the table name or key attribute names may be user-influenced, and these values are passed into the shell context. Common patterns include using subprocess.run or os.popen without proper input validation. Even if the application primarily uses the Boto3 SDK, auxiliary tooling or legacy scripts might rely on shell invocation, expanding the attack surface.
Another scenario involves constructing AWS CLI commands with user-controlled filters or projection expressions. For instance, a developer might build a command that includes a FilterExpression using string interpolation. Special characters or operators in the input can alter the intended command structure when passed through a shell. While Boto3 does not typically require shell commands, the combination of Django, DynamoDB, and subprocess-based AWS interactions can inadvertently introduce injection points. The risk is compounded if the Django application runs in an environment with broad IAM permissions, as injected commands may access or modify sensitive DynamoDB resources.
To illustrate, a typical vulnerable pattern is:
import subprocess
def describe_table(request):
table_name = request.GET.get('table', 'default_table')
cmd = f'aws dynamodb describe-table --table-name {table_name}'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
return result.stdout
An attacker providing table_name as my-table; cat /etc/passwd can cause the shell to execute an additional command, leading to unauthorized data access. This highlights the importance of avoiding shell injection when integrating Django with DynamoDB through command-line utilities.
Dynamodb-Specific Remediation in Django — concrete code fixes
Remediation focuses on eliminating shell command construction and using safe, language-native AWS interactions. The primary fix is to avoid subprocess calls with user input entirely. Instead, use the AWS SDK for Python (Boto3) directly within Django views. This removes the shell as an injection vector and ensures that input is handled as structured data, not as command text.
Replace shell-based invocation with Boto3 client calls. For DynamoDB, this means using methods such as describe_table or query with parameters passed as dictionaries. User input should be validated and, where applicable, constrained to a known set of values (e.g., allowed table names) before being used in SDK operations.
Secure Django view example using Boto3:
import boto3
from django.http import JsonResponse
from botocore.exceptions import ClientError
def describe_table_safe(request):
table_name = request.GET.get('table')
# Validate against an allowlist to prevent abuse
allowed_tables = {'users', 'orders', 'products'}
if not table_name or table_name not in allowed_tables:
return JsonResponse({'error': 'Invalid table name'}, status=400)
client = boto3.client('dynamodb', region_name='us-east-1')
try:
response = client.describe_table(TableName=table_name)
return JsonResponse(response.get('Table', {}), safe=False)
except ClientError as e:
return JsonResponse({'error': str(e)}, status=500)
This approach ensures that table_name is never concatenated into a shell command. Input validation via an allowlist further reduces risk by limiting which DynamoDB tables can be inspected. Boto3 handles request signing and communication securely, without exposing shell injection surfaces.
When constructing key condition expressions or filter expressions, use DynamoDB’s built-in expression builders or parameterized structures instead of string concatenation. For example, avoid building expressions by directly inserting user input into a string. Instead, use placeholder values and bind parameters safely:
def query_users_safe(request):
user_id = request.GET.get('user_id')
allowed_prefixes = {'user#'}
if not user_id or not any(user_id.startswith(p) for p in allowed_prefixes):
return JsonResponse({'error': 'Invalid user_id'}, status=400)
client = boto3.resource('dynamodb', region_name='us-east-1')
table = client.Table('users')
try:
response = table.query(
KeyConditionExpression='user_id = :uid',
ExpressionAttributeValues={':uid': user_id}
)
return JsonResponse(response.get('Items', []), safe=False)
except ClientError as e:
return JsonResponse({'error': str(e)}, status=500)
By using parameterized expressions, you prevent injection through malformed key conditions. Additionally, ensure that the AWS credentials used by Boto3 have the minimum required permissions for the DynamoDB operations, following the principle of least privilege.
Finally, if shell commands are unavoidable in your workflow (for example, invoking AWS CLI from management scripts), rigorously sanitize and validate all inputs. Use shlex.quote to escape shell metacharacters and avoid shell=True where possible. However, the preferred path remains direct SDK usage within Django, as demonstrated above.
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 |