Sql Injection in Flask with Dynamodb
Sql Injection in Flask with Dynamodb — how this specific combination creates or exposes the vulnerability
SQL Injection is commonly associated with relational databases, but injection-style attacks can also manifest against NoSQL stores when user input is used to build query parameters or condition expressions. With AWS DynamoDB and Flask, risk arises primarily at the translation layer where raw request data is mapped to DynamoDB API calls. If a Flask endpoint directly interpolates untrusted input into key condition expressions, filter rules, or attribute names, an attacker can manipulate query behavior, bypass intended access controls, or extract unintended data.
Consider a Flask route that retrieves a user profile by user ID from a DynamoDB table. If the implementation uses request arguments to construct the key condition without validation, an attacker can inject additional filter logic or attempt to probe the table’s schema:
{'user_id': {'S': '[email protected] OR #status = :val'}, 'expression_attribute_values': {':val': {'BOOL': True}}}
DynamoDB itself does not parse SQL, but poor construction of the KeyConditionExpression or FilterExpression can lead to logic bypass (e.g., always-true conditions) or excessive data exposure through broader scans. In a Flask application, this often occurs when developers treat DynamoDB as a simple key-value store and concatenate strings to form expressions, mistakenly assuming NoSQL interfaces are immune to injection. Additionally, if the Flask app exposes an endpoint that forwards partial query fragments to DynamoDB based on unvalidated client input, it may unintentionally enable broader scans or expose secondary indexes, increasing data exposure risk.
Another concern involves attribute names. If Flask code dynamically injects attribute names into expressions using string formatting, an attacker can attempt to manipulate the structure of the request by supplying names that map to sensitive attributes or metadata. While DynamoDB does not execute arbitrary code, malformed or overly permissive expressions can change the semantics of the query, leading to authentication bypass or information leakage. These patterns align with the Injection entry in the OWASP API Top 10 and can have consequences similar to classic injection impacts: unauthorized data access and privilege escalation.
Dynamodb-Specific Remediation in Flask — concrete code fixes
Defensive patterns for DynamoDB in Flask focus on strict input validation, using parameterized expressions, and avoiding direct concatenation of user input into query components. Below are concrete, realistic code examples illustrating a vulnerable pattern and a remediated approach.
Vulnerable pattern (do not use)
from flask import Flask, request
import boto3
app = Flask(__name__)
ddb = boto3.resource('dynamodb')
table = ddb.Table('Users')
@app.route('/profile')
def get_profile():
user_id = request.args.get('user_id')
# UNSAFE: directly embedding user input into expression
response = table.query(
KeyConditionExpression=f'user_id = {user_id}'
)
return response['Items']
Remediated pattern with validation and expression names
from flask import Flask, request
import boto3
from boto3.dynamodb.conditions import Key
app = Flask(__name__)
ddb = boto3.resource('dynamodb')
table = ddb.Table('Users')
ALLOWED_CHARSET = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-@')
def is_safe_user_id(value: str) -> bool:
return all(c in ALLOWED_CHARSET for c in value)
@app.route('/profile')
def get_profile():
user_id = request.args.get('user_id', '')
if not is_safe_user_id(user_id):
return {'error': 'invalid user_id'}, 400
# Safe: using expression attribute names/values
response = table.query(
KeyConditionExpression=Key('user_id').eq(user_id)
)
return response['Items']
This approach ensures that the key value is constrained to a known safe character set and uses the boto3 Key helper to construct a parameterized expression, preventing unintended expression logic. For more complex queries, validate and map input to known safe values rather than echoing raw input into expression strings.
Filter expression with safe attribute mapping
from boto3.dynamodb.conditions import Attr
@app.route('/search')
def search_items():
status = request.args.get('status')
# Validate against an allowlist
allowed_status = {'active', 'pending', 'archived'}
if status not in allowed_status:
return {'error': 'invalid status'}, 400
# Use Attr with explicit attribute names; do not inject attribute names from input
response = table.scan(
FilterExpression=Attr('status').eq(status)
)
return response['Items']
When using scan with FilterExpression, prefer allowlists for attribute values and avoid constructing attribute names from client input. If you must support dynamic attribute names, maintain a strict server-side mapping and reject any input not explicitly permitted. These practices reduce the likelihood of injection-style manipulation and help keep the unauthenticated attack surface tested by middleBrick within acceptable bounds, as the scanner will flag endpoints that reflect or process untrusted input insecurely.
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 |