HIGH ssrf server sideginhmac signatures

Ssrf Server Side in Gin with Hmac Signatures

Ssrf Server Side in Gin with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Server-side request forgery (SSRF) in a Gin-based service that uses HMAC signatures illustrates how trust boundaries can be misaligned. HMAC signatures are typically intended to verify the origin and integrity of a request, but if the signature is validated only after the request is processed internally—or if the signature covers only a subset of the request data—attackers can induce the server to make arbitrary outbound HTTP calls.

Consider a Gin endpoint that accepts a URL to fetch metadata. The client provides a URL and an HMAC over selected parameters (e.g., URL and a timestamp). If the server uses the HMAC to confirm authenticity but then passes the supplied URL to an internal fetcher without strict validation, an attacker can supply a URL that resolves to internal services (e.g., http://127.0.0.1:8080/admin, cloud metadata endpoints like http://169.254.169.254/latest/meta-data/, or Kubernetes service DNS names). The HMAC check passes because the attacker’s signed payload matches the server’s expected scheme, yet the server performs the request on behalf of the attacker, leading to SSRF.

A concrete pattern: the client sends GET /fetch?url=...&ts=1700000000&sig=.... The server verifies sig over url and ts, then does not re-validate the URL’s host against a denylist or enforce a strict allowlist. Because the signature does not bind to an intended destination or enforce network boundaries, the server-side HTTP client can be tricked into reaching internal endpoints. This becomes especially dangerous when combined with features like OpenAPI/Swagger spec analysis, where internal schemas might hint at admin paths or metadata routes that are not publicly documented but are reachable from the compromised Gin service.

In secure designs, HMAC signatures should cover the intended destination and any policy constraints (e.g., allowed host patterns), and validation must occur before any network operation. Even with correct HMAC usage, SSRF can still arise if the server follows redirects to internal addresses or trusts user-supplied headers (such as X-Forwarded-Proto) that alter the request flow. Therefore, the combination of SSRF-prone URL handling and HMAC-based authentication in Gin requires strict input validation, destination allowlists, and network egress controls to prevent the server from acting as an unintended proxy.

Hmac Signatures-Specific Remediation in Gin — concrete code fixes

To remediate SSRF when using HMAC signatures in Gin, ensure the signature validates both the payload and the intended destination, and enforce strict network policies before making any outbound request. Below are concrete, working examples that demonstrate secure handling.

Example 1: HMAC verification with destination binding

Sign and verify a composite of the target host, path, and a timestamp to prevent the server from requesting arbitrary URLs. This ensures the server only performs requests to preapproved destinations.

// client-side: sign method, host, path, timestamp, and body
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/url"
	"strings"
)

func buildSignedRequest(targetURL, method, timestamp, secret string) (string, string) {
	u, _ := url.Parse(targetURL)
	// canonical string: METHOD|host|path|ts
	canonical := strings.ToUpper(method) + "|" + u.Host + "|" + u.Path + "|" + timestamp
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(canonical))
	sig := hex.EncodeToString(h.Sum(nil))
	return targetURL, sig
}

func main() {
	url, sig := buildSignedRequest("https://api.example.com/v1/resource", "GET", "1700000000", "super-secret-key")
	fmt.Printf("url=%s&sig=%s\n", url, sig)
}

Example 2: Gin handler that validates HMAC and enforces destination allowlist

The server verifies the HMAC over the same canonical string and checks the host against an allowlist before performing the request.

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/http"
	"net/url"
	"strings"

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

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

func verifyHMAC(r *http.Request, secret string) bool {
	urlQ := r.URL.Query().Get("url")
	ts := r.URL.Query().Get("ts")
	sig := r.URL.Query().Get("sig")
	if urlQ == "" || ts == "" || sig == "" {
		return false
	}
	u, err := url.Parse(urlQ)
	if err != nil {
		return false
	}
	if !allowedHosts[u.Host] {
		return false
	}
	canonical := strings.ToUpper(r.Method) + "|" + u.Host + "|" + u.Path + "|" + ts
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(canonical))
	expected := hex.EncodeToString(h.Sum(nil))
	return hmac.Equal([]byte(expected), []byte(sig))
}

func fetchHandler(c *gin.Context) {
	if !verifyHMAC(c.Request, "super-secret-key") {
		c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid signature"})
		return
	}
	url := c.Query("url")
	// safe to proceed: host is verified, URL is from an allowed origin
	resp, err := httpGetWithTimeout(url, 5)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"data": resp})
}

func httpGetWithTimeout(target string, timeoutSec int) (interface{}, error) {
	// placeholder for an actual HTTP client with timeout and no redirect to internal IPs
	return map[string]string{"note": "success"}, nil
}

func main() {
	r := gin.Default()
	r.GET("/fetch", fetchHandler)
	r.Run()
}

Key practices:

  • Include the intended host and path in the HMAC canonical string so the signature is bound to a specific destination.
  • Validate the host against an allowlist before any network call; reject private IPs and internal DNS names.
  • Do not follow redirects to internal addresses; configure the HTTP client to disable redirect handling or sanitize Location headers.
  • Enforce timeouts and disable automatic credential propagation to internal endpoints to reduce the impact of a potential SSRF.

Frequently Asked Questions

Why does HMAC verification alone not prevent SSRF in Gin APIs?
HMAC verifies integrity and origin but does not restrict where the server is allowed to send requests. If the server uses the signed URL directly without host allowlisting or network controls, an attacker can still induce requests to internal or sensitive endpoints.
What additional controls should be applied alongside HMAC signatures in Gin to mitigate SSRF?
Use destination allowlists, validate and normalize URLs before use, disable redirects, enforce timeouts, avoid forwarding sensitive headers, and monitor egress traffic. These controls ensure that even when a signature is valid, the server cannot reach unintended internal resources.