SSRF

SSRF and Go (Gin) remediation [GHSA-5f7v-4f6g-74rj]

[Updated Mar 2026] Updated GHSA-5f7v-4f6g-74rj

Overview

SSRF (Server-Side Request Forgery) can enable an attacker to cause your Go (Gin) service to make arbitrary requests from your server to internal or external resources. In cloud and container environments, this can lead to access to internal services, metadata endpoints, or other protected resources, potentially exposing sensitive data, bypassing network controls, or pivoting to further targets. Go's net/http client is powerful but permissive when given user-controlled URLs, which is a common pattern in Gin handlers that fetch remote resources based on request parameters. Misconfigurations-such as using http.Get with a user-supplied URL or failing to validate destinations-can enable SSRF in production APIs, gateways, or proxy-like endpoints. This guide focuses on typical Gin-based patterns, their real-world impact, and concrete mitigations. No CVEs are cited here as per the request, but the vulnerability class is well-known in frameworks that fetch user-supplied URLs.

Code Fix Example

Go (Gin) API Security Remediation
Vulnerable:
package main

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

func vulnerableFetchHandler(c *gin.Context) {
  raw := c.Query("url")
  resp, err := http.Get(raw)
  if err != nil {
    c.String(500, "fetch error: %v", err)
    return
  }
  defer resp.Body.Close()
  c.String(200, "status: %s", resp.Status)
}

Fixed:
package main

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

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

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 }
  }
  return false
}

func secureFetchHandler(c *gin.Context) {
  raw := c.Query("url")
  u, err := url.Parse(raw)
  if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
    c.String(400, "invalid URL")
    return
  }
  host := u.Hostname()
  if !allowedHosts[host] {
    c.String(400, "host not allowed")
    return
  }
  ips, err := net.LookupIP(host)
  if err == nil {
    for _, ip := range ips {
      if isPrivateIP(ip) || ip.IsLoopback() {
        c.String(400, "private/internal host not allowed")
        return
      }
    }
  }
  client := &http.Client{Timeout: 5 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error {
    if len(via) >= 5 { return http.ErrUseLastResponse }
    return nil
  }}
  resp, err := client.Get(u.String())
  if err != nil {
    c.String(500, "fetch error: %v", err)
    return
  }
  defer resp.Body.Close()
  c.String(200, "status: %s", resp.Status)
}

func main() {
  r := gin.Default()
  r.GET("/vuln", vulnerableFetchHandler)
  r.GET("/secure", secureFetchHandler)
  r.Run()
}

CVE References

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