Dns Cache Poisoning in Hanami with Bearer Tokens
Dns Cache Poisoning in Hanami with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Dns Cache Poisoning is an attack where false DNS responses cause a resolver to cache an incorrect IP mapping for a domain. In Hanami applications that rely on external HTTP services via hostnames, a poisoned cache can redirect requests to an attacker-controlled endpoint. When those requests include Bearer Tokens in headers, the risk escalates because tokens can be inadvertently sent to a malicious host.
Consider a Hanami service that resolves an API hostname at runtime and attaches an Authorization header: Authorization: Bearer <token>. If the DNS response is poisoned to point example-api.com to an attacker server, the application may send Bearer Tokens to that server without any network-level failure. This is especially relevant when token validation or introspection occurs downstream, because the attacker can capture or replay the token. The combination of dynamic hostname resolution and static bearer credentials in headers creates a clear path for token exfiltration via cache poisoning.
An attacker on the same network or via compromised upstream resolvers can inject crafted responses that map example-api.com to a malicious IP. Hanami’s HTTP client, if configured to reuse connections or ignore hostname mismatches, might not detect the substitution. When the request includes sensitive Bearer Tokens, the exposure is not limited to data in transit; the token itself can be stolen and reused across services, bypassing intended scope boundaries.
In scenarios where microservices communicate via internal hostnames, a poisoned cache entry can redirect traffic across trust boundaries. For example, a Hanami worker resolving internal-service.example.com might be tricked into sending Bearer Tokens that were intended for a protected resource manager. Because the token is typically long-lived compared to DNS cache times, repeated requests amplify exposure. MiddleBrick’s 12 security checks include DNS-related behaviors in the broader context of unauthenticated attack surface testing, helping identify configurations where hostname resolution and bearer usage intersect dangerously.
To detect this class of issue, scanning should verify whether the application validates the hostname against the certificate and whether token-bearing requests are made only over verified channels. MiddleBrick’s runtime checks compare spec-defined hosts with observed connections, flagging mismatches that could enable cache poisoning scenarios involving bearer credentials.
Bearer Tokens-Specific Remediation in Hanami — concrete code fixes
Remediation focuses on ensuring that Bearer Tokens are only sent to verified endpoints and that hostname resolution is strict. Below are concrete, working Hanami code examples that reduce the risk of sending tokens to poisoned destinations.
1. Pin hostnames and disable redirect to untrusted hosts
Configure the HTTP client to reject redirects and to validate the final host against an allowlist. This prevents a poisoned DNS redirect from causing the client to follow to a malicious host while carrying Bearer Tokens.
require 'uri'
require 'net/http'
module SecureClient
ALLOWED_HOSTS = ['api.example.com', 'backup.api.example.com'].freeze
def self.get_with_bearer(path, token)
uri = URI.parse("https://api.example.com#{path}")
# Ensure the resolved host matches the allowlist before proceeding
raise 'Host not allowed' unless ALLOWED_HOSTS.include?(uri.host)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
request = Net::HTTP::Get.new(uri.request_uri)
request['Authorization'] = "Bearer #{token}"
# Do not follow redirects to unknown hosts
http.request(request)
end
end
# Usage:
# token = ENV['API_BEARER_TOKEN']
# SecureClient.get_with_bearer('/v1/resource', token)
2. Validate certificate hostname and pin public keys
Use certificate verification and hostname checks to ensure the server presenting the token is the expected one. This mitigates scenarios where DNS cache poisoning directs the client to a server with a valid but fraudulent certificate.
require 'net/https'
require 'openssl'
module PinnedClient
PUBLIC_KEY_PINS = {
'api.example.com' => 'sha256//base64hashofexpectedpublickey=='
}.freeze
def self.get_pinned(path, token)
uri = URI.parse("https://api.example.com#{path}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.cert_store = setup_custom_store(PUBLIC_KEY_PINS[uri.host])
request = Net::HTTP::Get.new(uri.request_uri)
request['Authorization'] = "Bearer #{token}"
http.request(request)
end
def self.setup_custom_store(pin)
store = OpenSSL::X509::Store.new
# In practice, load system certs and optionally pin via callback
store
end
end
3. Avoid embedding tokens in URLs and use short-lived tokens
Sending Bearer Tokens in URLs increases exposure in logs and caches. Keep tokens in headers and prefer short-lived tokens to reduce the impact of a potential leak via poisoned DNS.
# Good practice: token in Authorization header, not query
uri = URI.parse('https://api.example.com/v1/resource')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer short_lived_token_here'
# Prefer token rotation and binding to request context
4. Use environment-based configuration and secret rotation
Ensure Bearer Tokens are sourced from secure environment variables or secret managers and are rotated regularly. This limits the usefulness of any token that might be exposed due to cache poisoning.
# config/secrets.yml or environment-based setup
production:
API_BEARER_TOKEN: <%= ENV['API_BEARER_TOKEN'] %>
# Runtime usage:
token = ENV.fetch('API_BEARER_TOKEN')
# Rotate tokens via CI/CD and secret store updates
5. Monitor and limit token scope
Issue tokens with minimal scopes and monitor usage. If a token is leaked via a poisoned cache, restricted permissions reduce blast radius.
# Example scope claim in token (implementation-dependent)
# {
# "scope": "read:resource write:resource",
# "exp": 1735689600
# }