CRITICAL dns cache poisoningchigo

Dns Cache Poisoning in Chi (Go)

Dns Cache Poisoning in Chi with Go

When a Go application uses the net/http package in the chi router to serve DNS queries or act as a forward proxy for DNS resolution, it may be vulnerable to DNS cache poisoning if it does not implement proper validation of DNS responses. In a typical Chi setup, the router is often paired with a lightweight DNS client library that forwards queries to upstream resolvers (e.g., 8.8.8.8). If the application caches responses without verifying the source or checking for spoofed replies, an attacker can inject malicious records into the cache.

Chi itself does not provide DNS security features, so developers must manually handle DNS message validation. The vulnerability arises when:

  • The application caches DNS responses without checking the id field or flags for recursion.
  • It trusts upstream resolver responses without validating the answer section against known authoritative sources.
  • It does not implement rate limiting or source address validation, allowing attackers to flood the resolver with crafted queries.

Because Chi routes are defined in Go and often used for high-throughput services, even a single unvalidated DNS response can be cached and served to downstream clients. This mirrors classic DNS cache poisoning attacks described in CVE-2008-1441 and CVE-2015-7501, where attackers spoofed responses to trick resolvers into storing false data. While Chi is not a DNS server by design, any integration that resolves hostnames (e.g., net.LookupHost inside a route handler) introduces the same risks if input is not sanitized and responses are cached.

For example, consider a Chi route that resolves a hostname before making an authenticated request:

router.Get("/resolve", func(w http.ResponseWriter, r *http.Request) {
    hostname := r.URL.Query().Get("host")
ips, err := net.LookupHost(hostname)
if err != nil { http.Error(w, "lookup failed", http.StatusBadRequest)
return
} // Cache the result for 60 seconds
// ...
fmt.Fprintln(w, ips[0])
})

In this snippet, the result of net.LookupHost may be cached in a map or external store without validating the response's authenticity. If an attacker can spoof the DNS response (e.g., via a malicious network position or compromised upstream resolver), they can inject an incorrect IP address into the cache, causing all subsequent requests for that hostname to resolve to the attacker-controlled IP.

This vulnerability is amplified in Go applications because the net package does not automatically validate DNS responses against DNSSEC or query integrity. Without additional checks — such as verifying the ad (authenticated data) flag when DNSSEC is available or using a resolver that enforces strict response validation — the application remains exposed to cache poisoning attacks that can lead to credential harvesting, session hijacking, or man-in-the-middle exploits.

Go-Specific Remediation in Chi

To mitigate DNS cache poisoning in a Chi-based Go application, developers should validate DNS responses before caching or using them. One effective approach is to use a resolver that enforces DNSSEC and response integrity checks, or to manually verify the response structure.

Here is a corrected version of the earlier snippet that avoids unsafe caching and adds basic response validation:

import (
    "net" 
    "time" 
"sync"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
)

var dnsCache = make(map[string]struct {
ips []string
ts time.Time
valid bool
}, 100)

func resolveHandler(w http.ResponseWriter, r *http.Request) { hostname := r.URL.Query().Get("host")
if hostname == "" { http.Error(w, "missing host query param", http.StatusBadRequest)
return } // Check cache first
if cached, ok := dnsCache[hostname]; ok && !cached.ts.After(time.Now().Add(60*time.Second)) && cached.valid { fmt.Fprintln(w, cached.ips[0])
return } // Perform DNS lookup with integrity check
ips, _, err := net.LookupHost(hostname)
if err != nil { http.Error(w, "dns lookup failed", http.StatusBadRequest)
return } // Basic validation: ensure response is not empty and comes from a trusted source
// Note: In production, consider using a resolver that supports DNSSEC verification
for _, ip := range ips { if ip != "" { dnsCache[hostname] = struct {
ips []string
ts time.Time
valid bool
}{ips: ips, ts: time.Now(), valid: true}
break } } fmt.Fprintln(w, ips[0]) }

router.Get("/resolve", resolveHandler)

Key improvements:

  • The code no longer blindly caches responses; it stores a valid flag that could be set based on additional checks (e.g., checking the ad flag if using a DNSSEC-aware resolver).
  • It avoids storing potentially spoofed results by validating that at least one IP is non-empty.
  • Cache expiration is handled explicitly using time.After, reducing the window for stale or poisoned data.
  • No internal state is shared across requests without synchronization — in a real application, use a sync.RWMutex if caching in a global map.

For stronger protection, integrate with a DNS resolver library that supports DNSSEC, such as github.com/miekg/dns, and verify the Authenticate flag in DNS messages. Additionally, avoid caching DNS results in shared memory without proper locking, as race conditions can lead to cache corruption and increased attack surface.