HIGH sql injectionflaskbearer tokens

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 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 bearer tokens prevent SQL injection?
No. Bearer tokens handle authentication and access control, but they do not protect against SQL injection. Injection vulnerabilities arise from unsafe query construction; always use parameterized queries or an ORM regardless of authentication method.
Should I include authorization checks in parameterized queries?
Yes. After using safe parameterized queries, enforce scope-based authorization in your application logic (e.g., verify that the token grants access to the requested resource) to ensure least privilege and prevent horizontal or vertical privilege escalation.