Overview
SSRF can allow attackers to make your server reach internal resources and cloud metadata endpoints, potentially exposing sensitive data or enabling lateral movement. In production, misconfigured services and open proxies amplify the impact, making even private networks accessible to adversaries. There are no CVEs provided for this guide.
In Go with the Gin framework, SSRF typically occurs when a handler accepts a URL from a client (via query or body) and uses net/http to fetch that URL without proper validation. This can lead to requests to internal services, cloud metadata endpoints, or other restricted destinations, bypassing network segmentation.
This guide outlines a defense-in-depth approach tailored to Go/Gin: validate and restrict URLs, implement an allowlist, enforce outbound access controls, use timeouts and a dedicated HTTP client, and centralize checks to prevent SSRF across handlers.
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"io"
"net/http"
"net/url"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// Vulnerable: uses user-supplied URL directly
r.GET("/ssrf/vuln", func(c *gin.Context) {
target := c.Query("target")
resp, err := http.Get(target)
if err != nil {
c.String(500, "error: %v", err)
return
}
defer resp.Body.Close()
c.Status(resp.StatusCode)
io.Copy(c.Writer, resp.Body)
})
// Fixed: validate URL against allowlist and restricted host
r.GET("/ssrf/fix", func(c *gin.Context) {
target := c.Query("target")
if !isAllowed(target) {
c.String(400, "target 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, "error: %v", err)
return
}
defer resp.Body.Close()
c.Status(resp.StatusCode)
io.Copy(c.Writer, resp.Body)
})
r.Run(":8080")
}
func isAllowed(raw string) bool {
u, err := url.Parse(raw)
if err != nil { return false }
// example allowlist: only https://trusted.example.com
if u.Scheme != "https" { return false }
host := u.Hostname()
allowed := map[string]bool{"trusted.example.com": true}
return allowed[host]
}