HIGH dns cache poisoninghanami

Dns Cache Poisoning in Hanami

Hanami-Specific Remediation

The fix is to ensure that any hostname used in an outbound request is validated against a known‑good list, or that the application performs its own DNS resolution with security‑hardened options and does not rely solely on the system resolver’s potentially poisoned cache.

Below is a Hanami‑style solution using an allowlist and Ruby’s Resolv::DNS with a short timeout and the use_tcp flag to reduce susceptibility to UDP‑based cache poisoning.

# lib/web/extensions/safe_http.rb
module Web::Extensions
  module SafeHttp
    ALLOWED_HOSTS = %w[api.example.com images.cdn.com].freeze

    def safe_get(uri_string)
      uri = URI.parse(uri_string)
      raise "Invalid URI" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)

      # 1. Host allowlist
      unless ALLOWED_HOSTS.include?(uri.host)
        raise "Host not allowed: #{uri.host}"
      end

      # 2. Perform DNS query with hardened resolver
      resolver = Resolv::DNS.new(nameserver: ["8.8.8.8"], udp_timeout: 2)
      resolver.timeout = 2
      # Force TCP for the query (harder to poison)
      addresses = resolver.getresources(uri.host, Resolv::DNS::Resource::IN::A)
      if addresses.empty?
        raise "Unable to resolve host"
      end

      # 3. Use the first resolved IP for the connection (bypasses system cache)
      ip = addresses.first.address
      http_uri = URI::HTTP.build(host: ip, port: uri.port, path: uri.path, query: uri.query)

      Net::HTTP.start(http_uri.host, http_uri.port, use_ssl: uri.is_a?(URI::HTTPS)) do |http|
        request = Net::HTTP::Get.new(http_uri.request_uri)
        response = http.request(request)
        response.body
      end
    end
  end
end

Now refactor the vulnerable action to use the extension:

# apps/web/actions/images/proxy.rb
module Web::Actions::Images
  class Proxy < Web::Action
    include Web::Extensions::SafeHttp

    def handle(params, response)
      target = params[:url] || ""
      begin
        response.body = safe_get(target)
        response.status = 200
      rescue => e
        response.status = 400
        response.body = { error: e.message }.to_json
      end
    end
  end
end

Additional mitigations that fit naturally into a Hanami project:

  • Configure Hanami’s config.security.allowed_outbound_hosts (custom setting) and read it in a middleware or action.
  • Use Hanami::Utils::URI to safely parse and reconstruct URIs, avoiding injection of malformed components.
  • Set short open and read timeouts on Net::HTTP (read_timeout = 2) to limit the window for a poisoned response to cause damage.
  • If you rely on external gems (e.g., faraday or httparty), wrap them with the same allowlist/resolver logic.

After applying these changes, a middleBrick scan will no longer report an SSRF finding for the proxy endpoint, confirming that the DNS cache poisoning vector has been mitigated.

Frequently Asked Questions

Can middleBrick detect DNS cache poisoning that only affects internal DNS resolvers and not the public internet?
middleBrick performs unauthenticated, black‑box checks from the public internet. If an organization’s internal resolver is poisoned but external resolvers return correct records, the scanner will not see the malicious resolution. To catch such scenarios, run middleBrick against an internal staging endpoint or integrate the scanner into a private CI environment where the same DNS infrastructure is used.
Does fixing DNS cache poisoning in Hanami require changes to the application’s OpenAPI/Swagger spec?
No. The remediation lives in the Ruby code (allowlist validation and hardened DNS lookup). middleBrick will still parse the OpenAPI spec to correlate findings with specific operations, but the spec itself does not need to be updated unless you want to document the new security constraints (e.g., adding a x‑allowed‑hosts extension).