Description
template.HTMLAttr treats a string as a trusted HTML attribute without escaping it. If you construct an entire attribute from user input and convert it to this type, an attacker can use quotes to break out of the value and inject an event handler or another attribute.
Potential impact
- An attacker may execute JavaScript in the victim's page.
- This may expose data accessible to the page, alter its content, or send requests with the user's privileges.
Remediation
- Keep attribute names and structure fixed in the template, and pass values as ordinary strings.
- Use the context-aware escaping in
html/templateinstead of converting user input totemplate.HTMLAttr. - Restrict characters and length according to the value's intended use.
Examples
Before
go
package main
import (
"html/template"
"net/http"
)
func unsafeHandler(w http.ResponseWriter, r *http.Request) {
// Read user input from the query parameter
color := r.URL.Query().Get("color") // Expected input, e.g. "red"
// Unsafe: concatenate user input into an HTML attribute
// Attack example: color="red" onclick="alert(1)"
attr := "style=\"color:" + color + "\""
// template.HTMLAttr does not escape the value
safeAttr := template.HTMLAttr(attr)
// Use attr directly in the template
t := template.Must(template.New("page").Parse(`
<html><body>
<p {{.}}>Hello</p>
</body></html>`))
// Injected attributes may execute code in the browser
_ = t.Execute(w, safeAttr)
}
After
go
package main
import (
"html/template"
"net/http"
"regexp"
)
var pageTmpl = template.Must(template.New("page").Parse(`
<html><body>
<p style="color:{{.Color}}">Hello</p>
</body></html>`))
// Limit characters and length; this does not validate CSS color names
var colorRe = regexp.MustCompile(`^[a-zA-Z0-9_-]{1,20}$`)
func safeHandler(w http.ResponseWriter, r *http.Request) {
color := r.URL.Query().Get("color")
if !colorRe.MatchString(color) {
// Use the default when validation fails
color = "black"
}
// Pass an ordinary string to html/template
// The template escapes it for the output context
data := struct {
Color string
}{Color: color}
if err := pageTmpl.Execute(w, data); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
}
Explanation: Before, input such as red" onclick="alert(1) can create a new event attribute. After, Color is passed as an ordinary string and receives filtering and escaping appropriate for a CSS value. The regular expression limits characters and length; it does not guarantee that every accepted value is a valid CSS color.