Dns Cache Poisoning in Django with Hmac Signatures
Dns Cache Poisoning in Django with Hmac Signatures — how this specific combination creates or exposes the vulnerability
DNS cache poisoning compromises the integrity of domain name resolution by injecting falsified DNS responses into a resolver’s cache. When a Django application uses HMAC signatures to validate the provenance of data but relies on external services whose identities are resolved via DNS, the two issues intersect in subtle ways.
Consider a Django service that validates incoming HMAC-signed requests by verifying a signature against a shared secret. If the application performs a DNS lookup to locate a verification service (for example, to fetch a public key or to validate an origin hostname), a poisoned cache may return an attacker-controlled IP address. The application then routes verification traffic or retrieves keys from the malicious host, believing it is communicating with the legitimate service. Because the HMAC verification logic trusts the DNS‑resolved endpoint, the attacker can intercept or manipulate the verification path without invalidating the signature itself.
In practice, this scenario arises when custom hostname verification is combined with DNS‑dependent service discovery. An attacker who can poison the cache may redirect traffic to a server they control, and if the Django app uses the resolved hostname as part of its trust decisions (e.g., checking that the certificate matches the resolved hostname), the HMAC validation may still pass because the attacker can present a matching private key or manipulate what the app considers authoritative data. The HMAC signature remains valid, but the context that binds the signature to a specific network identity has been subverted.
Django’s own security facilities do not directly mitigate DNS cache poisoning; they assume the network path to external services is trustworthy. If you use HMAC signatures to protect API calls or webhook processing, and your code resolves hostnames dynamically, you must harden DNS handling independently. Threats like CVE‑2020‑26222 (a BIND vulnerability) and generic cache poisoning techniques illustrate why relying on unverified DNS responses is risky in security‑sensitive flows.
To reduce exposure, avoid using DNS for runtime trust decisions, pin hostnames or IPs where possible, and enforce strict certificate and hostname checks. Treat HMAC verification as strong integrity protection, but recognize that it does not protect against a compromised resolution path. Combine HMAC with secure transport, certificate pinning, and verified endpoint configuration to ensure that the entity you trust is truly the entity you intended.
Hmac Signatures-Specific Remediation in Django — concrete code fixes
Remediation focuses on removing reliance on DNS for trust, hardening endpoint verification, and ensuring HMAC checks operate over a validated, static destination. Below are concrete patterns you can adopt in Django.
1. Use a static, configured endpoint instead of DNS lookup
Define the verification service URL in settings and avoid runtime DNS resolution for security‑critical hosts.
import hmac
import hashlib
import os
from django.conf import settings
# settings.py
VERIFICATION_ENDPOINT = 'https://verify.example.com/webhook/' # static, DNS‑resolved at deploy time
SHARED_SECRET = os.getenv('VERIFICATION_SECRET')
def verify_signature(payload: bytes, received_signature: str) -> bool:
expected = hmac.new(
SHARED_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received_signature)
def webhook_view(request):
if request.method != 'POST':
return HttpResponseNotAllowed(['POST'])
payload = request.body
received = request.headers.get('X-Signature')
if not verify_signature(payload, received):
return HttpResponseForbidden()
# process trusted payload
return HttpResponse('OK')
2. Pin hostnames with custom HTTP adapters (optional)
If you must make outbound calls, pin the hostname by overriding the connection pool’s DNS resolution or using a verified session. This example uses requests with a fixed IP and SNI verification to avoid cache‑dependent routing.
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.sslmatch import CertificateMatching
class FixedResolverAdapter(HTTPAdapter):
def send(self, request, **kwargs):
# Ensure the request resolves to a pre‑approved IP
trusted_ip = '192.0.2.10' # your vetted IP
request.url = request.url.replace('verify.example.com', trusted_ip)
# Enforce strict hostname verification even with overridden URL
kwargs['verify'] = '/path/to/ca-bundle.pem'
return super().send(request, **kwargs)
session = requests.Session()
session.mount('https://', FixedResolverAdapter())
# Use session for verification calls
3. Enforce certificate and hostname checks for outbound TLS
When your HMAC verification involves fetching keys or confirming identity, always validate the server certificate and hostname, and avoid disabling verification.
import requests
response = requests.get(
'https://verify.example.com/keys/current.pem',
timeout=5,
verify=True, # use system CA bundle
# optionally provide a specific CA bundle:
# verify='/path/to/ca-bundle.pem'
)
response.raise_for_status()
public_key = response.content
4. Treat HMAC as one layer; combine with transport security
HMAC ensures payload integrity and authenticity, but it does not hide metadata or protect against DNS manipulation. Use HTTPS with strong cipher suites, and consider short lifetimes for secrets and signatures to limit exposure if a resolver is poisoned.
By removing dynamic DNS from the trust path, pinning endpoints, and validating certificates, you ensure that HMAC verification operates over a reliable, attacker‑controlled‑resistant channel. middleBrick scans can help surface risky patterns such as dynamic host resolution in outbound calls; the Pro plan’s continuous monitoring can alert you if configurations drift.