Cross-Site Scripting via Manual HTML Construction in Go

High Risk Cross-Site Scripting (XSS)
goxsshtmlfmtstring-concatenation

What it is

XSS vulnerabilities occur when Go applications manually construct HTML using fmt.Sprintf() or string concatenation with user input, bypassing html/template's automatic escaping. This allows attackers to inject malicious scripts that execute in users' browsers, steal sessions, manipulate page content, or perform unauthorized actions.

package main

import (
    "fmt"
    "net/http"
)

func welcomeHandler(w http.ResponseWriter, r *http.Request) {
    username := r.URL.Query().Get("username")
    
    // VULNERABLE: fmt.Sprintf with user input in HTML
    htmlResponse := fmt.Sprintf(`<html><body>
        <h1>Welcome %s!</h1>
    </body></html>`, username)
    
    w.Header().Set("Content-Type", "text/html")
    w.Write([]byte(htmlResponse))
}

func commentHandler(w http.ResponseWriter, r *http.Request) {
    comment := r.FormValue("comment")
    
    // VULNERABLE: String concatenation in HTML
    html := "<div class='comment'>" + comment + "</div>"
    fmt.Fprintf(w, "<html><body>%s</body></html>", html)
}

// Attack: username = "<script>alert('XSS')</script>"
package main

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

func welcomeHandler(w http.ResponseWriter, r *http.Request) {
    username := r.URL.Query().Get("username")
    
    // SECURE: Use html/template for automatic escaping
    tmpl := `<html><body>
        <h1>Welcome {{.Username}}!</h1>
    </body></html>`
    
    t := template.Must(template.New("welcome").Parse(tmpl))
    data := struct{ Username string }{username}
    
    w.Header().Set("Content-Type", "text/html")
    t.Execute(w, data)
}

func commentHandler(w http.ResponseWriter, r *http.Request) {
    comment := r.FormValue("comment")
    
    // SECURE: Template handles escaping
    tmpl := `<html><body>
        <div class="comment">{{.Comment}}</div>
    </body></html>`
    
    t := template.Must(template.New("comment").Parse(tmpl))
    t.Execute(w, struct{ Comment string }{comment})
}

💡 Why This Fix Works

The vulnerable code uses fmt.Sprintf() and string concatenation to build HTML with user input, bypassing escaping and allowing script injection. The secure version uses html/template which automatically escapes user input, preventing XSS attacks.

Why it happens

Building HTML strings with fmt.Sprintf() containing user input.

Root causes

Using fmt.Sprintf() for HTML

Building HTML strings with fmt.Sprintf() containing user input.

String Concatenation in HTML

Concatenating user input directly into HTML strings.

Bypassing Template Escaping

Not using html/template package for HTML generation.

Fixes

1

Use html/template Package

Replace manual HTML construction with html/template for automatic escaping.

2

Use html.EscapeString()

If templates aren't feasible, manually escape with html.EscapeString().

3

Never Concatenate User Input

Avoid string concatenation or fmt formatting with user input in HTML contexts.

Detect This Vulnerability in Your Code

Sourcery automatically identifies cross-site scripting via manual html construction in go and many other security issues in your codebase.