Ssrf Server Side in Gin with Api Keys
Ssrf Server Side in Gin with Api Keys — how this specific combination creates or exposes the vulnerability
Server Side Request Forgery (SSRF) in a Gin-based API that relies on API keys occurs when an attacker can coerce the server into making arbitrary outbound HTTP requests on its behalf. Because Gin does not inherently validate or restrict outbound destinations, any server-side code that constructs URLs from user input can be abused. When API keys are involved, the risk is compounded because the server may use those keys to authenticate to downstream services, effectively exposing privileged credentials to the attacker.
Consider a scenario where an endpoint accepts a URL to proxy or fetch external data and embeds an API key in the request header. If the URL is user-controlled and not properly validated, an attacker can supply an SSRF target such as http://169.254.169.254/latest/meta-data/iam/security-credentials/ (AWS instance metadata) or internal services like http://redis:6379. The Gin handler forwards the request with the API key attached, allowing the attacker to reach internal endpoints that would otherwise be unreachable. This pattern violates the unauthenticated attack surface that middleBrick scans, where endpoints accepting external input can trigger SSRF without requiring authentication.
In a black-box scan, middleBrick tests endpoints that accept parameters and checks whether the application can reach internal resources or leak sensitive data. If a Gin endpoint uses an API key to call an external service and reflects or leaks the key in error messages or logs, middleBrick’s Data Exposure and SSRF checks can surface the issue. The scanner does not exploit or fix, but it reports the path and potential impact, highlighting how an API key can be inadvertently exposed when SSRF is present.
Api Keys-Specific Remediation in Gin — concrete code fixes
Mitigating SSRF when API keys are used in Gin requires both input validation and architectural safeguards. Never forward user-controlled URLs to external services, and avoid embedding API keys in requests that traverse user-supplied paths. Instead, use allow-listed external hosts and manage keys via environment variables or secure vaults, never passing them through user-controlled parameters.
Below are concrete, working examples in Go using the Gin framework that demonstrate secure handling of API keys and prevention of SSRF.
Example 1: Fixed external call with allow-listed host
Instead of accepting a full URL, accept only an operation or resource identifier, and construct the URL server-side against an allow-listed host:
package main
import (
"context"
"net/http"
"os"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
apiKey := os.Getenv("EXTERNAL_API_KEY")
if apiKey == "" {
panic("EXTERNAL_API_KEY environment variable is required")
}
r.GET("/external-data/:resource", func(c *gin.Context) {
resource := c.Param("resource")
// Allow-list known resources to prevent SSRF
allowed := map[string]bool{"status": true, "health": true}
if !allowed[resource] {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "resource not allowed"})
return
}
req, _ := http.NewRequestWithContext(c.Request.Context(), http.MethodGet, "https://api.example.com/"+resource, nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil || resp.StatusCode >= 400 {
c.AbortWithStatusJSON(http.StatusBadGateway, gin.H{"error": "upstream error"})
return
}
defer resp.Body.Close()
c.Status(resp.StatusCode)
})
r.Run()
}
Example 2: Rejecting URLs with dangerous patterns
If your use case requires accepting a URL, validate it strictly and reject private IP ranges and internal hostnames:
package main
import (
"net"
"net/http"
"net/url"
"os"
"github.com/gin-gonic/gin"
)
func isPrivate(host string) bool {
ip := net.ParseIP(host)
if ip == nil {
// Try to resolve or check hostname; simplified for example
return false
}
return ip.IsPrivate()
}
func main() {
r := gin.Default()
apiKey := os.Getenv("EXTERNAL_API_KEY")
r.POST("/fetch", func(c *gin.Context) {
var body struct {
Url string `json:"url"`
}
if c.BindJSON(&body) != nil || body.Url == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid url"})
return
}
parsed, err := url.Parse(body.Url)
if err != nil || parsed.Scheme == "file" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid url scheme"})
return
}
if isPrivate(parsed.Hostname()) {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "private host not allowed"})
return
}
req, _ := http.NewRequestWithContext(c.Request.Context(), http.MethodGet, body.Url, nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil || resp.StatusCode >= 400 {
c.AbortWithStatusJSON(http.StatusBadGateway, gin.H{"error": "upstream error"})
return
}
defer resp.Body.Close()
c.Status(resp.StatusCode)
})
r.Run()
}
Key remediation points:
- Do not concatenate user input into full URLs without strict allow-listing of hosts and paths.
- Do not include API keys in query strings or logs; pass them via headers using values from secure environment variables.
- Reject private IP addresses and internal hostnames to prevent SSRF to internal services.
- Use context propagation to respect request cancellation and timeouts.
These patterns reduce the attack surface so that API keys remain confined to intended external services and are not inadvertently exposed via SSRF paths.