Sql Injection in Flask with Api Keys
Sql Injection in Flask with Api Keys — how this specific combination creates or exposes the vulnerability
SQL Injection in a Flask API that uses API keys typically arises when developer-supplied keys influence dynamic query construction. Consider a route that accepts an API key via request headers and uses it to build SQL without parameterization:
import sqlite3
from flask import request, jsonify
def get_user():
api_key = request.headers.get('X-API-Key')
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
cursor.execute(f"SELECT * FROM users WHERE api_key = '{api_key}'")
row = cursor.fetchone()
return jsonify(row)
Here, the API key is interpolated directly into the SQL string. Even if the API key is not user-controlled in the traditional sense (e.g., client-supplied), it becomes an attacker-controlled vector if the endpoint is exposed without authentication checks or if keys are leaked. An attacker who discovers or guesses a valid key could manipulate the query only if the key itself is used unsafely elsewhere, but more critically, this pattern often coexists with other endpoints where user input (such as user_id) is concatenated alongside the key context, enabling classic SQL Injection:
cursor.execute(f"SELECT * FROM resources WHERE owner_key = '{api_key}' AND id = {user_id}")
The broader risk with API keys in Flask is not solely about SQL strings; it is about how their presence can mask authorization gaps. If route-level checks rely only on key validity and not on scoped permissions, an attacker may leverage SQL Injection in another parameter to bypass ownership checks (BOLA/IDOR). Moreover, if the API key is transmitted or logged insecurely, it can be exfiltrated and reused, amplifying the impact of a successful injection. In OpenAPI-first workflows, unauthenticated scans may flag endpoints that accept keys but still concatenate parameters, revealing injection surfaces that exist despite the apparent protection of an authentication gate.
Because middleBrick scans the unauthenticated attack surface, it can detect endpoints that accept API keys yet construct queries unsafely, highlighting SQL Injection alongside authorization and data exposure findings. This combination underscores why key handling must be isolated from query building and always validated against least-privilege principles.
Api Keys-Specific Remediation in Flask — concrete code fixes
Remediation centers on strict separation of code and data, scoped authorization checks, and secure key management. Never interpolate API keys or any user-influenced values into SQL strings. Use parameterized queries consistently, even for key comparison, and validate the key’s scope before allowing access to sensitive operations.
Secure Flask implementation with parameterized queries and key validation:
import sqlite3
from flask import request, jsonify
def get_user():
api_key = request.headers.get('X-API-Key')
if not api_key:
return jsonify({'error': 'missing key'}), 401
user_id = request.args.get('user_id', type=int)
if user_id is None:
return jsonify({'error': 'invalid user_id'}), 400
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id = ? AND api_key = ?", (user_id, api_key))
row = cursor.fetchone()
if not row:
return jsonify({'error': 'forbidden'}), 403
return jsonify(row)
If API keys are used as bearer tokens for authentication rather than direct row identifiers, store only hashes (similar to passwords) and compare securely:
import sqlite3
import hashlib
from flask import request, jsonify, g
def hash_key(key: str) -> str:
return hashlib.sha256(key.encode('utf-8')).hexdigest()
def get_own_resource():
provided = request.headers.get('X-API-Key')
if not provided:
return jsonify({'error': 'missing key'}), 401
hashed = hash_key(provided)
resource_id = request.args.get('resource_id', type=int)
if resource_id is None:
return jsonify({'error': 'invalid resource_id'}), 400
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
cursor.execute(
"SELECT r.id, r.data FROM resources r JOIN api_keys k ON r.key_id = k.id WHERE k.hashed_key = ? AND r.id = ?",
(hashed, resource_id)
)
row = cursor.fetchone()
if not row:
return jsonify({'error': 'forbidden'}), 403
return jsonify(row)
Additional practices include rotating keys via a secure admin interface, setting short expirations, and logging failed auth attempts without exposing keys. In CI/CD, the GitHub Action can enforce that no new endpoints with concatenated queries are introduced by failing builds when risk thresholds are exceeded. For ongoing assurance, the Pro plan’s continuous monitoring can detect regressions in key handling, while the MCP Server allows you to scan APIs directly from your AI coding assistant during development.
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 |