SSRF

SSRF in Go (Gin) - Remediation Guide [Apr 2026] [GHSA-pf3h-qjgv-vcpr]

[Updated Apr 2026] Updated GHSA-pf3h-qjgv-vcpr

Overview

SSRF vulnerabilities occur when a server-side endpoint accepts a user-supplied URL and makes a request on behalf of the user. In Go apps using Gin this is common for endpoints that fetch content, proxy data, or preflight resources. If the server can reach internal networks or cloud metadata endpoints, an attacker can reach sensitive services, exfiltrate data, or trigger unintended actions. Impact includes access to internal services, cloud metadata endpoints, or other protected resources, potentially bypassing firewalls and exposing credentials. In cloud or container environments, SSRF can enable lateral movement, credential theft, and service disruption. Manifestation in Gin often happens when a handler reads a query parameter (or body) and passes it directly to net/http or a proxy, without validating the target. Common patterns include content fetchers, image proxies, or dynamic request forwards. Remediation approach centers on strict input validation and network controls: whitelist allowed hosts, constrain schemes, enforce timeouts, block or control redirects, and isolate outbound requests behind a safe gateway. Add logging and tests to prevent regressions.

Code Fix Example

Go (Gin) API Security Remediation
// Vulnerable pattern (Go/Gin)
package main

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

func main() {
  r := gin.Default()
  r.GET("/fetch-vuln", vulnHandler)
  r.GET("/fetch-secure", secureHandler)
  r.Run()
}

func vulnHandler(c *gin.Context) {
  raw := c.Query("url")
  if raw == "" {
    c.String(400, "missing url")
    return
  }
  resp, err := http.Get(raw)
  if err != nil {
    c.String(502, "fetch error: %v", err)
    return
  }
  defer resp.Body.Close()
  body, _ := io.ReadAll(resp.Body)
  c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), body)
}

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

func isAllowedURL(u *url.URL) bool {
  if u == nil { return false }
  if u.Scheme != "http" && u.Scheme != "https" { return false }
  host := u.Hostname()
  return allowedHosts[host]
}

func secureHandler(c *gin.Context) {
  raw := c.Query("url")
  if raw == "" { c.String(400, "missing url"); return }
  u, err := url.Parse(raw)
  if err != nil { c.String(400, "invalid url"); return }
  if !isAllowedURL(u) { c.String(403, "forbidden"); return }
  client := &http.Client{
    Timeout: 5 * time.Second,
    CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
    Transport: &http.Transport{ Proxy: nil }, // disable proxies
  }
  resp, err := client.Get(u.String())
  if err != nil { c.String(502, "fetch error: %v", err); return }
  defer resp.Body.Close()
  body, _ := io.ReadAll(resp.Body)
  c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), body)
}

CVE References

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