Guessing, spraying, and stuffing
Finding accounts to try
On an ordinary morning, Cedar Inc.'s identity provider records a handful of failed sign-ins. Someone mistyped a password, someone else tried last year's. One day the pattern is different: within an hour, failures arrive for hundreds of Cedar accounts, one or two attempts each, all from a few networks. Somebody is guessing. Before anyone can guess a password, though, they need a list of accounts to guess against.
For a workforce, that list is often easy to build. Cedar's addresses follow a pattern such as [email protected], and employees' names appear on the company website, in press releases and on professional profiles. Customer accounts at the photo application are harder to list from outside, so attackers ask the service itself.
Sign-in, registration and password reset forms can each answer the question "does this account exist?" A sign-in page that says "No account with that email" for one address and "Wrong password" for another has answered it. So has a registration form that says "This email is already registered", or a reset page that confirms it sent a link only when the address belongs to a customer. Learning which accounts exist from a service's own responses is called account enumeration.
Timing can give the same answer when the words do not. If the service runs its deliberately slow password hash only when the account exists, a request for a real account takes noticeably longer than one for an unknown address. Consistent responses close these gaps: the same message and status for an unknown account and a wrong password, a reset page that always says "If an account exists for this address, we've sent a link", and the same work, including a hash comparison, whatever the outcome. Registration can stay consistent too. The form always says to check your email, and someone who already has an account receives a message saying their address was used to register again, with links to sign in or reset.
Three ways to guess
With a list in hand, guessing takes three main shapes. What separates them is how the attempts are spread across accounts.
Brute force aims many passwords at one account, often thousands of guesses from one or a few sources. Against a sign-in page that limits attempts, it rarely succeeds unless the password is very short or very common. It is far more dangerous offline, against a stolen database of password hashes, which is why services store passwords with the slow, salted hashing described in Passwords and PINs.
Password spraying turns brute force around. It tries a few common passwords, the kind that "season and year" habits produce, against many accounts, with one or two attempts each. No single account sees enough failures to stand out, yet in a large organization a few people will have chosen one of the passwords being tried. The wave of guesses at Cedar had exactly this shape.
Credential stuffing does not guess at all. It takes email and password pairs leaked from other services and tries each pair once, relying on the reuse that Passwords and PINs warns about. Only a small share of pairs work, but across millions of pairs that is still thousands of accounts. Customer services such as the photo application see the most of it, because their customers sign up with personal addresses that already appear in other breaches.
| Attack | Accounts targeted | Attempts per account | Sources | Typical success |
|---|---|---|---|---|
| Brute force | One, or a few | Hundreds to thousands | One or a few networks | Low against a sign-in page that limits attempts, unless the password is very weak. High offline against stolen hashes of weak passwords. |
| Password spraying | Hundreds to thousands | One or two per password tried | A few networks, sometimes spread out to look ordinary | A small share of accounts: those that use one of the common passwords tried. |
| Credential stuffing | Everyone on a leaked list, often millions | Usually one | Many networks, often thousands of addresses | A small share of pairs, which still means many accounts at that scale. |
Slowing guesses without locking people out
The obvious response is account lockout: after a few failures, refuse every sign-in to the account until a timer runs out or someone unlocks it. Lockout does stop a brute-force attack on one account. It does nothing against spraying, which never reaches the limit on any single account. Worse, it hands attackers a weapon. Anyone who knows an employee's address can type a few wrong passwords and lock that employee out, and a script can lock out a whole finance team on the day invoices are due.
Throttling slows guesses without shutting the owner out. Each failure on an account adds a growing delay before the next attempt is accepted, so a person who mistypes twice barely notices, while a brute-force attack slows to a few guesses an hour. The service can also keep accepting the owner from a device that has signed in successfully before, or through a phishing-resistant method, while it slows attempts from everywhere else. NIST SP 800-63B-4 does set a hard ceiling: after no more than 100 consecutive failures with one authenticator, the service must disable that authenticator until the owner sets it up again. Growing delays keep both an attacker and a forgetful owner far below that ceiling, so the limit exists without becoming a tool for anyone who wants to deny access.
Limits keyed to the account cannot see spraying or stuffing, so services add limits keyed to other things. A limit per network source caps how many attempts, and how many different accounts, one address can try. Attackers respond by spreading attempts across thousands of addresses on residential networks and hosting providers, so each source stays under its limit. That leaves the view across the whole service: the overall rate of failures, the share of attempts for accounts that do not exist, and the ratio of failed to successful sign-ins. When those move sharply, the service can ask for extra proof from every unfamiliar device until the wave passes.
MFA changes what a correct guess is worth, because a guessed password alone no longer finishes the sign-in. It still tells the attacker that the password is right. A correct password followed by a failed or abandoned second step should therefore be treated as a password that someone else knows, and Getting around MFA shows what attackers try next with it.
Refusing passwords that are already known
Limits slow guessing down. They do not help when the attacker's first try is right, as in stuffing, or when the password is one of the few a sprayer tries. The stronger defense is to stop those passwords being chosen at all. When Riley, who works in Cedar's finance team, sets a new password, Cedar can compare it with collections of passwords exposed in earlier breaches and with lists of the most common choices, and refuse a match. SP 800-63B-4 requires this check, and Passwords and PINs explains why it works better than rules about capital letters and symbols.
A service sees a password in readable form only when it is set or entered, since afterward it keeps just a salted hash. So screening happens then: when a password is chosen, and optionally at sign-in, when a password that has since turned up in a new breach can be flagged and changed once the sign-in is complete.
Breach collections hold billions of entries, so many services consult a lookup service instead of keeping a copy. Sending the password, or even its full hash, to that service would create a new leak. Instead, the service hashes the candidate password and sends only the first few characters of the hash. The lookup returns every breached hash that begins with those characters, often hundreds of them, and the service compares the rest locally:
digest = lookup_hash(candidate_password)
prefix, rest = digest[:5], digest[5:]
known = breach_lookup.hashes_starting_with(prefix) # hundreds of unrelated entries
if rest in known:
refuse("This password has appeared in a data breach elsewhere.")
The lookup never learns which password was checked or whether it matched, because the request looks the same as one for any of the hundreds of passwords that share its prefix. This approach is often described as a k-anonymity check. When a password is refused, the message should say why in plain words, and suggest a password manager, so that the next choice is a unique password rather than a small change to the same one.
What guessing looks like in the records
Each attack leaves a different shape in the sign-in records, and the shape tells you which limit should have caught it. Records never hold the passwords that were tried, so the shape has to come from everything else: which accounts, how many attempts on each, from where, how fast, and why each attempt failed.
Brute force is the easiest to see: a long run of failures on one account, close together, from one source, until throttling stretches the gaps. Spraying appears only when you look across accounts: hundreds of accounts with one or two failures each, within the same hour, from the same few sources and often the same client software. That is what Cedar's records showed. Its sign-in limits slowed the wave down, and none of the guesses succeeded.
Stuffing looks like noise at first. There is one attempt per account across a very large number of accounts, from many networks, often with a high share of failures for accounts that do not exist, because the leaked list came from another service. The successes hidden among those failures matter most. A success from a network that produced hundreds of failures in the same minutes, or a correct password followed by a failed second step, marks an account whose password is known. That account needs a forced password change and a look at what happened after it was signed in, even if the rest of the attack was blocked.
Try reading the shapes yourself. Each excerpt below is a short piece of a fictional sign-in record.
Name the attack
Simulation. These sign-in record excerpts are made up for practice. Checking an answer happens only in your browser and sends nothing.
The guessing at Cedar ended with nothing taken. The attack that came the next morning did not guess at all. It asked Riley for the password directly, which is where Phishing and relayed sign-ins begins.