Description:
POST /api/v1/users.2fa.sendEmailCode is reachable without authentication and (unlike its sibling users.sendConfirmationEmail, which has rateLimiterOptions: 1/60s) carries no per-target rate limit. EmailCheck.sendEmailCode() generates a fresh 6-digit code on every call and overwrites the single stored services.emailCode.code — so each anonymous call both sends an email to the victim and invalidates any code the victim is currently using. (User enumeration via response differences was already fixed — thanks! — but the flood/overwrite abuse remains.)
Steps to reproduce:
- On a fresh workspace, register an initial user (becomes admin), then a victim user with a verified email and email 2FA enabled (account settings, or provisionally via DB).
- Anonymous caller (no credentials at all) fires 8 rapid requests:
for i in $(seq 1 8); do
curl -s -X POST http://localhost:3000/api/v1/users.2fa.sendEmailCode \
-H 'Content-Type: application/json' \
-d '{"emailOrUsername":"alice@example.com"}' -o /dev/null -w '%{http_code} ';
done
- Inspect the victim's mailbox (any SMTP sink works) and the
users collection (services.emailCode).
Expected behavior:
Per-target throttling (e.g., 1 mail/60 s, 5/hour) comparable to users.sendConfirmationEmail, and no attacker-driven invalidation of an unexpired code (resend same code or keep remaining TTL).
Actual behavior:
- 8/8 requests return
200 within 0.44 s; 8 "Authentication code" emails arrive at the victim's address.
- Consecutive mails carry different codes (observed
285206 → 917527); services.emailCode.code is a single bcrypt field rewritten on every call — the victim's in-flight code dies each time the attacker calls.
- A limit does kick in around the 9th–10th request within 60 s (429), which blunts but does not remove the flood (~8–9 mails/minute/attacker-IP, anonymously).
Server Setup Information:
- Version of Rocket.Chat Server: 8.10.0-develop (branch develop, commit
151b7df, 2026-10-01)
- License Type: Community (no license)
- Number of Users: 2 (1 admin for setup, 1 victim with email 2FA) — attack is unauthenticated
- Operating System: Linux (Docker)
- Deployment Method: docker (built from source following the official CI pipeline: yarn install →
turbo run build → meteor build → apps/meteor/.docker/Dockerfile.alpine)
- Number of Running Instances: 1
- DB Replicaset Oplog: enabled (single-node replica set rs0)
- NodeJS Version: 24.15.0
- MongoDB Version: 8.0
- SMTP: local sink on the instance's SMTP host/port
Client Setup Information
- Desktop App or Browser Version: none — plain REST via curl
- Operating System: any
Description:
POST /api/v1/users.2fa.sendEmailCodeis reachable without authentication and (unlike its siblingusers.sendConfirmationEmail, which hasrateLimiterOptions: 1/60s) carries no per-target rate limit.EmailCheck.sendEmailCode()generates a fresh 6-digit code on every call and overwrites the single storedservices.emailCode.code— so each anonymous call both sends an email to the victim and invalidates any code the victim is currently using. (User enumeration via response differences was already fixed — thanks! — but the flood/overwrite abuse remains.)Steps to reproduce:
userscollection (services.emailCode).Expected behavior:
Per-target throttling (e.g., 1 mail/60 s, 5/hour) comparable to
users.sendConfirmationEmail, and no attacker-driven invalidation of an unexpired code (resend same code or keep remaining TTL).Actual behavior:
200within 0.44 s; 8 "Authentication code" emails arrive at the victim's address.285206→917527);services.emailCode.codeis a single bcrypt field rewritten on every call — the victim's in-flight code dies each time the attacker calls.Server Setup Information:
151b7df, 2026-10-01)turbo run build→meteor build→apps/meteor/.docker/Dockerfile.alpine)Client Setup Information