Overview
Affected Versions
Squidex: versions prior to 7.23.0. For Go Gin, this CVE is context for SSRF risk; Go Gin itself does not have a CVE listed here (N/A in terms of this vulnerability), but the remediation applies to any Go (Gin) application that proxies user-supplied URLs.
Code Fix Example
package main
import (
"crypto/tls"
"io"
"net"
"net/http"
"net/url"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/vuln/fetch", fetchVulnerable)
r.GET("/sec/fetch", fetchSecure)
r.Run(":8080")
}
// Vulnerable: directly uses user-supplied URL, no validation
func fetchVulnerable(c *gin.Context) {
target := c.Query("url")
resp, err := http.Get(target)
if err != nil {
c.String(500, "request error: %v", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
c.Data(http.StatusOK, "text/plain; charset=utf-8", body)
}
// Secure: validates destination and constrains outbound requests
func fetchSecure(c *gin.Context) {
target := c.Query("url")
parsed, err := url.Parse(target)
if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") {
c.String(http.StatusBadRequest, "invalid URL or scheme")
return
}
// Whitelist approved destinations only
allowed := map[string]struct{}{
"example.com": {},
"api.example.local": {},
}
host := parsed.Hostname()
if _, ok := allowed[host]; !ok {
c.String(http.StatusForbidden, "host not allowed")
return
}
// Restricted HTTP client: timeouts, no redirects, and hardened transport
tr := &http.Transport{
DialContext: (&net.Dialer{Timeout: 5 * time.Second, KeepAlive: 30 * time.Second}).DialContext,
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
DisableKeepAlives: true,
}
client := &http.Client{
Transport: tr,
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// Do not follow redirects to prevent SSRF chaining
return http.ErrUseLastResponse
},
}
resp, err := client.Get(target)
if err != nil {
c.String(http.StatusBadGateway, "upstream error: %v", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), body)
}