A Simple Solution to Credential Stuffing
Intro
I previously wrote in another blog last year about the responsibilities companies have to protect their users when it comes to vulnerabilities and not just their own assets. Although not a continuation of that specific topic, I felt compelled to write this post due to the string of recent events and blame shifting I’ve seen in the news recently with Disney+, Ring, Nest, and so many others. It typically goes like this; There’s a breaking news headline about hackers breaching a service and gaining access to customer accounts. Shortly after, the company denies any such event and instead blames it’s users for reusing passwords that belong to another organization’s previous data breach. They wash their hands of it and move along. It should go without saying that this is bad publicity for the company involved, the users directly affected, and it also doesn’t look good when you’re pointing the finger at your new subscribers (*cough* Disney). While true there’s some fault on the user, the company also owns some responsibility in allowing it to happen, especially when it can so easily be prevented.
A technique known as Credential Stuffing is something we red teamers have been using since the beginning of time, but it’s recently gotten a lot of attention in the Cybersecurity community. Tools such as Snipr make it easy for an attacker to plug in a set of credentials and see what other services those work on, like your Netflix account. I’ve previously written some OSINT blog posts about the process of scraping public breach lists for credentials so I won’t do that today.
Although the adoption of password managers is growing, many people still reuse passwords on multiple sites. (Do you?) According to a recent report by Google, nearly 1.5% of all accounts belong to a public breach list. That doesn’t even account for the shared credentials that are not yet exposed due to a prior breach. Password managers don’t just make it easy to remember credentials, they make it easy to issue new ones on demand when you first sign up, as well. Most will now even tell you if one of your passwords are on a breach list due to reuse, which is great! 1Password, LastPass, KeePass, and others are great examples of this adoption. Even authorization services such as Auth0 are baking this in by default.
Password reuse has become such an epidemic that organizations like Microsoft have recently started looking for and flagging known compromised credentials. What a great start, but this is only half of the solution with it being a reactive, point-in-time approach. We need to protect users from using bad credentials in the first place and as an organization, we don’t want to have any users at any point that may have compromised credentials, exposing ourselves to risk and bad PR. So how do we do this? Well, it’s not a new idea but I’ve been pulling my hair out each time I read an article where the company who’s being reported on shames and blames their own users. Fight for the user instead!
Solutions
Simply put, we need to do our best to both catch and prevent known compromised passwords from being used. Despite our best efforts to educate the public, there will always be people who reuse passwords for convenience or because they’re risk takers. Let’s talk about convenience!
Encourage Strong and Unique Passwords
I’m picking on streaming services because both Disney+ and Netflix already have a history for user accounts being compromised. I’m assuming a small portion of these cases are from users who get caught sharing their passwords with friends and later claim to have been breached when caught, but I can’t back that up with anything. Some accounts are compromised by hackers selling credentials which were captured through phishing campaigns, which may very well be unique. Most though, are due to password reuse or a lack of complexity. Why is this? I believe it’s a combination of factors. For one, password managers aren’t easy to use when you’re signing up on a TV remote. Similarly, no one wants to spend 15 minutes typing in a complex password on a virtual keyboard. Companies can do a better job by requiring registration on another device first, such as a mobile phone or computer, leveraging your password manager. Then, to sign in on your TV, use a PIN or other temporary code to link the already authorized account. I’ve seen this with Amazon Prime and Plex (Update: My buddy Kevin pointed out Disney+ offers this) and think it’s both convenient and secure. The last thing you want to do is discourage complex and unique passwords by making it too time consuming to do so.
I also see a lot of complaints from vendors such as Arlo in forums where they defend a lack of Multi-Factor Authentication (MFA) support in the name of convenience. They claim 2FA is too much when you quickly want to check your security cameras. I get it, I do, but there are ways to do this that aren’t intrusive. Take Google for example, who fully support MFA but if you have an active session you don’t have to use it every time you open the Gmail app. Fingerprints are another form of second factor you can use to quickly authenticate in addition to your password. This doesn’t seem like a good enough excuse to me to not implement, let alone enforce, MFA.
Password managers should also make it easy to generate new passwords when signing up for any new service. Many now use browser plugins which integrate into the browser for a seamless experience. They sync as well with other devices, such as your mobile phone, so you’re not cutting corners when away from your PC. Enough about password managers as promised, but they do play an important role when it comes to the balance between security and convenience and I wanted to at least address it.
Catching Compromised Credentials
I previously mentioned Microsoft, but there are many other companies who look for and force password resets for accounts that are using previously compromised credentials. This is a great practice and everyone should be doing this! Some of these passwords may even be flagged because they’re non-unique and they’ve actually been used by others who have been in public breach lists. I’m sure your use of “Password123” wasn’t leaked by you on another site because, unfortunately, there are 22,914 other people with that password at the time of this writing. Yikes! However, catching passwords after-the-fact is not a complete solution alone. There’s an amount of time between checks that puts both the user and the service at risk.
Preventing Compromised Credentials
I’ve blogged before about Troy Hunt’s service, HaveIBeenPwned, which allows you to type an email address in and get notified in the event your accounts are leaked. The service is mostly known for its reactive alerting approach as well, but they offer another, lesser-known password API (Pwned Passwords). At a high level, this is a database full of compromised hashed passwords from various breach dumps and paste sites that you can search against. They are not plain-text passwords but instead are used for matching your plain-text password against, once hashed as SHA-1. It’s a very comprehensive list, possibly the best, with over half a billion records! It can be downloaded offline as well to protect hashed credentials from being shared with them directly. You can also opt to send over a partial hash (first five characters) if you prefer. Microsoft likely used this list when they searched their database for compromised credentials, I know many do. Although to do this specifically they would have to somehow know your password in plain text or keep it in an insecure SHA1 format, which I’m hoping they didn’t.
However, this API can be used proactively in the same way. This is the entire purpose of my blog. I want to raise awareness to developers and organizations alike that by simply screening logins and better yet, registrations, in real-time against this list, you can prevent (known) compromised accounts from touching your database. Of course, they can be later compromised or shared with another service after yours which is why you still want to do periodic checks in addition. I very rarely see this done today (which is so crazy to me) and would like to see it implemented everywhere. I think eventually it will catch on and be part of every registration and login process, similar to the password complexity scale you see now. That’s great, but let’s see if that complex password is unique too!
Implementing “Pwned Passwords” Checks
Let’s say you have a registration site where you accept Name, Username, and Password input fields. Let’s say it looks something like this completely random design:
Now, instead of just accepting any password that’s “complex” (REALLY?! 6 characters and no special character requirements?!) and taking you to the payment screen, let’s think about our users and implement a simple check to see if that password is really any good. (Should have an “Enter at Your Own Risk” sign, Pirates of the Caribbean-style)
Let’s take “Password123” and hash it as SHA-1:
b2e98ad6f6eb8508dd6a14cfa704bad7f05f6fb1
Now, let’s check the first five characters against the Pwned Passwords API:
curl -XGET ‘https://api.pwnedpasswords.com/range/b2e98'
Cool! We get 520 results with a count of times that password has been seen, which looks something like this:
Now that we have the results locally, let’s grep for what’s left of our specific password hash: (after the 5 characters)
curl -XGET ‘https://api.pwnedpasswords.com/range/b2e98' | grep -i ‘ad6f6eb8508dd6a14cfa704bad7f05f6fb1’
Now that we understand how the API works at a basic level, let’s put it into use on the server so that all of this is automated instantly when we submit a password from our browser! This can easily be done in PHP, Python, Node, etc. It can even be done on the front-end with client-side code, but probably shouldn’t. I’ll share an example of my use on GitHub soon. Here’s what that completely hypothetical registration site would now look like:
There you go! You’ve implemented a simple few lines of code which goes a long way in protecting your customers. It also shares the link so they can see which breaches they were exposed in and can learn more about the dangers of using shared credentials. You’ll also want to apply this to the login page so you catch existing members who’s credentials have become compromised over time. If they’re detected upon logging in, simply display a similar message and redirect them to the password reset page. The burden of shared credentials is one we all must bear together. We have a responsibility to our users to crack down on this bad practice.
Now, was that so hard? I still have a little hair left after pulling most of it out. :)