Lack of Rate Limiting
Lack of Rate Limiting at a glance
Overview
Rate limiting controls the number of requests a client can make to an application within a given timeframe. Without proper rate limiting, attackers can make unlimited requests to brute force credentials, enumerate users, scrape data, or overwhelm servers with traffic.
Common targets include login endpoints (for credential stuffing), password reset pages (for enumeration), API endpoints (for scraping), search functionality (for data extraction), and registration pages (for spam). Missing rate limits also enable application-layer DDoS attacks that can take down services.
Where it occurs
Rate limiting vulnerabilities occur in authentication or resource-intensive endpoints lacking effective limits, distributed tracking, lockouts, or monitoring to prevent automated abuse.
Impact
Lack of rate limiting enables successful brute force and credential stuffing attacks, user enumeration exposing valid accounts, large-scale data scraping, API abuse and quota exhaustion, service degradation or denial for legitimate users, and resource exhaustion leading to downtime.
Prevention
Prevent abuse with layered rate limiting: key on IP/user/API key/session; tune thresholds; add backoff, lockouts, CAPTCHA; use distributed enforcement and WAF; return 429 with Retry-After; monitor and log repeat offenders.
Examples
Switch tabs to view language/framework variants.
Login endpoint without rate limiting enables credential stuffing
No rate limiting allows unlimited login attempts.
from flask import Flask, request
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
# BUG: No rate limiting
username = request.json['username']
password = request.json['password']
if check_credentials(username, password):
return {'token': generate_token(username)}
return {'error': 'Invalid credentials'}, 401- Line 7: No rate limiting
Without rate limiting, attackers can test millions of password combinations.
from flask import Flask, request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import redis
app = Flask(__name__)
limiter = Limiter(
app,
key_func=get_remote_address,
storage_uri="redis://localhost:6379"
)
failed_attempts = redis.Redis()
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute")
def login():
username = request.json['username']
password = request.json['password']
# Check account lockout
attempts = failed_attempts.get(f"attempts:{username}")
if attempts and int(attempts) >= 5:
return {'error': 'Account locked'}, 429
if check_credentials(username, password):
failed_attempts.delete(f"attempts:{username}")
return {'token': generate_token(username)}
# Increment failed attempts
failed_attempts.incr(f"attempts:{username}")
failed_attempts.expire(f"attempts:{username}", 900)
return {'error': 'Invalid credentials'}, 401- Line 16: Rate limit per IP
- Line 22: Account-level lockout
Implement both IP-based rate limiting and account-level lockouts.
Engineer Checklist
-
Implement rate limiting on all authentication endpoints
-
Rate limit password reset and forgot password pages
-
Add limits to expensive operations (search, reports, uploads)
-
Use multiple rate limit keys (IP, user, session, API key)
-
Implement progressive delays after failures
-
Add account lockout after repeated failed logins
-
Use distributed rate limiting (Redis) for scalability
-
Implement CAPTCHA after threshold violations
-
Return 429 status with Retry-After header
-
Monitor and alert on rate limit violations
-
Automatically block repeat offenders
-
Log all violations for security analysis
-
Test rate limits under load
-
Document rate limits in API documentation
End-to-End Example
A login endpoint has no rate limiting, allowing attackers to perform unlimited credential stuffing attempts.
# Vulnerable: No rate limiting
@app.route('/login', methods=['POST'])
def login():
username = request.json['username']
password = request.json['password']
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
return jsonify({'token': create_token(user)})
return jsonify({'error': 'Invalid credentials'}), 401# Secure: Rate limiting with multiple strategies
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import redis
from datetime import datetime, timedelta
limiter = Limiter(
app,
key_func=get_remote_address,
storage_uri="redis://localhost:6379"
)
redis_client = redis.Redis()
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute") # IP-based rate limit
def login():
username = request.json['username']
password = request.json['password']
# Check account lockout
lockout_key = f"lockout:{username}"
if redis_client.get(lockout_key):
return jsonify({'error': 'Account temporarily locked'}), 429
# Track failed attempts for this account
attempts_key = f"attempts:{username}"
attempts = int(redis_client.get(attempts_key) or 0)
if attempts >= 5:
# Lock account for 30 minutes
redis_client.setex(lockout_key, 1800, '1')
redis_client.delete(attempts_key)
# Log security event
log_security_event('account_lockout', username, get_remote_address())
return jsonify({'error': 'Too many failed attempts. Account locked for 30 minutes.'}), 429
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
# Success - clear attempts
redis_client.delete(attempts_key)
return jsonify({'token': create_token(user)})
# Failed attempt - increment counter with TTL
redis_client.incr(attempts_key)
redis_client.expire(attempts_key, 3600) # Reset after 1 hour
# Progressive delay based on attempts
delay = min(attempts * 0.5, 3.0) # Up to 3 second delay
time.sleep(delay)
return jsonify({'error': 'Invalid credentials'}), 401Discovery
Send rapid repeated requests to authentication endpoints and observe if they are accepted without limits.
-
1. Test for rate limiting
httpAction
Send rapid burst of requests
Request
POST https://api.example.com/loginResponse
Status: 200Body:{ "note": "All requests succeed without throttling" }Artifacts
no_rate_limit unlimited_requests -
2. Test credential stuffing feasibility
httpAction
Attempt multiple login attempts
Request
POST https://api.example.com/loginBody:"username=test&password=test123"
Response
Status: 200Body:{ "note": "Thousands of login attempts possible without lockout" }Artifacts
brute_force_possible no_account_lockout -
3. Test API endpoint abuse
httpAction
Mass query expensive endpoints
Request
GET https://api.example.com/search?q=*Response
Status: 200Body:{ "note": "Resource-intensive queries unlimited" }Artifacts
api_abuse no_throttling
Exploit steps
Attacker uses automated tools to send thousands of login attempts with common passwords or leaked credentials, eventually gaining access to accounts.
-
1. Credential stuffing attack
Brute force user accounts
httpAction
Test leaked credentials against all user accounts
Request
POST https://api.example.com/loginResponse
Status: 200Body:{ "note": "Successful account takeovers from leaked password lists" }Artifacts
compromised_accounts valid_credentials -
2. API resource exhaustion
Exhaust API quota
httpAction
Send unlimited expensive queries
Request
GET https://api.example.com/export?format=pdf&size=largeResponse
Status: 200Body:{ "note": "Service degraded for legitimate users" }Artifacts
service_degradation resource_exhaustion -
3. Scraping and data harvesting
Mass data extraction
httpAction
Enumerate and download all accessible resources
Request
GET https://api.example.com/users/{1..1000000}Response
Status: 200Body:{ "note": "Complete database scraped" }Artifacts
user_database mass_data_theft
Specific Impact
Account compromise through brute force, user enumeration revealing valid accounts, service degradation from excessive requests.
Fix
Implement multi-layered rate limiting: IP-based limits, per-account limits, and account lockout. Use progressive delays to slow down brute force attempts. Track failed attempts in distributed storage (Redis) for scalability. Log security events for monitoring.
Detect This Vulnerability in Your Code
Sourcery automatically identifies lack of rate limiting vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free