From 61f1899ad02ba74eba12543439bed2286d82e747 Mon Sep 17 00:00:00 2001 From: Kirill Danshin Date: Sun, 8 Nov 2020 16:07:18 +0300 Subject: [PATCH] fix issue #875 Signed-off-by: Kirill Danshin --- header.go | 8 +++++++- http_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/header.go b/header.go index e13f6852db..ddd2837594 100644 --- a/header.go +++ b/header.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "strings" "sync" "sync/atomic" "time" @@ -2167,9 +2168,14 @@ func nextLine(b []byte) ([]byte, []byte, error) { return b[:n], b[nNext+1:], nil } +var ( + foldReplacer = strings.NewReplacer("\r\n", " ", "\r", " ", "\n", " ") +) + func initHeaderKV(kv *argsKV, key, value string, disableNormalizing bool) { kv.key = getHeaderKeyBytes(kv, key, disableNormalizing) - kv.value = append(kv.value[:0], value...) + // https://tools.ietf.org/html/rfc7230#section-3.2.4 + kv.value = append(kv.value[:0], foldReplacer.Replace(value)...) } func getHeaderKeyBytes(kv *argsKV, key string, disableNormalizing bool) []byte { diff --git a/http_test.go b/http_test.go index c67f52092b..2f91e345ce 100644 --- a/http_test.go +++ b/http_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "mime/multipart" "reflect" + "strconv" "strings" "testing" "time" @@ -30,6 +31,53 @@ func TestFragmentInURIRequest(t *testing.T) { } } +func TestIssue875(t *testing.T) { + type testcase struct { + uri string + expectedRedirect string + expectedLocation string + } + + var testcases = []testcase{ + { + uri: `http://localhost:3000/?redirect=foo%0d%0aSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`, + expectedRedirect: "foo\r\nSet-Cookie: SESSIONID=MaliciousValue\r\n", + expectedLocation: "Location: foo Set-Cookie: SESSIONID=MaliciousValue", + }, + { + uri: `http://localhost:3000/?redirect=foo%0dSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`, + expectedRedirect: "foo\rSet-Cookie: SESSIONID=MaliciousValue\r\n", + expectedLocation: "Location: foo Set-Cookie: SESSIONID=MaliciousValue", + }, + { + uri: `http://localhost:3000/?redirect=foo%0aSet-Cookie:%20SESSIONID=MaliciousValue%0d%0a`, + expectedRedirect: "foo\nSet-Cookie: SESSIONID=MaliciousValue\r\n", + expectedLocation: "Location: foo Set-Cookie: SESSIONID=MaliciousValue", + }, + } + + for i, tcase := range testcases { + caseName := strconv.FormatInt(int64(i), 10) + t.Run(caseName, func(subT *testing.T) { + ctx := &RequestCtx{ + Request: Request{}, + Response: Response{}, + } + ctx.Request.SetRequestURI(tcase.uri) + + q := string(ctx.QueryArgs().Peek("redirect")) + if q != tcase.expectedRedirect { + subT.Errorf("unexpected redirect query value, got: %+v", q) + } + ctx.Response.Header.Set("Location", q) + + if !strings.Contains(ctx.Response.String(), tcase.expectedLocation) { + subT.Errorf("invalid escaping, got\n%s", ctx.Response.String()) + } + }) + } +} + func TestRequestCopyTo(t *testing.T) { t.Parallel()