challenge: add cookie prefix to cookies tied to host/pubkey to prevent reuse
This commit is contained in:
12
README.md
12
README.md
@@ -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/) | [](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/) | [](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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user