From 41cba31e66c979c413a4368c4f3d82ebadf0fb5b Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 25 Sep 2025 13:24:01 -0700 Subject: [PATCH] mime/multipart: percent-encode CR and LF in header values to avoid CRLF injection When provided with a field or file name containing newlines, multipart.FileContentDisposition and other header-producing functions could create an invalid header value. In some scenarios, this could permit a malicious input to perform a CRLF injection attack: field := "field" evilFile := "name\"\r\nEvil-Header: \"evil" fmt.Printf("Content-Disposition: %v\r\n", multipart.FileContentDisposition(field, evilFile)) // Prints: // Content-Disposition: form-data; name="field"; filename="name" // Evil-Header: "evil" Percent-endode \r and \n characters in headers, as recommended by https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm The above algorithm also recommends using percent-encoding for quotes, but preserve the existing backslash-escape behavior for now. Empirically, browsers understand backslash-escape in attribute values. Fixes #75557 Change-Id: Ia203df6ef45a098070f3ebb17f9b6cf80c520ed4 Reviewed-on: https://go-review.googlesource.com/c/go/+/706677 Auto-Submit: Damien Neil Reviewed-by: Nicholas Husin Reviewed-by: Nicholas Husin LUCI-TryBot-Result: Go LUCI --- src/mime/multipart/writer.go | 14 +++++++++++++- src/mime/multipart/writer_test.go | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/mime/multipart/writer.go b/src/mime/multipart/writer.go index 8806ab960b..5d14069369 100644 --- a/src/mime/multipart/writer.go +++ b/src/mime/multipart/writer.go @@ -125,8 +125,20 @@ func (w *Writer) CreatePart(header textproto.MIMEHeader) (io.Writer, error) { return p, nil } -var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"", "\r", "%0D", "\n", "%0A") +// escapeQuotes escapes special characters in field parameter values. +// +// For historical reasons, this uses \ escaping for " and \ characters, +// and percent encoding for CR and LF. +// +// The WhatWG specification for form data encoding suggests that we should +// use percent encoding for " (%22), and should not escape \. +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm +// +// Empirically, as of the time this comment was written, it is necessary +// to escape \ characters or else Chrome (and possibly other browsers) will +// interpet the unescaped \ as an escape. func escapeQuotes(s string) string { return quoteEscaper.Replace(s) } diff --git a/src/mime/multipart/writer_test.go b/src/mime/multipart/writer_test.go index 4af6d8c597..c234b96108 100644 --- a/src/mime/multipart/writer_test.go +++ b/src/mime/multipart/writer_test.go @@ -184,6 +184,7 @@ func TestFileContentDisposition(t *testing.T) { {`somefield`, `somefile"withquotes".txt`, `form-data; name="somefield"; filename="somefile\"withquotes\".txt"`}, {`somefield\withbackslash`, "somefile.txt", `form-data; name="somefield\\withbackslash"; filename="somefile.txt"`}, {"somefield", `somefile\withbackslash.txt`, `form-data; name="somefield"; filename="somefile\\withbackslash.txt"`}, + {"a\rb\nc", "e\rf\ng", `form-data; name="a%0Db%0Ac"; filename="e%0Df%0Ag"`}, } for i, tt := range tests { if found := FileContentDisposition(tt.fieldname, tt.filename); found != tt.want { -- 2.52.0