Dns Cache Poisoning in Django with Basic Auth
Dns Cache Poisoning in Django with Basic Auth — how this specific combination creates or exposes the vulnerability
DNS Cache Poisoning can affect Django applications that use Basic Authentication, particularly when authentication requests or redirects rely on hostnames that are resolved through a shared or compromised DNS resolver. If an attacker can poison DNS records for your domain or an upstream service, they may redirect authentication traffic to a malicious host that captures credentials or tokens passed in the Authorization header. In Django, Basic Auth is often enforced via middleware or view decorators that inspect the Authorization header and validate credentials against the database. When DNS resolution is manipulated, the client may unknowingly send credentials to an attacker-controlled endpoint, bypassing intended network boundaries and exposing authentication material even if the application itself uses HTTPS.
Consider a Django service that authenticates against an internal API using Basic Auth. If the internal API hostname is resolved via DNS and that record is poisoned, the client may establish a TLS session with an attacker host presenting a valid certificate for a different domain. Because the Authorization header is sent with each request, the attacker can harvest credentials. This risk is compounded in environments using service discovery or dynamic host resolution where DNS caching is aggressive. Django’s HTTP client (e.g., requests or urllib) will follow redirects and cache DNS results based on system or resolver settings, potentially trusting poisoned responses for the duration of the cache TTL.
Additionally, if your Django application serves APIs that accept host-based routing or virtual hosting, an attacker who poisons DNS to point a legitimate domain to a malicious server can intercept unauthenticated or weakly authenticated flows before Basic Auth credentials are validated. This is especially relevant when endpoints are constructed dynamically using hostname information or when CORS policies rely on origin hostnames. The vulnerability is not in Basic Auth itself but in the dependency on DNS for routing and service discovery. Using fixed IPs, strict Host header validation, and pinned TLS certificates can reduce exposure, but DNS Cache Poisoning remains a concern when external resolvers are involved.
Basic Auth-Specific Remediation in Django — concrete code fixes
To mitigate DNS Cache Poisoning risks when using Basic Authentication in Django, focus on reducing reliance on dynamic DNS resolution and hardening how credentials are transmitted and validated. Use fixed, well-known endpoints with IP-based routing where feasible, and enforce strict Host header validation in your Django settings. Configure your HTTP client to avoid following redirects to different hosts and disable caching of DNS-derived connections for sensitive authentication flows.
Below are concrete code examples for securing Basic Auth in Django.
1. Enforce Host header validation
Ensure incoming requests target the expected host to prevent host-based redirection attacks.
from django.http import HttpResponseBadRequest
from django.utils.deprecation import MiddlewareMixin
class HostHeaderValidationMiddleware(MiddlewareMixin):
ALLOWED_HOSTS = {'api.example.com', 'admin.example.com'}
def process_request(self, request):
if request.get_host().split(':')[0] not in self.ALLOWED_HOSTS:
return HttpResponseBadRequest('Invalid host header')
2. Use HTTP client with strict settings
When Django makes outbound requests using Basic Auth, configure the session to avoid insecure redirects and hostname mismatches.
import requests
from requests.auth import HTTPBasicAuth
session = requests.Session()
session.max_redirects = 0 # avoid following redirects to untrusted hosts
session.verify = '/path/to/custom/ca-bundle.pem' # pin trusted CA
response = session.get(
'https://api.example.com/v1/endpoint',
auth=HTTPBasicAuth('username', 'secure-password')
)
3. Avoid dynamic hostname construction
Do not build URLs from user input or mutable headers. Use hardcoded or configuration-based endpoints.
from django.conf import settings
import requests
from requests.auth import HTTPBasicAuth
def call_authenticated_service(user, password):
url = settings.AUTH_SERVICE_URL # configured in settings, not user-supplied
resp = requests.get(
url,
auth=HTTPBasicAuth(user, password),
timeout=5
)
resp.raise_for_status()
return resp.json()
4. Use HTTPS with certificate pinning
Pin the server certificate or public key to prevent attackers from using compromised DNS to present a valid but fraudulent cert.
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.example.com/secure',
auth=HTTPBasicAuth('user', 'pass'),
cert=('/path/client.crt', '/path/client.key'),
verify='/path/server-ca.pem'
)