Fix open redirect bypass in saferedirect

Relative redirect URLs are now normalized before validation. Paths
containing backslashes (including percent-encoded %5c) are rejected,
closing a bypass where /../\evil.com passed checks but http.Redirect
normalized to /\evil.com.

Reported by Fushuling and RacerZ.

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-05-26 11:41:33 -07:00
parent bf6fc2d509
commit 59f8e943fb
3 changed files with 89 additions and 2 deletions

View File

@@ -103,6 +103,34 @@ func TestSafeRedirect_Validate(t *testing.T) {
expectedURL: "",
expectedIsValid: false,
},
{
name: "path traversal backslash bypass",
allowedHost: saferedirect.StaticHosts("example.com"),
redirectURL: "/../\\evil.com/phishing",
expectedURL: "",
expectedIsValid: false,
},
{
name: "embedded backslash",
allowedHost: saferedirect.StaticHosts("example.com"),
redirectURL: "/foo/..\\evil.com/phishing",
expectedURL: "",
expectedIsValid: false,
},
{
name: "percent-encoded backslash",
allowedHost: saferedirect.StaticHosts("example.com"),
redirectURL: "/%5cevil.com/phishing",
expectedURL: "",
expectedIsValid: false,
},
{
name: "path normalization",
allowedHost: saferedirect.StaticHosts("example.com"),
redirectURL: "/foo/../dashboard",
expectedURL: "/dashboard",
expectedIsValid: true,
},
}
for _, tt := range tests {
@@ -168,6 +196,13 @@ func TestSafeRedirect_GetSafeRedirectURL(t *testing.T) {
fallbackURL: "/home",
expectedURL: "/home",
},
{
name: "path traversal backslash bypass",
allowedHost: saferedirect.StaticHosts("example.com"),
redirectURL: "/../\\evil.com/phishing",
fallbackURL: "/home",
expectedURL: "/home",
},
}
for _, tt := range tests {
@@ -235,6 +270,14 @@ func TestSafeRedirect_Redirect(t *testing.T) {
expectedStatus: http.StatusFound,
expectedURL: "/home",
},
{
name: "path traversal backslash bypass",
allowedHost: saferedirect.StaticHosts("example.com"),
redirectURL: "/../\\evil.com/phishing",
fallbackURL: "/home",
expectedStatus: http.StatusFound,
expectedURL: "/home",
},
}
for _, tt := range tests {