From 94e9a5e19b831504eca2b7202b78d1a48c4be547 Mon Sep 17 00:00:00 2001 From: Roberto Clapis Date: Mon, 18 Nov 2019 10:05:07 +0100 Subject: [PATCH] text/template: harden JSEscape to also escape ampersand and equal MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Ampersand and equal are not dangerous in a JS/JSString context but they might cause issues if interpolated in HTML attributes. This change makes it harder to introduce XSS by misusing escaping. Thanks to t1ddl3r for reporting this common misuse scenario. Fixes #35665 Change-Id: Ice6416477bba4cb2ba2fe2cfdc20e027957255c0 Reviewed-on: https://go-review.googlesource.com/c/go/+/207637 Reviewed-by: Filippo Valsorda Reviewed-by: Mike Samuel Reviewed-by: Andrew Bonventre Reviewed-by: Daniel Martí --- src/html/template/example_test.go | 6 +++--- src/text/template/exec_test.go | 2 ++ src/text/template/funcs.go | 8 +++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/html/template/example_test.go b/src/html/template/example_test.go index 533c0dd961..9d965f1943 100644 --- a/src/html/template/example_test.go +++ b/src/html/template/example_test.go @@ -116,9 +116,9 @@ func Example_escape() { // "Fran & Freddie's Diner" <tasty@example.com> // "Fran & Freddie's Diner" <tasty@example.com> // "Fran & Freddie's Diner"32<tasty@example.com> - // \"Fran & Freddie\'s Diner\" \x3Ctasty@example.com\x3E - // \"Fran & Freddie\'s Diner\" \x3Ctasty@example.com\x3E - // \"Fran & Freddie\'s Diner\"32\x3Ctasty@example.com\x3E + // \"Fran \x26 Freddie\'s Diner\" \x3Ctasty@example.com\x3E + // \"Fran \x26 Freddie\'s Diner\" \x3Ctasty@example.com\x3E + // \"Fran \x26 Freddie\'s Diner\"32\x3Ctasty@example.com\x3E // %22Fran+%26+Freddie%27s+Diner%2232%3Ctasty%40example.com%3E } diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 2b299b0bf6..aa5cd4c552 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -909,6 +909,8 @@ func TestJSEscaping(t *testing.T) { {`Yukihiro says "今日は世界"`, `Yukihiro says \"今日は世界\"`}, {"unprintable \uFDFF", `unprintable \uFDFF`}, {``, `\x3Chtml\x3E`}, + {`no = in attributes`, `no \x3D in attributes`}, + {`' does not become HTML entity`, `\x26#x27; does not become HTML entity`}, } for _, tc := range testCases { s := JSEscapeString(tc.in) diff --git a/src/text/template/funcs.go b/src/text/template/funcs.go index 0985eda317..0568c798a8 100644 --- a/src/text/template/funcs.go +++ b/src/text/template/funcs.go @@ -642,6 +642,8 @@ var ( jsQuot = []byte(`\"`) jsLt = []byte(`\x3C`) jsGt = []byte(`\x3E`) + jsAmp = []byte(`\x26`) + jsEq = []byte(`\x3D`) ) // JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. @@ -670,6 +672,10 @@ func JSEscape(w io.Writer, b []byte) { w.Write(jsLt) case '>': w.Write(jsGt) + case '&': + w.Write(jsAmp) + case '=': + w.Write(jsEq) default: w.Write(jsLowUni) t, b := c>>4, c&0x0f @@ -704,7 +710,7 @@ func JSEscapeString(s string) string { func jsIsSpecial(r rune) bool { switch r { - case '\\', '\'', '"', '<', '>': + case '\\', '\'', '"', '<', '>', '&', '=': return true } return r < ' ' || utf8.RuneSelf <= r -- 2.50.0