Cross-Site Scripting via template.URL in Go

High Risk Cross-Site Scripting (XSS)
goxssurltemplatejavascript-scheme

What it is

XSS vulnerabilities occur when untrusted data is passed to template.URL, which bypasses URL escaping. Attackers can inject malicious URL schemes like javascript: or data: that execute scripts when users click links or when URLs are loaded in iframes, leading to session theft, phishing, and data exfiltration.

package main

import (
    "html/template"
    "net/http"
)

func linkHandler(w http.ResponseWriter, r *http.Request) {
    redirectURL := r.URL.Query().Get("url")
    
    // VULNERABLE: template.URL bypasses escaping
    safeURL := template.URL(redirectURL)
    
    tmpl := `<html><body>
        <a href="{{.URL}}">Click here</a>
    </body></html>`
    
    t := template.Must(template.New("link").Parse(tmpl))
    t.Execute(w, struct{ URL template.URL }{safeURL})
}

// Attack: url=javascript:alert('XSS')
// Result: <a href="javascript:alert('XSS')">Click here</a>
// This executes JavaScript when clicked
package main

import (
    "html/template"
    "net/http"
    "net/url"
)

var allowedSchemes = map[string]bool{
    "http":  true,
    "https": true,
    "mailto": true,
}

func linkHandler(w http.ResponseWriter, r *http.Request) {
    redirectURL := r.URL.Query().Get("url")
    
    // SECURE: Validate URL scheme
    if !isValidURL(redirectURL) {
        http.Error(w, "Invalid URL", http.StatusBadRequest)
        return
    }
    
    tmpl := `<html><body>
        <a href="{{.URL}}">Click here</a>
    </body></html>`
    
    t := template.Must(template.New("link").Parse(tmpl))
    // SAFE: Pass as string, template will escape
    t.Execute(w, struct{ URL string }{redirectURL})
}

func isValidURL(urlStr string) bool {
    parsedURL, err := url.Parse(urlStr)
    if err != nil {
        return false
    }
    // Check against scheme allowlist
    return allowedSchemes[parsedURL.Scheme]
}

💡 Why This Fix Works

The vulnerable code uses template.URL with user input, bypassing escaping and allowing javascript: or data: URL schemes that execute code. The secure version validates the URL scheme against an allowlist and passes the URL as a string for automatic escaping.

Why it happens

Passing user-controlled data to template.URL which bypasses escaping.

Root causes

Using template.URL with User Input

Passing user-controlled data to template.URL which bypasses escaping.

Missing URL Scheme Validation

Not validating URL schemes to reject javascript:, data:, vbscript: schemes.

String Concatenation Before template.URL

Building URLs with user input then marking as template.URL.

Fixes

1

Validate URL Schemes

Check URLs use only safe schemes (http, https, mailto) before using.

2

Use String Type Instead

Pass URLs as strings and let html/template automatically escape them.

3

Implement URL Allowlists

Validate URLs against allowlists of permitted hosts and schemes.

Detect This Vulnerability in Your Code

Sourcery automatically identifies cross-site scripting via template.url in go and many other security issues in your codebase.