Dangling Dns in Gin
How Dangling DNS Manifests in Gin
In Gin, a dangling DNS vulnerability typically arises when an application dynamically constructs outbound requests based on user-supplied input, often to interact with external services (payment gateways, OAuth providers, webhooks). The vulnerability occurs when the application fails to validate that a user-provided hostname resolves to an expected, legitimate IP address. An attacker can supply a domain they control (e.g., attacker.com), causing the Gin server to make a request to the attacker's server. This can lead to credential theft, data exfiltration, or server-side request forgery (SSRF) if the request includes internal headers or tokens.
Common Gin-specific patterns include:
- Unvalidated URL parameters in HTTP clients: Using
http.Get(userSuppliedURL)or similar without restricting the hostname. - Dynamic proxy/redirect logic: A Gin handler that reads a
redirect_uriorcallback_urlfrom a query parameter or JSON body and uses it in ahttp.Redirector an outboundhttp.Postcall. - Webhook/notification endpoints: An admin feature to configure a third-party webhook URL, stored in a database, that the Gin app calls without validating the stored URL's hostname resolves to an approved list.
Example vulnerable Gin handler that fetches a user-provided URL:
func FetchExternalData(c *gin.Context) {
targetURL := c.Query("url") // User input: http://attacker.com/exfil?data=secret
resp, err := http.Get(targetURL)
if err != nil {
c.JSON(500, gin.H{"error": "failed to fetch"})
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
c.Data(200, "application/json", body)
}
Here, an attacker can make the Gin server issue a request to any domain, potentially accessing internal services (if the Gin server runs in a VPC) or leaking data via query parameters.
Gin-Specific Detection with middleBrick
middleBrick detects dangling DNS/SSRF vulnerabilities in Gin APIs through a combination of OpenAPI spec analysis and active runtime probing. Since the scanner operates as a black-box, it first parses your OpenAPI/Swagger definition (if available) to identify parameters that likely accept URLs (e.g., named url, callback, redirect_uri, webhook). It then sends crafted requests to those endpoints, substituting the parameter value with a domain under the scanner's control (e.g., http://scan.middlebrick.io/probe).
If the Gin application makes an outbound request to that controlled domain, middleBrick's infrastructure receives the callback, confirming that user input reaches an HTTP client. The scanner also checks for:
- DNS resolution of private IP ranges: Probing with hostnames that resolve to
127.0.0.1,169.254.169.254(AWS metadata), or10.0.0.0/8to test for internal network access. - Protocol smuggling: Testing if the endpoint accepts non-HTTP schemes like
file://orgopher://that could lead to local file read or port scanning. - OpenAPI spec mismatches: If your spec declares a parameter as a simple
stringbut the runtime behavior treats it as a URL, middleBrick flags a potential security misconfiguration.
For a Gin API, the scan would target any endpoint with query/path/body parameters that accept URLs. The resulting report includes a per-category breakdown under SSRF and Input Validation, with a prioritized finding that includes the exact request/response evidence and remediation guidance specific to Gin's context.
Gin-Specific Remediation
Remediation in Gin involves two layers: strict input validation and safe outbound request handling. Never trust user-supplied URLs. Instead, implement an allowlist of permitted domains or use a robust URL parser to enforce the hostname is public and expected.
1. Validate and sanitize user input: Use Gin's binding and validation tags to reject malformed URLs early. Parse the URL and check its hostname against an allowlist.
type WebhookRequest struct {
URL string `json:"url" binding:"required,url"`
}
func SetWebhook(c *gin.Context) {
var req WebhookRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "invalid URL"})
return
}
parsedURL, err := url.Parse(req.URL)
if err != nil || !isAllowedHost(parsedURL.Hostname()) {
c.JSON(400, gin.H{"error": "URL host not allowed"})
return
}
// Safe to use parsedURL
saveToDatabase(parsedURL.String())
c.JSON(200, gin.H{"status": "saved"})
}
func isAllowedHost(host string) bool {
allowed := map[string]bool{
"api.payment-gateway.com": true,
"notifications.trusted-service.io": true,
}
return allowed[host]
}
2. Use safe HTTP clients with restricted transport: When making outbound requests, use a custom http.Transport that blocks private IP ranges and non-HTTP schemes.
var safeTransport = &http.Transport{
DisableKeepAlives: true,
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 0,
}).DialContext,
// Prevent connection to private IPs
Dial: func(network, address string) (net.Conn, error) {
host, _, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ips, err := net.LookupIP(host)
if err != nil {
return nil, err
}
for _, ip := range ips {
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsPrivate() {
return nil, fmt.Errorf("connection to private IP blocked")
}
}
return net.Dial(network, address)
},
}
safeClient := &http.Client{Transport: safeTransport, Timeout: 10 * time.Second}
func SafeFetch(c *gin.Context) {
target := c.Query("url")
parsed, err := url.Parse(target)
if err != nil || !isAllowedHost(parsed.Hostname()) {
c.JSON(400, gin.H{"error": "invalid host"})
return
}
resp, err := safeClient.Get(parsed.String())
// ... handle response
}
3. Consider network-level isolation: Run your Gin application in a container or VPC with egress firewall rules that only allow outbound traffic to known, necessary external services. This is a defense-in-depth measure that complements application-level validation.
By combining allowlist validation with a restricted HTTP client, you eliminate the dangling DNS vector in your Gin handlers. middleBrick's scan will verify that these controls are effective by attempting to trigger an outbound request to a probe domain; a passing scan indicates the vulnerability is remediated.