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> <html>
<head> <head>
<title>{{ .Title }}</title> <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"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
{{ range $key, $value := .Meta }} {{ range $key, $value := .Meta }}
{{ if eq $key "refresh"}} {{ if eq $key "refresh"}}
@@ -151,7 +151,7 @@
<img <img
id="image" id="image"
style="width:100%;max-width:256px;" 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 }} {{if .Challenge }}
<p id="status">Loading challenge <em>{{ .Challenge }}</em>...</p> <p id="status">Loading challenge <em>{{ .Challenge }}</em>...</p>
@@ -173,18 +173,24 @@
<div></div> <div></div>
</div> </div>
{{end}} {{end}}
<details> <details style="padding-bottom: 2em;">
<summary>Why am I seeing this?</summary> <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>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>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> </details>
<noscript> <noscript>
<p> <p>
Sadly, you may need to enable JavaScript to get past this challenge. This is required because AI companies have changed 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. the social contract around how website hosting works.
</p> </p>
</noscript> </noscript>
{{if .Redirect }}
<a role="button" href="{{ .Redirect }}">Refresh page</a>
{{end}}
<div id="testarea"></div> <div id="testarea"></div>
</div> </div>

View File

@@ -70,13 +70,26 @@
{{end}} {{end}}
<div id="spinner"></div> <div id="spinner"></div>
<details> <details style="padding-bottom: 2em;">
<summary>Why am I seeing this?</summary> <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>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>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> </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 id="testarea"></div>
</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) state.Mux.Handle(fmt.Sprintf("GET %s/verify-challenge", c.Path), c.ServeVerifyChallenge)
} else if c.Verify != nil { } else if c.Verify != nil {
state.Mux.HandleFunc(fmt.Sprintf("GET %s/verify-challenge", c.Path), func(w http.ResponseWriter, r *http.Request) { 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()) data := RequestDataFromContext(r.Context())
@@ -380,15 +386,15 @@ func (state *State) setupRoutes() error {
state.addTiming(w, "challenge-verify", "Verify client challenge", time.Since(start)) state.addTiming(w, "challenge-verify", "Verify client challenge", time.Since(start))
if err != nil { 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 return err
} else if !ok { } 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) 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 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) token, err := c.IssueChallengeToken(state.privateKey, key, []byte(result), data.Expires)
if err != nil { if err != nil {
@@ -398,12 +404,12 @@ func (state *State) setupRoutes() error {
} }
data.Challenges[c.Id] = challenge.VerifyResultPASS data.Challenges[c.Id] = challenge.VerifyResultPASS
http.Redirect(w, r, r.FormValue("redirect"), http.StatusTemporaryRedirect) http.Redirect(w, r, redirect, http.StatusTemporaryRedirect)
return nil return nil
}() }()
if err != nil { if err != nil {
utils.ClearCookie(utils.CookiePrefix+c.Name, w) 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 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) { 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()) data := RequestDataFromContext(r.Context())
key := state.GetChallengeKeyForRequest(challengeName, data.Expires, r) 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 { if ok, err := c.Verify(key, result, r); err != nil {
return err return err
} else if !ok { } 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) 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 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) token, err := c.IssueChallengeToken(state.privateKey, key, []byte(result), data.Expires)
if err != nil { if err != nil {
@@ -467,7 +474,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
switch httpCode { switch httpCode {
case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect: 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: default:
w.Header().Set("Content-Type", mimeType) w.Header().Set("Content-Type", mimeType)
w.WriteHeader(httpCode) w.WriteHeader(httpCode)
@@ -480,7 +487,7 @@ func NewState(p policy.Policy, settings StateSettings) (state *State, err error)
}() }()
if err != nil { if err != nil {
utils.ClearCookie(utils.CookiePrefix+challengeName, w) 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 return
} }
}) })

View File

@@ -2,13 +2,33 @@ package utils
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url" "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) { func MakeReverseProxy(target string) (*httputil.ReverseProxy, error) {
u, err := url.Parse(target) u, err := url.Parse(target)
if err != nil { if err != nil {