Sql Injection in Flask with Bearer Tokens
Sql Injection in Flask with Bearer Tokens — how this specific combination creates or exposes the vulnerability
SQL injection in a Flask API that uses bearer tokens typically occurs when user-controlled data — such as path parameters, query strings, or request body fields — is concatenated into SQL strings without proper parameterization. The presence of bearer tokens does not change how SQL injection works, but it changes how an attacker can reach vulnerable endpoints. An API endpoint that validates a bearer token may still be vulnerable if it builds SQL dynamically using string interpolation or concatenation, even when authentication succeeds.
Consider a Flask route that extracts a user_id from the URL after validating a bearer token, then uses that value directly in a query:
@app.route('/users/<int:user_id>')
def get_user(user_id):
auth = request.headers.get('Authorization')
if not validate_bearer(auth):
return {'error': 'unauthorized'}, 401
# Vulnerable: direct string formatting
query = f'SELECT id, name FROM users WHERE id = {user_id}'
result = db.engine.execute(text(query))
return {'user': result.first()}
Even though the route appears to accept only integers, using an f-string means the SQL is constructed outside of the database driver’s parameterization path. An attacker who obtains a valid bearer token (e.g., via token leakage or a compromised client) can still attempt injection if the application logic allows non-integer input or if the token grants broader access than intended. The token provides access control, but it does not sanitize inputs or enforce safe query construction.
Another common pattern involves string identifiers such as usernames or slugs combined with bearer tokens:
@app.route('/profiles/<username>')
def get_profile(username):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not verify_token_scope(token, username):
return {'error': 'forbidden'}, 403
# Vulnerable: concatenation with user input
sql = 'SELECT * FROM profiles WHERE username = \'%s\'' % username
rows = db.session.execute(text(sql))
return {'profile': rows.first()}
Here, the bearer token might be used to derive or scope access, but the SQL string is still built via Python string formatting. If username is not strictly validated or parameterized, an attacker can supply values like ' OR 1=1 -- to manipulate the query logic. In this scenario, the bearer token does not prevent injection because the vulnerability lies in query construction, not authentication.
SQL injection risks with bearer tokens are particularly concerning when combined with over-privileged tokens or missing scope checks. If a token grants access to multiple endpoints, a single vulnerable route can expose more data than intended. Moreover, injection findings often map to OWASP API Top 10 A03:2023 (Injection) and common compliance frameworks such as PCI-DSS and SOC2, underscoring the need to treat authenticated endpoints with the same rigor as unauthenticated ones.
Bearer Tokens-Specific Remediation in Flask — concrete code fixes
Remediation centers on ensuring that all SQL construction uses parameterized queries or an ORM, and that bearer token handling does not introduce implicit trust. Below are concrete, safe patterns for Flask APIs that use bearer tokens.
1. Use SQLAlchemy text() with bound parameters
Always pass user input as parameters, never via string formatting:
@app.route('/users/<int:user_id>')
def get_user(user_id):
auth = request.headers.get('Authorization')
if not validate_bearer(auth):
return {'error': 'unauthorized'}, 401
# Safe: parameterized query
from sqlalchemy import text
query = text('SELECT id, name FROM users WHERE id = :uid')
result = db.engine.execute(query, {'uid': user_id})
return {'user': result.first()}
2. Use an ORM with typed inputs
An ORM like SQLAlchemy ORM or Peewee further reduces risk by separating data from commands:
@app.route('/profiles/<username>')
def get_profile(username):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not verify_token_scope(token, username):
return {'error': 'forbidden'}, 403
# Safe: ORM query with filtering
profile = db.session.query(Profile).filter_by(username=username).first()
return {'profile': profile}
3. Validate and normalize inputs before use
Even when using parameters, validate types and formats. For usernames, enforce a strict pattern:
import re
from flask import abort
USERNAME_RE = re.compile(r'^[A-Za-z0-9_.-]{3,32}$')
def get_profile(username):
if not USERNAME_RE.match(username):
abort(400, 'Invalid username')
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not verify_token_scope(token, username):
abort(403, 'Insufficient scope')
# Safe: validated input used as parameter
query = text('SELECT * FROM profiles WHERE username = :uname')
row = db.engine.execute(query, {'uname': username}).first()
return {'profile': row}
4. Centralize token validation and scope checks
Avoid scattering validation logic. A small wrapper can reduce mistakes:
def require_scope(required_scope):
def decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
auth = request.headers.get('Authorization')
if not auth or not auth.startswith('Bearer '):
return {'error': 'unauthorized'}, 401
token = auth.split(' ', 1)[1]
if not token_has_scope(token, required_scope, **kwargs):
return {'error': 'forbidden'}, 403
return f(*args, **kwargs)
return wrapped
return decorator
@app.route('/items/<int:item_id>')
@require_scope('items:read')
def get_item(item_id):
query = text('SELECT id, name, owner_id FROM items WHERE id = :iid')
row = db.engine.execute(query, {'iid': item_id}).first()
if not row:
abort(404)
return {'item': row}
These patterns ensure that bearer tokens are used for access decisions while SQL construction remains safe. They align with remediation guidance that prioritizes parameterized queries, strict input validation, and consistent authorization checks.
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 |