Dangling Dns in Gin (Go)
Dangling DNS in Gin with Go — how this specific combination creates or exposes the vulnerability
Dangling DNS occurs when a domain name resolves to an IP address that no longer hosts the intended service, often due to misconfigured cloud resources, abandoned subdomains, or expired DNS records. In the context of a Gin-based Go API, this vulnerability becomes exploitable when the API server or its dependencies (e.g., third-party services, webhooks, or external APIs) rely on DNS-resolved endpoints that can be hijacked by an attacker who registers the dangling IP.
For example, if a Gin API uses a cloud load balancer (like AWS ELB or GCP Cloud Load Balancing) for a microservice and the DNS record (e.g., api.internal.example.com) still points to the load balancer's IP after the service is decommissioned, an attacker can claim that IP address (via cloud provider's IP reuse mechanisms) and intercept traffic intended for the decommissioned service. If the Gin API makes outbound calls to this dangling endpoint — such as for logging, payment processing, or external API validation — without proper validation, it may inadvertently send sensitive data (headers, tokens, payloads) to the attacker-controlled server.
Gin’s flexibility in HTTP client usage exacerbates this risk. Developers often use net/http or libraries like go-resty/resty/v2 within Gin handlers to call external services. If these calls use DNS-resolved hostnames without certificate pinning, strict SNI validation, or IP allowlisting, and the target domain is dangling, the request proceeds to the attacker’s server. Unlike SSRF, which involves tricking the server into making internal requests, dangling DNS exploits external trust in DNS resolution — making it harder to detect with standard input validation.
This issue is particularly dangerous in environments with ephemeral infrastructure (e.g., Kubernetes, serverless) where DNS records are not cleaned up promptly. middleBrick’s unauthenticated black-box scan can detect dangling DNS risks by attempting to resolve subdomains associated with the target API and checking if the resolved IP addresses are associated with cloud providers and whether they respond with unexpected services (e.g., hosting a web server on port 80/443 that returns attacker-controlled content). It does not require credentials or agents — only the public API URL — and reports findings under the 'Data Exposure' and 'SSRF' categories, with remediation guidance focused on DNS hygiene and outbound request validation.
Go-Specific Remediation in Gin — concrete code fixes
To mitigate dangling DNS risks in a Gin-based Go API, implement strict validation for outbound HTTP requests. The following code demonstrates a secure HTTP client wrapper that enforces certificate validation, IP allowlisting, and SNI verification — critical controls when calling external services via DNS-resolved hostnames.
package main
import (
"crypto/tls"
"net"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// secureHTTPClient returns an *http.Client with strict TLS and dialing controls
func secureHTTPClient(allowedIPs map[string]bool) *http.Client {
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false, // Enforce certificate validation
MinVersion: tls.VersionTLS12,
},
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
// Custom dial hook to validate resolved IP against allowlist
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
// Override dial to validate IP
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// Split host:port
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
// Resolve hostname to IPs
ips, err := net.LookupIP(host)
if err != nil {
return nil, err
}
// Check if any resolved IP is in allowlist
for _, ip := range ips {
if allowedIPs[ip.String()] {
// Proceed with original dial
return (&net.Dialer{}).DialContext(ctx, network, addr)
}
}
return nil, fmt.Errorf("resolved IP not in allowlist for %s", host)
},
},
Timeout: 10 * time.Second,
}
}
func main() {
router := gin.New()
// Define allowed IPs for external services (e.g., payment gateway, logging endpoint)
allowed := map[string]bool{
"203.0.113.45": true, // Example: trusted payment API IP
"198.51.100.10": true, // Example: logging service
}
secureClient := secureHTTPClient(allowed)
router.GET("/process-payment", func(c *gin.Context) {
// Example: Outbound call to payment gateway
req, err := http.NewRequestWithContext(c.Request.Context(), "GET", "https://payments.example.com/charge", nil)
if err != nil {
c.JSON(500, gin.H{"error": "failed to create request"})
return
}
resp, err := secureClient.Do(req)
if err != nil {
// Log error but do not expose internal details
c.JSON(502, gin.H{"error": "external service unavailable"})
return
}
defer resp.Body.Close()
// Process response...
c.JSON(200, gin.H{"status": "payment processed"})
})
router.Run(":8080")
}
This approach ensures that even if a DNS record for payments.example.com becomes dangling and resolves to an attacker-controlled IP, the request is blocked unless the resolved IP is explicitly allowlisted. Combine this with regular DNS audits (using tools like dig or cloud provider DNS analytics) to identify and clean up stale records. middleBrick’s scan can validate whether your API’s outbound calls are vulnerable to dangling DNS by testing resolution behavior and reporting any mismatches between expected and observed IP responses under the 'Inventory Management' and 'SSRF' checks.
Frequently Asked Questions
How does dangling DNS differ from SSRF in the context of a Gin API?
SSRF involves tricking the server into making unintended internal requests (e.g., to localhost or internal metadata services) via user-controlled input. Dangling DNS exploits external trust: the server legitimately resolves a hostname to an IP, but that IP is no longer associated with the intended service and has been claimed by an attacker. In Gin, SSRF risks arise from improper validation of user-supplied URLs in API parameters, while dangling DNS risks stem from hardcoded or configured external service dependencies with poor DNS hygiene. middleBrick tests for both: SSRF via active probing of internal networks, and dangling DNS by verifying whether resolved IPs for external endpoints behave as expected.
Can I use certificate pinning in Go to mitigate dangling DNS risks?
Yes, certificate pinning adds a critical layer of defense. By validating that the leaf certificate or public key matches a known good value, you ensure that even if DNS resolves to an attacker’s IP, the TLS handshake fails unless the attacker possesses the legitimate private key — which they cannot obtain without compromising the certificate authority or the service owner. In Go, implement pinning using tls.Config with VerifyPeerCertificate or libraries like github.com/hashicorp/go-rootcerts. For example:
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
cert, _ := x509.ParseCertificate(rawCerts[0])
// Expected SHA-256 hash of the legitimate certificate
expected := "a1b2c3d4..." // 64-char hex string
actual := fmt.Sprintf("%x", sha256.Sum256(cert.Raw))
if actual != expected {
return fmt.Errorf("certificate pinning failed")
}
return nil
},
}
Combine pinning with IP allowlisting for defense in depth. middleBrick’s LLM/AI security checks include certificate validation probing, and its reports will flag missing or weak TLS configurations under the 'Encryption' category.