If you work with Go web applications, you've probably used Go's standard library html/template for safe dynamic HTML. It's a cornerstone for many web servers who want to avoid XSS (cross-site scripting) nightmares. Unfortunately, a 2023 vulnerability, CVE-2023-39318, shows that not even core Go libraries are immune from subtle HTML parsing bugs.
In this article, we'll walk through what went wrong, demonstrate how it could be exploited with clear code, and offer solutions. Don't worry — we'll use simple language and explain every step.
What Is CVE-2023-39318?
In short, Go's html/template package does not properly handle certain kinds of comments inside <script> tags. Specifically:
And on hashbang comments (#! ...), sometimes used for scripts
Because of this flaw, the template parser can get confused about where scripts start and end — causing data to be inserted into scripts without proper escaping. That's a direct line to a possible Cross-Site Scripting (XSS) attack.
Here’s a classic example of what you might do in a Go web server
package main
import (
"html/template"
"net/http"
)
var tmpl = template.Must(template.New("page").Parse(`
<!DOCTYPE html>
<html>
<head>
<title>Test Page</title>
</head>
<body>
<script>
// Some JS setup
{{.JS}}
</script>
</body>
</html>
`))
func handler(w http.ResponseWriter, r *http.Request) {
jsData := r.FormValue("js")
tmpl.Execute(w, map[string]interface{}{
"JS": jsData,
})
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":808", nil)
}
Let’s say someone visits http://localhost:808/?js=alert(1) — as you’d expect, they see an alert pop up. If "JS" was trusted data, maybe that's okay. But if that's user input, all bets are off.
Why Isn't This Escaping User Input?
Normally, if you put {{.JS}} inside HTML, the html/template package escapes any user-provided content for you:
" becomes "
But in the context of <script> ... </script>, Go’s template engine does not auto-escape like it would for HTML. Instead, it passes the string in as raw JavaScript.
Here's Where The Bug Comes In
The security model works *as long as* Go's template parser can keep track of where the script context begins and ends. But CVE-2023-39318 shows a failure here: if you put *weird comments* in or around the <script> tag, Go's parser gets confused and can insert data in places it shouldn't, unescaped.
Suppose someone submits the following payload for "js"
<!--
alert('XSS');
// -->
In a naive template, it looks like
<script>
<!--
alert('XSS');
// -->
</script>
Browsers will happily parse and execute alert('XSS');. But Go’s escaping engine might not see that as true script context, and could mishandle escaping — because the template engine is not expecting HTML comments (like <!-- ... -->) inside <script>.
Even more subtle: hashbang (#!) comments cause confusion.
Example payload
#! alert('Gotcha')
Let's see it in action. Run the earlier Go server, and then try
http://localhost:808/?js=<!--%Aalert('Pwned')//-->;
In HTML, %A is a linebreak, so in the page
<script>
<!--
alert('Pwned')
// -->
</script>
On older browsers (and surprisingly still in some), the browser will execute alert('Pwned').
But the real danger is template action confusion. If an attacker can trick the escaping so that injected code is inserted *as code*, XSS is inevitable.
Links and References
- Go security advisory: html/template: improper handling of HTML-like “ contexts
- Go CVE record: CVE-2023-39318
- Go official issue tracker: html/template: improper handling of comments in