Skip to content

Concept map· 6 connected terms

Brute-Force Attack

Also called: Brute force · OTP brute force · Password guessing

In one line
An automated attack that guesses passwords, PINs or one-time codes over and over until one works; attempt limits and short code lifetimes defeat it.

Reviewed

What is a brute-force attack?

A brute-force attack is automated guessing: a script tries passwords, PINs or one-time codes one after another until the system accepts one. Its power comes from volume, so its weakness is anything that caps volume. A six-digit code has exactly 1,000,000 possibilities and a four-digit PIN only 10,000. If an endpoint lets an attacker try thousands of guesses an hour against one code, a correct guess is only a matter of time.

The attack comes in several variants. Classic brute force tries every value against one account. Password spraying tries a few common passwords across many accounts to stay under per-account lockouts. Credential stuffing replays username and password pairs leaked from other sites. On OTP flows, attackers hammer the verify endpoint for one phone number, or keep requesting fresh codes and guess each one a few times, hoping new codes reset the counter.

It is different from SMS pumping (artificially inflated traffic), which abuses the sending step rather than guessing codes: bots trigger codes to premium number ranges to farm termination fees. A login or verification endpoint needs defences against both: rate limiting and send caps for pumping, and attempt limits, short expiry and lockouts for brute force. A successful guess ends in account takeover; pumping ends in an inflated messaging bill.

Key takeaways

  • A brute-force attack automates guessing of passwords, PINs or one-time codes until one is accepted.
  • A six-digit OTP has 1,000,000 combinations; capping each code at five tries keeps a random guess's odds at five in a million.
  • Limit attempts per code, per phone number or account and per IP address, and expire codes within minutes.
  • Credential stuffing replays leaked passwords and often works first time, so it needs breach checks and anomaly detection, not just lockouts.
  • SMS pumping abuses the sending step; brute force attacks the verification step. Defend both separately.

Fire 40 guesses at a verify endpoint

Refill +0.05 / s

0 / 5 tokens

Each dot = 1 token

Last burst · 40 OTP guesses

5 admitted — one token each. 35 rejected with HTTP 429 Too Many Requests: the bucket ran dry.

Tokens left
0
Admitted
5
Rejected (429)
35
Full again in
1 min 40 s
0.05 tokens / s

The bucket holds 5 tokens, so up to 5 OTP guesses can go out in one burst. After that, OTP guesses get through only as fast as tokens drip back in: 0.05 per second is the sustained limit. Watch the dots refill, then send another burst before it is full.

An attempt limiter for one phone number: 5 guesses up front, then one more every 20 seconds. Send a burst to watch 35 of 40 guesses rejected with 429, and slide the refill rate to see how fast an attacker could really try.

A burst of 40 hit a full bucket of 5: 5 accepted, 35 rejected with 429.

Key facts at a glance

A brute-force attack: key facts and limits

Six-digit code space
1,000,000 combinations
Four-digit PIN space
10,000 combinations
Odds with 5 tries per code
5 in 1,000,000 (0.0005%)
Exhausting 6 digits at 50 guesses/s
About 5.6 hours
WhatsApp OTP expiry footer
code_expiration_minutes, 1–90
NIST SP 800-63B ceiling
No more than 100 consecutive failures per account

Why does a brute-force attack matter?

Every code you send through an authentication template or an SMS OTP is only as strong as the endpoint that checks it. Without attempt limits, a million combinations is not much: at 50 guesses a second, an attacker walks the entire six-digit space in under six hours, and on average finds the code in half that. A few lines of limiter logic turn those odds into a handful of chances in a million, and make the attack loud enough to alert on.

How does a brute-force attack work?

  1. Cap attempts per code

    Allow three to five wrong guesses per issued code, then invalidate it and require a new one. With five attempts, a random guesser succeeds about five times in a million.

  2. Expire codes quickly

    Keep OTP validity to a few minutes and make codes single-use. WhatsApp authentication templates can show an expiry with code_expiration_minutes (1–90), and authentication messages default to a 10-minute time-to-live.

  3. Limit by number, account and IP

    Use token buckets or sliding windows keyed on each, plus a daily cap of failures per phone number, so requesting a new code or rotating IP addresses does not reset the attacker's budget.

  4. Back off and lock out briefly

    Add growing delays after failures and short, time-based lockouts after repeated ones, and tell the real owner. Avoid permanent lockouts, which let attackers lock real users out at will.

  5. Detect stuffing and spraying

    Check new passwords against breach lists, watch for many accounts failing from one IP range, and step up to stronger factors such as a passkey; for a business phone number, PIN verification adds a second secret to guess.

Common wrong turns

Common mistakes with a brute-force attack

  • Limiting per IP only

    Attackers spread guesses across thousands of IP addresses. Key limits on the phone number or account and on the code itself as well.

  • Letting a new code reset the counter

    If requesting a fresh OTP resets the attempt count, attackers get unlimited guesses in batches. Track failures per phone number across codes, with a daily cap.

  • Long-lived codes

    A code valid for a day gives an attacker a day. Expire codes in minutes and invalidate each one after its first successful use.

  • Telling attackers which part was wrong

    Different answers for "code expired", "code wrong" and "user not found" help attackers tune their scripts. Return one generic failure and log the detail server-side.

  • Permanent lockouts

    Locking an account for good after five failures hands attackers a denial-of-service button. Use time-based lockouts and notify the owner.

A brute-force attack in code

import crypto from "node:crypto";
const MAX_PER_CODE = 5; // wrong guesses before the code is burned
const MAX_PER_NUMBER_DAY = 20; // failures per phone number per day, across all codes
const hmac = (s) => crypto.createHmac("sha256", process.env.OTP_PEPPER).update(s).digest();
// The code was stored at send time as { mac, attempts: 0 } with a 5-minute TTL.
export async function verifyOtp(redis, phone, guess) {
const dayKey = `otp:fail:${phone}:${new Date().toISOString().slice(0, 10)}`;
if (Number(await redis.get(dayKey)) >= MAX_PER_NUMBER_DAY) return { ok: false };
const rec = await redis.hgetall(`otp:${phone}`);
if (!rec.mac) return { ok: false }; // expired, used or never sent
if (crypto.timingSafeEqual(hmac(guess), Buffer.from(rec.mac, "hex"))) {
await redis.del(`otp:${phone}`); // single use
return { ok: true };
}
const attempts = await redis.hincrby(`otp:${phone}`, "attempts", 1);
await redis.multi().incr(dayKey).expire(dayKey, 86400).exec();
if (attempts >= MAX_PER_CODE) await redis.del(`otp:${phone}`); // burn the code
return { ok: false }; // same answer for every failure
}

Per-code and per-number limits together: asking for a fresh code does not reset the daily budget.

Example: Closing an open verify endpoint

A Lucknow lending app signs customers in with a six-digit WhatsApp OTP. During a security test, a tester scripts guesses against the verify endpoint from 200 rotating IP addresses and finds no limit at all. The fix: each code now expires after five minutes and is burned after five wrong guesses, each phone number is capped at 20 failed attempts a day across all its codes, and failures return one generic message. Rerun, the same test has about two chances in 100,000 of success in a whole day, and the failure spike pages the on-call engineer within minutes.

Good practice

Best practices for a brute-force attack

  • Store OTPs as an HMAC with a server-side key rather than in plain text, and delete them after use.
  • Alert when verification success rates drop or failures for one number spike; both signal guessing.
  • Use six-digit codes rather than four for anything that protects an account or a payment.
  • Offer passkeys or authenticator apps for staff dashboards so there is no short code to guess at all.

Brute-Force Attack FAQs

How many attempts should an OTP allow?+
Three to five wrong attempts per code is a common limit. After that, invalidate the code, require a new one, and cap total failures per phone number per day so new codes do not reset the count.
Is a 4-digit OTP secure enough?+
It is much weaker: 10,000 combinations against 1,000,000 for six digits. With strict attempt limits it can do for low-risk actions, but use six digits for sign-ins and payments.
What is the difference between brute force and credential stuffing?+
Brute force guesses values; credential stuffing replays real passwords leaked from other sites. Stuffing often succeeds on the first try, so lockouts alone miss it: check for breached passwords and watch for unusual sign-in patterns.
Does rate limiting alone stop brute-force attacks?+
Not on its own. A per-IP limit is bypassed with many IP addresses. Combine per-code attempt caps, per-account limits, short expiry and alerts on failure spikes.

See InfiQ pricing

Try it on the official WhatsApp Business API

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.