HIGH dangling dnsginhmac signatures

Dangling Dns in Gin with Hmac Signatures

Dangling Dns in Gin with Hmac Signatures — how this specific combination creates or exposes the vulnerability

A dangling DNS record occurs when a hostname remains in DNS but no service is configured to accept traffic at that address. In Gin, if route handling depends on hostname-based routing or virtual hosting and the application validates requests using HMAC signatures without also validating that the request target matches an expected service endpoint, the combination can expose a dangling DNS vector.

Consider a Gin service that uses a shared domain with virtual hosting. The application verifies an HMAC signature to authenticate the request origin, but it does not verify that the request Host header or the request target resolves to an expected, in-scope service. An attacker who can control DNS records (e.g., through a compromised or misconfigured DNS zone) can point a subdomain to an attacker-controlled IP. Because the Gin app only validates the HMAC, it may accept requests that appear authenticated but are routed to an unintended or unmanaged backend. This can lead to request smuggling, SSRF against internal services, or unauthorized routing to deprecated infrastructure.

For example, suppose the application computes the HMAC over the request path and a timestamp, and uses the Host header as part of the signing key material. If the DNS record for api.partner.example.com is dangling and points to a server the attacker controls, the attacker can forward requests to api.partner.example.com. The Gin app validates the HMAC using the shared secret and proceeds because the signature matches, but the request never reaches the intended backend. This mismatch between the authenticated identity and the actual network endpoint is the crux of the dangling DNS exposure in this context.

In the context of middleBrick’s checks, this pattern is flagged under BOLA/IDOR and Property Authorization, because the authorization decision does not account for endpoint reachability or network ownership. The scanner also flags missing hostname binding as an input validation issue, since the application should validate that the request target resolves to an expected, whitelisted service. An OpenAPI/Swagger analysis that resolves $ref definitions across specs can highlight which paths rely on host-based routing, making it easier to correlate with DNS findings.

To illustrate the Gin code pattern that can lead to this issue, here is an example that signs requests with HMAC but does not enforce hostname binding:

import (
    "crypto/hmac"
    "crypto/sha256"
    "fmt"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
)

func VerifyHMAC(secret []byte) gin.HandlerFunc {
    return func(c *gin.Context) {
        timestamp := c.GetHeader("X-Timestamp")
        receivedMAC := c.GetHeader("X-Signature")
        if timestamp == "" || receivedMAC == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing headers"})
            return
        }
        payload := fmt.Sprintf("%s|%s", c.Request.Method, timestamp)
        mac := hmac.New(sha256.New, secret)
        mac.Write([]byte(payload))
        expectedMAC := fmt.Sprintf("%x", mac.Sum(nil))
        if !hmac.Equal([]byte(expectedMAC), []byte(receivedMAC)) {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid signature"})
            return
        }
        c.Next()
    }
}

func main() {
    r := gin.Default()
    secret := []byte("shared-secret")
    r.Use(VerifyHMAC(secret))
    r.GET("/resource", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "ok"})
    })
    r.Run()
}

In this example, the HMAC is computed from the HTTP method and a timestamp, but the Host header and the resolved network endpoint are not part of the validation. If DNS for the host is dangling, an attacker can still present a valid HMAC and have the request processed, because Gin does not check that the request arrived at the expected hostname or resolves to an expected target.

Hmac Signatures-Specific Remediation in Gin — concrete code fixes

Remediation focuses on ensuring that HMAC validation includes sufficient context to prevent acceptance of requests that arrive via unintended network endpoints. In Gin, you should incorporate the Host header and, when possible, the resolved target or a canonical route identifier into the signed payload. You should also validate that the request target matches an expected service or prefix, especially when virtual hosting is used.

Here is a revised Gin example that includes the Host header in the HMAC computation and performs basic hostname validation:

import (
    "crypto/hmac"
    "crypto/sha256"
    "fmt"
    "net"
    "net/http"
    "strings"
    "time"

    "github.com/gin-gonic/gin"
)

var allowedHosts = map[string]bool{
    "api.example.com": true,
    "admin.example.com": true,
}

func VerifyHMACWithHost(secret []byte, allowedHosts map[string]bool) gin.HandlerFunc {
    return func(c *gin.Context) {
        host := c.Request.Host
        if !allowedHosts[host] {
            c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "host not allowed"})
            return
        }
        timestamp := c.GetHeader("X-Timestamp")
        receivedMAC := c.GetHeader("X-Signature")
        if timestamp == "" || receivedMAC == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing headers"})
            return
        }
        // Ensure timestamp is recent to prevent replay
        ts, err := time.Parse(time.RFC3339, timestamp)
        if err != nil || time.Since(ts) > 5*time.Minute {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "timestamp invalid"})
            return
        }
        payload := fmt.Sprintf("%s|%s|%s", host, c.Request.Method, timestamp)
        mac := hmac.New(sha256.New, secret)
        mac.Write([]byte(payload))
        expectedMAC := fmt.Sprintf("%x", mac.Sum(nil))
        if !hmac.Equal([]byte(expectedMAC), []byte(receivedMAC)) {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid signature"})
            return
        }
        c.Next()
    }
}

func main() {
    r := gin.Default()
    secret := []byte("shared-secret")
    r.Use(VerifyHMACWithHost(secret, allowedHosts))
    r.GET("/resource", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "ok"})
    })
    r.Run()
}

This approach binds the HMAC to the Host header and enforces an allowlist of acceptable hosts, reducing the risk that a dangling DNS record can be abused. For production, you should also validate that the resolved IP address belongs to an expected range or that the request targets a specific path prefix tied to a known service. The middleware can be extended to check SNI or other connection-level metadata when terminating TLS at a layer below Gin.

When using OpenAPI/Swagger, annotate which hosts and paths are authoritative. Tools like middleBrick can cross-reference the spec’s $ref resolutions with runtime behavior to highlight routes that lack hostname binding or rely on ambiguous virtual hosting, which is valuable during continuous monitoring via the Pro plan or when scanning with the CLI using middlebrick scan <url>.

Finally, integrate these checks into your CI/CD pipeline with the GitHub Action to fail builds if risk scores degrade, and use the MCP Server to scan APIs directly from your IDE. These steps help ensure that HMAC-signed endpoints in Gin remain bound to their intended network identities even when DNS configurations change.

Frequently Asked Questions

How does including the Host header in the HMAC prevent dangling DNS abuse?
Including the Host header binds the signature to the expected request hostname. If DNS is dangling and resolves to an unexpected IP, the Host header will not match the allowed list or the signer’s intent, causing the request to be rejected before HMAC validation is bypassed.
Can middleBrick detect missing hostname binding in Gin HMAC setups?
Yes. middleBrick scans unauthenticated attack surfaces and, when an OpenAPI/Swagger spec is available, cross-references path and host definitions with runtime findings. This can highlight routes that lack hostname binding and are vulnerable in configurations where HMAC does not include the Host header.