AOSSIE-Org / AOSSIE-Org/DebateAI

[BUG]: Password reset code stored in cleartext — vulnerable to offline brute-force by anyone with DB read access

Open
#507 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
84
Forks
198
Avg merge
2d 19h
Merged PRs (30d)
30

Description

### 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

Contributor guide

No contributing guide indexed for this repository

Research direction

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.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, mongodb
Domain
authentication, backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
62/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.