R68: cache expiry TOCTOU, logout open-redirect, session max-age

This commit is contained in:
2026-08-13 17:06:23 +01:00
parent 2941260a81
commit d5fddebeaa
8 changed files with 223 additions and 19 deletions
+69
View File
@@ -5,6 +5,8 @@ import (
"crypto/rsa"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
)
@@ -1317,3 +1319,70 @@ func TestServeHTTP_EventStream_AppliesRolesGate(t *testing.T) {
}
})
}
// TestLogout_NoOpenRedirectViaIdP regresses a post-logout open redirect:
// handleLogout built the IdP's post_logout_redirect_uri from the
// client-controllable request host (X-Forwarded-Host), so an attacker
// could steer the browser to an arbitrary origin after logout. With no
// configured postLogoutRedirectURI the parameter must be omitted, never
// host-derived.
func TestLogout_NoOpenRedirectViaIdP(t *testing.T) {
sessionManager := createTestSessionManager(t)
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("generate key: %v", err)
}
idToken, err := createTestJWT(key, "RS256", "test-key-id", map[string]interface{}{"sub": "[email protected]"})
if err != nil {
t.Fatalf("craft id token: %v", err)
}
oidc := &TraefikOidc{
logger: NewLogger("error"),
sessionManager: sessionManager,
endSessionURL: "https://idp.example.com/session/end",
tokenBlacklist: NewCache(),
tokenCache: NewTokenCache(),
}
req := httptest.NewRequest("GET", "/logout", nil)
req.Header.Set("X-Forwarded-Host", "attacker.example")
session, err := sessionManager.GetSession(req)
if err != nil {
t.Fatalf("create session: %v", err)
}
session.SetUserIdentifier("[email protected]")
if err := session.SetAuthenticated(true); err != nil {
t.Fatalf("authenticate: %v", err)
}
session.SetIDToken(idToken)
rec := httptest.NewRecorder()
if err := session.Save(req, rec); err != nil {
t.Fatalf("save session: %v", err)
}
for _, c := range rec.Result().Cookies() {
req.AddCookie(c)
}
rw := httptest.NewRecorder()
oidc.handleLogout(rw, req)
loc := rw.Header().Get("Location")
if loc == "" {
t.Fatalf("expected logout redirect to the IdP end-session URL")
}
u, err := url.Parse(loc)
if err != nil {
t.Fatalf("parse redirect %q: %v", loc, err)
}
plr := u.Query().Get("post_logout_redirect_uri")
if plr == "" {
// Correct: no configured target, param omitted.
return
}
if strings.Contains(plr, "attacker.example") {
t.Fatalf("open redirect via IdP: post_logout_redirect_uri=%q came from client host", plr)
}
}