HIGH padding oraclefastapijwt tokens

Padding Oracle in Fastapi with Jwt Tokens

Padding Oracle in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability

A padding oracle arises when an application reveals whether decrypted data has valid padding, enabling an attacker to iteratively decrypt ciphertext without knowing the key. In FastAPI applications that accept JSON Web Tokens (JWT), this typically occurs when the server performs decryption and signature verification in a way that leaks padding validity through timing differences or error messages.

JWTs are often represented as three dot-separated base64url-encoded parts: header.payload.signature. When the payload is encrypted (JWE) rather than signed (JWS), the server must decrypt it before validating claims. If the cryptographic library returns distinct errors for invalid padding versus invalid signature, an attacker can supply modified ciphertexts and observe differences in HTTP response codes or response times. FastAPI routes that parse JWTs via libraries that do not use constant-time verification or that expose stack traces can inadvertently act as an oracle.

Consider a FastAPI endpoint that expects an encrypted JWT in an Authorization header:

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt

app = FastAPI()
security = HTTPBearer()

SECRET_KEY = "example-secret"
ALGORITHM = "HS256"

async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
    try:
        payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except jwt.PyJWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )

@app.get("/items/")
async def read_items(user: dict = Depends(get_current_user)):
    return {"message": "secure data"}

If the jwt.decode implementation uses an algorithm that is vulnerable to padding manipulation (e.g., when keys are mismanaged and the token is expected to be encrypted rather than signed), and the library reveals padding errors distinctly, an attacker can perform adaptive chosen-ciphertext attacks. Even when using HMAC-SHA algorithms, improper handling of token validation order can expose behavior that aids an attacker who can distinguish between malformed tokens and valid-but-modified tokens.

In practice, a FastAPI application using JWTs may expose a padding oracle if:

  • It returns different HTTP status codes or messages for decryption failures versus signature failures.
  • It uses non-constant-time verification functions that early-exit on padding errors.
  • Stack traces or debug details are returned in responses, making the oracle observable.

middleBrick scans this surface by testing unauthenticated endpoints that accept JWTs, checking for timing variance and informative error messages, and mapping findings to the Authentication and Input Validation checks. This helps surface whether the API unintentionally exposes behavior that could assist a padding oracle attack.

Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes

Remediation centers on ensuring that token validation fails in a uniform, opaque manner and that algorithms and libraries are configured to avoid padding-sensitive operations. Below are concrete, working FastAPI examples that address padding-related risks when using JWTs.

1) Use signed JWTs (JWS) with strong algorithms and avoid encrypted JWTs (JWE) unless you have a vetted library that uses constant-time decryption. Always explicitly specify algorithms and verify the signature before processing claims.

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt

app = FastAPI()
security = HTTPBearer()

SECRET_KEY = "your-256-bit-secret"
ALGORITHM = "HS256"

def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
    try:
        # Explicitly restrict algorithms to prevent algorithm confusion
        payload = jwt.decode(
            credentials.credentials,
            SECRET_KEY,
            algorithms=[ALGORITHM],
            options={"verify_signature": True, "verify_exp": True},
        )
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token expired",
            headers={"WWW-Authenticate": "Bearer"},
        )
    except jwt.InvalidTokenError:
        # Use a generic message and consistent HTTP status to avoid leaking validation details
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )

@app.get("/items/")
async def read_items(token_info: dict = Depends(verify_token)):
    return {"message": "secure data", "user": token_info}

2) Ensure your JWT library is up to date and avoid constructs that rely on encryption modes susceptible to padding oracles (e.g., certain CBC-based JWE). If you must handle encrypted tokens, prefer libraries that implement constant-time decryption and avoid returning distinct errors for padding failures.

# Example of requesting a signed compact JWT (JWS) instead of an encrypted JWT
# This token is generated with HS256 and contains claims as a JSON payload.
# client = httpx.Client()
# response = client.post("/token", json={"alg": "HS256", "typ": "JWT"}, data=payload)

3) Add consistent error handling and avoid exposing stack traces in production. Configure FastAPI to return generic responses for validation errors and disable debug exceptions in production.

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI(debug=False)

@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.detail},
    )

4) When possible, use asymmetric algorithms (e.g., RS256) and validate audience and issuer claims to further reduce risk. Always validate token structure before attempting decryption, and enforce short expiration times to limit the window for replay or adaptive attacks.

import jwt
from jwt import PyJWKClient

url = "https://example.com/.well-known/jwks.json"
jwks_client = PyJWKClient(url)
signing_key = jwks_client.get_signing_key_from_jwt(token)

payload = jwt.decode(
    token,
    signing_key.key,
    algorithms=["RS256"],
    audience="my-audience",
    issuer="https://auth.example.com",
    options={"verify_exp": True, "require": ["iss", "aud"]},
)

These patterns reduce the risk that token handling introduces a padding oracle by ensuring uniform failure behavior, limiting the information exposed during validation, and using well-vetted algorithms and libraries.

Frequently Asked Questions

Can FastAPI JWT endpoints be tested automatically for padding oracle behavior?
Yes. middleBrick scans unauthenticated API endpoints that accept JWTs and checks for timing differences and distinct error messages that could indicate an oracle. It maps findings to Authentication and Input Validation checks and provides remediation guidance.
What is the difference between JWS and JWE in the context of JWTs and padding risks?
JWS (JSON Web Signature) uses a digital signature to ensure integrity and authenticity without decryption; padding risks are typically not relevant. JWE (JSON Web Encryption) encrypts the payload and requires decryption, where padding schemes can introduce oracles if error handling leaks padding validity. Prefer JWS or use constant-time JWE libraries.