No rate limiting
Stopping attackers one request at a time
Rate limit - what it means
Broadly, rate limiting is a method of preventing a user (human or bot) from repeating an action in quick succession too many times (sometimes with the intent of causing damage). It sees a broad range of applications, from preventing DoS attacks at the proxy level to locking accounts to prevent brute-force attacks. While it can be admittedly annoying at times, an application without any form of rate limiting is begging to be targeted. And if you really want to avoid annoyance, you should be using a password manager to prevent typos.
About this lesson
In this lesson, you will learn about the issues that arise in an application that employs no rate limiting techniques, as well as how you can go about implementing those protections. First, we’ll look at a Node.js program that allows for brute force attacks on login details, and which has a sneaky flaw allowing a user to cause a DoS. We’ll then look at what could have been done to prevent these, accompanied by some general information for rate limiting techniques.
Brute force speeds up
Depending on the speed, a computer can guess anywhere from tens of thousands of passwords per second, all the way up to hundreds of millions (maybe billions!) per second. Do you feel secure with the password you have now?
You recently heard about XSS attacks and are wanting to try one out for yourself (you know, just for fun). The popular new social media app — YourPlace — lets you create social networks, comment on your friends’ profile pages, and see what everyone else is doing. Your good friend, Sanni Camcar, was recently bragging about how they only have compliments on their profile page, and that they get so many of them. Not that you’re jealous, but you think it’d be a pretty good prank if their comments were suddenly flooded with spam!
You know your friend is not very security conscious, and that once at a party you remember seeing a sticky note with their last name written down and a bunch of numbers. You’d be darned if you can remember what those numbers were though! Luckily, from having an account yourself, you know that YourPlace enforces a maximum password length of 10 characters. “Camcar” is 6 letters, so there are at most 10,000 numbers that can be at the end. So let's try them all!
No rate limit attack
- STEP 1
- STEP 2
- STEP 3
- STEP 4
- STEP 5
Setting the stage
Let's see if we can log into your friend's account and if we can, let's see if we can make a bunch of requests and fill her profile with spam!
Let’s take a look at a minimal login page in Flask (NOTE: while this example is specific to Flask, its principles and solutions are ideas that can be applied to any framework):
Here, the vulnerability is subtle (and has a running theme we’re about to notice) — there’s nothing stopping us just trying again if the login is unsuccessful! If we submit a username + password combination, it simply tells us we got the username/password wrong and takes us back to the login page with no repercussions. There was nothing limiting the rate at which we could submit login attempts. Now, onto why we could post so many comments.
Similarly, here we see the problem is that the only thing restricting our ability to make requests to this endpoint is effectively the speed of our internet. We can make comments as often as we like, and if, like in the example, we have something which has limited access to information (like scrolling for comments), then that ability is something that can be abused.
So, what’s the answer? Hint: it’s in the name of the module!
As we pointed out, these vulnerabilities are familiar in style, and in fact are just examples of the same problem: the lack of rate limiting. So, as you might guess, the solution to that is to, well, rate limit! The idea here is that, if you have an entry point/point of user interaction that an attacker can abuse to harm your services, then limiting the rate at which a user can interact with that point can decrease your risk.
Here we'll provide a few examples of rate limiting and their applications, tying them into the example above. Note especially that, in some cases, these techniques can actually assist each other! For example (as we will see in a second), limiting requests can prevent brute force attacks if they come from a single source, however, a resourceful attacker may have access to more than one attacking machine, thus, combining that with another tactic such as CAPTCHAs or locking accounts can help!
Captcha + OTP
You’ve probably come across CAPTCHAs before. They’re those little picture games you play sometimes when logging into your account to “prove you’re not a bot”. Well, turns out they may just be what you need to prevent bots from attacking you! It is currently quite difficult for a program to correctly (and in a timely manner) solve a CAPTCHA, making them an effective measure for preventing bots/scripts from performing things, such as brute force attacks, or posting large quantities of comments.
Many modern Python frameworks have support for CAPTCHA. Taking our login example from above, we can implement a CAPTCHA over the top of it using Flask as such:
OTPs (one-time passwords) can be useful for verifying a user's identity, acting as a preventative measure for automated attacks. It can also alert your user to someone malicious trying to access their account. Take a look at the “Keep Learning” for more information on these!
One approach to put a hard stop to any form of automated/brute force attack on login attempts is to simply not allow it. An attacker can’t try logging in 100k times if you’re only allowed 5 logins!
This approach can have numerous flavors. One such approach is to lock the account after 5 unsuccessful logins. The logical flow of something like this could look like the following:
A potential problem with this approach is that an attacker may purposely lock out accounts as a form of DoS attack, by simply entering an incorrect password 5+ times. Whether this is a significant risk for your application will depend on the application's use case.
An alternative is to implement timeouts for each login. For example, you may only be able to attempt to log into an account once every 5 seconds. A legitimate user would likely not even notice this restriction, but it would significantly slow a brute-force attack, which would typically be able to make many login attempts per second. This approach combined with a strongly enforced password policy can make brute-force attacks largely infeasible.
An example many of you may be able to relate to is your phone’s lock screen. You don’t want to call support every time you mistype 5 times, but you also don’t want to be locked out 200,000 years because your toddler got hold of your phone. So what’s the right decision? In this case, while a multi-year lockout is annoying, it’s an edge case that can be remediated with a Support call. Good choice, Apple!
There are policy approaches too. For example, if you are looking to monetize your services, then forcing a malicious actor to pay for your services is one way to deter them! If usage of your API is dependent on a user paying, then an attacker is less likely to abuse it to cause harm to your services (unless they have already stolen someone else’s account). Similarly, if you design your application such that a user isn’t supposed to interact with it that fast, then protections can be built in as part of application design! These decisions are project dependent, so maybe let it sit in the back of your head when you next go to add a feature!
Request Limiting (stress-reducing)
Going up a level, you don’t need to do everything yourself either! Many of these protections can be done at the server/network level. There exist robust systems for identifying these forms of attacks. They use techniques such as analyzing packet structure to identify clusters of similar packets, assessing how quickly a user is accessing information (to decide if that is more consistent with human or robot behavior), and more. You’re probably also using these services — things like Cloudflare, AWS, etc. all have inbuilt protection mechanisms for these attacks, and all you have to do is activate them!
This may look like adding a request limit to your servers (only allowing a certain amount of requests in at a time) or you could limit it on a per-IP basis. Or per page. Or whatever setup you have, and that’s the beauty of its malleability — it can be applied to almost all scenarios! Check the Keep Learning section below for information on Cloudflare’s rate limiting.
Test your knowledge!
What is one potential side effect of implementing a strict rate limiting policy to prevent brute force attacks in applications?
To learn more about rate limiting, check out some other great content:
- Cloudflare information on rate limiting
- An NPM package for implementing Google RECaptcha in Express
- OWASP CAPTCHA defeat information
- OWASP account lockout mechanisms
Now you know more about rate limiting and its importance! You also know about risk and mitigation techniques. We hope that you will apply this knowledge to make your applications safer. We'd really appreciate it if you could take a minute to rate how valuable this lesson was for you and provide feedback to help us improve! Also, make sure to check out our lessons on other common vulnerabilities.