Merge pull request #20 from aaronjmars/security/open-redirect-guard

fix(security): block open redirect in login redirect target
This commit is contained in:
kunkun
2026-06-01 11:17:39 +08:00
committed by GitHub
3 changed files with 69 additions and 6 deletions
+15 -3
View File
@@ -543,11 +543,23 @@ func decodeState(state string) string {
if err != nil {
return "/"
}
redirect := string(data)
if !strings.HasPrefix(redirect, "/") {
return safeRedirectPath(string(data))
}
// safeRedirectPath 仅放行站内相对路径,拦截开放重定向。浏览器会忽略 URL 中的
// Tab/换行/回车,并把 //host 或 /\host 解析为协议相对的跨站地址,因此先剥离这些
// 控制字符,再拒绝 // 与 /\ 前缀。
func safeRedirectPath(redirect string) string {
cleaned := strings.Map(func(r rune) rune {
if r == '\t' || r == '\n' || r == '\r' {
return -1
}
return r
}, redirect)
if !strings.HasPrefix(cleaned, "/") || strings.HasPrefix(cleaned, "//") || strings.HasPrefix(cleaned, "/\\") {
return "/"
}
return redirect
return cleaned
}
func RequestOrigin(r *http.Request) string {
+41
View File
@@ -0,0 +1,41 @@
package service
import (
"encoding/base64"
"testing"
)
func TestSafeRedirectPath(t *testing.T) {
cases := map[string]string{
"/": "/",
"/canvas/abc": "/canvas/abc",
"/login?redirect=/x": "/login?redirect=/x",
"": "/",
"//evil.com": "/",
"/\\evil.com": "/",
"https://evil.com": "/",
"http://evil.com": "/",
"javascript:alert(1)": "/",
"evil.com": "/",
"/\t/evil.com": "/", // browsers strip the tab → //evil.com
"/normal\tpath": "/normalpath",
}
for in, want := range cases {
if got := safeRedirectPath(in); got != want {
t.Errorf("safeRedirectPath(%q) = %q, want %q", in, got, want)
}
}
}
func TestDecodeStateRejectsOpenRedirect(t *testing.T) {
for _, in := range []string{"//evil.com", "/\\evil.com", "https://evil.com"} {
state := base64.RawURLEncoding.EncodeToString([]byte(in))
if got := decodeState(state); got != "/" {
t.Errorf("decodeState(state(%q)) = %q, want \"/\"", in, got)
}
}
state := base64.RawURLEncoding.EncodeToString([]byte("/canvas/1"))
if got := decodeState(state); got != "/canvas/1" {
t.Errorf("decodeState(state(/canvas/1)) = %q, want /canvas/1", got)
}
}