[Security] Pre-registration email verification code is brute-forceable — attempts counted but never enforced #691

Closed
opened 2026-07-17 23:00:05 +00:00 by spikerj · 1 comment
Owner

Vulnerability

VerifyPreRegistrationEmailCommandHandler verifies a 6-digit email code by a direct local comparison (preReg.EmailVerificationCode == request.Code). It does preReg.EmailVerificationAttempts++ (line 64) and persists it, but never checks that counter against a maximum. So the code (10^6 possibilities) can be brute-forced within the pre-registration expiry window — the attempt counter exists but its enforcement was never added. There is no per-endpoint attempt cap; the code relies solely on any external rate-limiting.

Impact: an attacker who can reach the endpoint for a given PreRegistrationId could guess the code and mark an email verified without controlling it (registration/verification bypass).

The phone path is NOT affected — it uses the Twilio Verify API, which enforces attempt limits server-side.

Fix (this PR)

Enforce a max-attempts cap (5) in the email handler before validating: once EmailVerificationAttempts >= 5, reject with 'Too many verification attempts. Please request a new code.' The reset-on-resend already exists (ResendPreRegistrationCodesCommandHandler sets EmailVerificationAttempts = 0), so a legitimate user who mistypes recovers by requesting a new code — no usability regression. Added a unit test asserting that at the cap, even a correct code is rejected.

Note

Application-level attempt capping is defense-in-depth over network rate-limiting (which is per-IP and can be rotated); a short numeric code needs both.

## Vulnerability `VerifyPreRegistrationEmailCommandHandler` verifies a **6-digit** email code by a direct local comparison (`preReg.EmailVerificationCode == request.Code`). It does `preReg.EmailVerificationAttempts++` (line 64) and persists it, but **never checks that counter against a maximum**. So the code (10^6 possibilities) can be brute-forced within the pre-registration expiry window — the attempt counter exists but its enforcement was never added. There is no per-endpoint attempt cap; the code relies solely on any external rate-limiting. Impact: an attacker who can reach the endpoint for a given `PreRegistrationId` could guess the code and mark an email verified without controlling it (registration/verification bypass). The phone path is NOT affected — it uses the Twilio Verify API, which enforces attempt limits server-side. ## Fix (this PR) Enforce a max-attempts cap (5) in the email handler before validating: once `EmailVerificationAttempts >= 5`, reject with 'Too many verification attempts. Please request a new code.' The reset-on-resend already exists (`ResendPreRegistrationCodesCommandHandler` sets `EmailVerificationAttempts = 0`), so a legitimate user who mistypes recovers by requesting a new code — no usability regression. Added a unit test asserting that at the cap, even a correct code is rejected. ## Note Application-level attempt capping is defense-in-depth over network rate-limiting (which is per-IP and can be rotated); a short numeric code needs both.
Author
Owner

Resolved in spikersoft-backend PR #387 (merged to master). Enforced a 5-attempt cap on the 6-digit email verification code before validation (reset on resend); the code is no longer brute-forceable. Unit test added. Closing.

Resolved in spikersoft-backend PR #387 (merged to `master`). Enforced a 5-attempt cap on the 6-digit email verification code before validation (reset on resend); the code is no longer brute-forceable. Unit test added. Closing.
Sign in to join this conversation.