Phishing Api Keys in Django with Bearer Tokens
Phishing API Keys in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
In Django, developers commonly use HTTP Bearer Tokens to secure API endpoints. A Bearer Token is sent in the Authorization header as Authorization: Bearer <token>. When these tokens are improperly handled, they become targets for phishing. An attacker can craft a convincing social-engineering scenario (e.g., a fake admin portal or a spoofed support email) that tricks a user or an automated service into revealing the token. Because Bearer Tokens rely on secrecy of the token itself rather than additional context (such as client TLS certificates), any leak effectively grants full access to the scoped API resources until the token is rotated.
In a Django application, this risk is amplified when tokens are stored or logged insecurely. For example, if a token is placed in a request header and the Django app inadvertently includes it in logs, error messages, or referrer headers, a phishing site that captures logs or error reports can obtain the token. Additionally, if the Django backend exposes an endpoint that echoes the Authorization header for debugging, an attacker can use a phishing page to trick users into making a request that reveals their token via that endpoint.
The unauthenticated attack surface matters here because middleBrick scans test endpoints without credentials. If your Django API exposes token-intolerant behavior over HTTP (e.g., no HSTS, no secure cookie settings), a scanner can detect whether tokens are accepted over insecure channels and whether authorization is enforced consistently. Misconfigured CORS can also allow a malicious site to make authenticated requests from a victim’s browser, effectively phishing the token through cross-origin interactions.
Consider a real-world pattern: a Django REST Framework API that uses a custom Authorization header parser. If the parser falls back to accepting a token via a query parameter (e.g., ?access_token=xxx) for convenience, a phishing site can embed an image tag or script that loads the API URL with the token in the URL. URLs are often leaked in browser history, server logs, and Referer headers, which exposes the token. The scanner’s checks for Input Validation and Data Surface can surface these risky fallback pathways.
LLM/AI Security checks are particularly relevant in this scenario. An attacker may use prompt injection techniques to coax an AI-powered component in your Django service (such as a code assistant or internal summarizer) into outputting tokens or debug information. Output scanning can detect whether PII or API keys appear in responses, and active prompt injection probes can verify whether system prompt leakage or data exfiltration paths exist. This is why middleBrick’s LLM-specific tests complement traditional API security checks when assessing token-related risks.
Bearer Token-Specific Remediation in Django — concrete code fixes
Remediation focuses on ensuring Bearer Tokens are transmitted and stored securely, and that Django does not inadvertently expose them. Always serve APIs over HTTPS and enforce HSTS so tokens cannot be downgraded to HTTP. Use HttpOnly, Secure cookies only if storing tokens in browser-accessible storage; for mobile or SPA clients, prefer short-lived tokens stored in memory and rotated frequently.
Example 1: Secure Authorization header parsing in Django REST Framework
Ensure your authentication class only reads the token from the Authorization header and rejects tokens provided via query parameters or cookies.
import rest_framework.authentication
from rest_framework import exceptions
class BearerTokenAuthentication(rest_framework.authentication.BaseAuthentication):
def authenticate(self, request):
auth_header = request.headers.get('Authorization')
if not auth_header:
return None
# Only accept Bearer scheme
parts = auth_header.split()
if len(parts) != 2 or parts[0].lower() != 'bearer':
raise exceptions.AuthenticationFailed('Invalid Authorization header format.')
token = parts[1]
# Validate token with your backend (e.g., introspection or JWT decode)
# Do NOT fall back to request.query_params or request.COOKIES
user = self.validate_token(token)
if user is None:
raise exceptions.AuthenticationFailed('Invalid token.')
return (user, token)
def validate_token(self, token):
# Replace with actual validation logic
# e.g., verify JWT, call introspection endpoint
return {'id': 1} # simplified
Example 2: Reject tokens in URLs and enforce secure transmission
Configure Django settings and middleware to prevent tokens from appearing in URLs or logs.
# settings.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Disable query parameter debugging that may leak tokens
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'myapp.auth.BearerTokenAuthentication',
],
'ALLOW_SESSION_AUTHENTICATION': False,
}
Example 3: Avoid logging Authorization headers
Ensure request processing does not capture tokens in structured logs or error reports.
# middleware.py
import logging
logger = logging.getLogger(__name__)
class SafeLoggingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Redact Authorization header from logs
safe_headers = dict(request.headers)
safe_headers['Authorization'] = '[redacted]' if 'Authorization' in safe_headers else None
logger.debug('Request received', extra={'headers': safe_headers, 'path': request.path})
response = self.get_response(request)
return response
Example 4: Validate and scope tokens to prevent misuse
Use token introspection or JWT validation with audience and scope checks to limit what an exposed token can do.
import jwt
def validate_token(self, token):
try:
payload = jwt.decode(
token,
key='your-public-key-or-secret',
algorithms=['RS256'],
audience='my-api-audience',
issuer='https://auth.example.com/',
)
required_scope = 'api:read'
if required_scope not in payload.get('scope', '').split():
raise exceptions.AuthenticationFailed('Insufficient scope.')
return payload.get('user_id')
except jwt.ExpiredSignatureError:
raise exceptions.AuthenticationFailed('Token expired.')
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed('Invalid token.')