A (naive) demo on how to stop credential stuffing attacks.
The other night, I couldn’t sleep as the State Farm credential stuffing attack was bothering me. We’ve done a great job of stopping credential stuffing attacks at my current gig (Precognitive) and my first thought is always, “How did this happen?”. With some simple tooling you can stop many of the less sophisticated credential stuffing attacks that happen on a daily basis and/or use a vendor to stop the more sophisticated fraud rings. A large majority of the credential stuffing attacks I’ve seen are very elementary in nature and IMHO every company should at least have something in place. In many of the conversations I’ve had, people have no idea what credential stuffing is or how to stop it, so I end up explaining it. That night when I got up at 3am to ponder why/how these companies keep getting credential stuffed, I took the time to build out a quick example of an attack and a lib to stop it. That demo has now turned into this post.
What is Credential Stuffing?
First off we need to define Account Takeover, as credential stuffing is a key tool in the fraudster toolbelt when attempting to commit Account Takeover fraud. Account Takeover fraud is when a fraudster takes over a good account and uses the account to commit fraud. This is done by taking over an account and either: selling the account, selling the information syphoned from the account, committing fraud with the account, or using the information to create a new account (that can then be used to commit fraud).
Back to credential stuffing, let’s take a look at the OWASP definition:
Credential stuffing is the automated injection of breached username/password pairs in order to fraudulently gain access to user accounts. This is a subset of the brute force attack category: large numbers of spilled credentials are automatically entered into websites until they are potentially matched to an existing account, which the attacker can then hijack for their own purposes.
source: https://www.owasp.org/index.php/Credential_stuffing
That feels a bit abstract, especially for the non-security types. Let’s simplify this a bit: a credential stuffing attack at its core is when a fraudster takes a list of email & password combos (sometimes from another data breach), tries to log in on a website and sees which combinations let them into the account. They then use the account(s) to commit fraud or sell them on the dark web.
A “naive” demo.
Now we know what a credential stuffing attack is, let’s dive into an example. Before I began, I needed a working authentication page so I downloaded the Hackathon-Starter and got to work.
I googled the top 10 most used passwords (sorry I don’t have the exact source as it was 3am but this will do), added a couple to the list and built out a fake user list (if your email is on that list I apologize as I tried to make them as unique as possible). I then dropped both data sets into JSON files.
My credential stuffing attack would rely on the fact many users reuse passwords across different websites and utilize very weak passwords (see top 10 list). If my list of emails is big enough I should get a couple of hits. I want to mention here, the reason I keep calling this demo naive is because most credentials stuffing hacks are much more sophisticated and usually are used to validate email & password lists bought on the dark web, but I digress.
After I had the data in place, I set up my little cred-stuffer script. Before I could put pen to paper I needed to see what type of login I was dealing with and if I could just do a simple POST request or use something to simulate the browser. Unfortunately for hacker me, a CSRF code was set on the form, so I couldn’t (easily) do a simple POST request.
I decided to install puppeteer and write a simple script to simulate the browser. All I’m doing is looping through the list of emails, then looping through the list of passwords for each email. I then have puppeteer “type” in the email and password than submit the form. See the code below (only 67 lines not counting comments).
As you can see in the script, I write the successful entries to a file called results.json. This list can now be sold on the dark web for some bitcoin!
Now I want to set aside my white hat in favor of putting on my security hardhat to figure out how to stop this attack. From my experiences in the industry there are a host of techniques you can use. I will focus on two that should be very familiar to many: Block Lists and Velocity Checks (aka throttling).
I added a lib called ATOStopper (naming is hard…just google Phil Karlton) that I used to stop the attack. The first thing I added were the block lists. I added the block list types of: email, handle and domain.
The problem here is this will only block accounts we know are bad. Also, not a fan of the blocklist being hard coded (how can I update the block list without redeploying?). Future me would put that in a database that is easy to update via an API or a UI.
The next tool I added was the velocity check. A velocity check is “checking” how often we see a data point (or multiple data points in a composite key) over an interval, such as “Have I seen a login from this IP address in the last 30 minutes?”.
If we see the same IP address 20 times in 15 minutes or if we see the same email 7 times in 15 minutes we will start to block requests. This technique is very successful in blocking many attacks as fraudsters don’t always remember to rotate IPs or emails in a realistic manner. It also has some drawbacks such as mobile IPs which are NAT’d and can share the same IP address for many logins, even though they are valid logins. There are a number of other issues I won’t dig into in this post (think about the expiration, email me and I can explain).
We also ran the ATO check preauth, but I would recommend running it after authentication so you can improve your logic. By doing this you can build a velocity rule that is a composite of `failed:ipAddress:${ipAddress}` so you are really checking how many failed login attempts vs general login attempts.
A more sophisticated fraudster could also circumvent this ATOStopper lib by attacking at a slower interval, only checking the email once every 15 minutes, rotating the IP addresses, using a mobile IP address (if you allow that in your code) or a number of other techniques.
How to handle an ATO Attack.
Lastly, I want to talk about what you SHOULD do when you know it’s an ATO attack and you start to block logins. You should provide the same error as if the login failed, not a different error of some sort, such as a generic 403. We do this so the fraudster can’t figure out that the 403 was probably a successful login but blocked due to a fraud prevention tool. Now you may ask, why would I care if they are prevented from taking over my organization’s accounts? Well I’m glad you asked, I hope that the main reason you want ATO protection is not just to stop attacks but also to protect your users. If someone is able to figure out your users email and password combo they can then use that information to try and hack other sites (most users use the same password all over the web). As good citizens of the web, we should work to shield not only our own sites but others from attack. If we are able to have the majority of the web protected, our herd immunity will protect sites with less security, which could in turn possibly protect your own site(s)!
One more thought, you should also not tell the user “email not found” in an error message as this allows a user to figure out what emails are in your system. As mentioned above if someone else gets “hacked” they can then attack your site by figuring out which emails are in your system.
Now that you have a general idea of what (and what not) to do, this sounds like a lot of work to build out and also something that you will need to put a lot of work into maintaining. I’d recommend finding a solid vendor to solve this problem because they will have the latest techniques in place, make sure you are up to date with the latest attack vectors and automate most if not all of the maintenance process.
…And on that note, see below :)
Need Help? — Try Precognitive
You can use a version of the naive demo to stop attacks but if you want to do more than just stop the simplest of credential stuffing attacks you should take a look at Precognitive. We’ve spent the last 3+ years building a platform that not only stops all types of credential stuffing but also utilizes behavioral analytics, native device integrations, and data modeling to stop multiple Account Takeover attack vectors.
Also, if you use Auth0 we have an integration that will be released in the next month or so!
This post is a part of a series of posts around Account Takeover and how to stop it. Do you want to learn more? Feel free to email me directly.
Full Code
View on Github or below.
Credits
Hackathon-Starter for an awesome boilerplate I can use to test my hacking skills 😄.