SSRF

SSRF in Go Gin remediation [CVE-2026-34526]

[Fixed 2026-04] Updated CVE-2026-34526

Overview

Real-world impact: SSRF vulnerabilities allow an attacker to coerce the server into making HTTP requests to internal or protected resources. In the SillyTavern project, CVE-2026-34526 described a host validation flaw in a local UI that only allowed dotted-quad IPv4 addresses, leaving localhost, IPv6 loopback, and DNS names resolving to internal addresses open to SSRF exploitation. Because a separate port check limited exploitation to default ports, the impact could still affect services behind firewalls. The issue maps to CWE-918 and was patched in SillyTavern version 1.17.0. In Go/Gin environments, SSRF manifests similarly if user input is used to compose outgoing requests without strict host validation, proper DNS resolution checks, or an allowlist, enabling attackers to reach internal systems or services.

Affected Versions

SillyTavern before 1.17.0; patched in 1.17.0

Code Fix Example

Go (Gin) API Security Remediation
// Vulnerable pattern (Go + Gin)
package main

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

func main() {
  r := gin.Default()
  r.GET("/fetch", func(c *gin.Context) {
    target := c.Query("url")
    // Unsafe: directly uses user-supplied URL
    resp, err := http.Get(target)
    if err != nil {
      c.String(500, "error: %v", err)
      return
    }
    defer resp.Body.Close()
    c.Status(resp.StatusCode)
  })
  r.Run()
}

// Safe pattern (Go + Gin)
package main

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

func isSafeURL(u *url.URL) bool {
  if u == nil { return false }
  if u.Scheme != "http" && u.Scheme != "https" { return false }
  host := u.Hostname()
  if host == "" { return false }
  if strings.EqualFold(host, "localhost") { return false }

  ips, err := net.LookupIP(host)
  if err != nil { return false }
  for _, ip := range ips {
    if ip.IsLoopback() { return false }
    if ip4 := ip.To4(); ip4 != nil {
      // IPv4 private ranges
      if ip4[0] == 10 { return false }
      if ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31 { return false }
      if ip4[0] == 192 && ip4[1] == 168 { return false }
      if ip4[0] == 127 { return false }
    } else {
      // IPv6 private/local ranges (fc00::/7)
      if ip[0] == 0xfc || ip[0] == 0xfd {
        return false
      }
      if ip == nil {
        return false
      }
    }
  }
  return true
}

func main() {
  r := gin.Default()
  client := &http.Client{
    Timeout: 10 * time.Second,
    Transport: &http.Transport{
      DialContext: (&net.Dialer{ Timeout: 5 * time.Second }).DialContext,
      TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
      Proxy: http.ProxyFromEnvironment,
    },
  }

  r.GET("/fetch", func(c *gin.Context) {
    raw := c.Query("url")
    parsed, err := url.Parse(raw)
    if err != nil {
      c.String(400, "invalid url: %v", err)
      return
    }
    if !isSafeURL(parsed) {
      c.String(400, "blocked URL")
      return
    }

    resp, err := client.Get(raw)
    if err != nil {
      c.String(502, "request failed: %v", err)
      return
    }
    defer resp.Body.Close()
    c.Status(resp.StatusCode)
  })

  r.Run()
}

CVE References

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