Side Channel Attack in Fastapi with Basic Auth
Side Channel Attack in Fastapi with Basic Auth — how this combination creates or exposes the vulnerability
A side channel attack in FastAPI using HTTP Basic Auth exploits timing or behavioral differences in how the server validates credentials, rather than a direct flaw in the authentication algorithm itself. When Basic Auth is used, the client sends an Authorization header containing a base64-encoded username:password string. FastAPI typically decodes this header and compares the provided credentials against a backend store or constant value. If the comparison is performed with a naive string equality check, the server’s response time can vary depending on how many characters of the secret match the expected value.
Consider a FastAPI endpoint that validates a hardcoded password using Python’s == operator. Python’s string comparison is short-circuiting: it stops evaluating as soon as a mismatch is found. An attacker can measure response times across many requests, sending a guessed password character by character. Correctly matched prefixes yield slightly slower responses because the comparison proceeds to the next character, while incorrect guesses fail early. By aggregating timing measurements, the attacker can infer the correct password or hash byte by byte. This is a classic timing side channel, and it becomes practical even without local network access if the API is reachable over the internet.
The risk is compounded when the Basic Auth credentials are used to gate sensitive operations or to derive downstream tokens, because successful inference effectively bypasses authentication. Unlike network-layer attacks, side channel attacks often require minimal privileges—sometimes only unauthenticated access to the endpoint—and they leave few traces in standard logs. MiddleBrick’s security checks include timing-related heuristics among its 12 parallel scans, helping to surface inconsistent response behavior that may indicate a side channel. Because FastAPI applications commonly expose authentication via public endpoints during development or testing, this combination of Basic Auth and implicit timing behavior is particularly noteworthy.
Other side channels can manifest at the protocol or implementation level. For example, an API that reveals whether a username exists before checking the password introduces an account enumeration side channel, which can be chained with timing attacks to accelerate credential inference. Similarly, error messages that differ between ‘user not found’ and ‘invalid password’ provide actionable feedback to an attacker. In architectures where FastAPI services sit behind load balancers or reverse proxies, inconsistent network conditions can mask timing signals, but well-crafted statistical analysis can still recover useful information. The key takeaway is that transport-layer protections like TLS do not prevent side channels; they only protect confidentiality in transit, not timing or behavioral leakage in the application logic.
Basic Auth-Specific Remediation in Fastapi — concrete code fixes
To mitigate side channel attacks in FastAPI with Basic Auth, you must ensure constant-time comparison and avoid leaking information through timing, error messages, or conditional branches. The following approaches are practical and can be integrated into existing FastAPI applications without requiring architectural overhaul.
Constant-time credential comparison
Replace standard equality checks with a constant-time comparison routine. Python’s hmac.compare_digest is designed for this purpose and should be used to compare decoded credentials or derived hashes. Even when using third-party authentication libraries, verify that they employ constant-time primitives internally.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import secrets
import hmac
app = FastAPI()
security = HTTPBasic()
# A stored secret should be a constant-time comparable value, e.g., a hash
EXPECTED_USER = "admin"
EXPECTED_PASS_HASH = secrets.token_bytes(32) # In practice, use a proper KDF
def verify_credentials(credentials: HTTPBasicCredentials) -> bool:
# Simulate a stored salt and derived key; here we use a fixed hash for example
# In production, use argon2, scrypt, or PBKDF2
if credentials.username != EXPECTED_USER:
# Still perform a dummy comparison to avoid timing leaks on username
hmac.compare_digest(b"dummy", EXPECTED_PASS_HASH)
return False
# Use constant-time comparison for the password component
return hmac.compare_digest(credentials.password.encode("utf-8"), EXPECTED_PASS_HASH)
@app.get("/secure")
def read_secure(credentials: HTTPBasicCredentials = Depends(security)):
if not verify_credentials(credentials):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
headers={"WWW-Authenticate": "Basic"},
)
return {"message": "Authenticated"}
This pattern ensures that both username and password checks take roughly the same amount of time, reducing information leakage. Note that the example uses a fixed hash for illustration; in production, you should use a memory-hard key derivation function with a per-user salt stored in a secure database.
Uniform error handling and response behavior
Make responses identical regardless of whether a username is valid or a password is incorrect. Return the same HTTP status code and body shape, and avoid branching logic that changes content based on credential validity. This prevents account enumeration and reduces the attacker’s ability to correlate timing with specific conditions.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import hmac
app = FastAPI()
security = HTTPBasic()
# Dummy secret to simulate a stored value
DUMMY_HASH = b"\x00" * 32
def safe_check(credentials: HTTPBasicCredentials) -> bool:
# Always perform a comparison of similar length to prevent timing leaks
username_ok = hmac.compare_digest(credentials.username.encode("utf-8"), b"admin")
password_ok = hmac.compare_digest(credentials.password.encode("utf-8"), DUMMY_HASH)
return username_ok and password_ok
@app.get("/login")
def login(credentials: HTTPBasicCredentials = Depends(security)):
if not safe_check(credentials):
# Same response for any failure
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Unauthorized",
headers={"WWW-Authenticate": "Basic"},
)
return {"ok": True}
Additionally, ensure that logging does not inadvertently disclose partial credentials or usernames. MiddleBrick’s checks for unsafe consumption and information exposure can help identify endpoints where error messages or logs create side channels.
Operational practices
Use HTTPS to protect credentials in transit, but remember that TLS does not eliminate side channels at the application layer. Rotate credentials regularly and avoid embedding them in source code. If possible, migrate from Basic Auth to token-based schemes with built-in protections against timing and replay attacks. MiddleBrick’s dashboard and CLI can be used to track security scores over time and integrate scans into CI/CD pipelines, ensuring that regressions in authentication handling are caught before deployment.