Research/wpmgr-invite-skip-2fa
0-dayNo CVEHighPublic

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.

Plain text
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-2FA

Wrong 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.