HIGH dns cache poisoningdjangomutual tls

Dns Cache Poisoning in Django with Mutual Tls

Dns Cache Poisoning in Django with Mutual Tls — how this specific combination creates or exposes the vulnerability

DNS cache poisoning (also known as DNS spoofing) occurs when an attacker injects a malicious DNS response that causes a resolver to cache a forged mapping between a hostname and an IP address. In a Django deployment that uses mutual TLS (mTLS), the client presents a certificate during the TLS handshake, but the initial network path to the backend service can still be subject to DNS manipulation if controls are incomplete.

Django itself does not perform DNS resolution for incoming requests in a way that is directly exploitable via cache poisoning, but the framework often relies on external services—such as databases, message brokers, or downstream APIs—identified by hostnames. If Django uses a hostname (for example, db.internal.example.com) to establish an mTLS connection and the hostname is resolved before the TLS handshake, an attacker on the network path could poison the resolver’s cache to point that hostname to a malicious server. Because mTLS requires client-side certificate validation, an attacker must also present a certificate trusted by the client to complete the handshake; however, if the client’s trust store is misconfigured or if hostname verification is not enforced, the poisoned DNS entry can lead to connections being redirected to an impersonator.

The combination of Django and mTLS can expose risk when DNS resolution occurs in the application or its dependencies and the following conditions are present:

  • Hostnames are used in mTLS destination configurations (e.g., {'host': 'api.internal.example.com', 'port': 443}) and are resolved at runtime rather than being overridden by IP addresses or strict certificate pinning.
  • The TLS client does not strictly validate the server hostname against the certificate’s Subject Alternative Name (SAN) or Common Name (CN).
  • DNS resolvers used by the runtime environment (e.g., the OS resolver or a library-level resolver) are not hardened against cache poisoning (e.g., lack of DNSSEC or randomized query IDs).

For example, if a Django service uses the requests library with an mTLS client certificate to call an external API and the DNS for the target hostname is poisoned, the application may unknowingly send traffic—including client certificates and sensitive data—to an attacker’s server. Even with mTLS, if the client does not enforce hostname verification, the server certificate presented by the attacker might be accepted, leading to a man-in-the-middle scenario despite the use of mutual authentication.

To mitigate this specific combination, treat hostnames used in mTLS configurations as critical as the certificates themselves. Use IP addresses where feasible, or ensure strict hostname verification, DNSSEC, and runtime environment hardening. The following sections outline concrete remediation steps tailored to Django with mTLS.

Mutual Tls-Specific Remediation in Django — concrete code fixes

Remediation focuses on preventing trust of poisoned DNS results by removing reliance on dynamic hostname resolution for mTLS endpoints, enforcing strict hostname verification, and ensuring the Django environment uses hardened TLS configurations.

1. Use IP addresses or local service references instead of dynamic hostnames

Where possible, avoid using hostnames that require DNS resolution for mTLS backend calls. Use IP addresses or service discovery mechanisms that bypass DNS caching. If you must use hostnames, resolve them once at startup and cache the IP in configuration, rather than resolving on every request.

# settings.py
import os

# Example: define the mTLS API target as an IP or a static override
MTLS_API_HOST = os.getenv('MTLS_API_HOST', '192.0.2.10')  # Prefer IP
MTLS_API_PORT = int(os.getenv('MTLS_API_PORT', '443'))

2. Enforce hostname verification in the TLS client

When using requests with mTLS, ensure that hostname verification is not disabled. Do not set verify=False or use a custom adapter that skips hostname checks. Instead, provide the CA bundle and let requests validate the server certificate against the hostname.

# utils/mtls_client.py
import requests

def get_mtls_session(cert_path, key_path, ca_bundle_path):
    session = requests.Session()
    session.cert = (cert_path, key_path)
    session.verify = ca_bundle_path  # Ensure verify is a path or boolean True
    # requests enforces hostname verification by default when verify=True/ca bundle is provided
    return session

def fetch_protected_resource():
    session = get_mtls_session('/path/client.crt', '/path/client.key', '/path/ca-bundle.pem')
    # Use a static or config-driven host; avoid runtime DNS lookups for sensitive endpoints
    response = session.get(f'https://{os.getenv("MTLS_API_HOST")}:{os.getenv("MTLS_API_PORT")}/api/data')
    response.raise_for_status()
    return response.json()

3. Pin server certificates or public keys for critical endpoints

For high-value targets, pin the server certificate or its public key. This ensures that even if DNS is poisoned, the attacker cannot present a valid pinned certificate.

# utils/pinned_client.py
import requests
import ssl

def pinned_session():
    session = requests.Session()
    session.cert = ('/path/client.crt', '/path/client.key')
    session.verify = '/path/ca-bundle.pem'
    # Example of certificate pinning by public key hash (requires additional logic in production)
    return session

4. Configure the Python environment and OS to favor DNSSEC and randomized queries

While Django does not directly control DNS behavior, ensure the runtime environment uses DNS resolvers that support DNSSEC and have source port randomization. In containerized deployments, configure the container’s DNS options to use trusted resolvers (e.g., 1.1.1.1 with DNSSEC enabled).

5. Validate and log suspicious TLS handshakes Use Django logging to capture TLS metadata for auditability. While this does not prevent poisoning, it helps detect anomalies such as unexpected certificate subjects or cipher suites when connecting to mTLS-enabled backends.

# settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'requests': {
            'handlers': ['console'],
            'level': 'WARNING',
            'propagate': False,
        },
    },
}

Frequently Asked Questions

Does using mTLS alone prevent DNS cache poisoning attacks against Django?
No. Mutual TLS protects the authenticity of the endpoints if certificate and hostname verification are enforced, but it does not prevent DNS cache poisoning. If hostnames are resolved dynamically and an attacker poisons the cache, connections can be redirected unless hostname verification is strict and DNS resolvers are hardened.
What is the most important step to secure Django mTLS integrations against DNS-based redirection?
Avoid reliance on runtime DNS resolution for mTLS backend targets. Use static IPs or resolve hostnames once at startup, enforce strict hostname verification in the TLS client, and pin certificates where feasible. Additionally, configure the runtime environment to use DNSSEC-capable resolvers.