From 45ff8f526e02885726fbd4c3057c1341d4f7763f Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Thu, 24 Jul 2025 14:57:38 +0000 Subject: [PATCH] fix(lib): add additional validation logic for XSS protection Signed-off-by: Xe Iaso --- lib/anubis.go | 29 ++++++++++++++--------------- lib/anubis_test.go | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/lib/anubis.go b/lib/anubis.go index fbf1b5c..eb88b70 100644 --- a/lib/anubis.go +++ b/lib/anubis.go @@ -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 diff --git a/lib/anubis_test.go b/lib/anubis_test.go index c4fa136..d1e7212 100644 --- a/lib/anubis_test.go +++ b/lib/anubis_test.go @@ -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) + } + }) + } + }) }