Example finding How it works Coverage PENTEST METHODOLOGY DOCS PRICING FAQ MCP CONTACT LOG IN SIGN UP →

Credential stuffing attack signs: how to find it and stop it

FIX / CREDENTIAL STUFFING / 9 MIN READ

The main sign of credential stuffing is a high number of failed logins for many different email addresses, often with a few successes that look normal. Attackers take real email and password pairs from old breaches of other sites. A script tries each pair on your login form. A user who reuses a password on your site loses the account.

This guide lists the signs that you can see in your logs, how to confirm the attack, and how to stop it. It also shows how to check which accounts the attacker entered.

What does credential stuffing mean?

Credential stuffing is an attack with stolen logins. The attacker does not guess passwords. The attacker already has a list of email and password pairs from a breach of another service. The attacker tests each pair on many sites. A site that accepts the pair gives the attacker an account.

The attack works because many people reuse a password. A user who has the same password on a small forum and on your app is at risk after the forum has a breach. Your app did not leak the password, but your app still pays the cost.

The attack differs from brute force in several ways:

Point Brute force Credential stuffing
Target One account or a few accounts Many accounts
Guesses per account Many One or two
Passwords Guesses from a word list Real passwords from breaches
Success rate Very low Low, but not zero
Typical sign Many failures on one email Failures spread over many emails

Because each account gets only one or two tries, a limit that counts failures per account does not trigger. This is why many apps miss the attack. The defenses in this guide target the spread of the attack.

What are the signs of a credential stuffing attack?

The OWASP Credential Stuffing Prevention Cheat Sheet lists several signs. Use this list for your own logs.

Sign What it looks like
Large login volume from one source One IP address or one subnet sends many logins.
One address, many accounts The same IP address tries many different emails.
Risky network origin Logins come from proxies, anonymization services, or hosting provider ranges.
Distributed low volume traffic Each IP address sends only a few requests, but the total is large.
Unfamiliar devices or places Successful logins come from a new browser, device, or country.
Mismatched client signals A mobile user agent comes with a connection pattern of a script.

You can see more signs in your own business data:

  • The rate of failed logins rises, but no single account has many failures.
  • The share of logins that fail is much higher than usual.
  • Many failed logins use emails that have no account in your app. The attacker tries a list that came from other sites.
  • A group of accounts signs in successfully from the same address in a short time.
  • Support gets messages about password reset emails that the users did not ask for.
  • Users report actions on their account that they did not do.
  • The load of the login endpoint rises at night or at a time when your users are not active.

The OWASP guide also warns that attackers spread the traffic over many addresses so that each address stays quiet. A rule that blocks one address after a set number of requests does not stop this kind of attack. OWASP advises that you do not rely on a single predictable volume limit, and that you look at short and long time windows.

How do I confirm the attack?

Use three checks in order.

  1. Group the failures by IP address. In your auth logs, count failed logins for each IP address. A high count for a few addresses is a clear sign. A flat count over many addresses is a sign of a proxy network.
  2. Group the failures by email. Count the number of different emails per hour. A normal app has a low number of distinct failing emails. A spike with a flat count per email points to credential stuffing.
  3. Compare with the past. Open the failed login count of the previous weeks. A sudden step up with no product change is a strong sign.

If you use Supabase, use the Auth logs in the dashboard. Look at the login events and the error codes for each time window. If you use a custom backend, add a log line for each failed login with the time, the email, and the IP address. Without these logs, you cannot see the attack.

If you have a table of login events, a query like this one shows the spread:

select date_trunc('hour', created_at) as hour,
       count(*) as failures,
       count(distinct email) as distinct_emails,
       count(distinct ip) as distinct_ips
from login_events
where success = false
  and created_at > now() - interval '3 days'
group by 1
order by 1 desc;

The table name and columns are an example. Use your own names. A high distinct_emails value with a low failure count per email means credential stuffing.

Note: Do not use the user agent as the only sign. Attackers copy normal browser strings.

What do I do first? Fix it now, in order of urgency

Caution: Do not lock all accounts that were targeted for a long time. You can lock out real users, and the attacker does not care.

  1. Turn on a rate limit at the edge. Limit the login path per IP address in your CDN or host. A low limit stops simple scripts.
  2. Turn on CAPTCHA on the login form. The Supabase CAPTCHA guide supports hCaptcha and Cloudflare Turnstile. It protects the sign-in, sign-up, and password reset forms. The OWASP guide says that CAPTCHA is useful for slowing bots, that it is not perfect, and that you should apply it to suspicious logins.
  3. Find the accounts that the attacker entered. Use the damage checks in the next section.
  4. Force a password reset for those accounts. Sign out all sessions at the same time.
  5. Turn on MFA. Offer it to all users. Require it for admin accounts.
  6. Turn on the leaked password check. See the next section.
  7. Send a notice to affected users. Tell them to choose a new, unique password.
  8. Keep the login errors generic. The response must not show whether an email has an account.

How do I check new passwords against breaches?

The attack succeeds only with passwords that appear in breach lists. You can reject such passwords when the user sets them. The Supabase password security guide describes leaked password protection. Supabase Auth checks passwords against the HaveIBeenPwned Pwned Passwords API and rejects passwords that are known to have leaked. The guide says that this feature is available on the Pro Plan and above. You turn it on in the Auth settings of your project, under the Email provider.

On the same page, the guide recommends a large minimum password length and advises against a minimum below 8 characters.

The check applies when a user sets a new password. It does not find old passwords that are already in use. To cover those, ask users to change the password at the next login, or send a notice to all users.

For another stack, use the Pwned Passwords API in your sign-up and password change code. The OWASP guide recommends this check and refers to the ASVS control for it.

How do I make a limit that fits this attack?

Use limits that look at the whole login endpoint and not only at one account. These three limits work together:

  1. A limit per IP address. This stops a script on one address. Supabase limits the /token endpoint per IP address, and the default is 150 requests per 5 minutes, with bursts up to 30. The Supabase rate limits page lists this value. Lower it if your users never need that many.
  2. A limit for the whole endpoint. Set a total failure rate for all logins. When the rate passes a threshold, turn on CAPTCHA for every login until the rate falls. This covers the case with many addresses.
  3. A limit per account. Add a short and growing delay after a few failures on the same account. This does not stop the stuffing, but it helps if the attacker repeats a list.

For an app that is behind a server, make sure that the limit sees the real user address. The Supabase rate limits page explains that a server can cause the limit to apply to the server address. It states that you can pass the end user address in the Sb-Forwarded-For header, and that this needs a secret API key. Read the page before you change the setting.

How do I check the damage?

The key question is which accounts the attacker entered. Failed logins do not show this. Successful logins do.

  1. Find the time window. Use the failure spike to set the start and the end of the attack.
  2. List the successful logins in the window. Include logins from the addresses that sent many failures. Include logins from any address that logged into many accounts.
  3. Look for one address with many accounts. A real user signs in to one account. An address that signs in to ten accounts in one minute is an attacker.
  4. Compare with normal sign-in places. Check the country and the device of each login against the history of the user.
  5. Read the account actions. Look at what each account did after the login. Check data exports, password changes, email changes, API key creation, saved card changes, and orders.
  6. Check the email changes. An attacker often changes the email to keep access. If you find a changed email, treat the account as taken over.
  7. Check the reset emails. Look for password reset requests during the window. They can show that the attacker is trying to take the accounts.
  8. Check the admin accounts. Review each admin login in the window.

For each account that the attacker entered, sign out all sessions, reset the password, and check the email address. Tell the user what happened. The user must also change the same password on other sites.

Note: Some of the attacker logins can look normal. The attacker uses the right password and may use a proxy in the country of the user. Look at the pattern over many accounts and not at one login.

How do I stop the next attack?

Use these controls as a permanent baseline.

  1. Offer MFA to every user. The OWASP guide says that MFA is by far the best defense against most password-related attacks. A password from a breach is not enough to sign in.
  2. Check passwords against breach lists. Do this at sign-up and at each password change.
  3. Use CAPTCHA with a trigger. Show it for risky logins, for example when the failure rate is high.
  4. Look at the device. Ask for an extra step when a login comes from a new device or place. The OWASP guide notes that device data comes from the client and can be spoofed, so use it as one layer.
  5. Alert on the spread. Send yourself a message when the number of distinct failing emails passes a set number per hour.
  6. Notify users of new logins. Send an email when a login comes from a new device. The real user can then report it.
  7. Keep the logs. Store the time, the email, the IP address, and the result of each login. Keep them for at least a few weeks.
  8. Do not use IP blocking as the only defense. OWASP states that IP blocking should not be the sole or primary defense.

For more controls on your public endpoints, read API abuse protection. For the sign-in flows, read the authentication implementation guide. If the attacker changed an email, read auth flows: magic link, OTP and reset.

When should I get help?

Get help in these cases:

  • You find that the attacker entered many accounts, or any admin account.
  • The attacker used the accounts to take data or to spend money.
  • The attack uses thousands of addresses, and your limits do not stop it. A bot protection service or a CDN with bot rules can help.
  • Your app holds personal data or payment data. You may have a duty to report the breach.
  • You cannot see the attack in your logs because you do not keep login events. Ask a security engineer to help you set up logging first.

After you apply the fixes, watch the failure rate and the distinct email count for a week. A flat line without spikes shows that the controls work.

Common questions

What is the main sign of credential stuffing?
You see many login attempts for many different emails, and most of them fail. The attempts often come from a small set of sources or from a large proxy network.
How is credential stuffing different from brute force?
Brute force guesses many passwords for one account. Credential stuffing tries one known password for each of many accounts.
Is my app a target if it is small?
Yes. The attacker uses a script and a list of leaked logins. The script tries every site with a login form.
Does a strong password policy stop the attack?
Not by itself. The attacker uses real passwords that users chose for other sites. Check new passwords against a list of leaked passwords.
What is the best single defense?
MFA. The OWASP guide calls it the best defense against most password-related attacks.

Check your login defenses

A VibeEval scan tests the login endpoints of your deployed app and reports when the app lets a script send many logins without a block.

14-day free trial · No credit card · Cancel anytime

SCAN YOUR APP →