Skip to content
GHSA 7554 Gx6g Xx77

GHSA 7554 Gx6g Xx77

github.com • October 19, 2026

phpMyFAQ's user control panel requires the current password only when changing the password or disabling 2FA; changing the account e-mail requires just the (hijacked) session and the page's CSRF token. An attacker with a stolen session can swap the victim's e-mail to their own mailbox, then use phpMyFAQ's own anonymous password-reset flow: the signed reset link is delivered to the attacker's address, and consuming it sets a new password the attacker knows. For accounts without 2FA this is a complete account takeover that circumvents exactly the protection GHSA-6r2c-694w-24qv added (current-password verification for password changes); for 2FA accounts the attacker still ends up controlling both the password and the registered e-mail, with the TOTP gate as the only remaining barrier. Verified end-to-end at runtime on 4.1.8 (non-2FA account); the missing step-up is additionally confirmed in 4.2.0-alpha.2 and on the current main branch, with no fix on any branch.

Controller/Frontend/UserController.php::updateData() ( api/user/data/update ): only two branches demand the current password, the password change itself ( changePassword() ; verifyCurrentPassword() at HEAD, Frontend/Api/UserController.php ) and disabling 2FA ( requireTwoFactorStepUp() ). The 'email' => $email write (HEAD lines 125-134; 4.1.8 lines 83-105) requires only the session and the 'ucp' CSRF token; no step-up, and no notification to the old address.

Controller/Frontend/UnauthorizedUserController.php::requestReset() (HEAD lines 86-151; 4.1.8 equivalent lines 78/107): the reset link is matched with hash_equals($user->getUserData('email'), $email) and sent to that address , i.e. to the attacker's newly-registered mailbox.

reset() (HEAD lines 158-218) consumes the signed link and calls changePassword() , sidestepping the current-password step-up that GHSA-6r2c-694w-24qv (fixed 4.1.8) introduced precisely to stop a hijacked session from changing the password.

The e-mail-change path predates and survives the GHSA-6r2c fix: the 4.1.8 fix diff only wraps the faqpassword branch and the 2FA-off branch. Present on 4.1.8, 4.2.0-alpha.2 and current main; not covered by any of the project's advisories (none describes the e-mail-swap → reset-delivery chain).

OS-independent; verified in Docker (php:8.3-cli) with the published 4.1.8 release (victim account admin without 2FA enabled, so every step is attributable to the session hijack alone). Note for reproduction: php's built-in server does not read .htaccess , so serve the app with a router script mapping /api/* , /admin/* and clean URLs onto index.php . Outgoing mail is captured with any local SMTP sink writing the message to a file. Step [0] simply establishes the victim session that stands in for the stolen one.

poc-email-reset.php (runs inside the container: php /app/poc-email-reset.php ):

Verbatim output (4.1.8):

The e-mail change succeeded with an empty faqpassword_current (the only branches consulting the current password are the password change and disabling 2FA), the reset link was delivered to the attacker's mailbox, and the attacker's new password logged straight into the admin dashboard.

Expected: changing the account e-mail from a session should require the current password (the same step-up GHSA-6r2c established for password changes), or at minimum notify the old address and delay reset delivery, because the e-mail address is the reset-channel trust anchor. Actual: the e-mail is replaced with only the session and the page CSRF token, and the anonymous reset flow delivers the password-reset link to the attacker's mailbox. Boundary note, verified during reproduction: on a 2FA-enabled victim the same chain still swaps the e-mail and resets the password, but login remains held at the TOTP step (and the same request attempting to disable 2FA is correctly rejected by the step-up) — the takeover shown above is complete for non-2FA accounts.

A hijacked phpMyFAQ session becomes permanent at will: the attacker controls both the account's password and its registered e-mail (the reset-channel trust anchor), so the legitimate user cannot recover the account through the address-based flow. For accounts without 2FA — the default — this is a complete takeover (superadmin being the high-value target); for 2FA accounts it is credential control short of the TOTP gate.

In updateData() , apply the same verifyCurrentPassword() step-up to e-mail changes (HEAD Frontend/Api/UserController.php:129-131 , a one-branch change). Defense in depth: notify the address on any e-mail change, and refuse password-reset delivery to an address changed within a short cooldown window.

Extracted Entities

Attack Types (1)

MITRE ATT&CK (1)

Platforms (1)

Tools (1)