Cross-Site Scripting in Go Lang
Vulnerable example
The server()
function that handles HTTP GET requests reads the parameter param
from the query string and returns it (as is) in the HTTP response. The default Content-Type
response header is determined by the http.DetectContentType function which implements the algorithm described by the WhatWG spec.
package main
import "io"
import "net/http"
func server(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, r.URL.Query().Get("param"))
}
func main() {
http.HandleFunc("/", server)
http.ListenAndServe(":5000", nil)
}
By sending a payload with param=hello
, the browser developer tool shows that the Content-Type
is set to text/plain
(which is not harmful and rendered as simple text by the browser).
By sending a request with param=<script>alert(1)</script>
, the Content-Type
of the response is set to text/html
, resulting in an exposure to Cross-Site Scripting.
Prevention
The above exposure can be remediated by performing output encoding of the user-controlled parameter. Go’s html/template
package includes several output encoding functions.
Context | API |
---|---|
HTML | HTMLEscapeString |
URL Attributes | URLQueryEscaper |
JavaScript | JSEscapeString |
CSS | CSS |
In the previous example, the issue can be remediated by performing Output Encoding of the user supplied input using the HTMLEscapeString
function:
import "io"
import "net/http"
func server(w http.ResponseWriter, r * http.Request) {
encodedParam = template.HTMLEscapeString(r.URL.Query().Get("param"))
io.WriteString(w, encodedParam)
}
func main() {
http.HandleFunc("/", server) http.ListenAndServe(":8080", nil)
}
Vulnerable Example
The server()
function that handles HTTP GET requests reads the parameter error
from the query string and includes it as part of a template (text/template
module). The default Content-Type response header is determined by the http.DetectContentType function that implements the algorithm described by the WhatWG spec.
import "net/http"
import "text/template"
func server(w http.ResponseWriter, r *http.Request) {
error := r.URL.Query().Get("param")
tmpl := template.New("error")
tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
tmpl.ExecuteTemplate(w, "T", error)
}
func main() {
http.HandleFunc("/", server)
http.ListenAndServe(":5000", nil)
}
By sending a request with param=<script>alert(1)</script>
, the Content-Type
of the response is set to text/html
, resulting in an exposure to Cross-Site Scripting.
Prevention
Replace the text/template
import with html/template
, which already includes built-in Output Encoding capabilities.
import "net/http"
import "html/template"
func server(w http.ResponseWriter, r *http.Request) {
error: = r.URL.Query().Get("param")
tmpl: = template.New("error")
tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)
tmpl.ExecuteTemplate(w, "T", error)
}
func main() {
http.HandleFunc("/", server)
http.ListenAndServe(":5000", nil)
}
By sending a request with error=<script>alert(1)</script>
, the payload is correctly encoded, and thus harmless.
References
Checkmarx - Go Secure Coding Practices OWASP - Cross-Site Scripting (XSS) OWASP - Cross-Site Scripting Prevention Cheat Sheet