SSRF

SSRF in Go (Gin) remediation guide [Mar 2026] [GHSA-3ww8-jw56-9f5h]

[Updated Mar 2026] Updated GHSA-3ww8-jw56-9f5h

Overview

SSRF vulnerabilities in Go apps using Gin can enable attackers to reach internal resources, cloud metadata endpoints, or protected services by abusing user-supplied URLs. This risk is especially dangerous for apps that fetch remote content or proxy client requests without proper validation. In Gin-based services, SSRF often happens when a handler takes a user-provided URL and passes it directly to net/http. Attackers can target internal networks, service meshes, or metadata endpoints, potentially exposing secrets or compromising systems. Lack of timeouts, redirects, or network segmentation amplifies the issue. This guide covers practical mitigations: validate and normalize URLs, restrict schemes to http/https, implement a host/IP allowlist or private IP checks, set tight request timeouts, and prefer a controlled fetch path with egress controls. Also enable logging and security testing. Finally, maintain a security-focused review process and include SSRF tests in CI to detect regressions as the code evolves.

Code Fix Example

Go (Gin) API Security Remediation
package main

import (
	"io"
	"net"
	"net/http"
	"net/url"
	"time"
	gin "github.com/gin-gonic/gin"
)

// Vulnerable example
func vulnerable() {
	r := gin.Default()
	r.GET("/fetch", func(c *gin.Context) {
		target := c.Query("url")
		resp, err := http.Get(target)
		if err != nil { c.String(500, `error`); return }
		defer resp.Body.Close()
		body, _ := io.ReadAll(resp.Body)
		c.Data(200, `text/plain`, body)
	})
	r.Run()
}

// Fixed example
func fixed() {
	r := gin.Default()
	r.GET("/fetch", func(c *gin.Context) {
		raw := c.Query("url")
		u, err := url.Parse(raw)
		if err != nil { c.String(400, `invalid url`); return }
		if u.Scheme != `http` && u.Scheme != `https` { c.String(400, `unsupported scheme`); return }
		host := u.Hostname()
		addrs, err := net.LookupIP(host)
		if err != nil { c.String(502, `dns error`); return }
		for _, ip := range addrs {
			if isPrivateIP(ip) { c.String(403, `forbidden address`); return }
		}
		client := &http.Client{ Timeout: 5 * time.Second }
		resp, err := client.Get(u.String())
		if err != nil { c.String(502, `fetch error`); return }
		defer resp.Body.Close()
		body, _ := io.ReadAll(resp.Body)
		c.Data(200, `text/plain`, body)
	})
	r.Run()
}

func isPrivateIP(ip net.IP) bool {
	if ip4 := ip.To4(); ip4 != nil {
		if ip4[0] == 10 { return true }
		if ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31 { return true }
		if ip4[0] == 192 && ip4[1] == 168 { return true }
		if ip4[0] == 127 { return true }
	}
	if ip.IsLoopback() { return true }
	return false
}

func main() {
	// Uncomment to run the vulnerable or fixed version
	// vulnerable()
	// fixed()
}

CVE References

Choose which optional cookies to allow. You can change this any time.