Added refresh button to challenges and deny pages where reasonable, ensure no open redirect or other

This commit is contained in:
WeebDataHoarder
2025-04-07 19:24:22 +02:00
parent e08a5697f6
commit 1c2d1e008c
5 changed files with 71 additions and 19 deletions

View File

@@ -2,7 +2,7 @@
<html>
<head>
<title>{{ .Title }}</title>
<link rel="stylesheet" href="{{ .Path }}/embed/assets/static/anubis/style.css?cacheBust={{ .Random }}"/>
<link rel="stylesheet" href="{{ .Path }}/assets/static/anubis/style.css?cacheBust={{ .Random }}"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
{{ range $key, $value := .Meta }}
{{ if eq $key "refresh"}}
@@ -151,7 +151,7 @@
<img
id="image"
style="width:100%;max-width:256px;"
src="{{ .Path }}/embed/assets/static/logo.png?cacheBust={{ .Random }}"
src="{{ .Path }}/assets/static/logo.png?cacheBust={{ .Random }}"
/>
{{if .Challenge }}
<p id="status">Loading challenge <em>{{ .Challenge }}</em>...</p>
@@ -173,18 +173,24 @@
<div></div>
</div>
{{end}}
<details>
<details style="padding-bottom: 2em;">
<summary>Why am I seeing this?</summary>
<p>You are seeing this because the administrator of this website has set up <a href="https://git.gammaspectra.live/git/go-away">go-away</a> to protect the server against the scourge of <a href="https://thelibre.news/foss-infrastructure-is-under-attack-by-ai-companies/">AI companies aggressively scraping websites</a>. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.</p>
<p>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).</p>
<p>If you have any issues contact the administrator and provide this request id: <em>{{ .Id }}</em></p>
<p>If you have any issues contact the administrator and provide this Request Id: <em>{{ .Id }}</em></p>
</details>
<noscript>
<p>
Sadly, you may need to enable JavaScript to get past this challenge. This is required because AI companies have changed
the social contract around how website hosting works.
</p>
</noscript>
{{if .Redirect }}
<a role="button" href="{{ .Redirect }}">Refresh page</a>
{{end}}
<div id="testarea"></div>
</div>

View File

@@ -70,13 +70,26 @@
{{end}}
<div id="spinner"></div>
<details>
<details style="padding-bottom: 2em;">
<summary>Why am I seeing this?</summary>
<p>You are seeing this because the administrator of this website has set up <a href="https://git.gammaspectra.live/git/go-away">go-away</a> to protect the server against the scourge of <a href="https://thelibre.news/foss-infrastructure-is-under-attack-by-ai-companies/">AI companies aggressively scraping websites</a>. This can and does cause downtime for the websites, which makes their resources inaccessible for everyone.</p>
<p>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).</p>
<p>If you have any issues contact the administrator and provide the request id: <em>{{ .Id }}</em></p>
<p>If you have any issues contact the administrator and provide the Request Id: <em>{{ .Id }}</em></p>
</details>
<noscript>
<p>
Sadly, you may need to enable JavaScript to get past this challenge. This is required because AI companies have changed
the social contract around how website hosting works.
</p>
</noscript>
{{if .Redirect }}
<div class="button-row">
<a role="button" class="ui small primary button" href="{{ .Redirect }}">Refresh page</a>
</div>
{{end}}
<div id="testarea"></div>
</div>

View File

@@ -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
}
})

View File

@@ -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
}
})

View File

@@ -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 {