go.lang.security.audit.xss.no-fprintf-to-responsewriter.no-fprintf-to-responsewriter

profile photo of semgrepsemgrep
Author
6,305
Download Count*

Detected 'Fprintf' or similar writing to 'http.ResponseWriter'. 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-fprintf-to-responsewriter
    message: Detected 'Fprintf' or similar writing to 'http.ResponseWriter'. 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-not: fmt.$PRINTF($WRITER, "...")
      - pattern: fmt.$PRINTF($WRITER, ...)
    languages:
      - go

Examples

no-fprintf-to-responsewriter.go

// cf. https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/

package main

import (
	"fmt"
	"net/http"
)


func isValid(token string) bool {
	return true
}

func vulnerableHandler(w http.ResponseWriter, r *http.Request) {
  r.ParseForm()
  tok := r.FormValue("token")
  if !isValid(tok) {
    // ruleid:no-fprintf-to-responsewriter
    fmt.Fprintf(w, "Invalid token: %q", tok)
  }
  // ...
}

// cf. https://github.com/wrfly/container-web-tty//blob/09f891f0d12d0a930f37b675e2eda5784733579a/route/asset/bindata.go#L242
func dirList(w http.ResponseWriter, r *http.Request, f http.File) {
	dirs, err := f.Readdir(-1)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
    // ok:no-fprintf-to-responsewriter
		fmt.Fprint(w, "Error reading directory")
		return
	}
	sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
  w.Header().Set("Content-Type", "text/html; charset=utf-8")
  // ok:no-fprintf-to-responsewriter
  fmt.Fprintf(w, "<pre>\n")
  for _, d := range dirs {
    name := d.Name()
    if d.IsDir() {
      name += "/"
    }
    // name may contain '?' or '#', which must be escaped to remain
    // part of the URL path, and not indicate the start of a query
    // string or fragment.
    url := url.URL{Path: filepath.Join(r.RequestURI, name)}
    // ruleid:no-fprintf-to-responsewriter
    fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), d.Name())
  }
  // ok:no-fprintf-to-responsewriter
  fmt.Fprintf(w, "</pre>\n")
}