SSRF

SSRF in Go Gin: remediation guide [CVE-2026-5417]

[Updated Apr 2026] Updated CVE-2026-5417

Overview

CVE-2026-5417 describes a server-side request forgery vulnerability in Dataease SQLbot (up to version 1.6.0) where the function get_es_data_by_http in the Elasticsearch Handler manipulated the address argument, enabling remote SSRF. An attacker could trigger requests from the server to arbitrary endpoints, potentially reaching internal services and data. This exploit has been publicly disclosed and is addressed by upgrading to 1.7.0. The weakness aligns with CWE-918: Server-Side Request Forgery. In Go applications using the Gin framework, SSRF surfaces when a handler uses a client-supplied URL to fetch data without validation or host restrictions. Publicly exposed endpoints may proxy or retrieve resources based on user input, allowing attackers to access internal networks, cloud metadata endpoints, or other sensitive services.

Affected Versions

Dataease SQLbot <= 1.6.0 (CVE-2026-5417); fixed in 1.7.0

Code Fix Example

Go (Gin) API Security Remediation
Vulnerable:
package main

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

type Request struct {
  URL string `json:"url"`
}

func main() {
  r := gin.Default()
  r.POST("/fetch", func(c *gin.Context) {
    var req Request
    if err := c.ShouldBindJSON(&req); err != nil {
      c.JSON(400, gin.H{"error": "invalid input"})
      return
    }
    // Vulnerable: directly fetch client-supplied URL
    resp, err := http.Get(req.URL)
    if err != nil {
      c.JSON(502, gin.H{"error": "fetch failed"})
      return
    }
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    c.Data(resp.StatusCode, "application/octet-stream", body)
  })
  r.Run()
}

Fixed:
package main

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

func isPrivateHost(host string) bool {
  addrs, err := net.LookupIP(host)
  if err != nil {
    return false
  }
  for _, ip := range addrs {
    if ip.IsPrivate() {
      return true
    }
  }
  return false
}

func main() {
  r := gin.Default()
  r.POST("/fetch", func(c *gin.Context) {
    var req struct{ URL string `json:"url"` }
    if err := c.ShouldBindJSON(&req); err != nil {
      c.JSON(400, gin.H{"error": "invalid input"})
      return
    }

    u, err := url.Parse(req.URL)
    if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
      c.JSON(400, gin.H{"error": "invalid url"})
      return
    }
    if isPrivateHost(u.Hostname()) {
      c.JSON(400, gin.H{"error": "private/internal host not allowed"})
      return
    }
    // Optional allowlist (example) to restrict permissible hosts
    allowed := map[string]bool{"example.com": true}
    if !allowed[u.Hostname()] {
      c.JSON(403, gin.H{"error": "host not allowed"})
      return
    }

    client := &http.Client{
      Timeout: 5 * time.Second,
      Transport: &http.Transport{
        TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
        DialContext: (&net.Dialer{Timeout: 5 * time.Second}).DialContext,
      },
      CheckRedirect: func(req *http.Request, via []*http.Request) error {
        if len(via) > 1 {
          return http.ErrUseLastResponse
        }
        return nil
      },
    }

    resp, err := client.Get(req.URL)
    if err != nil {
      c.JSON(502, gin.H{"error": "fetch failed"})
      return
    }
    defer resp.Body.Close()
    data, _ := io.ReadAll(resp.Body)
    c.Data(resp.StatusCode, "application/octet-stream", data)
  })
  r.Run()
}

CVE References

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