go.lang.security.audit.xss.no-printf-in-responsewriter.no-printf-in-responsewriter

profile photo of semgrepsemgrep
Author
5,552
Download Count*

Detected 'printf' or similar in 'http.ResponseWriter.write()'. This bypasses HTML escaping that prevents cross-site scripting vulnerabilities. Instead, use the 'html/template' package to render data to users.

Run Locally

Run in CI

Defintion

rules:
  - id: no-printf-in-responsewriter
    message: Detected 'printf' or similar in 'http.ResponseWriter.write()'. This
      bypasses HTML escaping that prevents cross-site scripting vulnerabilities.
      Instead, use the 'html/template' package to render data to users.
    metadata:
      owasp:
        - A07:2017 - Cross-Site Scripting (XSS)
        - A03:2021 - Injection
      cwe:
        - "CWE-79: Improper Neutralization of Input During Web Page Generation
          ('Cross-site Scripting')"
      references:
        - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
      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)
    severity: WARNING
    patterns:
      - pattern-either:
          - pattern-inside: |
              func $HANDLER(..., $WRITER http.ResponseWriter, ...) {
                ...
              }
          - pattern-inside: |
              func(..., $WRITER http.ResponseWriter, ...) {
                ...
              }
      - pattern: |
          $WRITER.Write(<... fmt.$PRINTF(...) ...>, ...)
    languages:
      - go

Examples

no-printf-in-responsewriter.go

package main

import (
	"fmt"
	"log"
	"net/http"
)

func getMovieQuote() map[string]string {
	m := make(map[string]string)
	m["quote"] = "I'll be back."
	m["movie"] = "The Terminator"
	m["year"] = "1984"

	return m
}

func indexPage(w http.ResponseWriter, r *http.Request) {
    const tme = `<html>`

	const template = `
	<html>
	<body>
	  <h1>Random Movie Quotes</h1>
	  <h2>%s</h2>
	  <h4>~%s, %s</h4>
	</body>
	</html>`

	quote := getMovieQuote()

	quoteText := quote["quote"]
	movie := quote["movie"]
	year := quote["year"]

	w.WriteHeader(http.StatusAccepted)
	// ruleid: no-printf-in-responsewriter
	w.Write([]byte(fmt.Sprintf(template, quoteText, movie, year)))
}

func errorPage(w http.ResponseWriter, r *http.Request) {
	params := r.URL.Query()
	urls, ok := params["url"]
	if !ok {
		log.Println("Error")
		return
	}
	url := urls[0]

    const template = `
	<html>
	<body>
	  <h1>error; page not found. <a href="%s">go back</a></h1>
	</body>
	</html>`

	w.WriteHeader(http.StatusAccepted)
	// ruleid: no-printf-in-responsewriter
	w.Write([]byte(fmt.Sprintf(template, url)))
}

func main() {
	http.HandleFunc("/", indexPage)
	http.HandleFunc("/error", errorPage)
	http.ListenAndServe(":8080", nil)
}