Description
Rendering user input in HTML without appropriate escaping may let attackers inject <script> tags or event handlers such as onclick. If a browser interprets the input as active page content, the attacker's JavaScript can execute in the victim's browser, enabling session abuse, phishing or keylogging.
Potential impact
- Attackers may steal script-readable cookies or tokens, or make requests with the user's permissions.
- Malicious JavaScript can alter the DOM, redirect the browser or record keystrokes.
- Forged login or payment forms may trick users into disclosing sensitive data.
- Malicious popups and redirects can damage trust and cause financial or legal consequences.
Remediation
- For HTML responses, use trusted templates with
html/templateand pass user input as ordinary data. http.ResponseWriter.Write()is not inherently unsafe. Do not serve HTML assembled directly from user input.- Handle values for their HTML, URL or JavaScript context, and never build template source from user input.
- Do not cast untrusted input to trust-marking types such as
template.HTML,template.URLortemplate.JS. - Validate URL and name formats, but do not treat input validation as a replacement for output-context handling.
Examples
These examples return HTML. URL-context handling in html/template restricts dangerous schemes and attribute injection; it does not define a policy for allowed external-link domains.
Before
go
package main
import (
"fmt"
"net/http"
)
// Before: write user input directly through ResponseWriter
func errorPage(w http.ResponseWriter, r *http.Request) {
// A query parameter controlled by the user
url := r.URL.Query().Get("url")
// HTML containing user input
const template = `
<html>
<body>
<h1>error; page not found. <a href="%s">go back</a></h1>
</body>
</html>`
// Format the string, then write it directly
// The url may contain "javascript:alert(1)" or "\" onclick=\"alert(1)"
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(fmt.Sprintf(template, url)))
}
func main() {
http.HandleFunc("/error", errorPage)
http.ListenAndServe(":8080", nil)
}
After
go
package main
import (
"html/template"
"log"
"net/http"
)
// After: automatic escaping with html/template
// The template receives data at {{.URL}},
// and html/template applies context-aware escaping
var errorTmpl = template.Must(template.New("error").Parse(`
<html>
<body>
<h1>error; page not found. <a href="{{.URL}}">go back</a></h1>
</body>
</html>`))
func errorPageSafe(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
// Use a default when the URL is empty
if url == "" {
url = "/"
}
w.WriteHeader(http.StatusBadRequest)
// Pass the data structure to the template
data := struct {
URL string
}{URL: url}
if err := errorTmpl.Execute(w, data); err != nil {
log.Println("template execute error:", err)
}
}
func main() {
http.HandleFunc("/error", errorPageSafe)
http.ListenAndServe(":8080", nil)
}
Explanation:
- Before:
fmt.Sprintf()inserts theurlparameter directly into HTML, thenw.Write()sends it to the browser. Input such as" onclick="alert(1)can break out of the<a>attribute and introduce JavaScript. - After: An ordinary string is passed to the trusted template's
hrefposition.html/templatehandles that URL context, restricting dangerous schemes and attribute breakout. Do not bypass this handling by casting user input totemplate.URLortemplate.HTML.