Django admin: its login signs an administrator into all of Postulo with a password alone, skipping the second factor #367
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#367
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 on 2026-09-29 in a read-only review of postulo at
95348908d, by reading the admin site and signing in through it in a test.Checked again on 2026-10-01 against
mainat0a5cad587: still present.When the admin is mounted,
ThrottledAdminSite.loginadds a rate limit and then hands over to Django's stock admin login view. That view checks the username and password and callsauth.login, which sets the same session cookie the rest of Postulo uses. allauth's login flow is never involved, so the stages Postulo relies on for a password sign-in never run. That covers the TOTP or passkey code (allauth.mfa'sAuthenticateStage) and the e-mail verification gate. An administrator who has set up a second factor is one password away from a full session.core/admin_site.py:34-49(ThrottledAdminSite.login, ending inreturn super().login(request, extra_context));config/urls.py:33-36(mounted whenPOSTULO_ADMIN_URLis set).allauth.account.decorators.secure_admin_login, which allauth ships for this purpose, is used nowhere insrcortests./accounts/login/redirects to/accounts/2fa/authenticate/, as it should;<admin path>login/redirects to the admin index with an authenticated session;/applications/then answers 200, and so does every other page, with no code asked for.ThrottledAdminSite, wrap the view with allauth's decorator,secure_admin_login(super().login), or redirect toaccount_loginwithnextset to the admin index. An unauthenticated visitor is then sent to Postulo's own sign-in, which already carries the rate limit and every stage, and the admin's own throttle becomes redundant. Say in the wiki's Hardening page that the admin signs in through the ordinary sign-in.Done when
tests/security/test_admin_exposure.pymounts the admin, gives a staff account a verified address and an active TOTP authenticator, and posts the right password to the admin's login. It asserts that no authenticated session results, and that the response leads to Postulo's sign-in or second-factor step. Today the session is created.