A Ravens verify screen asks for a two-digit code and sets no limit on attempts, so you can try every possibility until the code is accepted.
A verify screen with a two-digit code and a verify button. The server accepts or rejects and never counts attempts, so a hundred tries cover every possibility.
Guess the code by trying every possibility.
You brute-forced the code.
Why the solution worked
No attempt limit turns a short code into worthless protection, since the attacker tries the whole space quickly. The fix is a per-account attempt limit, increasing delay, a temporary lock and a longer code.
The root cause
No limit, delay or lock on attempts. The root is the absence of any attempt-rate control.
How the code is fixed
Enforce a per-account limit, increasing delay, a temporary lock and a longer code.
Vulnerable code
app.post('/verify', (req, res) => {
res.json({ ok: req.body.code === otp }); // unlimited guesses
});
Safe code
app.post('/verify', rateLimitPerAccount({ max: 5, window: '15m', lockAfter: 10 }), (req, res) => {
res.json({ ok: req.body.code === otp });
});
Impact in the real world
No rate limit allows guessing codes, passwords and one-time codes, a common cause of account takeover.
How defenders detect it
Defenders alert on many failed attempts against one account and enforce a limit, delay and lock.