go.lang.security.audit.xss.template-html-does-not-escape.unsafe-template-type

profile photo of semgrepsemgrep
Author
6,305
Download Count*

Semgrep could not determine that the argument to 'template.HTML()' is a constant. 'template.HTML()' and similar does not escape contents. Be absolutely sure there is no user-controlled data in this template. If user data can reach this template, you may have a XSS vulnerability. Instead, do not use this function and use 'template.Execute()'.

Run Locally

Run in CI

Defintion

rules:
  - id: unsafe-template-type
    message: Semgrep could not determine that the argument to 'template.HTML()' is a
      constant. 'template.HTML()' and similar does not escape contents. Be
      absolutely sure there is no user-controlled data in this template. If user
      data can reach this template, you may have a XSS vulnerability. Instead,
      do not use this function and use 'template.Execute()'.
    metadata:
      cwe:
        - "CWE-79: Improper Neutralization of Input During Web Page Generation
          ('Cross-site Scripting')"
      owasp:
        - A07:2017 - Cross-Site Scripting (XSS)
        - A03:2021 - Injection
      references:
        - https://golang.org/pkg/html/template/#HTML
        - https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/vulnerability/xss/xss.go#L33
      category: security
      technology:
        - go
      confidence: LOW
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - audit
      likelihood: LOW
      impact: MEDIUM
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Cross-Site-Scripting (XSS)
    languages:
      - go
    severity: WARNING
    patterns:
      - pattern-not: template.$ANY("..." + "...")
      - pattern-not: template.$ANY("...")
      - pattern-either:
          - pattern: template.HTML(...)
          - pattern: template.CSS(...)
          - pattern: template.HTMLAttr(...)
          - pattern: template.JS(...)
          - pattern: template.JSStr(...)
          - pattern: template.Srcset(...)
          - pattern: template.URL(...)

Examples

template-html-does-not-escape.go

package main

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

func Fine(r *http.Request) template.HTML {
	// ok: unsafe-template-type
	return template.HTML("<html><body><h1>Hello, world</h1></body></html>")
}

func AlsoFine(r *http.Request) template.HTML {
	// ok: unsafe-template-type
	return template.HTML("<html><body><h1>" + "Hello, world</h1></body></html>")
}

func OthersThatAreFine(r *http.Request) template.HTML {
	// ok: unsafe-template-type
	a := template.HTMLAttr("<html><body><h1>Hello, world</h1></body></html>")
	// ok: unsafe-template-type
	a := template.JS("<html><body><h1>Hello, world</h1></body></html>")
	// ok: unsafe-template-type
	a := template.URL("<html><body><h1>Hello, world</h1></body></html>")
	// ok: unsafe-template-type
	a := template.CSS("<html><body><h1>Hello, world</h1></body></html>")
	// ok: unsafe-template-type
	a := template.Srcset("<html><body><h1>Hello, world</h1></body></html>")
}

func OthersThatAreNOTFine(r *http.Request, data string) template.HTML {
	// ruleid: unsafe-template-type
	a := template.HTMLAttr(fmt.Sprintf("<html><body><h1>%s</h1></body></html>", data))
	// ruleid: unsafe-template-type
	a := template.JS(fmt.Sprintf("<html><body><h1>%s</h1></body></html>", data))
	// ruleid: unsafe-template-type
	a := template.URL(fmt.Sprintf("<html><body><h1>%s</h1></body></html>", data))
	// ruleid: unsafe-template-type
	a := template.CSS(fmt.Sprintf("<html><body><h1>%s</h1></body></html>", data))
	// ruleid: unsafe-template-type
	a := template.Srcset(fmt.Sprintf("<html><body><h1>%s</h1></body></html>", data))
}

func Concat(r *http.Request) template.HTML {
	customerId := r.URL.Query().Get("id")
	tmpl := "<html><body><h1>" + customerId + "</h1></body></html>"

	// ruleid: unsafe-template-type
	return template.HTML(tmpl)
}

func ConcatBranch(r *http.Request) template.HTML {
	customerId := r.URL.Query().Get("id")
	doIt, err := strconv.ParseBool(r.URL.Query().Get("do"))
	if err != nil {
		return template.HTML("")
	}
	var tmpl string
	if doIt {
		tmpl = "<html><body><h1>" + customerId + "</h1></body></html>"
	} else {
		tmpl = ""
	}

	// ruleid: unsafe-template-type
	return template.HTML(tmpl)
}

func ConcatInline(r *http.Request) template.HTML {
	customerId := r.URL.Query().Get("id")

	// ruleid: unsafe-template-type
	return template.HTML("<html><body><h1>" + customerId + "</h1></body></html>")
}

func ConcatInlineOneside(r *http.Request) template.HTML {
	customerId := r.URL.Query().Get("id")

	// ruleid: unsafe-template-type
	return template.HTML("<html><body><h1>" + customerId)
}

func Formatted(r *http.Request) template.HTML {
	customerId := r.URL.Query().Get("id")
	tmpl, err := fmt.Printf("<html><body><h1>%s</h1></body></html>", customerId)
	if err != nil {
		return template.HTML("")
	}
	// ruleid: unsafe-template-type
	return template.HTML(tmpl)
}

func FormattedInline(r *http.Request) template.HTML {
	customerId := r.URL.Query().Get("id")
	// ruleid: unsafe-template-type
	return template.HTML(fmt.Sprintf("<html><body><h1>%s</h1></body></html>", customerId))
}

func main() {}