Dns Rebinding in Chi
How Dns Rebinding Manifests in Chi
Dns Rebinding attacks in Chi applications typically exploit the framework's HTTP client and DNS resolution behavior. When Chi routes make outbound requests to user-controlled domains, they can fall victim to DNS rebinding where an attacker controls both the initial and rebinding DNS responses.
The most common manifestation occurs in middleware that performs health checks or service discovery. Consider this vulnerable Chi middleware pattern:
func HealthCheckMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Vulnerable: user-controlled domain in config
healthURL := os.Getenv("HEALTH_CHECK_URL")
resp, err := http.Get(healthURL) // <-- DNS rebinding point
if err != nil {
http.Error(w, "Service unavailable", 503)
return
}
defer resp.Body.Close()
next.ServeHTTP(w, r)
})
}The attack works by registering a domain that initially resolves to the attacker's IP, then rapidly rebinding it to internal network addresses (192.168.x.x, 10.x.x.x, 127.0.0.1). Chi's default HTTP client follows these DNS changes, potentially exposing internal services.
Another Chi-specific pattern involves route parameters used for external API calls:
router := chi.NewRouter()
router.Get("/api/external/{domain}/{path}", func(w http.ResponseWriter, r *http.Request) {
vars := chi.RouteContext(r.Context()).URLParams
domain := vars["domain"]
path := vars["path"]
// Vulnerable: direct user input in URL construction
fullURL := fmt.Sprintf("https://%s/%s", domain, path)
resp, err := http.Get(fullURL) // <-- DNS rebinding vulnerability
if err != nil {
http.Error(w, err.Error(), 500)
return
}
defer resp.Body.Close()
w.Write([]byte("External data fetched"))
})This pattern is particularly dangerous because Chi's routing makes it trivial to extract and use user-controlled domains in outbound requests. The framework's simplicity becomes a liability when combined with DNS rebinding.
Chi-Specific Detection
Detecting DNS rebinding in Chi applications requires both static analysis and runtime scanning. Static analysis should focus on these Chi-specific patterns:
# Scan for vulnerable Chi patterns
rg "chi\.RouteContext|r\.URLParams" --type go
# Look for HTTP clients making requests with user input
rg "http\.Get|http\.Post" --type go
# Find middleware that makes external calls
rg "Middleware.*http\.Get|Middleware.*http\.Post" --type goFor runtime detection, middleBrick's black-box scanning identifies DNS rebinding vulnerabilities by testing Chi endpoints with controlled domains. The scanner monitors for:
- Internal IP address resolution changes during scan
- Unexpected responses from private network ranges
- Time-based DNS resolution patterns indicating rebinding
- Access to internal services not meant to be exposed
middleBrick specifically tests Chi applications by sending requests to domains that alternate between external and internal IPs, measuring response characteristics to confirm DNS rebinding exploitation.
Additional detection can be implemented in Chi middleware:
func DNSSecurityMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Block requests with internal IP patterns in URLs
if strings.Contains(r.URL.String(), "192.168.") ||
strings.Contains(r.URL.String(), "10.") ||
strings.Contains(r.URL.String(), "127.") {
http.Error(w, "Invalid request", 400)
return
}
// Rate limit requests to the same domain
domain := r.URL.Hostname()
// Implement rate limiting logic here
next.ServeHTTP(w, r)
})
}Chi-Specific Remediation
Remediating DNS rebinding in Chi requires a defense-in-depth approach using the framework's middleware capabilities. The primary defense is input validation and allowlisting:
var allowedDomains = map[string]bool{
"api.example.com": true,
"service.example.org": true,
}
func SecureExternalCallMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Extract domain from route or query
domain := chi.RouteContext(r.Context()).URLParams["domain"]
// Validate against allowlist
if !allowedDomains[domain] {
http.Error(w, "Invalid domain", 400)
return
}
// Resolve DNS once and cache (prevents rebinding)
ips, err := net.LookupIP(domain)
if err != nil || len(ips) == 0 {
http.Error(w, "Domain resolution failed", 502)
return
}
// Store resolved IP in context for single use
ctx := context.WithValue(r.Context(), "resolvedIP", ips[0])
next.ServeHTTP(w, r.WithContext(ctx))
})
}For HTTP client configuration, use timeouts and custom DNS resolvers:
func NewSecureHTTPClient() *http.Client {
return &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// Block internal network connections
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
ip := net.ParseIP(host)
if ip != nil {
if ip.IsPrivate() || ip.IsLoopback() {
return nil, errors.New("internal IP blocked")
}
}
return net.DialTimeout(network, addr, 10*time.Second)
},
},
}
}Implement middleware for Chi routes that accept external domains:
func ValidateExternalDomain(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
vars := chi.RouteContext(r.Context()).URLParams
domain := vars["domain"]
// Basic validation - allow only specific TLDs
if !strings.HasSuffix(domain, ".com") && !strings.HasSuffix(domain, ".org") {
http.Error(w, "Invalid domain format", 400)
return
}
// Check for internal IP patterns
if strings.Contains(domain, "192.168") || strings.Contains(domain, "10.") {
http.Error(w, "Internal network access denied", 403)
return
}
next.ServeHTTP(w, r)
})
}
// Usage in Chi router
router := chi.NewRouter()
router.Use(ValidateExternalDomain)
router.Get("/api/external/{domain}/{path}", externalHandler)