AOSSIE-Org / AOSSIE-Org/DebateAI
[BUG]: Password reset code stored in cleartext — vulnerable to offline brute-force by anyone with DB read access
- 主要言語
- TypeScript
- スター
- 84
- フォーク
- 198
- 平均マージ
- 2日 19時間
- マージ済み PR(30日)
- 30
説明
### Bug Description
The 6-digit password reset code is stored in the database in cleartext (`resetPasswordCode` field), and `VerifyForgotPassword` compares the submitted code directly against it. Because the code is only 6 digits (~1,000,000 possibilities), anyone who can read the database (a leaked backup, a compromised read replica, an insider, or a breach) can brute-force all possible codes offline and reset any user's password.
**Affected code** (`backend/controllers/auth.go`):
- `ForgotPassword` stores the raw code:
```go
"resetPasswordCode": resetCode,
```
- `VerifyForgotPassword` matches on the raw code:
```go
FindOne(dbCtx, bson.M{"email": request.Email, "resetPasswordCode": request.Code})
```
**Why a plain hash isn't enough:** a fast generic hash (e.g. SHA-256) of a 6-digit code is trivially reversible — an attacker just hashes all 10^6 codes and matches. So the standard fixes are:
1. **Store a server-keyed HMAC** of the code (HMAC with a secret key from config), and compute the same HMAC during confirmation. Without the server key, a DB reader can't precompute matches. OR
2. **Use a high-entropy random token** (e.g. 32+ bytes) instead of a 6-digit code, so brute-force is infeasible even if stored as-is or plainly hashed.
This was flagged by CodeRabbit (CWE-312, Cleartext Storage of Sensitive Information) during review of #485. It's pre-existing behavior — #485 only added code expiry — so it's tracked here as a separate follow-up.
Note: #485 already reduces the exposure window by expiring codes after 15 minutes, but the offline brute-force risk on a stored code remains until the code is HMAC'd or replaced with a high-entropy token.
@Ri1tik flagged by CodeRabbit during review of my #485 PR (reset-code expiry). It's pre-existing and separate from that PR, so I've filed it here as a follow-up. Could you assign this to me? I'll implement it as a server-keyed HMAC of the reset code (with the same HMAC computed at confirmation) so the stored value isn't brute-forceable, keeping the change scoped to the reset flow.
### Steps to Reproduce
1. Request a password reset for any account.
2. Inspect the user document in MongoDB (e.g. via mongosh or a DB viewer).
3. Observe `resetPasswordCode` is stored as the plain 6-digit code.
4. An attacker with read access could match/brute-force it offline against the confirmation logic.
### Logs and Screenshots
backend/controllers/auth.go — ForgotPassword:
"resetPasswordCode": resetCode, // stored in cleartext
backend/controllers/auth.go — VerifyForgotPassword:
FindOne(..., bson.M{"email": request.Email, "resetPasswordCode": request.Code}) // direct match
Flagged by CodeRabbit as CWE-312 during review of #485.
### Environment Details
- File: backend/controllers/auth.go (ForgotPassword, VerifyForgotPassword)
- Backend: Go / MongoDB
- Branch: main
- Related: follow-up to #485 (which adds expiry); this is a separate hardening concern (cleartext storage)
- Suggested fix: HMAC the code with a server-side secret, or switch to a high-entropy token
### Impact
Medium - Feature works but has issues
### Code of Conduct
- [x] I have joined the [Discord server](https://discord.gg/hjUhu33uAn) and will post updates there
- [x] I have searched existing issues to avoid duplicates
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
調査の方向性
Start in backend/controllers/auth.go at ForgotPassword and VerifyForgotPassword, then trace the existing reset-code configuration and expiry flow from related issue #485. Done means the database no longer stores a directly brute-forceable reset code, confirmation still succeeds for valid codes, and expired codes remain rejected.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- go, mongodb
- 領域
- authentication, backend, security
- issue の種類
- バグ
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 活発
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 62/100