Lack of Rate Limiting

Rate LimitingAPI AbuseBrute ForceEnumeration

Lack of Rate Limiting at a glance

What it is: Missing or insufficient rate limiting that allows attackers to make unlimited requests, enabling brute force attacks, resource exhaustion, data scraping, and denial of service.
Why it happens: Rate limiting vulnerabilities occur when authentication or resource-intensive endpoints lack effective limits, can be bypassed, or are not monitored, allowing abuse through excessive or distributed requests.
How to fix: Apply rate limits on all public endpoints with progressive delays, lockouts, and multi-key controls, and monitor for violations to detect and block abuse.

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.

sequenceDiagram participant Attacker participant App participant DB loop Unlimited attempts Attacker->>App: POST /login (username, password) App->>DB: Check credentials DB-->>App: Invalid App-->>Attacker: 401 Unauthorized end Attacker->>Attacker: Try 10,000 passwords Attacker->>App: POST /login (correct password) App->>DB: Check credentials DB-->>App: Valid App-->>Attacker: 200 OK + session Note over App: Missing: Rate limiting<br/>Missing: Account lockout<br/>Missing: Progressive delays
A potential flow for a Lack of Rate Limiting exploit

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.

Vulnerable
Python • Flask — Bad
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.

Secure
Python • Flask — Good
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
PYTHON
# 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
PYTHON
# 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'}), 401

Discovery

Send rapid repeated requests to authentication endpoints and observe if they are accepted without limits.

  1. 1. Test for rate limiting

    http

    Action

    Send rapid burst of requests

    Request

    POST https://api.example.com/login

    Response

    Status: 200
    Body:
    {
      "note": "All requests succeed without throttling"
    }

    Artifacts

    no_rate_limit unlimited_requests
  2. 2. Test credential stuffing feasibility

    http

    Action

    Attempt multiple login attempts

    Request

    POST https://api.example.com/login
    Body:
    "username=test&password=test123"

    Response

    Status: 200
    Body:
    {
      "note": "Thousands of login attempts possible without lockout"
    }

    Artifacts

    brute_force_possible no_account_lockout
  3. 3. Test API endpoint abuse

    http

    Action

    Mass query expensive endpoints

    Request

    GET https://api.example.com/search?q=*

    Response

    Status: 200
    Body:
    {
      "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. 1. Credential stuffing attack

    Brute force user accounts

    http

    Action

    Test leaked credentials against all user accounts

    Request

    POST https://api.example.com/login

    Response

    Status: 200
    Body:
    {
      "note": "Successful account takeovers from leaked password lists"
    }

    Artifacts

    compromised_accounts valid_credentials
  2. 2. API resource exhaustion

    Exhaust API quota

    http

    Action

    Send unlimited expensive queries

    Request

    GET https://api.example.com/export?format=pdf&size=large

    Response

    Status: 200
    Body:
    {
      "note": "Service degraded for legitimate users"
    }

    Artifacts

    service_degradation resource_exhaustion
  3. 3. Scraping and data harvesting

    Mass data extraction

    http

    Action

    Enumerate and download all accessible resources

    Request

    GET https://api.example.com/users/{1..1000000}

    Response

    Status: 200
    Body:
    {
      "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