go.lang.security.audit.xss.no-direct-write-to-responsewriter.no-direct-write-to-responsewriter

profile photo of semgrepsemgrep
Author
6,087
Download Count*

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

Run Locally

Run in CI

Defintion

rules:
  - id: no-direct-write-to-responsewriter
    languages:
      - go
    message: Detected directly writing or similar in 'http.ResponseWriter.write()'.
      This bypasses HTML escaping that prevents cross-site scripting
      vulnerabilities. Instead, use the 'html/template' package and render data
      using 'template.Execute()'.
    metadata:
      category: security
      cwe:
        - "CWE-79: Improper Neutralization of Input During Web Page Generation
          ('Cross-site Scripting')"
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      owasp:
        - A07:2017 - Cross-Site Scripting (XSS)
        - A03:2021 - Injection
      references:
        - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
      technology:
        - go
      confidence: LOW
      cwe2022-top25: true
      cwe2021-top25: true
      subcategory:
        - audit
      likelihood: LOW
      impact: MEDIUM
      vulnerability_class:
        - Cross-Site-Scripting (XSS)
    patterns:
      - pattern-either:
          - pattern-inside: |
              func $HANDLER(..., $WRITER http.ResponseWriter, ...) {
                ...
              }
          - pattern-inside: |
              func $HANDLER(..., $WRITER *http.ResponseWriter, ...) {
                ...
              }
          - pattern-inside: |
              func(..., $WRITER http.ResponseWriter, ...) {
                ...
              }
      - pattern-either:
          - pattern: $WRITER.Write(...)
          - pattern: (*$WRITER).Write(...)
      - pattern-not: $WRITER.Write([]byte("..."))
    severity: WARNING

Examples

no-direct-write-to-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 healthCheck(w http.ResponseWriter, r *http.Request) {
    // ok: no-direct-write-to-responsewriter
    w.Write([]byte("alive"))
}

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-direct-write-to-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-direct-write-to-responsewriter
    w.Write([]byte(fmt.Sprintf(template, url)))
}

func writeErrorResponse(rw *http.ResponseWriter, status int, body string) {
    (*rw).WriteHeader(status)
    // ruleid: no-direct-write-to-responsewriter
    (*rw).Write([]byte(body))
}

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