Description
The 2-factor recovery flow allows an attacker with valid username/password to repeatedly request short-lived recovery tokens and brute-force backup (recovery) codes. Each token permits ~10 attempts before expiring, but the attacker can simply re-login to obtain another token and continue trying codes until one succeeds. A successful recovery code exchange returns a token that grants full account access, enabling actions like changing the account email or disabling 2FA — resulting in full account takeover.
Reproduce
- Preconditions: an existing account (no privileged access required) and a list of candidate recovery codes.
 
- Login to the application: 
POST /api/login (or use the UI at http://10.0.0.94/desktop#/login) with valid username and password. The app returns a short-lived bearer token for the 2FA/recovery step. 
- Choose the “use backup code” option on the 2FA prompt. Submit a recovery code to the recovery endpoint:
 
POST /api/2fa/recovery.json HTTP/1.1
Host: 10.0.0.94
Content-Type: application/json
Authorization: Bearer [Redacted short-lived token]
Cookie: [Redacted]
Content-Length: 30
{"recoveryCode":"8cv9j-i136a"}
- Observe response (success/failure). The token allows roughly 10 attempts before it expires; re-login yields a new token.
 
- Repeat: re-login and submit another batch of up to ~10 codes, repeat until a correct code is found. On success the system returns an auth token that enables full account access (e.g., email change, disable 2FA).
 
- Impact: automated chaining of login → 10 attempts → re-login → 10 attempts allows an attacker to brute-force a user’s set of backup codes and fully take over the account.
 
Recommendation
- Scope tokens tightly: Issue recovery tokens with the narrowest possible scope (only allow a single recovery code submission) and make them single-use. A token that permits only one submission removes the lease for brute force chaining.
 
- Enforce server-side attempt limits: Track and enforce per-account and per-token attempt counters server-side (e.g., allow ≤3 failed recovery submissions per token/account before temporary lockout) and block further attempts for a cooldown period.
 
- Rate limit and backoff: Implement strict rate limits and exponential backoff for recovery submissions per account, per IP, and per token. Consider short account lockouts or progressive delays after repeated failures.
 
- Treat recovery flow as high-sensitivity: Require additional verification for sensitive post-recovery actions (changing email, disabling 2FA). For example, require the account password again or confirmation through the registered email before making those changes.
 
- Use one-time backup codes: Ensure backup codes are generated server-side as single-use values and stored hashed. When a code is used (or exhaustion is detected), invalidate it immediately.
 
- Protect token issuance: Throttle the login endpoint itself (limit how often an account can obtain a new recovery token) and monitor for repeated login+recovery patterns indicative of automated attacks.
 
- Detect and alert: Log recovery attempts and alert on suspicious patterns (high volume of recovery attempts across accounts, repeated re-logins immediately followed by recovery submissions).
 
- UX/security improvements: Consider requiring a short (e.g., 24-48 hour) cooling period after N failed recovery attempts or sending an out-of-band notification (email/SMS) when recovery is attempted or when recovery settings are changed.
 
- Defense-in-depth: Deploy a WAF or bot-protection (CAPTCHA, device fingerprinting) on the recovery endpoint to slow/stop automated brute-force chains.
 
Applying the combination of scoped single-use tokens, server-enforced attempt limits, and requiring re-authentication for sensitive changes will prevent the described chaining attack and greatly reduce the risk of account takeover.