HIGH sql injectionflaskapi keys

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 IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

Does using API keys in headers fully prevent SQL Injection?
No. API keys alone do not prevent SQL Injection. If endpoints concatenate user input into queries—whether alongside keys or not—they remain vulnerable. Always use parameterized queries and validate inputs independently of authentication mechanisms.
Should API keys be stored in plaintext in the database?
No. Store only salted hashes of keys, similar to passwords. If keys must be compared, hash user-supplied keys and compare against stored hashes; never store or query by plaintext keys.