Defending Your Web App: A Guide to Rate Limiting and Brute Force Attack Prevention

Written by shad0wpuppet | Published 2024/01/22
Tech Story Tags: ip-address | http-headers | cybersecurity | x-forwarded-for | software-testing | software-qa | api-rate-limiting | brute-force-attack

TLDRImplementing robust rate-limiting measures is essential for web applications to prevent brute force attacks and potential service overload. Rate-limiting techniques and insights into testing and bypassing rate limits. The article covers the automation approach, header manipulations, endpoint variations, and login-related strategies. The use of Cloudflare for restoring original visitor IPs is also explored, with a caution to thoroughly test and assess potential impacts on the application before implementation.via the TL;DR App

If your application includes fundamental features like login, registration, password reset/recovery, resend confirmation links, and other specific functionalities requiring server requests, it's crucial to implement mechanisms against brute force attacks and the generation of a substantial load on your service. Without such mechanisms, your application may be vulnerable to various threats, including sending an excessive number of emails/OTPs to users, potentially leading to financial and reputational damage.

Many web applications lack adequate rate-limiting measures, relying solely on the limitations imposed by their business logic, such as restricting the number of requests based on a payment model. Some applications, however, do incorporate rate limits, particularly for operations like login attempts, registration, and other critical functionalities. These implementations often depend on the X-Forwarded-For header for IP address tracking.

To illustrate a simple example, I came up with this code snippet on Flask

from flask import Flask, request, jsonify
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)
limiter = Limiter(
    app,
    key_func=get_remote_address,
    storage_uri="memory://",)

def get_ipaddr():
    # Retrieve the client's IP address from the request
    # X-Forwarded-For header is used to handle requests behind a proxy
    ip_address = request.headers.get('X-Forwarded-For', request.remote_addr)
    return ip_address

# Rate limit to 5 requests per minute per IP
@limiter.limit("5 per minute")
@app.route('/')
def index():
    ip_address = get_ipaddr()
    return ip_address

In the following sections, I'll explain various approaches to testing and attempting to bypass rate limits in your application.

To efficiently test your app for this type of vulnerability, automation is a powerful tool. You can achieve this by employing scripts, such as those in Python (as I often do), or using tools like Burp Suite (btw a great tool for testers and cybersecurity professionals). Also, tools like Postman might be used to automate checks relatively easily.

Testing Rate Limit

Changing IP value in X-Forwarded-For header

X-Originating-IP: 127.0.0.1

Use different IP values in every request you send.

Use double X-Forwared-For header.

X-Forwarded-For:
X-Forwarded-For: 127.0.0.1

Try the same with different headers.

X-Originating-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
X-Client-IP: 127.0.0.1
X-Host: 127.0.0.1
X-Forwared-Host: 127.0.0.1

Change other headers

Try changing the user-agent, content-type, accept-language, etc, or cookies, anything that could be used as a user identifier.

If there is a rate limit of 3 tries per IP, every three tries, change the IP value of the header (or other headers or params in your requests).

Add blank chars in params

Try adding to the params you send

%00, %0d%0a, %0d, %0a, %09, %0C, %20

For example

param1=value1%%0d%0a 
param2=value2%00

For example, if you are requesting OTP for email verification and you only have 3 tries, use the 3 tries for

[email protected]
[email protected]%00
[email protected]%0d%0a
And so on

Use similar endpoints

If you are testing, for example,/API/v1/signup endpoint, then try to perform brute-force to /Signup, /SignUp, /sign-up. Try adding blank chars (from the above) to the original endpoints.

Adding extra params to the requested API endpoint

If the limit is on the requests of /api/v1/resetpassword endpoint, try brute-forcing it by adding some query params - once the rate limit is reached, try, for example, /api/v1/resetpassword?param1=value1

Logins matter

It might be the case that an app has flawed logic - if you log into your account before each attempt/series of attempts, the rate limit is reset for your IP, and you can continue your password brute-force attack. If you are testing a login feature, you can do this in Burp Suit with a Pitchfork attack in the settings (or you can write your own script for this) for each attempt/series of attempts.


Here is my example of how I automated a simple check for the X-Forwarded-For header just to get a POW:

from random import randint
import requests
import json


url = "https://yourapp.net/api/v1/regconfirm-resend"
data = {
   "email": "[email protected]"
}

N = 100


def generate_random_ip():
   return '.'.join(
       str(randint(0, 255)) for _ in range(4)
   )


for _ in range(N):
   headers = {
       "Host": "yourapp.net",
       "Content-Type": "application/json",
       "X-Forwarded-For": generate_random_ip()
   }
   response = requests.post(url, headers=headers, data=json.dumps(data))
   print(headers)
   print(f"Status Code: {response.status_code}, Response: {response.text}")

Restoring original visitor IPs

A possible solution might be the use of Cloudflare and its mechanisms. A detailed explanation can be found here restoring-original-visitor-ips. I will provide only a brief overview of its defense mechanisms.

If you use applications that depend on the incoming IP address of the original visitor, a Cloudflare IP address is logged by default. The original visitor IP address appears in an appended HTTP header called CF-Connecting-IP. By following our web server instructions, you can log the original visitor IP address at your origin server. If this HTTP header is not available when requests reach your origin server, check your Transform Rules and Managed Transforms configuration.

If Pseudo IPv4 is set to Overwrite Headers - Cloudflare overwrites the existing Cf-Connecting-IP and X-Forwarded-For headers with a pseudo IPv4 address while preserving the real IPv6 address in CF-Connecting-IPv6 header.

NOTE: Remember, when implementing such a defense mechanism, perform thorough testing. This approach might be subsequently applied to the entire cluster and could adversely affect certain functions and microservices where it is unnecessary. In a nutshell, while fixing a security breach, be cautious, as it could potentially impact the entire application. Analyze which part of your system might be affected negatively and test everything before shipping changes to the prod env.


Also published here.


Written by shad0wpuppet | I'm a Software QA Team Lead and Engineer/Analyst with 10+ years of experience working with all sorts of web apps
Published by HackerNoon on 2024/01/22