SSRF

SSRF in Go (Gin): Secure Guide [Apr 2026] [GHSA-x5vx-vrpf-r45f]

[Updated Apr 2026] Updated GHSA-x5vx-vrpf-r45f

Overview

SSRF (Server-Side Request Forgery) vulnerabilities in Go applications using Gin can allow an attacker to coerce your server into making HTTP requests to arbitrary destinations. This could lead to access to internal networks, cloud instance metadata services, or internal APIs that should never be exposed to end users. In practice, an endpoint that accepts a user-supplied URL to fetch or proxy a resource can be abused to enumerate internal services, exfiltrate data, or pivot to additional attack vectors. In Gin-based services, SSRF commonly manifests when you implement handlers that fetch remote content based on request parameters, or when you build a simple image proxy or API gateway that forwards requests to client-supplied URLs. If the code uses http.Get or a default http.Client without validating the target, an attacker can direct your server to contact internal resources such as 169.254.169.254, 10.x.x.x, or other internal endpoints. Remediation should limit external requests to known-safe endpoints, validate and sanitize inputs, and harden the Go http client. Do not proxy or fetch arbitrary user-provided URLs without a strict allowlist, scheme restrictions, and network egress controls. Implement a wrapper fetch routine that enforces URL validation, host allowlists, timeouts, and safe redirect handling. Additionally, monitor and audit: log URL targets, enable alerting on attempts to access disallowed destinations, and consider network egress controls such as firewall rules or VPC security groups to prevent unintended internal access. Testing with fuzzed URLs and security scanning can help detect SSRF patterns during development.

Code Fix Example

Go (Gin) API Security Remediation
package main

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

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

func main() {
  r := gin.Default()
  r.GET("/vuln-fetch", vulnerableHandler)
  r.GET("/fix-fetch", fixedHandler)
  r.Run(":8080")
}

func vulnerableHandler(c *gin.Context) {
  target := c.Query("url")
  resp, err := http.Get(target) // vulnerable: user-controlled URL
  if err != nil {
     c.String(500, "request failed: %v", err)
     return
  }
  defer resp.Body.Close()
  data, _ := io.ReadAll(resp.Body)
  c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), data)
}

func fixedHandler(c *gin.Context) {
  target := c.Query("url")
  if !isAllowed(target) {
     c.String(400, "URL not allowed")
     return
  }
  client := &http.Client{
     Timeout: 5 * time.Second,
     CheckRedirect: func(req *http.Request, via []*http.Request) error {
        return http.ErrUseLastResponse
     },
  }
  resp, err := client.Get(target)
  if err != nil {
     c.String(500, "request failed: %v", err)
     return
  }
  defer resp.Body.Close()
  data, _ := io.ReadAll(resp.Body)
  c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), data)
}

func isAllowed(rawurl string) bool {
  u, err := url.Parse(rawurl)
  if err != nil {
     return false
  }
  if u.Scheme != "http" && u.Scheme != "https" {
     return false
  }
  if _, ok := allowedHosts[u.Host]; !ok {
     return false
  }
  return true
}

CVE References

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