challenge: add cookie prefix to cookies tied to host/pubkey to prevent reuse

This commit is contained in:
WeebDataHoarder
2025-04-23 22:37:01 +02:00
parent 49e46e7e9f
commit 3bbd50764a
7 changed files with 28 additions and 18 deletions

View File

@@ -409,9 +409,6 @@ services:
GOAWAY_CHALLENGE_TEMPLATE: forgejo GOAWAY_CHALLENGE_TEMPLATE: forgejo
GOAWAY_CHALLENGE_TEMPLATE_THEME: forgejo-dark GOAWAY_CHALLENGE_TEMPLATE_THEME: forgejo-dark
# specify a DNSBL for usage in conditions. Defaults to DroneBL
# GOAWAY_DNSBL: "dnsbl.dronebl.org"
# Backend to match. Can be subdomain or full wildcards, "*.example.com" or "*" # Backend to match. Can be subdomain or full wildcards, "*.example.com" or "*"
GOAWAY_BACKEND: "git.example.com=http://forgejo:3000" GOAWAY_BACKEND: "git.example.com=http://forgejo:3000"
@@ -426,9 +423,12 @@ services:
## Other Similar Projects ## Other Similar Projects
* [Anubis](https://anubis.techaro.lol/): Proxy that uses JavaScript proof of work to weight request based on rules [[source]](https://github.com/TecharoHQ/anubis)
* [powxy](https://sr.ht/~runxiyu/powxy/): Powxy is a reverse proxy that protects your upstream service by challenging clients with SHA-256 proof-of-work. [[source](https://git.sr.ht/~runxiyu/powxy)] | Project | Forge | Description |
* [anticrawl](https://flak.tedunangst.com/post/anticrawl): Go http handler / proxy for regex based rules [[source]](https://humungus.tedunangst.com/r/anticrawl) |:-------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------|
| [Anubis](https://anubis.techaro.lol/) | [![GitHub](https://img.shields.io/badge/GitHub-TecharoHQ/anubis-blue?style=flat&logo=github&labelColor=fff&logoColor=24292f)](https://github.com/TecharoHQ/anubis)<br/>Go / [MIT](https://github.com/TecharoHQ/anubis/blob/main/LICENSE) | Proxy that uses JavaScript proof of work to weight request based on simple match rules |
| [powxy](https://sr.ht/~runxiyu/powxy/) | [![sourcehut](https://img.shields.io/badge/sourcehut-~runxiyu/powxy-blue?style=flat&logo=sourcehut&labelColor=fff&logoColor=000)](https://git.sr.ht/~runxiyu/powxy)<br/> Go / [BSD 2-Clause](https://git.sr.ht/~runxiyu/powxy/tree/master/item/LICENSE) | Powxy is a reverse proxy that protects your upstream service by challenging clients with SHA-256 proof-of-work. |
| [anticrawl](https://flak.tedunangst.com/post/anticrawl) | [[source]](https://humungus.tedunangst.com/r/anticrawl)<br/>Go / None | Go http handler / proxy for regex based rules |
## Development ## Development

View File

@@ -23,7 +23,7 @@ func FillRegistration(state challenge.StateInterface, reg *challenge.Registratio
return challenge.VerifyResultFail return challenge.VerifyResultFail
} }
utils.SetCookie(utils.CookiePrefix+reg.Name, token, expiry, w, r) utils.SetCookie(challenge.RequestDataFromContext(r.Context()).CookiePrefix+reg.Name, token, expiry, w, r)
uri, err := challenge.RedirectUrl(r, reg) uri, err := challenge.RedirectUrl(r, reg)
if err != nil { if err != nil {

View File

@@ -3,6 +3,7 @@ package challenge
import ( import (
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
@@ -37,6 +38,7 @@ type RequestData struct {
ChallengeState map[Id]VerifyState ChallengeState map[Id]VerifyState
RemoteAddress net.IP RemoteAddress net.IP
State StateInterface State StateInterface
CookiePrefix string
r *http.Request r *http.Request
@@ -75,6 +77,13 @@ func CreateRequestData(r *http.Request, state StateInterface) (*http.Request, *R
data.query = condition.NewValuesMap(r.URL.Query()) data.query = condition.NewValuesMap(r.URL.Query())
data.header = condition.NewMIMEMap(textproto.MIMEHeader(r.Header)) data.header = condition.NewMIMEMap(textproto.MIMEHeader(r.Header))
sum := sha256.New()
sum.Write([]byte(r.Host))
sum.Write([]byte{0})
sum.Write(state.PublicKey())
sum.Write([]byte{0})
data.CookiePrefix = utils.CookiePrefix + hex.EncodeToString(sum.Sum(nil)[:4]) + "-"
r = r.WithContext(context.WithValue(r.Context(), requestDataContextKey{}, &data)) r = r.WithContext(context.WithValue(r.Context(), requestDataContextKey{}, &data))
return r, &data return r, &data
@@ -118,7 +127,7 @@ func (d *RequestData) EvaluateChallenges(w http.ResponseWriter, r *http.Request)
verifyResult, verifyState, err := reg.VerifyChallengeToken(d.State.PublicKey(), key, r) verifyResult, verifyState, err := reg.VerifyChallengeToken(d.State.PublicKey(), key, r)
if err != nil && !errors.Is(err, http.ErrNoCookie) { if err != nil && !errors.Is(err, http.ErrNoCookie) {
// clear invalid cookie // clear invalid cookie
utils.ClearCookie(utils.CookiePrefix+reg.Name, w, r) utils.ClearCookie(d.CookiePrefix+reg.Name, w, r)
} }
// prevent evaluating the challenge if not solved // prevent evaluating the challenge if not solved

View File

@@ -133,14 +133,14 @@ func FillRegistration(state challenge.StateInterface, reg *challenge.Registratio
if err != nil { if err != nil {
return challenge.VerifyResultFail return challenge.VerifyResultFail
} }
utils.SetCookie(utils.CookiePrefix+reg.Name, token, expiry, w, r) utils.SetCookie(data.CookiePrefix+reg.Name, token, expiry, w, r)
return challenge.VerifyResultNotOK return challenge.VerifyResultNotOK
} else { } else {
token, err := reg.IssueChallengeToken(state.PrivateKey(), key, nil, expiry, true) token, err := reg.IssueChallengeToken(state.PrivateKey(), key, nil, expiry, true)
if err != nil { if err != nil {
return challenge.VerifyResultFail return challenge.VerifyResultFail
} }
utils.SetCookie(utils.CookiePrefix+reg.Name, token, expiry, w, r) utils.SetCookie(data.CookiePrefix+reg.Name, token, expiry, w, r)
return challenge.VerifyResultOK return challenge.VerifyResultOK
} }
} }

View File

@@ -138,7 +138,7 @@ func VerifyHandlerFunc(state StateInterface, reg *Registration, verify VerifyFun
if err != nil { if err != nil {
return err return err
} else if !verifyResult.Ok() { } else if !verifyResult.Ok() {
utils.ClearCookie(utils.CookiePrefix+reg.Name, w, r) utils.ClearCookie(data.CookiePrefix+reg.Name, w, r)
state.ChallengeFailed(r, reg, nil, redirect, nil) state.ChallengeFailed(r, reg, nil, redirect, nil)
responseFunc(state, data, w, r, verifyResult, nil, redirect) responseFunc(state, data, w, r, verifyResult, nil, redirect)
return nil return nil
@@ -146,9 +146,9 @@ func VerifyHandlerFunc(state StateInterface, reg *Registration, verify VerifyFun
challengeToken, err := reg.IssueChallengeToken(state.PrivateKey(), key, []byte(token), expiration, true) challengeToken, err := reg.IssueChallengeToken(state.PrivateKey(), key, []byte(token), expiration, true)
if err != nil { if err != nil {
utils.ClearCookie(utils.CookiePrefix+reg.Name, w, r) utils.ClearCookie(data.CookiePrefix+reg.Name, w, r)
} else { } else {
utils.SetCookie(utils.CookiePrefix+reg.Name, challengeToken, expiration, w, r) utils.SetCookie(data.CookiePrefix+reg.Name, challengeToken, expiration, w, r)
} }
data.ChallengeVerify[reg.id] = verifyResult data.ChallengeVerify[reg.id] = verifyResult
state.ChallengePassed(r, reg, redirect, nil) state.ChallengePassed(r, reg, redirect, nil)
@@ -157,7 +157,7 @@ func VerifyHandlerFunc(state StateInterface, reg *Registration, verify VerifyFun
return nil return nil
}() }()
if err != nil { if err != nil {
utils.ClearCookie(utils.CookiePrefix+reg.Name, w, r) utils.ClearCookie(data.CookiePrefix+reg.Name, w, r)
state.ChallengeFailed(r, reg, err, redirect, nil) state.ChallengeFailed(r, reg, err, redirect, nil)
responseFunc(state, data, w, r, VerifyResultFail, fmt.Errorf("access denied: error in challenge %s: %w", reg.Name, err), redirect) responseFunc(state, data, w, r, VerifyResultFail, fmt.Errorf("access denied: error in challenge %s: %w", reg.Name, err), redirect)
return return

View File

@@ -137,19 +137,21 @@ func FillRegistration(state challenge.StateInterface, reg *challenge.Registratio
defer response.Body.Close() defer response.Body.Close()
defer io.Copy(io.Discard, response.Body) defer io.Copy(io.Discard, response.Body)
data := challenge.RequestDataFromContext(r.Context())
if response.StatusCode != params.HttpCode { if response.StatusCode != params.HttpCode {
token, err := reg.IssueChallengeToken(state.PrivateKey(), key, sum, expiry, false) token, err := reg.IssueChallengeToken(state.PrivateKey(), key, sum, expiry, false)
if err != nil { if err != nil {
return challenge.VerifyResultFail return challenge.VerifyResultFail
} }
utils.SetCookie(utils.CookiePrefix+reg.Name, token, expiry, w, r) utils.SetCookie(data.CookiePrefix+reg.Name, token, expiry, w, r)
return challenge.VerifyResultNotOK return challenge.VerifyResultNotOK
} else { } else {
token, err := reg.IssueChallengeToken(state.PrivateKey(), key, sum, expiry, true) token, err := reg.IssueChallengeToken(state.PrivateKey(), key, sum, expiry, true)
if err != nil { if err != nil {
return challenge.VerifyResultFail return challenge.VerifyResultFail
} }
utils.SetCookie(utils.CookiePrefix+reg.Name, token, expiry, w, r) utils.SetCookie(data.CookiePrefix+reg.Name, token, expiry, w, r)
return challenge.VerifyResultOK return challenge.VerifyResultOK
} }
} }

View File

@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"git.gammaspectra.live/git/go-away/lib/condition" "git.gammaspectra.live/git/go-away/lib/condition"
"git.gammaspectra.live/git/go-away/lib/policy" "git.gammaspectra.live/git/go-away/lib/policy"
"git.gammaspectra.live/git/go-away/utils"
"github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt" "github.com/go-jose/go-jose/v4/jwt"
"github.com/goccy/go-yaml/ast" "github.com/goccy/go-yaml/ast"
@@ -193,7 +192,7 @@ var ErrVerifyVerifyMismatch = errors.New("verify: verification mismatch")
var ErrTokenExpired = errors.New("token: expired") var ErrTokenExpired = errors.New("token: expired")
func (reg Registration) VerifyChallengeToken(publicKey ed25519.PublicKey, expectedKey Key, r *http.Request) (VerifyResult, VerifyState, error) { func (reg Registration) VerifyChallengeToken(publicKey ed25519.PublicKey, expectedKey Key, r *http.Request) (VerifyResult, VerifyState, error) {
cookie, err := r.Cookie(utils.CookiePrefix + reg.Name) cookie, err := r.Cookie(RequestDataFromContext(r.Context()).CookiePrefix + reg.Name)
if err != nil { if err != nil {
return VerifyResultNone, VerifyStateNone, err return VerifyResultNone, VerifyStateNone, err
} }