Cryptographic Failures in Fastapi
How Cryptographic Failures Manifests in Fastapi
Cryptographic failures in FastAPI applications typically emerge through several common patterns that stem from both framework defaults and developer oversights. Understanding these specific manifestations is crucial for securing FastAPI APIs.
Weak Default Settings
FastAPI's defaults can be problematic. When using SECRET_KEY for session management without proper configuration, applications often default to insecure settings. The SECRET_KEY environment variable must be sufficiently random—at least 32 bytes of entropy—but many implementations use predictable values.
Insecure JWT Implementation
FastAPI's JWT handling often fails when developers use weak signing algorithms or expose endpoints that accept any algorithm. Consider this vulnerable pattern:
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer
from jose import jwt
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='token')
def verify_token(token: str = Depends(oauth2_scheme)):
try:
# Vulnerable: no algorithm validation
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256', 'none'])
return payload
except:
raise HTTPException(status_code=401, detail='Invalid token')
This code accepts both HS256 and 'none' algorithms, allowing attackers to forge tokens by removing signatures entirely.
Improper Secret Management
FastAPI applications frequently hardcode secrets or use weak key derivation functions. A common anti-pattern:
# Vulnerable: hardcoded secret
SECRET_KEY = 'this_is_not_secure_at_all_1234567890'
Even when using environment variables, developers might generate weak keys or reuse keys across environments.
Insufficient Key Length
FastAPI's cryptography often uses inadequate key lengths. For AES encryption, using 128-bit keys instead of 256-bit keys leaves applications vulnerable to brute-force attacks. Similarly, RSA keys shorter than 2048 bits are considered weak by modern standards.
Timing Attacks
FastAPI's password comparison implementations often use naive equality checks that leak timing information:
# Vulnerable: timing attack possible
if user.password == submitted_password:
return True
Attackers can exploit these timing differences to recover passwords character by character.
Insufficient Entropy
FastAPI applications frequently use predictable random number generators for cryptographic operations. Using Python's random module instead of secrets module for token generation creates vulnerabilities:
# Vulnerable: predictable tokens
import random
def generate_token():
return ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=16))
The secrets module should always be used for cryptographic operations in FastAPI.
Fastapi-Specific Detection
Detecting cryptographic failures in FastAPI requires both static analysis and runtime scanning. middleBrick provides specialized detection for FastAPI applications, identifying vulnerabilities that traditional scanners miss.
Runtime Scanning with middleBrick
middleBrick's black-box scanning approach is particularly effective for FastAPI applications because it tests the actual running API without requiring source code access. The scanner identifies cryptographic weaknesses through active probing:
# Scan your FastAPI endpoint
middlebrick scan https://api.yourfastapp.com
The scanner tests for JWT vulnerabilities by attempting algorithm confusion attacks, checking for weak signature validation, and probing for endpoints that accept unsigned tokens.
Static Analysis Patterns
middleBrick's OpenAPI analysis identifies cryptographic issues in your FastAPI specification. For applications using FastAPI's auto-generated OpenAPI documentation, the scanner cross-references endpoint definitions with cryptographic requirements:
{
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
}
}
The scanner flags missing algorithm specifications, weak key lengths, and improper JWT configurations.
LLM-Specific Cryptographic Checks
For FastAPI applications serving LLM endpoints, middleBrick performs unique cryptographic assessments. Many FastAPI LLM applications expose system prompts or model configurations without proper authentication:
# Vulnerable FastAPI LLM endpoint
@app.get('/model/config')
async def get_model_config():
return {
'system_prompt': 'You are a helpful assistant...',
'temperature': 0.7,
'max_tokens': 4096
}
middleBrick detects these endpoints and tests for prompt injection vulnerabilities that could lead to data exfiltration.
CI/CD Integration
middleBrick's GitHub Action allows FastAPI teams to catch cryptographic failures before deployment:
- name: middleBrick Security Scan
uses: middleBrick/middleBrick-action@v1
with:
api_url: 'https://staging.yourfastapp.com'
fail_below_score: 'B'
token: ${{ secrets.MIDDLEBRICK_TOKEN }}
This integration ensures cryptographic regressions are caught in the development pipeline.
Fastapi-Specific Remediation
Remediating cryptographic failures in FastAPI requires both framework-specific solutions and general cryptographic best practices. Here are FastAPI-specific implementations:
Secure JWT Implementation
FastAPI's JWT handling should use strict algorithm validation and proper key management:
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from passlib.context import CryptContext
from datetime import datetime, timedelta
SECRET_KEY = os.getenv('SECRET_KEY', default=None)
ALGORITHM = 'HS256'
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='token')
# Use passlib for secure password hashing
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
async def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({'exp': expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def verify_token(token: str = Depends(oauth2_scheme)):
try:
# Only allow the specified algorithm
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except JWTError:
raise HTTPException(status_code=401, detail='Invalid authentication credentials')
This implementation uses a single, hardcoded algorithm and proper error handling.
Secure Secret Management
FastAPI applications should use environment variables with proper validation:
import os
from fastapi import FastAPI
from dotenv import load_dotenv
load_dotenv()
# Validate SECRET_KEY length
SECRET_KEY = os.getenv('SECRET_KEY')
if SECRET_KEY and len(SECRET_KEY) < 32:
raise ValueError('SECRET_KEY must be at least 32 bytes')
app = FastAPI()
Using python-dotenv ensures secrets are loaded from environment files during development while requiring explicit environment variables in production.
Timing Attack Prevention
FastAPI's authentication should use constant-time comparisons:
from hmac import compare_digest
async def verify_password(plain_password: str, hashed_password: str) -> bool:
return compare_digest(
pwd_context.verify(plain_password, hashed_password),
True
)
The compare_digest function prevents timing attacks by ensuring comparison time is independent of input values.
Secure Random Token Generation
FastAPI applications should use cryptographically secure random number generation:
import secrets
from fastapi import FastAPI
app = FastAPI()
async def generate_secure_token():
return secrets.token_urlsafe(32) # 256-bit secure token
async def generate_verification_code():
return secrets.randbelow(1000000) # 6-digit code
The secrets module provides cryptographically strong random numbers suitable for security-sensitive operations.
Proper Key Length and Algorithm Selection
FastAPI's cryptographic operations should use appropriate key lengths:
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
# Generate 2048-bit RSA keys (minimum recommended)
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
# Serialize keys securely
pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(b'mypassword')
)
Always use key sizes that meet current security standards—2048 bits for RSA, 256 bits for AES.