Dangling Dns in Django with Bearer Tokens
Dangling Dns in Django with Bearer Tokens — how this specific combination creates or exposes the vulnerability
A dangling DNS record occurs when a hostname (e.g., internal.api.example.com) still resolves in DNS but no longer points to a controlled service. In Django, if your application uses bearer tokens for authorization and resolves such a hostname at runtime (for example, when calling an internal service or webhook), an attacker who can control the dangling DNS target may intercept or manipulate traffic. This can lead to privilege escalation, information exposure, or unauthorized actions when the token is passed to the resolved endpoint.
Consider a scenario where Django uses a bearer token to call an internal API discovered via a service name that later becomes dangling. The token is typically passed in the Authorization header. If the DNS record for that service resolves to an attacker-controlled server, any request carrying the bearer token can be intercepted. This is especially risky when the token has scopes or permissions beyond what the calling code intends (for example, a token with write access). The vulnerability is not in token generation or storage, but in the runtime resolution chain combined with how the token is used.
An example of unsafe usage in Django:
import requests
from django.conf import settings
def call_internal_service(user_id):
url = f"https://internal.api.example.com/users/{user_id}"
headers = {"Authorization": f"Bearer {settings.INTERNAL_API_TOKEN}"}
response = requests.get(url, headers=headers, timeout=5)
return response.json()
If internal.api.example.com is a dangling DNS record and resolves to an attacker server, the bearer token (settings.INTERNAL_API_TOKEN) is sent to an untrusted host. This exposes the token and can lead to unauthorized access to downstream services. The risk is amplified if the token is long-lived or has broad permissions, and if the application does not validate the server’s identity (e.g., via strict certificate pinning or verified hostname checks).
Django does not inherently protect against dangling DNS when making outbound requests. Even when using bearer tokens, developers must ensure that hostnames are reliably resolved to trusted infrastructure and that tokens are scoped to the minimal required audience. MiddleBrick’s scans include checks for unresolved or dangling hostnames in runtime configurations and for overly permissive use of bearer tokens in outbound calls, helping you detect these misconfigurations before they are exploited.
Bearer Tokens-Specific Remediation in Django — concrete code fixes
Remediation focuses on two aspects: ensuring DNS and endpoint resolution are reliable and restricting how bearer tokens are used in outbound calls. Below are concrete, safe patterns for Django.
1. Use a verified, static endpoint or service discovery mechanism. Instead of relying on dynamic DNS lookups for sensitive calls, prefer a fixed, well-known hostname or use an internal service registry that you control. If you must use a hostname that could change, pin it to an IP or use a configuration that is validated at startup.
2. Scope bearer tokens to specific audiences and validate outbound targets. Never use a broad internal token for arbitrary outbound calls. Use short-lived tokens and validate the request URL against an allowlist.
3. Enforce TLS and verify certificates. Do not disable verification. Use a CA bundle and, where possible, pin the certificate or public key for critical endpoints.
4. Centralize HTTP calls behind a secure client that enforces policies. This makes it easier to audit and apply security controls consistently.
Safe code example in Django:
import requests
from django.conf import settings
from requests.exceptions import SSLError, ConnectionError
ALLOWED_API_HOSTS = {"api.internal.example.com"} # strict allowlist
def call_internal_service(user_id):
host = "api.internal.example.com"
if host not in ALLOWED_API_HOSTS:
raise ValueError("Unauthorized outbound host")
url = f"https://{host}/users/{user_id}"
token = settings.INTERNAL_API_TOKEN
headers = {"Authorization": f"Bearer {token}"}
try:
response = requests.get(
url,
headers=headers,
timeout=5,
verify=True, # ensure TLS verification is enabled
)
response.raise_for_status()
return response.json()
except (SSLError, ConnectionError) as e:
# Log securely and fail closed
raise RuntimeError("Secure communication failed") from e
If you manage many services, consider a centralized HTTP client with connection pooling and consistent timeout/retry policies. For token handling, use short-lived bearer tokens obtained via a trusted mechanism (e.g., OAuth2 client credentials) and avoid storing them in settings files that may be exposed. MiddleBrick’s scans flag outbound calls that use bearer tokens to unresolved or non-allowlisted hosts and help you prioritize fixing dangling DNS or hostname resolution issues.