Shellshock in Django with Bearer Tokens
Shellshock in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Shellshock (CVE-2014-6271 and related variants) is a command injection vulnerability in the Bash shell that arises from improper environment variable handling. When a Django application relies on external processes or CGI-like behavior—such as invoking system utilities or subprocesses with data derived from HTTP headers—an attacker can inject malicious code by controlling environment variables. In API contexts that use Bearer Tokens, this risk emerges when tokens are passed via headers and then forwarded to subprocesses or logged in ways that become part of the environment.
Consider a scenario where Django authenticates requests using an Authorization header (e.g., Bearer token_value) and passes the token into a subprocess call for validation or enrichment. If the implementation uses functions like os.popen, subprocess.Popen, or os.system and includes the header value in the environment, an attacker can craft a token containing Bash-specific syntax. For example, a token like value; malicious_command or value{ echo injected; } can lead to arbitrary command execution when the subprocess invokes Bash to interpret variables. This is particularly dangerous if logging or debugging captures the full Authorization header and passes it to shell commands, effectively turning the Bearer Token into an injection vector.
Django’s own request handling does not directly invoke Bash, but integrations—such as custom authentication backends, management commands, or WSGI middleware—that shell out to external utilities can expose the attack surface. The combination of Bearer Tokens and Shellshock is hazardous because tokens often carry high privilege or traceability, and their uncontrolled use in shell contexts bypasses Django’s typical protections like CSRF middleware and model-level permissions. Moreover, if an API endpoint returns sensitive data and is also exposed to unauthenticated scanning (as in black-box tests), an attacker can probe for command injection without credentials, leveraging the token’s presence in environment variables to confirm exploitability.
OpenAPI/Swagger specifications (2.0, 3.0, 3.1) that define security schemes for Bearer Tokens must be carefully reviewed alongside runtime behavior. While the spec describes where tokens appear, it does not prevent developers from mishandling them in subprocess logic. Cross-referencing spec definitions with runtime findings helps identify whether Authorization headers are propagated to unsafe execution paths. This is where tools that scan the unauthenticated attack surface—testing endpoints without credentials—can surface risky patterns, especially when outputs are inspected for PII or code execution indicators.
Real-world patterns include using subprocess.run(['curl', '-H', f'Authorization: Bearer {token}'], ...) where token originates from request.META.get('HTTP_AUTHORIZATION'). If token is not strictly validated, an attacker-controlled token can break argument boundaries and introduce shell metacharacters. Even when using higher-level libraries, environment leakage through environ in WSGI servers can propagate the token to child processes, enabling Shellshock-style injection if any linked utility invokes Bash.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
Remediation focuses on preventing Bearer Token values from reaching shell contexts and ensuring strict input validation. The safest approach is to avoid shell invocation entirely. Replace subprocess calls with native HTTP clients or cryptographic verification. For example, instead of passing a token to a shell command, use Python’s requests library to call an introspection endpoint, or validate tokens locally using libraries like PyJWT for JWTs.
Example 1: Unsafe subprocess with Bearer Token (vulnerable)
import subprocess
import os
from django.http import HttpRequest
def handle_request(request: HttpRequest):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if auth.startswith('Bearer '):
token = auth[7:]
# Dangerous: token may contain shell metacharacters
subprocess.run(['curl', '-H', f'Authorization: Bearer {token}'], check=True)
Example 2: Safe alternative using requests (remediated)
import requests
from django.http import HttpRequest
def handle_request_safe(request: HttpRequest):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if auth.startswith('Bearer '):
token = auth[7:]
# Safe: no shell involvement; explicit header injection
headers = {'Authorization': f'Bearer {token}'}
response = requests.get('https://auth.example.com/introspect', headers=headers, timeout=5)
response.raise_for_status()
Example 3: Token validation without shell execution
import jwt
from django.http import HttpRequest, HttpResponseForbidden
def validate_token(request: HttpRequest):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if not auth.startswith('Bearer '):
return HttpResponseForbidden()
token = auth[7:]
try:
# Local validation; no subprocess or shell
payload = jwt.decode(token, options={'verify_signature': False})
# Apply business logic checks here
return payload
except jwt.PyJWTError:
return HttpResponseForbidden()
General hardening practices
- Never interpolate headers into shell commands; use native libraries.
- Sanitize and validate all Authorization header values before any use, even in logging.
- Ensure WSGI servers and reverse proxies do not forward Authorization headers to CGI-like subprocesses inadvertently.
- Use Django’s built-in authentication views and token handling rather than custom shell integrations.
These changes eliminate the path from Bearer Tokens to Bash environment manipulation while preserving the intended authentication flow. By focusing on secure token handling and avoiding shell constructs, you mitigate Shellshock risks specific to API authentication patterns.