go.lang.security.audit.xss.no-io-writestring-to-responsewriter.no-io-writestring-to-responsewriter

profile photo of semgrepsemgrep
Author
6,305
Download Count*

Detected 'io.WriteString()' writing directly 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-io-writestring-to-responsewriter
    message: Detected 'io.WriteString()' writing directly 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/
        - https://golang.org/pkg/io/#WriteString
      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: io.WriteString($WRITER, "...")
      - pattern: io.WriteString($WRITER, $STRING)
    languages:
      - go

Examples

no-io-writestring-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-io-writestring-to-responsewriter
    io.WriteString(w, fmt.Sprintf("Invalid token: %q", tok))
  }
  // ...
}

// cf. https://github.com/hashicorp/vault-plugin-database-mongodbatlas//blob/9cf156a44f9c8d56fb263f692541e5c7fbab9ab1/vendor/golang.org/x/net/http2/server.go#L2160
func handleHeaderListTooLong(w http.ResponseWriter, r *http.Request) {
	const statusRequestHeaderFieldsTooLarge = 431
	w.WriteHeader(statusRequestHeaderFieldsTooLarge)
  // ok:no-io-writestring-to-responsewriter
	io.WriteString(w, "<h1>HTTP Error 431</h1><p>Request Header Field(s) Too Large</p>")
}