From 1c2d1e008cd5acbfc9bd1700ab7385823a94f003 Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:24:22 +0200 Subject: [PATCH] Added refresh button to challenges and deny pages where reasonable, ensure no open redirect or other --- embed/templates/challenge-anubis.gohtml | 14 ++++++++++---- embed/templates/challenge-forgejo.gohtml | 17 +++++++++++++++-- lib/http.go | 20 +++++++++++++------- lib/state.go | 19 +++++++++++++------ utils/http.go | 20 ++++++++++++++++++++ 5 files changed, 71 insertions(+), 19 deletions(-) diff --git a/embed/templates/challenge-anubis.gohtml b/embed/templates/challenge-anubis.gohtml index 8bf61d4..63e6ec2 100644 --- a/embed/templates/challenge-anubis.gohtml +++ b/embed/templates/challenge-anubis.gohtml @@ -2,7 +2,7 @@ {{ .Title }} - + {{ range $key, $value := .Meta }} {{ if eq $key "refresh"}} @@ -151,7 +151,7 @@ {{if .Challenge }}

Loading challenge {{ .Challenge }}...

@@ -173,18 +173,24 @@
{{end}} -
+
Why am I seeing this?

You are seeing this because the administrator of this website has set up go-away to protect the server against the scourge of AI companies aggressively scraping websites. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.

Please note that some challenges requires the use of modern JavaScript features and some plugins may will disable. Please disable such plugins for this domain (for example, JShelter).

-

If you have any issues contact the administrator and provide this request id: {{ .Id }}

+

If you have any issues contact the administrator and provide this Request Id: {{ .Id }}

+ + + {{if .Redirect }} + Refresh page + {{end}} +
diff --git a/embed/templates/challenge-forgejo.gohtml b/embed/templates/challenge-forgejo.gohtml index c8902b0..318bb6f 100644 --- a/embed/templates/challenge-forgejo.gohtml +++ b/embed/templates/challenge-forgejo.gohtml @@ -70,13 +70,26 @@ {{end}}
-
+
Why am I seeing this?

You are seeing this because the administrator of this website has set up go-away to protect the server against the scourge of AI companies aggressively scraping websites. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.

Please note that some challenges requires the use of modern JavaScript features and some plugins may will disable. Please disable such plugins for this domain (for example, JShelter).

-

If you have any issues contact the administrator and provide the request id: {{ .Id }}

+

If you have any issues contact the administrator and provide the Request Id: {{ .Id }}

+ + + {{if .Redirect }} + + {{end}} +
diff --git a/lib/http.go b/lib/http.go index d89febf..1340c80 100644 --- a/lib/http.go +++ b/lib/http.go @@ -362,7 +362,13 @@ func (state *State) setupRoutes() error { state.Mux.Handle(fmt.Sprintf("GET %s/verify-challenge", c.Path), c.ServeVerifyChallenge) } else if c.Verify != nil { state.Mux.HandleFunc(fmt.Sprintf("GET %s/verify-challenge", c.Path), func(w http.ResponseWriter, r *http.Request) { - err := func() (err error) { + redirect, err := utils.EnsureNoOpenRedirect(r.FormValue("redirect")) + if redirect == "" { + _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, "") + return + } + + err = func() (err error) { data := RequestDataFromContext(r.Context()) @@ -380,15 +386,15 @@ func (state *State) setupRoutes() error { state.addTiming(w, "challenge-verify", "Verify client challenge", time.Since(start)) if err != nil { - state.logger(r).Error(fmt.Errorf("challenge error: %w", err).Error(), "challenge", c.Name, "redirect", r.FormValue("redirect")) + state.logger(r).Error(fmt.Errorf("challenge error: %w", err).Error(), "challenge", c.Name, "redirect", redirect) return err } else if !ok { - state.logger(r).Warn("challenge failed", "challenge", c.Name, "redirect", r.FormValue("redirect")) + state.logger(r).Warn("challenge failed", "challenge", c.Name, "redirect", redirect) utils.ClearCookie(utils.CookiePrefix+c.Name, w) - _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusForbidden, fmt.Errorf("access denied: failed challenge %s", c.Name), r.FormValue("redirect")) + _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusForbidden, fmt.Errorf("access denied: failed challenge %s", c.Name), redirect) return nil } - state.logger(r).Info("challenge passed", "challenge", c.Name, "redirect", r.FormValue("redirect")) + state.logger(r).Info("challenge passed", "challenge", c.Name, "redirect", redirect) token, err := c.IssueChallengeToken(state.privateKey, key, []byte(result), data.Expires) if err != nil { @@ -398,12 +404,12 @@ func (state *State) setupRoutes() error { } data.Challenges[c.Id] = challenge.VerifyResultPASS - http.Redirect(w, r, r.FormValue("redirect"), http.StatusTemporaryRedirect) + http.Redirect(w, r, redirect, http.StatusTemporaryRedirect) return nil }() if err != nil { utils.ClearCookie(utils.CookiePrefix+c.Name, w) - _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, r.FormValue("redirect")) + _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, redirect) return } }) diff --git a/lib/state.go b/lib/state.go index 10d638a..ed7d106 100644 --- a/lib/state.go +++ b/lib/state.go @@ -435,7 +435,14 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error) c.ServeVerifyChallenge = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - err := func() (err error) { + redirect, err := utils.EnsureNoOpenRedirect(r.FormValue("redirect")) + if err != nil { + _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, "") + return + } + + err = func() (err error) { + data := RequestDataFromContext(r.Context()) key := state.GetChallengeKeyForRequest(challengeName, data.Expires, r) @@ -449,13 +456,13 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error) if ok, err := c.Verify(key, result, r); err != nil { return err } else if !ok { - state.logger(r).Warn("challenge failed", "challenge", challengeName, "redirect", r.FormValue("redirect")) + state.logger(r).Warn("challenge failed", "challenge", challengeName, "redirect", redirect) utils.ClearCookie(utils.CookiePrefix+challengeName, w) - _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusForbidden, fmt.Errorf("access denied: failed challenge %s", challengeName), r.FormValue("redirect")) + _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusForbidden, fmt.Errorf("access denied: failed challenge %s", challengeName), redirect) return nil } - state.logger(r).Warn("challenge passed", "challenge", challengeName, "redirect", r.FormValue("redirect")) + state.logger(r).Warn("challenge passed", "challenge", challengeName, "redirect", redirect) token, err := c.IssueChallengeToken(state.privateKey, key, []byte(result), data.Expires) if err != nil { @@ -467,7 +474,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error) switch httpCode { case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect: - http.Redirect(w, r, r.FormValue("redirect"), httpCode) + http.Redirect(w, r, redirect, httpCode) default: w.Header().Set("Content-Type", mimeType) w.WriteHeader(httpCode) @@ -480,7 +487,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error) }() if err != nil { utils.ClearCookie(utils.CookiePrefix+challengeName, w) - _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, r.FormValue("redirect")) + _ = state.errorPage(w, r.Header.Get("X-Away-Id"), http.StatusInternalServerError, err, redirect) return } }) diff --git a/utils/http.go b/utils/http.go index 2a2e5b4..4a67afd 100644 --- a/utils/http.go +++ b/utils/http.go @@ -2,13 +2,33 @@ package utils import ( "context" + "errors" "fmt" "net" "net/http" "net/http/httputil" "net/url" + "strings" ) +func EnsureNoOpenRedirect(redirect string) (string, error) { + uri, err := url.Parse(redirect) + if err != nil { + return "", err + } + uri.Scheme = "" + uri.Host = "" + uri.User = nil + uri.Opaque = "" + uri.OmitHost = true + + if uri.Path != "" && !strings.HasPrefix(uri.Path, "/") { + return "", errors.New("invalid redirect path") + } + + return uri.String(), nil +} + func MakeReverseProxy(target string) (*httputil.ReverseProxy, error) { u, err := url.Parse(target) if err != nil {