pac4j session security: empty CSRF token accepted, session cookie never Secure, session map never evicted #63
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/support#63
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while enabling pac4j CSRF in a downstream HTMX app (MEDECA-348). Three independent items, all in the pac4j session/CSRF area. Each can be fixed without the others. Companion to #62, which covers the
matchers/authorizershook and the 4-hour token expiry.Item 1:
ConstantTimeCsrfAuthorizershould reject an empty request tokenIn pac4j-core 6.3.2,
CsrfAuthorizeraccepts a request token that equals either the current token or the previous one, and it reads an absent previous token as"".With the stock
DefaultCsrfTokenGenerator, no previous token exists before the session's first matched request. An emptypac4jCsrfTokentherefore passes the check on that first request — which is exactly the request a cross-site attacker can make, because sending no token at all is the easy case.MEDECA closes this in its own generator by writing previous = current, so a comparison against the previous token is never a comparison against
"". That fix belongs in the library, not in each consumer.Proposal:
ConstantTimeCsrfAuthorizerrejects an empty or blank request token before any comparison. Add a test that sends an emptypac4jCsrfTokenas the first matched request of a fresh session and expects a rejection.Item 2: the
pac4jSessionIdcookie can never beSecurePac4jConfigFactory.scala:35setsbut
callbackBaseis always a path, never a URL.Pac4jHttpSecurity.routemounts it asRouter(s"${config.callbackBase}" -> ...), so the value is of the form/callback. The condition is therefore false in every deployment, including production behind TLS, and the session cookie is sent over plain HTTP whenever anything downgrades the connection.This is a concrete security consequence of the naming problem recorded in #32 (
BaseUrioverloaded as path-prefix and as URL), but it can be fixed on its own.Proposal: take the flag from explicit configuration rather than deriving it from a path, defaulting to
true. A deployment that genuinely serves plain HTTP opts out.Item 3: the in-memory pac4j session map never evicts
CacheSessionRepository.scala:10inhttp4s-pac4jholds sessions in a plain map with no expiry and no size bound. A long-running server accumulates one entry per session forever.This is upstream code, not iw-support's, so the fix here is a
SessionRepositoryimplementation with a TTL (and the option of a shared store), offered beside the in-memory one — not a patch to the dependency.Proposal: ship an evicting
SessionRepositoryinserver/httpand name it indocs/CSRF_GUIDE.mdas the one to use for anything that runs longer than a development session.