Back to Blog
high SEVERITY7 min read

How Server-Side Request Forgery (SSRF) happens in Go HTTP handlers and how to fix it

A Server-Side Request Forgery (SSRF) vulnerability was discovered in `internal/web/controller/server.go` where the `applySubTemplate` endpoint accepted arbitrary URLs from user input and passed them directly to `serverService.ApplySubTemplateFromGithub()` without any host validation. An attacker could exploit this to make the server issue HTTP requests to internal network resources, cloud metadata endpoints, or redirect-controlled destinations. The fix introduces a strict allowlist that restrict

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 4, 2026•Reviewed September 4, 2026

Answer Summary

This is a Server-Side Request Forgery (SSRF) vulnerability (CWE-918) in a Go HTTP handler (`internal/web/controller/server.go`, line 340). The `applySubTemplate` endpoint bound a user-supplied URL directly to `serverService.ApplySubTemplateFromGithub(f.URL)` without validating the host, allowing attackers to redirect the server's HTTP client to internal resources. The fix adds an `isAllowedSubTemplateURL()` function that parses the URL and enforces a strict hostname allowlist (`github.com` and `raw.githubusercontent.com`), rejecting all other destinations before the service call is made.

Vulnerability at a Glance

cweCWE-918
fixAdded isAllowedSubTemplateURL() allowlist function restricting hosts to github.com and raw.githubusercontent.com
riskAttacker forces server to make HTTP requests to internal or unintended external hosts
languageGo
root causeUser-supplied URL passed to HTTP client without hostname validation in applySubTemplate handler
vulnerabilityServer-Side Request Forgery (SSRF)

How Server-Side Request Forgery (SSRF) Happens in Go HTTP Handlers and How to Fix It

The internal/web/controller/server.go file is responsible for handling web requests in this Go service — including an endpoint called applySubTemplate, which allows users to load configuration templates from a URL. A flaw in how that URL was handled created a textbook Server-Side Request Forgery (SSRF) vulnerability: the server would fetch any URL the user supplied, not just GitHub URLs as intended.

This post walks through exactly what went wrong, how an attacker could exploit it, and how an 11-line fix closes the door entirely.


The Vulnerability Explained

What the Code Was Doing

The applySubTemplate handler at line 340 of server.go accepted a form-bound struct containing a URL field and passed it directly to the service layer:

// BEFORE — vulnerable code (server.go ~line 348)
func (a *ServerController) applySubTemplate(c *gin.Context) {
    // ... form binding ...
    err := a.serverService.ApplySubTemplateFromGithub(f.URL)
    if err != nil {
        jsonMsg(c, err.Error(), nil)
        return
    }
    jsonMsg(c, "Sub HTML template downloaded & installed successfully!", nil)
}

Despite the method being named ApplySubTemplateFromGithub, there was no enforcement that f.URL actually pointed to GitHub. The name was aspirational, not enforced. Any URL a user submitted — http://169.254.169.254/latest/meta-data/, http://internal-db:5432/, file:///etc/passwd — would be forwarded to the HTTP client without question.

Compounding the risk, a CheckRedirect handler at line 630 processed HTTP redirects without validating where those redirects led. This meant an attacker could submit a seemingly-legitimate URL that immediately redirected to an internal target, bypassing any naive string-level checks.

A Concrete Attack Scenario

Consider an attacker targeting this application running in AWS EC2. They craft a POST request to the applySubTemplate endpoint:

POST /api/server/applySubTemplate
Content-Type: application/x-www-form-urlencoded

url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

The server dutifully fetches the AWS Instance Metadata Service (IMDS) endpoint. The response — containing IAM role credentials — is then processed as if it were a GitHub template. Even if the processing fails gracefully, the server has already made the outbound request, and a crafty attacker can often infer the response contents through timing, error messages, or side channels.

A two-step redirect attack looks like this:

  1. Attacker hosts https://attacker.com/redirect which returns HTTP 302 → http://10.0.0.1/admin
  2. Attacker submits url=https://attacker.com/redirect to the endpoint
  3. The CheckRedirect handler follows the redirect to the internal admin panel
  4. The server fetches and processes internal content

Why This Is Classified CWE-918

CWE-918: Server-Side Request Forgery covers exactly this pattern: a web application fetches a remote resource based on user-supplied input without validating that the destination is within an expected, safe set of targets. The impact ranges from internal network reconnaissance to credential theft to full remote code execution in environments with metadata services or internal APIs.


The Fix

The patch introduces a dedicated validation function, isAllowedSubTemplateURL, and calls it before the service invocation. Here's the complete before/after:

Before (Vulnerable)

func (a *ServerController) applySubTemplate(c *gin.Context) {
    var f struct{ URL string }
    if err := c.ShouldBind(&f); err != nil {
        jsonMsg(c, I18nWeb(c, "pages.server.loadError"), err)
        return
    }
    // ❌ No URL validation — f.URL is used as-is
    err := a.serverService.ApplySubTemplateFromGithub(f.URL)
    ...
}

After (Fixed)

func (a *ServerController) applySubTemplate(c *gin.Context) {
    var f struct{ URL string }
    if err := c.ShouldBind(&f); err != nil {
        jsonMsg(c, I18nWeb(c, "pages.server.loadError"), err)
        return
    }
    // ✅ Validate BEFORE calling the service
    if !isAllowedSubTemplateURL(f.URL) {
        jsonMsg(c, I18nWeb(c, "pages.server.loadError"), fmt.Errorf("only github.com and raw.githubusercontent.com URLs are allowed"))
        return
    }
    err := a.serverService.ApplySubTemplateFromGithub(f.URL)
    ...
}

// isAllowedSubTemplateURL restricts sub-template downloads to GitHub hosts only,
// preventing SSRF via arbitrary or redirect-controlled destinations.
func isAllowedSubTemplateURL(rawURL string) bool {
    u, err := url.Parse(rawURL)
    if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
        return false
    }
    host := strings.ToLower(u.Hostname())
    return host == "github.com" || host == "raw.githubusercontent.com"
}

Why This Fix Works

Several design decisions in isAllowedSubTemplateURL are worth highlighting:

  1. Proper URL parsing via url.Parse() — Rather than substring matching (which can be bypassed with https://evil.com/github.com/path), the fix uses Go's standard net/url package to parse the URL and extract only the hostname component.

  2. Scheme enforcement — The function explicitly rejects anything that isn't http or https. This blocks file://, ftp://, gopher://, and other schemes that could be abused in certain HTTP client configurations.

  3. u.Hostname() not u.Host — Hostname() strips the port number before comparison, so github.com:443 and github.com both resolve to github.com. Using u.Host directly would allow github.com.evil.com to be disguised as a port specification in some edge cases.

  4. Case normalization with strings.ToLower() — Hostnames are case-insensitive per RFC 1034. Lowercasing before comparison prevents bypasses like GitHub.COM.

  5. Two imports added, zero behavior changed for valid inputs — The net/url and strings packages are added to the import block. Requests with valid GitHub URLs pass through identically; only invalid URLs are now rejected early.


Key Takeaways

  • The function name ApplySubTemplateFromGithub was not a security control — naming a function after its intended input doesn't restrict actual input. Validation code must be explicit.
  • url.Parse() + u.Hostname() is the correct pattern in Go for extracting a comparable hostname from a user-supplied URL string; avoid substring or regex checks on raw URL strings.
  • Redirect chains can bypass entry-point validation — the CheckRedirect handler in server.go at line 630 is a second SSRF surface that should apply the same allowlist.
  • SSRF primitives are chain links, not dead ends — even if the template processing doesn't directly expose the fetched content, the outbound request itself can leak information or trigger side effects in internal services.
  • Two-step SSRF via open redirectors is a real attack pattern: submitting a URL that passes a naive check but immediately redirects to an internal target is a well-documented bypass technique.

How Orbis AppSec Detected This

  • Source: HTTP form parameter f.URL bound via c.ShouldBind() in the applySubTemplate handler (server.go:340)
  • Sink: a.serverService.ApplySubTemplateFromGithub(f.URL) — a function that issues an outbound HTTP request using the caller-supplied URL
  • Missing control: No hostname validation or allowlist between the form binding and the service call; the CheckRedirect handler also lacked destination validation
  • CWE: CWE-918 — Server-Side Request Forgery (SSRF)
  • Fix: Introduced isAllowedSubTemplateURL() to parse and allowlist only github.com and raw.githubusercontent.com before the service call is reached

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

The applySubTemplate vulnerability is a reminder that intent is not enforcement. A function named ApplySubTemplateFromGithub still accepted any URL until explicit validation was added. The fix — a small, focused isAllowedSubTemplateURL function using Go's standard net/url package — closes the SSRF surface with minimal code and zero impact on legitimate requests.

SSRF vulnerabilities are particularly dangerous in cloud-hosted services where metadata APIs, internal databases, and service meshes are reachable from the application's network context. Proactively validating URLs at handler boundaries, enforcing strict allowlists, and extending that validation to redirect handlers are the habits that keep Go services safe.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #196

Related Articles

critical

ffmpeg.Probe() Protocol Abuse in the GetSnapshot Thumbnailer

The local storage driver's thumbnail helpers — `GetSnapshot()` and `resizeImageToBufferWithFFmpegGo()` — handed user-controlled paths to `ffmpeg.Input()` and `ffmpeg.Probe()` with no restriction on which FFmpeg protocols those processes could open. A file that passed the existing `sanitizeFilePath` check (absolute, cleaned, regular file) could still be an HLS playlist or concat script pointing at `http://`, `subfile:` or `file:` targets, turning thumbnail generation into SSRF and arbitrary file

high

axios 1.18.1 CVE-2026-101898: HTTP/2 Ignores proxy and lookup

A dependency upgrade moved `axios` from 1.18.1 to 1.20.0 to pick up the fix for CVE-2026-101898, in which proxy and DNS resolution settings supplied through axios request configuration were not applied on the HTTP/2 request path. Applications that rely on `config.proxy`, `HTTP_PROXY`/`NO_PROXY` environment variables, or a custom `lookup` function as an SSRF or egress control could have those controls silently skipped for HTTP/2 requests.

critical

Cloudflare Worker Reverse Proxy SSRF via String Concatenation

A Cloudflare Worker reverse proxy built its upstream request URL by string-concatenating an environment variable with the incoming request's pathname and search string, letting a crafted path or query redirect requests to attacker-chosen hosts. The fix replaces concatenation with the `URL` object's `pathname` and `search` setters, which normalize input against the configured origin instead of blindly appending it.

high

fast-uri 3.1.2 IDN Host Bypass: CVE-2026-13676 Upgrade

A dependency tree in this repository resolved `fast-uri` 3.1.2, a version affected by CVE-2026-13676, in which improper Unicode hostname canonicalization lets a crafted URI parse to one host while the HTTP client connects to another. Because `fast-uri` is what `ajv` uses to parse and resolve URIs for `format: "uri"` validation and `$ref` resolution, any host allowlist built on its parsed output could be bypassed. The fix upgrades the resolved copy to `fast-uri` 4.1.4 and pins it with an npm `ove

high

boardcards.js parseArgs() SSRF: --board Flag Reaches Metadata IPs

The `parseArgs()` function in boardcards.js accepted a user-controlled `--board` command-line argument and passed it directly to fetch() calls without validating the URL scheme or hostname. An attacker could supply `http://169.254.169.254/latest/meta-data/` or other internal addresses to extract cloud credentials or scan internal networks.

high

fetch() with Automatic Redirects Leaks HTTPS Trust to api.tomys.top

The `withProxy()` fetch wrapper allowed automatic HTTP redirects from `api.tomys.top`, trusting the external API's domain integrity without validating final destinations. A compromised or malicious redirect response could send users to attacker-controlled phishing sites while maintaining HTTPS protocol and hostname checks that falsely appear secure.