Auth Bypass in Fastapi
How Auth Bypass Manifests in Fastapi
Authentication bypass in Fastapi applications often stems from subtle implementation mistakes that can have severe consequences. One common pattern involves improper use of dependency injection. Consider this vulnerable Fastapi route:
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordBearer
from pydantic import BaseModel
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")
class Item(BaseModel):
name: str
description: str
app = FastAPI()
async def get_current_user(token: str = Depends(oauth2_scheme)):
# Implementation that might not raise an exception on failure
return None
@app.post("/items/")
async def create_item(item: Item, current_user: Any = Depends(get_current_user)):
return {"item": item.dict(), "user": current_user}
The vulnerability here is that the get_current_user dependency doesn't properly handle authentication failures. If it returns None instead of raising an HTTPException, Fastapi will still process the request with an unauthenticated user.
Another Fastapi-specific auth bypass pattern involves misconfigured CORS settings. Fastapi's CORS middleware can be improperly configured:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
This configuration allows any origin to make credentialed requests, potentially enabling cross-site request forgery (CSRF) attacks that bypass authentication checks.
Fastapi's path parameter handling can also introduce auth bypass vulnerabilities. Consider this route:
@app.get("/users/me")
async def read_user_me(current_user: User = Depends(get_current_user)):
return current_user
@app.get("/users/{user_id}")
async def read_user(user_id: int):
# No authentication dependency
user = get_user_by_id(user_id)
return user
If the /users/me endpoint is protected but /users/{user_id} isn't, an attacker can enumerate user IDs and bypass authentication entirely.
Fastapi-Specific Detection
Detecting authentication bypass vulnerabilities in Fastapi requires both static analysis and runtime testing. For static analysis, look for these Fastapi-specific patterns:
Missing authentication dependencies in route handlers:
# Vulnerable - no Depends() for auth
@app.get("/admin/")
async def admin_dashboard():
return admin_panel
# Secure - proper dependency injection
@app.get("/admin/")
async def admin_dashboard(current_user: User = Depends(get_current_user)):
return admin_panel
Improper exception handling in dependencies:
# Vulnerable - doesn't raise on auth failure
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = verify_token(token)
return user # Returns None on failure
# Secure - raises HTTPException on failure
async def get_current_user(token: str = Depends(oauth2_scheme)):
user = verify_token(token)
if user is None:
raise HTTPException(status_code=401, detail="Unauthorized")
return user
For runtime detection, middleBrick's black-box scanning approach is particularly effective for Fastapi applications. The scanner tests unauthenticated endpoints by sending requests to your Fastapi routes and analyzing responses. For authentication bypass detection, middleBrick:
- Attempts to access protected endpoints without credentials
- Tests for IDOR vulnerabilities by modifying user IDs in path parameters
- Checks for missing authentication dependencies by analyzing route structure
- Tests CORS configurations for permissive settings
middleBrick's OpenAPI spec analysis is especially valuable for Fastapi since the framework auto-generates OpenAPI specs. The scanner cross-references your spec with actual runtime behavior to identify discrepancies that might indicate auth bypass vulnerabilities.
Fastapi-Specific Remediation
Securing Fastapi applications against authentication bypass requires implementing proper authentication patterns. Here are Fastapi-specific remediation techniques:
1. Use Fastapi's dependency injection system correctly:
from fastapi import HTTPException, status
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("sub")
if user_id is None:
raise credentials_exception
user = get_user_by_id(user_id)
if user is None:
raise credentials_exception
return user
except JWTError:
raise credentials_exception
2. Create reusable authentication dependencies:
async def get_current_active_user(current_user: User = Depends(get_current_user)):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
async def get_current_active_superuser(current_user: User = Depends(get_current_active_user)):
if current_user.role != "superuser":
raise HTTPException(status_code=400, detail="The user doesn't have enough privileges")
return current_user
3. Use Fastapi's built-in security features:
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer
bearer_scheme = HTTPBearer()
def verify_token(token: str = Depends(bearer_scheme)):
try:
payload = jwt.decode(token.credentials, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
4. Implement proper CORS configuration:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourdomain.com"], # Specific origins only
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Authorization", "Content-Type"],
)
5. Use middleware for global authentication checks:
from fastapi import Request, Response
async def auth_middleware(request: Request, call_next):
if not is_authenticated(request):
return JSONResponse(
status_code=401,
content={"detail": "Not authenticated"},
)
response = await call_next(request)
return response
app.add_middleware(auth_middleware)
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |