Dns Cache Poisoning with Hmac Signatures
How DNS Cache Poisoning Manifests in HMAC Signatures
HMAC signatures are a cornerstone of API authentication, providing integrity and authenticity by using a shared secret key. A critical vulnerability arises when an application retrieves this secret key—or the endpoint that validates it—from a DNS-resolvable hostname without validating the DNS response's authenticity. DNS cache poisoning (CVE-2020-1350, CVE-2021-25216) allows an attacker to corrupt a DNS resolver's cache, redirecting a domain (e.g., auth.internal.company.com) to an attacker-controlled IP. If the API client code fetches the HMAC verification key or interacts with an authentication service via this hostname, the attacker can intercept the request, exfiltrate the HMAC key, or serve a malicious endpoint that accepts forged signatures.
The attack manifests in specific code paths where DNS resolution is implicit and unvalidated. Consider an API client that loads an HMAC key from a configuration service:
import requests
# Vulnerable: fetches HMAC key from a DNS-resolved hostname without validation
response = requests.get('https://auth-service.internal/hmac-key')
hmac_key = response.text
# Later, the key is used to sign requests
import hmac, hashlib
signature = hmac.new(hmac_key.encode(), request_body, hashlib.sha256).hexdigest()If auth-service.internal is poisoned to resolve to attacker.com, the attacker receives the HMAC key. Alternatively, if the API endpoint itself uses a DNS-resolvable hostname in its signature verification logic (e.g., fetching public keys from a JWKS endpoint at https://keys.api.company.com/.well-known/jwks.json), poisoning that hostname lets the attacker replace legitimate keys with their own, bypassing signature validation entirely. This is a form of substitution of trust: the HMAC mechanism remains cryptographically sound, but the anchor of trust (the key source) is compromised via DNS.
Hmac Signatures-Specific Detection
Detecting DNS-dependent HMAC key retrieval requires analyzing both the API's specification and its runtime behavior. middleBrick's scanner tests for unvalidated DNS reliance by:
- OpenAPI/Swagger Analysis: Parsing the spec for external
$refURLs (e.g.,externalValue: 'https://auth.example.com/schema.json') that point to hostnames used in security schemes likeapiKey: in: headerwithname: X-HMAC-Signature. If the key source is a DNS-resolvable domain without certificate pinning or DNS pinning indicators, it flags a potential DNS poisoning vector. - Runtime Probing: Sending requests to the API and observing if the response includes hints of external key fetching (e.g.,
WWW-Authenticate: HMAC key URL="https://keys.example.com"). The scanner also attempts to resolve the hostname to multiple IPs and checks for inconsistent responses—a sign of DNS manipulation. - Configuration Checks: Looking for absence of DNS pinning (e.g., hardcoded IPs,
dns.resolverwith fixed nameservers) or certificate pinning in client-side code (if accessible via static analysis of frontend assets).
A middleBrick scan of a vulnerable endpoint might produce a finding like:
| Finding | Severity | Category |
|---|---|---|
| HMAC key source relies on DNS without validation | High | Authentication |
You can run this check yourself via the CLI:
middlebrick scan https://api.example.comThe report will highlight any DNS-dependent HMAC key sources, map them to OWASP API Top 10:API2:2023 (Broken Authentication), and provide a severity score based on exposure. The Pro plan's continuous monitoring can alert you if DNS configurations drift to become vulnerable.
Hmac Signatures-Specific Remediation
Remediation focuses on eliminating single-point DNS failures in the HMAC key management chain. The goal is to ensure the key source is immutable and validated, regardless of DNS state.
1. DNS Pinning (Certificate-Based): Resolve the hostname once at startup and cache the IP, or use a DNS resolver with a fixed nameserver (e.g., internal DNS) that is not externally accessible. In Python, use dnspython with a specific resolver:
import dns.resolver
# Pin DNS resolution to a trusted internal resolver
resolver = dns.resolver.Resolver()
resolver.nameservers = ['10.0.0.1'] # Internal DNS server IP
answers = resolver.resolve('auth-service.internal', 'A')
ip = answers[0].address
# Fetch HMAC key from the pinned IP, bypassing DNS
response = requests.get(f'https://{ip}/hmac-key', verify='/path/to/cert.pem')
# Verify the certificate's CN/SAN matches 'auth-service.internal' to prevent MITM2. Certificate Pinning: Pin the TLS certificate of the key service. This defeats DNS poisoning because even if the domain resolves to an attacker's IP, the TLS handshake will fail unless the attacker has the exact certificate. In Go:
import (
"crypto/tls"
"crypto/x509"
"net/http"
)
// Load the expected certificate
pinnedCert, _ := os.ReadFile("auth-service.crt")
roots := x509.NewCertPool()
roots.AppendCertsFromPEM(pinnedCert)
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: roots,
// Optional: pin specific certificate
// GetClientCertificate: func(*tls.CertRequestInfo) (*tls.Certificate, error) { return &pinnedCert, nil }
},
},
}
resp, _ := httpClient.Get("https://auth-service.internal/hmac-key")3. Embed Keys or Use Hardware Security Modules (HSMs): For maximum security, embed static HMAC keys in the application (if rotation is infrequent) or use an HSM that is accessed via a network path not reliant on DNS (e.g., direct IP with certificate pinning).
After remediation, re-scan with middleBrick to confirm the finding is resolved. The GitHub Action can enforce this in CI/CD by failing a build if the security score drops due to a new DNS-dependent HMAC configuration.