Prototype Pollution in Fastapi
How Prototype Pollution Manifests in Fastapi
Prototype Pollution in Fastapi applications occurs when untrusted data modifies JavaScript object prototypes, leading to unexpected behavior in Python-JavaScript bridges or when Fastapi interacts with JavaScript-based systems. This vulnerability is particularly relevant in Fastapi applications that handle JSON payloads or interact with JavaScript frontends.
The most common attack vector in Fastapi is through nested JSON objects where attackers exploit JavaScript's prototype chain. Consider this Fastapi endpoint:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/process-data")
def process_data(request: Request):
data = await request.json()
# Process nested data
result = data.get("user", {}).get("profile", {})
return resultAn attacker can send a payload like:
{
"user": {
"__proto__": {
"isAdmin": true
}
}
}In JavaScript environments, this would modify Object.prototype.isAdmin. While Python doesn't have prototype chains, Fastapi applications often serialize/deserialize data to/from JSON for JavaScript consumption, creating a bridge where prototype pollution can manifest.
Fastapi's dependency injection system can also be affected. Consider:
from fastapi import Depends
def get_user_info(user_data: dict = Depends()) -> dict:
# User data comes from request body
return user_data
@app.post("/user-profile")
def user_profile(user_info: dict = Depends(get_user_info)):
if user_info.get("isAdmin"):
return admin_dashboard()
return user_dashboard()If an attacker can manipulate the prototype chain through the dependency injection, they might bypass authorization checks.
Another Fastapi-specific scenario involves Pydantic models. Fastapi uses Pydantic for data validation, and prototype pollution can affect model behavior:
from fastapi import FastAPI
from pydantic import BaseModel
class UserProfile(BaseModel):
username: str
email: str
@app.post("/create-user")
def create_user(profile: UserProfile):
# Process user profile
return profile.dict()An attacker might try to exploit Pydantic's model validation by sending unexpected prototype properties that could affect validation logic or default values.
Fastapi-Specific Detection
Detecting Prototype Pollution in Fastapi applications requires both static code analysis and runtime monitoring. Here are Fastapi-specific detection strategies:
Input Validation with Pydantic Models
Fastapi's Pydantic integration provides the first line of defense. Create strict models that reject unexpected fields:
from pydantic import BaseModel, Field, ValidationError
from typing import Any
class StrictProfile(BaseModel):
username: str = Field(..., regex="^[a-zA-Z0-9_.-]{3,30}$")
email: str = Field(..., regex="^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
class Config:
extra = "forbid" # Reject any fields not explicitly definedThe extra = "forbid" setting prevents prototype pollution by rejecting unexpected properties.
Custom Request Parsing
Implement custom request parsing to detect suspicious patterns:
import re
from fastapi import Request, HTTPException
PROTO_PATTERN = re.compile(r"__(proto|constructor)__", re.IGNORECASE)
async def safe_json_request(request: Request):
data = await request.json()
if contains_prototype_pollution(data):
raise HTTPException(status_code=400, detail="Invalid request format")
return data
def contains_prototype_pollution(obj, path=""):
if isinstance(obj, dict):
for key, value in obj.items():
current_path = f"{path}.{key}" if path else key
if PROTO_PATTERN.search(key):
return True
if contains_prototype_pollution(value, current_path):
return True
elif isinstance(obj, list):
for index, item in enumerate(obj):
if contains_prototype_pollution(item, f"{path}[{index}]"):
return True
return FalseUsing middleBrick for Automated Detection
middleBrick's black-box scanning can detect Prototype Pollution vulnerabilities in Fastapi applications without requiring source code access. The scanner tests for:
- Prototype chain manipulation attempts in JSON payloads
- Unexpected property injection in API responses
- Authorization bypass through prototype manipulation
- Input validation bypass patterns
To scan your Fastapi API with middleBrick:
# Using the CLI
middlebrick scan https://your-fastapi-app.com/api/ --output report.json
# Or in CI/CD
middlebrick scan https://your-fastapi-app.com/api/ --fail-threshold CmiddleBrick specifically tests Fastapi's JSON handling by sending crafted payloads that attempt prototype pollution and analyzing the responses for successful manipulation.
Fastapi-Specific Remediation
Remediating Prototype Pollution in Fastapi applications requires a defense-in-depth approach. Here are Fastapi-specific solutions:
Strict Pydantic Models
The most effective defense is using strict Pydantic models with proper configuration:
from pydantic import BaseModel, Field, validator
from typing import Any
class SecureUserProfile(BaseModel):
username: str
email: str
@validator('username')
def validate_username(cls, v):
if not isinstance(v, str):
raise ValueError('Username must be a string')
if not re.match(r'^[a-zA-Z0-9_.-]{3,30}$', v):
raise ValueError('Invalid username format')
return v
@validator('email')
def validate_email(cls, v):
if not isinstance(v, str):
raise ValueError('Email must be a string')
if not re.match(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', v):
raise ValueError('Invalid email format')
return v
class Config:
extra = "forbid" # Reject any extra fields
allow_population_by_field_name = True
arbitrary_types_allowed = FalseRequest Body Sanitization
Implement middleware to sanitize incoming requests:
from fastapi import Request, HTTPException, Middleware
from fastapi.middleware import Middleware
class PrototypePollutionMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, request: Request, call_next):
if request.method in ['POST', 'PUT', 'PATCH']:
try:
body = await request.json()
if contains_prototype_pollution(body):
raise HTTPException(status_code=400, detail="Prototype pollution detected")
except Exception:
pass # Not JSON, skip validation
response = await call_next(request)
return response
app.add_middleware(PrototypePollutionMiddleware)Safe JSON Serialization
When returning data that originated from untrusted sources, ensure safe serialization:
from fastapi import FastAPI, Response
import json
app = FastAPI()
def safe_json_serialize(data):
# Remove any prototype-like properties before serialization
cleaned_data = remove_prototype_properties(data)
return json.dumps(cleaned_data)
def remove_prototype_properties(obj):
if isinstance(obj, dict):
return {k: remove_prototype_properties(v) for k, v in obj.items()
if not PROTO_PATTERN.search(k)}
elif isinstance(obj, list):
return [remove_prototype_properties(item) for item in obj]
else:
return obj
@app.post("/safe-endpoint")
def safe_endpoint(data: dict):
cleaned = remove_prototype_properties(data)
return Response(content=safe_json_serialize(cleaned), media_type="application/json")Continuous Monitoring with middleBrick
Integrate middleBrick into your Fastapi development workflow for ongoing protection:
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run middleBrick Scan
run: |
npm install -g middlebrick
middlebrick scan ${{ secrets.API_URL }} \
--fail-threshold B \
--output security-report.json
- name: Upload Report
uses: actions/upload-artifact@v2
with:
name: security-report
path: security-report.jsonThis GitHub Action scans your Fastapi API endpoints on every code change, ensuring prototype pollution vulnerabilities are caught early in the development cycle.