All vulnerabilities
GHSA-43g3-pp5p-fg96

phpmyfaq/phpmyfaq: SSO login (Keycloak and Microsoft Entra ID) bypasses TOTP two-factor authentication

phpmyfaq/phpmyfaq
CVSS
8.1High
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed inNone listedNo source names a fixed version yet
Published3 Oct 2026Updated 3 Oct 2026GitHub

What fixes it

No source in this snapshot names a fixed version.

Description

Summary

phpMyFAQ's SSO login flows (Keycloak and Microsoft Entra ID / Azure AD) grant a fully authenticated session immediately after the identity provider validates the user, without checking whether the linked account has TOTP two-factor authentication enabled. Any user with a valid IdP identity can log in as the corresponding phpMyFAQ account bypassing the second factor, even when 2FA is configured.

This is a different code path from CVE-2026-56737 (GHSA-8gpw-xvpf-hvx5), which covered the public POST /check password-factor bypass. Here the affected endpoints are the OAuth/OIDC callbacks, and 2FA is skipped entirely on successful SSO — not just the password step.

Root Cause

Both SSO callback controllers call $user->setLoggedIn(true) / updateSessionId(true) / saveToSession() / setSuccess(true) directly after successful IdP validation, without inspecting the account's twofactor_enabled user-data flag or deferring to the token step. The password and passkey login flows correctly defer via 2fa_pending_user_id — the SSO flows omit this.

Affected files:

  • phpmyfaq/services/azure/callback.php (legacy Azure/Entra ID handler, present in 4.1.x)
  • phpmyfaq/src/phpMyFAQ/Controller/Frontend/AzureAuthenticationController.php (4.2.0-alpha)
  • phpmyfaq/src/phpMyFAQ/Controller/Frontend/KeycloakAuthenticationController.php (4.2.0-alpha)

Impact

Complete bypass of the second authentication factor for every 2FA-enabled account reachable via SSO. In enterprise deployments where phpMyFAQ is federated with Keycloak or Entra ID, any authenticated IdP user who is also mapped to a phpMyFAQ admin/user account (including users who set up 2FA specifically to protect that account) is logged in without ever presenting the TOTP token. This defeats the entire purpose of enabling TOTP on SSO-linked accounts and lets a compromised IdP session or phished IdP credential produce full phpMyFAQ account takeover with no TOTP knowledge.

CWE-287 (Improper Authentication) / CWE-306 (Missing Authentication for Critical Function).

Attack Chain

  1. Entry: Attacker (holding a valid IdP identity — e.g. an ordinary Keycloak/Entra user in the org that is federated to phpMyFAQ) navigates to /auth/keycloak or /services/azure/index.php on the target phpMyFAQ instance.
  2. Guard: none — public OAuth initiation endpoint.
  3. Bypass proof: endpoint is designed to accept any visitor to start the OIDC dance.
  1. IdP round-trip: IdP authenticates the attacker and redirects back to the callback with an authorization code.
  2. Guard: IdP-level authentication (attacker must own the IdP identity).
  3. Bypass proof: the attacker holds a legitimate IdP account — this is not the barrier being bypassed. The barrier being bypassed is the *second factor on the phpMyFAQ side*.
  1. Callback processing (Keycloak): KeycloakAuthenticationController::callback exchanges the code, loads the linked phpMyFAQ user, and executes:
   $user->setLoggedIn(true);
   $user->setAuthSource(AuthenticationSourceType::AUTH_KEYCLOAK->value);
   $user->updateSessionId(true);
   $user->saveToSession();
   // ...
   $user->setSuccess(true);
  • Guard: $user->getUserData('twofactor_enabled') is available but never consulted. No 2fa_pending_user_id deferral like the password/passkey flows perform.
  • Bypass proof: fix commit ebca8fd1 (2026-09-27) adds exactly the missing if ((int) $user->getUserData('twofactor_enabled') === 1) gate and moves setLoggedIn/updateSessionId/saveToSession/setSuccess inside the else branch — the diff itself is the proof the gate was absent.
  1. Callback processing (Entra ID / Azure): identical pattern in both the legacy phpmyfaq/services/azure/callback.php (4.1.x, setLoggedIn(true) at line 72, setSuccess(true) at line 82) and the 4.2 controller AzureAuthenticationController.php (setLoggedIn(true) at line 132).
  2. Guard: none — same omission as Keycloak.
  3. Bypass proof: same fix commit ebca8fd1 plus its precursor e57e9a03 add the twofactor_enabled check to both files.
  1. Sink: Session is now authenticated as the target phpMyFAQ user; no token step is ever presented to the browser.
  2. Impact: full account takeover for any 2FA-enabled account reachable through SSO — including administrators.

Bypass Evidence

  • git show 4.1.8:phpmyfaq/services/azure/callback.php shows setLoggedIn(true) at line 72 and setSuccess(true) at line 82 with no reference to twofactor anywhere in the file: git show 4.1.8:phpmyfaq/services/azure/callback.php | grep -n twofactor → (empty).
  • git show 4.2.0-alpha.2:phpmyfaq/src/phpMyFAQ/Controller/Frontend/KeycloakAuthenticationController.php | grep -n twofactor → (empty); setLoggedIn(true) present at line 233.
  • git show 4.2.0-alpha.2:phpmyfaq/src/phpMyFAQ/Controller/Frontend/AzureAuthenticationController.php | grep -n twofactor → (empty); setLoggedIn(true) present at line 132.
  • Fix commits ebca8fd1 and e57e9a03 on branch main (2026-09-27) add the getUserData('twofactor_enabled') === 1 deferral to the token?user-id= step in all three files. git merge-base --is-ancestor ebca8fd1 4.2.0-alpha.2 returns non-zero — the fix is NOT in the latest release.

Proof of Concept

Preconditions: phpMyFAQ is configured with either Keycloak or Entra ID SSO; a target user account (e.g. an admin) has enabled TOTP two-factor authentication; the attacker holds any IdP identity that is linked (by email / username / preferred_username claim) to that phpMyFAQ account (a common outcome of federated deployments).

  1. Attacker opens https://target/auth/keycloak (or the Entra initiation endpoint).
  2. Attacker authenticates to Keycloak/Entra with their own valid IdP credentials.
  3. IdP redirects back to the phpMyFAQ callback with an authorization code.
  4. phpMyFAQ exchanges the code, loads the linked account, sets LoggedIn=true, writes the session, redirects to ./.
  5. The attacker now holds an authenticated session for the phpMyFAQ account without ever seeing the TOTP token page.

Response on the successful callback is a 302 to ./ with a Set-Cookie: PHPSESSID=... binding a fully logged-in session — no TOTP challenge issued.

Affected Versions

  • phpmyfaq/services/azure/callback.php (Entra ID): <= 4.1.8 (and all 4.1.x releases) and >= 4.2.0-alpha, <= 4.2.0-alpha.2 via the new controller.
  • KeycloakAuthenticationController.php: >= 4.2.0-alpha, <= 4.2.0-alpha.2 (Keycloak SSO was introduced in the 4.2 alpha stream; not present in 4.1.x).

Fix is committed on main in ebca8fd1 / e57e9a03 (2026-09-27) but is not yet included in any tagged release.

Suggested Fix

Already implemented on main — see commit ebca8fd1. Pattern: after IdP validation and before setLoggedIn(true), check (int) $user->getUserData('twofactor_enabled') === 1; if true, verify isTwoFactorLockedOut(), set 2fa_pending_user_id / 2fa_pending_remember_me in the session, and redirect to token?user-id=<id> instead of finalizing the login. This mirrors the password and passkey flows and closes the SSO gap. Backport to 4.1.x for the legacy phpmyfaq/services/azure/callback.php.

--- Reported by zx (Jace) — GitHub: @manus-use

Text from GitHub Advisory Database

Affected versions 1

  • phpmyfaq/phpmyfaq, composer<= 4.2.0-alpha.2GitHub