WPMgr v0.61.199, invitation accept skips 2FA
WPMgr v0.61.199 invitation.Service.Accept calls SessionManager.Login instead of issueSessionOrChallenge. Accept with a stolen password and no TOTP mints wpmgr_session while POST /auth/login still returns 202 two_factor_required. The session can activate the victim org. Labbed. No CVE yet.
- Name
- WPMgr v0.61.199, invitation accept skips 2FA
- Type
- 0-day analysis
- CVE
- n/a
- CVE Risk
- high
- Disclosure Status
- public
- Vendor
- mosamlife
- Affected
- WPMgr v0.61.199 (e1b3a163). Attacker-owned org plus the victim's password. SSO-only empty password_hash is refused.
- Published
- 09 Nov 2026
- Updated
- 09 Nov 2026
- Tags
- 0-day, wpmgr, 2fa, auth, cwe-287, remote
Two doors. The lock is on one of them.
Password login on WPMgr v0.61.199 does the right thing when 2FA is on. You send email and password, you get 202 two_factor_required, no cookie. Invitation accept is a fourth way to mint a session. It never asks. If someone already stole your password, they invite your address into an organisation they own, accept that invite with the password and no authenticator code, and WPMgr hands them a full session as you. 2FA is still marked on. From that session they switch into your other organisations.
The victim does not have to open mail. POST /api/v1/members returns the raw accept token in the JSON.
This is WPMgr v0.61.199 (e1b3a163988c9f1c31bc93922ea34322fa188792). No CVE yet. CWE-287. 8.1 High (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N). Remote 2FA bypass. Stolen password plus an attacker-owned org. Pack: abraxas/wpmgr-invite-skip-2fa.
ADR-056 B2, minus one caller
Dashboard 2FA is supposed to run every human session-issuing path through auth.Handler.issueSessionOrChallenge. Password login, SSO, email verify, bootstrap. Invitation accept calls SessionManager.Login after VerifyPassword. Authenticator never re-checks TOTP. POST /api/v1/orgs/{id}/activate then walks every org the victim already belongs to.
The 2FA tests pin B2 on those four issuers. Invitation is not in the list. That is how I stopped treating this as a documentation gap.
Password reset, by contrast, deliberately does not mint a session and revokes trusted devices. I almost filed reset as a sibling. It is the closed control.
The lab
Victim enrolls TOTP so two_factor_enabled=true. Password-only login is 202, no session. Owner invite, 64-char token. POST /api/v1/invitations/accept with token plus password, no TOTP: 200, wpmgr_session, not two_factor_required. GET /auth/me is the victim with 2FA still on. POST /api/v1/orgs/{victimOrg}/activate is 200.
SSO-only victims (empty password_hash) are refused this path. I confirmed that in source and left it out of the oracle.
IOC login-password-only status=202 two_factor_required=True session=False
IOC accept-without-totp status=200 two_factor_required=False session=True
IOC accept-me status=200 email=victim@example.com two_factor_enabled=True
IOC activate-victim-org status=200
SUCCESS WPMGR-H05-INVITE-2FA invite-accept-skipped-2fa-session WPMGR-H05-SESSION-WITHOUT-2FAWrong turn: treating site-share and client-portal accept as the same ATO. Org invite is the one that becomes the victim user. The others mint a collaborator row.
What an attacker can do
Needs an organisation they already own and the victim's password. In ordinary words: they already know the password, 2FA is supposed to be the rest of the lock, and the invite flow opens a side door.
| Privilege | Method / sink | Recon / integrity / availability | Lab-proven or inferred |
|---|---|---|---|
| Mint an invite token for the victim without mail | POST /api/v1/members returns accept_link |
Recon (token) | Lab-proven. 64-char |
| Password login still challenges 2FA | POST /auth/login no TOTP |
n/a (negative) | Lab-proven. 202 |
| Session as the victim with no TOTP | POST /api/v1/invitations/accept |
Integrity (authn) | Lab-proven. 200, cookie |
| Confirm 2FA is still enrolled | GET /auth/me |
Recon | Lab-proven |
| Switch the stolen session into the victim's org | POST /api/v1/orgs/{victimOrg}/activate |
Integrity (tenant switch) | Lab-proven. 200 |
Stays closed on this leftover
| Axis | Method / sink | Why it is closed |
|---|---|---|
| Integrity | Password login, SSO, email verify, bootstrap | issueSessionOrChallenge |
| Integrity | Password reset | No session |
| Integrity | SSO-only empty password_hash |
Accept refuses |
| Integrity | Unauthenticated with no stolen password | Accept still VerifyPasswords |
The fix
Route invitation accept through issueSessionOrChallenge. If TwoFactorEnabled is set, return 202 and hold the pending accept until a TOTP or recovery code succeeds. Stop returning raw accept_link tokens in member-create JSON if mail is the intended channel.
I am @abraxas_null. Site abraxaslabs.tech. GitHub abraxas. Mail abraxas.null@proton.me.