In a nutshell, Credential Stuffing refers to using previously breached credentials in a brute force based login attack on a different service. These attacks are incredibly common, and range from extremely basic brute force attempts to massive distributed and complex attacks.
This is the first of a two-part series, which will go over how these attacks work and how you can protect your applications. In the second part, we'll dive deeper into advanced techniques used in more sophisticated attacks and how they can be detected and defeated.
Anatomy of a Credential Stuffing Attack
Credential Stuffing attacks begin with a set of previously compromised credentials. These lists are distributed both publicly and privately between attackers and contain pairs of either usernames and passwords, or emails and passwords.
The attacker will load these into a stuffing tool, and configure it to mimic the login request of a legitimate user. One configured, most tools allow these to be exported as "Configs", which are often shared among attackers.
Proxies are generally used to launch these attacks, and range in detectability. Simple attacks will use publicly exposed proxies which are often already blacklisted and easy to detect, while sophisticated proxy attacks can be almost impossible to detect (often using the IP addresses of real people, either through a residential proxy service or infected systems).
Attacks are generally launched at a high concurrency due to the massive amount of credential pairs that must be tested to find successful matches. More sophisticated tools/attackers will slowly ramp up/down the request rate in an attempt to avoid detection.
Protecting Applications from Basic Credential Stuffing Attacks
While credential stuffing attacks are extremely widespread, there's a lot of applications out there that have little or no protection from even the most basic of attacks. Worse still, they often have no logging or monitoring to identify possible attacks (and detect breaches).
Multi-Factor Authentication
The most effective and also probably the easiest protection you can implement is 2FA/MFA (Multi Factor Authentication). Since credential stuffing attacks are random and untargeted any form of MFA is generally sufficient to stop attacks dead in their tracks. It's sometimes possible that the attacker could also have access to the email of the victim, so supporting TOTP (Time Based) MFA is a good idea.
There's often a lot of pushback to implementing mandatory MFA, so consider reducing friction by only requiring MFA on suspicious requests, or those from IP addresses not previously used by the user.
Captchas
Another relatively easy safeguard to implement is some kind of bot protection puzzle. Popular examples are Recaptcha or Hcaptcha, and are generally free and simple to implement. Unfortunately, they are horrible in terms of user experience (I'm sure you've wanted to smash your keyboard trying to figure out what Google considers a traffic light before).
A balanced approach here could be requiring a captcha after a number of logins from the same IP address in a short time period. For example, if there's more than 3 logins in 15 minutes a captcha solve is required for any further logins. Be sure to include successful logins as well, as it's possible an attacker may be checking accounts that they have previously found working on your service.
It's worth noting that pretty much all captcha services can be bypassed, either by automated software (i.e. OCR) or by human captcha solving services (where attackers pay a few dollars per thousand captchas and have them solved by low cost labour), but this massively increases the cost and complexity of the attack.
Web Application Firewalls
Having a good web application firewall in front of your authentication services will be able to block a large portion of low complexity attacks, particularly those originating from blacklisted proxies or from tools with known signatures.
They can also be extremely useful in blocking more advanced attacks, through fingerprinting or custom rules. Some WAFs, for example cloudflare, can also be configured to challenge users without javascript enabled, adding another level of protection from basic credential stuffing tools.
Force users to use strong, unique passwords
In general, these attacks are successful because most people share passwords between different services they use, or use a very close variation such as incrementing a number. You should force your users to use a complex password, but focus on trying to make sure it's unique.
Most attacks use credentials that have been stolen and published publicly online, but luckily Troy Hunt maintains the excellent Have I Been Pwned service. They offer a free developer API that you can use to check if a password was ever in a breach before without exposing the actual password to their API. On one application I worked on, a huge 11% of people attempted to use a previously breached password.
Email users about suspicious logins
While this isn't actually a defense, it's still a good idea to alert users if you detect a login that doesn't match their usual patterns (see the logging section below).
For example, you could lock an account accessed by an open proxy, from a different country or matching other known attack patterns.
Unfortunately, these emails are often ineffective (missed/ignored by the account holder). A stronger approach would be forcing a password reset, but there's often pushback as this hurts the product UX.
Detecting attacks and historical breaches
You've almost certainly been hit by some kind of credential stuffing attack in the past, but did you detect it? Most of the smaller companies I've worked with had absolutely no logging or monitoring around their authentication services.
Authentication logs
Every single login attempt should be logged, including some basic data points about the attempt. For example, you might log:
- Email/username
- If the user existed in the database
- If the login attempt was successful
- If the login attempt passed/failed 2FA
- The IP address of the request
- The User Agent of the request
- The time of the attempt
These logs are incredibly useful, both for preventing future attacks and detecting past attacks or breaches. For example, you could use these logs to determine which users may have been affected by an attack (by IP/ASN/etc), and force a password change.
You may also want to consider logging usage or access to sensitive parts of your application, for example, creation of API tokens, password/email changes, team invites, etc. Attackers often use these functions to maintain persistence in an attacked account.
Authentication metrics
While detailed authentication logs are great for detecting historical attacks or breaches, real time metrics are needed to detect in progress attacks. Metrics allow you to react quickly and take the required actions to slow or prevent an attack.
In a basic implementation, sending a simplified record of your authentication logs is generally sufficient and allows you to create a range of alerting conditions. For example, you could send the following metrics (in relation to time):
- Failed Logins
- Successful Logins
- 2FA Attempts
- 2FA Passes
- 2FA Fails
Based on these points, you can also create composite metrics, for example, Auth Failure %
or % Successful that failed 2FA
.
For smaller companies lacking metrics, I often suggest using AWS Cloudwatch, which is easy to set up and costs almost nothing. They support anomaly detection and have SDKs in pretty much every language. For larger companies, use your existing metrics platform, or consider something like DataDog.
Alerting Conditions
There's a lot of potential alerting conditions that you can set up, but here's some basic ones that apply to most applications.
Login rate attempt anomaly
Probably the most obvious indicator of a credential stuffing attack is a huge increase in attempted logins. Even in more sophisticated attacks, the amount of attempts will generally be detectable unless you have an enormous amount of authentication traffic. I like to use anomaly detection here (automatically detects changes in login rate), but you can also set this to a baseline that shouldn't be exceeded.
Login failure percentage
Another pointer to a stuffing attack is a very high failure percentage. Basic, unsophisticated attacks may have a success rate of 0.1%
to 1%
(that is, once successful login for every 100-1000 attempts), while sophisticated ones can range from 1%
to 20%
. This metric is less useful without a reasonable baseline login rate, so you may have to increase the window for smaller companies.
Failed login rate
If you don't have constant authentication traffic, consider setting up alerting on the failure metric. This should generally be negligible in normal traffic (< 1-2%
), and increases are a good indication of an attack.
Successful logins that failed 2FA
This metric is particularly important if you offer optional 2FA. Since protected accounts are generally useless to attackers, they'll often ignore the 2FA related requests completely. An increase in successful logins that either didn't attempt or failed 2FA can indicate an attack.
Wrapping Up
Credential stuffing is almost impossible to completely eliminate, but a large majority of attacks can be blocked with relatively simple measures. Having detailed logging and monitoring is essential in both detecting and blocking attacks and should be used by every application out there, large and small.
If you're a higher risk target (generally a company providing a paid service with mass appeal, for example streaming or sport) chances are the measures above (except 2FA) will be ineffective against sophisticated attacks. In part two of this series, I'll go into more detail on strategies and techniques that attackers use to evade detection, and how to approach detection and blocking complex, distributed attacks.