fix(lib): add additional validation logic for XSS protection

Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Xe Iaso 2025-07-24 14:57:38 +00:00
parent 5700512da5
commit 45ff8f526e
No known key found for this signature in database
2 changed files with 53 additions and 16 deletions

View File

@ -384,6 +384,20 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
lg := internal.GetRequestLogger(r)
localizer := localization.GetLocalizer(r)
redir := r.FormValue("redir")
redirURL, err := url.ParseRequestURI(redir)
if err != nil {
lg.Error("invalid redirect", "err", err)
s.respondWithError(w, r, localizer.T("invalid_redirect"))
return
}
if redirURL.Scheme != "" && redirURL.Scheme != "http" && redirURL.Scheme != "https" {
lg.Error("XSS attempt blocked, invalid redirect scheme", "scheme", redirURL.Scheme)
s.respondWithStatus(w, r, localizer.T("invalid_redirect"), http.StatusBadRequest)
return
}
// Adjust cookie path if base prefix is not empty
cookiePath := "/"
if anubis.BasePrefix != "" {
@ -398,21 +412,6 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
return
}
redir := r.FormValue("redir")
redirURL, err := url.ParseRequestURI(redir)
if err != nil {
lg.Error("invalid redirect", "err", err)
s.respondWithError(w, r, localizer.T("invalid_redirect"))
return
}
if redirURL.Scheme != "" && redirURL.Scheme != "http" && redirURL.Scheme != "https" {
lg.Error("XSS attempt blocked, invalid redirect scheme", "scheme", redirURL.Scheme)
s.respondWithStatus(w, r, localizer.T("invalid_redirect"), http.StatusBadRequest)
return
}
// used by the path checker rule
r.URL = redirURL

View File

@ -870,10 +870,48 @@ func TestPassChallengeXSS(t *testing.T) {
t.Fatalf("can't do request: %v", err)
}
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusBadRequest {
body, _ := io.ReadAll(resp.Body)
t.Errorf("wanted status %d, got %d. body: %s", http.StatusBadRequest, resp.StatusCode, body)
}
})
}
t.Run("no test cookie", func(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
nonce := 0
elapsedTime := 420
calculated := ""
calcString := fmt.Sprintf("%s%d", chall.Challenge, nonce)
calculated = internal.SHA256sum(calcString)
req, err := http.NewRequest(http.MethodGet, ts.URL+"/.within.website/x/cmd/anubis/api/pass-challenge", nil)
if err != nil {
t.Fatalf("can't make request: %v", err)
}
q := req.URL.Query()
q.Set("response", calculated)
q.Set("nonce", fmt.Sprint(nonce))
q.Set("redir", tc.redir)
q.Set("elapsedTime", fmt.Sprint(elapsedTime))
req.URL.RawQuery = q.Encode()
// Do NOT add the test cookie here
resp, err := cli.Do(req)
if err != nil {
t.Fatalf("can't do request: %v", err)
}
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("wanted status %d, got %d. body: %s", http.StatusBadRequest, resp.StatusCode, body)
}
})
}
})
}