Type Confusion in Fastapi
How Type Confusion Manifests in Fastapi
Type confusion in FastAPI occurs when the framework's type hints and Pydantic models are bypassed or manipulated, allowing attackers to exploit mismatched data types between what the API expects and what it processes. This vulnerability is particularly dangerous in FastAPI because of its strong typing system and automatic data validation.
Consider this FastAPI endpoint:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Union
app = FastAPI()
class User(BaseModel):
id: int
name: str
@app.post("/user/")
async def create_user(user: User):
return {"user_id": user.id, "message": "User created"}
An attacker can exploit type confusion by sending a string where an integer is expected:
{
"id": "0x1234",
"name": "malicious"
}
FastAPI's Pydantic model will attempt to coerce this string to an integer, potentially resulting in unexpected behavior or security issues. The coercion might succeed (0x1234 becomes 4660) or fail, but the failure handling itself can be exploited.
A more subtle attack involves leveraging FastAPI's Union types:
from typing import Union
class Payment(BaseModel):
amount: Union[int, float]
method: str
Here, an attacker might send a string that looks numeric but contains malicious content, exploiting how FastAPI's JSON parser handles type conversion. The framework's automatic type coercion can lead to unexpected behavior when combined with database operations or business logic.
FastAPI's dependency injection system can also be vulnerable. Consider:
from fastapi import Depends
async def get_user_id(user_id: int = Depends()) -> int:
return user_id
@app.get("/user/{user_id}")
async def get_user_profile(user_id: int = Depends(get_user_id)):
# Database query using user_id
return {"user_id": user_id}
If the dependency injection system doesn't properly validate types, an attacker could manipulate the parameter resolution, potentially leading to SQL injection or authorization bypass.
FastAPI's automatic OpenAPI generation can also leak information about type expectations, helping attackers craft targeted payloads. The generated schema shows exactly what types are expected, making it easier to craft type confusion attacks.
FastAPI-Specific Detection
Detecting type confusion in FastAPI requires both static analysis and runtime testing. The framework's strong typing system actually makes some vulnerabilities easier to spot if you know what to look for.
Static Analysis with middleBrick:
middleBrick's scanner can identify type confusion vulnerabilities by analyzing your FastAPI application's type hints and Pydantic models. The scanner examines:
- Parameter type annotations in route handlers
- Pydantic model field types and their validation logic
- Union type usage that might allow unexpected type coercion
- Database query patterns that might be vulnerable to type manipulation
- Automatic OpenAPI schema generation for type information leakage
The scanner tests each endpoint with crafted payloads designed to trigger type coercion failures and examines the responses for inconsistent behavior or error messages that reveal implementation details.
Runtime Testing Approach:
Beyond static analysis, you need runtime testing to catch type confusion vulnerabilities. This involves:
import pytest
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_type_confusion():
# Test string where integer expected
response = client.post("/user/", json={"id": "malicious_string", "name": "test"})
assert response.status_code == 422 # Should reject invalid type
# Test numeric string with malicious content
response = client.post("/user/", json={"id": "0x1234", "name": "test"})
assert response.status_code in [200, 422] # Should handle gracefully
# Test Union type confusion
response = client.post("/payment/", json={"amount": "1000.00", "method": "credit"})
assert response.status_code in [200, 422]
middleBrick's continuous monitoring feature can automatically run these tests on a schedule, alerting you when new type confusion vulnerabilities are introduced.
Database Layer Detection:
Type confusion often manifests at the database layer. middleBrick can scan for patterns like:
# Vulnerable pattern
user_id = int(request.query_params.get('user_id', 0))
query = f"SELECT * FROM users WHERE id = {user_id}"
The scanner identifies when type conversions happen without proper validation, especially in SQL query construction.
FastAPI-Specific Remediation
Remediating type confusion in FastAPI requires a multi-layered approach that leverages the framework's built-in features while adding additional validation where necessary.
Strict Pydantic Validation:
Use Pydantic's strict mode to prevent unwanted type coercion:
from pydantic import BaseModel, Field
from typing import Union
class User(BaseModel):
id: int = Field(..., ge=1) # Positive integer only
name: str
class Config:
# Prevent type coercion
extra = "forbid"
# Use strict types
arbitrary_types_allowed = False
The strict mode ensures that Pydantic won't attempt to coerce incompatible types, rejecting them outright instead.
Custom Validators:
Add custom validators to handle specific type validation requirements:
from pydantic import validator
class Payment(BaseModel):
amount: Union[int, float]
method: str
@validator('amount')
def amount_must_be_positive(cls, v):
if isinstance(v, str):
try:
v = float(v)
except ValueError:
raise ValueError('Amount must be numeric')
if v <= 0:
raise ValueError('Amount must be positive')
return v
This ensures that even if a string is received, it's properly validated before processing.
Route-Level Validation:
Add explicit validation at the route level:
from fastapi import HTTPException
from typing import Annotated
@app.post("/user/")
async def create_user(user: Annotated[User, Depends()]):
if not isinstance(user.id, int):
raise HTTPException(
status_code=400,
detail="User ID must be an integer"
)
return {"user_id": user.id}
Database Query Safety:
Use parameterized queries instead of string formatting:
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@app.get("/user/{user_id}")
async def get_user_profile(user_id: int, db: AsyncSession = Depends(get_db)):
stmt = select(User).where(User.id == user_id)
result = await db.execute(stmt)
return result.scalar_one_or_none()
This prevents type confusion from leading to SQL injection.
Comprehensive Testing:
Implement comprehensive test coverage for type handling:
import pytest
from fastapi.testclient import TestClient
@pytest.mark.parametrize("payload,expected_status", [
({"id": "123", "name": "test"}, 200), # Should coerce
({"id": "malicious", "name": "test"}, 422), # Should reject
({"id": 123.45, "name": "test"}, 200), # Should coerce
({"id": None, "name": "test"}, 422), # Should reject
])
async def test_user_creation(payload, expected_status):
response = client.post("/user/", json=payload)
assert response.status_code == expected_status
middleBrick's Pro plan includes automated testing that runs these scenarios continuously, ensuring type confusion vulnerabilities don't creep back into your codebase.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |