Sanitize redirect Location header to prevent CRLF injection (#2186)

Route RequestCtx.Redirect Location updates through the canonical response
header setter so CR and LF bytes are normalized before serialization.

Add regression coverage for query-only and fragment-only redirects containing
CRLF, and verify the serialized response cannot emit an injected header line.
This commit is contained in:
Erik Dubbelboer
2026-04-27 12:28:30 +09:00
committed by GitHub
parent b8d29bee6e
commit 19b39dd08a
2 changed files with 9 additions and 1 deletions
+1 -1
View File
@@ -1450,7 +1450,7 @@ func (ctx *RequestCtx) RedirectBytes(uri []byte, statusCode int) {
}
func (ctx *RequestCtx) redirect(uri []byte, statusCode int) {
ctx.Response.Header.setNonSpecial(strLocation, uri)
ctx.Response.Header.SetCanonical(strLocation, uri)
statusCode = getRedirectStatusCode(statusCode)
ctx.Response.SetStatusCode(statusCode)
}
+8
View File
@@ -567,12 +567,14 @@ func TestRequestCtxRedirect(t *testing.T) {
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "#aaa", "http://qqq/foo/bar?baz=111#aaa")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "?abc=de&f", "http://qqq/foo/bar?abc=de&f")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "?abc=de&f#sf", "http://qqq/foo/bar?abc=de&f#sf")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "?\r\nInjected: yes", "http://qqq/foo/bar? Injected: yes")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "x.html", "http://qqq/foo/x.html")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "x.html?a=1", "http://qqq/foo/x.html?a=1")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "x.html#aaa=bbb&cc=ddd", "http://qqq/foo/x.html#aaa=bbb&cc=ddd")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "x.html?b=1#aaa=bbb&cc=ddd", "http://qqq/foo/x.html?b=1#aaa=bbb&cc=ddd")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "/x.html", "http://qqq/x.html")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "/x.html#aaa=bbb&cc=ddd", "http://qqq/x.html#aaa=bbb&cc=ddd")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "#\r\nInjected: yes", "http://qqq/foo/bar?baz=111# Injected: yes")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "http://foo.bar/baz", "http://foo.bar/baz")
testRequestCtxRedirect(t, "http://qqq/foo/bar?baz=111", "https://foo.bar/baz", "https://foo.bar/baz")
testRequestCtxRedirect(t, "https://foo.com/bar?aaa", "//google.com/aaa?bb", "https://google.com/aaa?bb")
@@ -595,6 +597,12 @@ func testRequestCtxRedirect(t *testing.T, origURL, redirectURL, expectedURL stri
if string(loc) != expectedURL {
t.Fatalf("unexpected redirect url %q. Expecting %q. origURL=%q, redirectURL=%q", loc, expectedURL, origURL, redirectURL)
}
if strings.ContainsAny(redirectURL, "\r\n") {
s := ctx.Response.String()
if strings.Contains(s, "\r\nInjected: yes\r\n") {
t.Fatalf("serialized response contains injected header line: %q", s)
}
}
}
func TestServerResponseServerHeader(t *testing.T) {