Security Tip: Don't Forget Rate Limiting
[Tip#43] It's essential for limiting bot attacks, and don't forget it on other sensitive routes like authentication...
#1 → Exposed API Keys & Passwords
#2 → Missing Authorisation
#3 → Missing Content Security Policy (CSP)
#4 → Missing Security Headers
#5 → Insecure Function Use
#6 → Outdated & Vulnerable Dependencies
#7 → Cross-Site Scripting (XSS)
#8 → Insufficient Rate Limiting
#9 → Missing Subresource Integrity (SRI)
#10 → Insufficient Input Validation & Mass-Assignment Vulnerabilities
Rate limiting is essential for web apps because it helps prevent abusive behaviour by limiting the number of requests that can be made to a server in a specific period of time.
Rate limiting helps against:
- Preventing or slowing down brute-force & credential stuffing attacks by making repeated attempts too slow to try in bulk. Individual and targeted requests can still go through, but these attacks often rely on making a significant number of requests to identify working accounts and passwords.
- Slowing down Denial-Of-Service (DoS) attacks by limiting the number of requests that can trigger slow operations which use up resources and slow down the targeted site.
- Prevent user enumeration by (you guessed it!) slowing down the number of requests so an attacker cannot check if a large list of email addresses is present on the site.
- Prevent unexpected abuse of an API endpoint, which could lead to data being scraped or system resources being used up by a single user.
- Prevents guessing random tokens, which is especially important if you’re using time limited codes with a significant window and low entropy. (I’ll explain this one below!)
So yeah, you could say it’s helpful. 😉
I recommend running rate limiting on all authentication routes, anything with a guessable random token, and API routes. You could also expand it to cover any sensitive routes or routes that provide data that is at risk of being scraped. As long as you set sensible limits, it won’t affect your users, but will protect you from various attacks.
In the past, we’ve talked about:
- Rate Limiting Logins
- Defining Multiple Rate Limits for more flexibility
- Safely Verifying One Time Codes with Rate Limiting
- Using a Timebox to prevent Timeless Timing Attacks that bypass Rate Limiting
- Using Transliteration to bypass Rate Limiting (This is a fun one!)
Guessing Random Tokens?
My favourite example of why rate limiting is important comes from a site I was auditing. I discovered the SMS-based One-Time-Password (OTP) was being verified on a route without rate limiting, and the token expired in 10 minutes from being sent.
The tokens sent via SMS were a six digit number, which means possible combinations are from 000000
to 999999
. That’s only 1,000,000 combinations, and if we divide that by 10 minutes and 60 seconds…
We just need to send 1,667 request per second to try every combination!
This may sound like a large number of requests for your app to handle per second, but consider that half the tokens generated will be in the first half of that number (000000
→ 500000
), so we only need 834 per second for a 50% success rate! Plus, if you can re-request tokens to be sent when old ones expire, you could knock that requests per second speed down quite significantly over the course of an hour or so.
Throw in some simple rate limiting, and trying to guess a combination with a limitation of 5 requests per second becomes 138 days!