BLACKMYTH
Cloud
IoT
Mobile
Flutter
Intercept
Network
VLAN
Hopping
Thick-Client
Fiddler
Web
Desync-Attack
HTTP1.1
HTTP2.0
Rate-Limit-Bypass
Technique-1
Technique-2
Home
Contact
Copyright © 2025 |
Nachiket
Home
>
Web
>
Rate-Limit-Bypass
> Technique-2
Now Loading ...
Technique-2
Rate-Limit Evasion Through Race Condition
About the bog Author: Nachiket Rathod Genre: Web Application Objective: Bypassing a robust login rate-limiting mechanism through Race Condition. TL;DR: Breaking the Limit Encountered a secure login rate-limiting mechanism that blocks access for a set duration after multiple failed login attempts. Standard bypass methods failed like trying to pick a digital lock with a toothpick 😜 but with persistence and creative problem-solving approach, the rate limit was successfully bypassed, reinforcing the importance of perseverance in security testing. 🚀 Challenges 🔐 We’ve tried the all possible known methods but no luck! Attempted Bypasses: Using Special Characters: ❌ Null Byte (%00) at the end of the email. Common characters that help bypassing the rate limit: 0d, %2e, %09, %0, %00, %0d%0a, %0a, %0C, %20. Adding HTTP Headers & IP Spoof: ❌ X-Forwarded-For: IP X-Forwarded-IP: IP X-Client-IP: IP X-Remote-IP: IP X-Originating-IP: IP X-Host: IP X-Client: IP X-Forwarded: 127.0.0.1 X-Forwarded-By: 127.0.0.1 X-Forwarded-For: 127.0.0.1 X-Forwarded-For-Original: 127.0.0.1 X-Forwarder-For: 127.0.0.1 X-Forward-For: 127.0.0.1 Forwarded-For: 127.0.0.1 Forwarded-For-Ip: 127.0.0.1 X-Custom-IP-Authorization: 127.0.0.1 X-Originating-IP: 127.0.0.1 X-Remote-IP: 127.0.0.1 X-Remote-Addr: 127.0.0.1 Investigating Rate Limiting Mechanism: 🕵️♂️ After exploring all possible known methods, an analysis was conducted to understand how the rate-limiting was implemented. Requests were intercepted, and a brute-force attempt was made using an intruder. Over 100 different password combinations were tested. Upon completing the attack, it was observed that the application enforced rate limiting after three unsuccessful attempts, displaying the error message: "Too many incorrect attempts. Please try again later after 10 minutes." Race Condition: 🏃 Race conditions are a common security vulnerability related to business logic flaws. They happen when a website processes multiple requests at the same time without proper safeguards. This allows different threads to access and modify the same data simultaneously, leading to conflicts or unexpected behaviour. In a race condition attack, an attacker sends carefully timed requests to trigger these conflicts and manipulate the application for malicious purposes. 👉 The key challenge in exploiting race conditions is ensuring that multiple requests are processed simultaneously with minimal timing differences, ideally within 1 millisecond or less. HTTP/2: Single-Packet Attack v/s. HTTP/1.1: Last-Byte Synchronization 👉 HTTP/2: Allows two requests to be sent over a single TCP connection, minimizing network jitter. However, due to server-side variations, relying on just two requests may not consistently trigger a race condition. 👉 HTTP/1.1: Last-Byte Sync: Enables pre-sending most of the data for 20-30 requests, holding back a small portion, and then releasing it simultaneously to force synchronized processing on the server. Steps for Last-Byte Sync Preparation: 1.) Send request headers and body, leaving out the final byte, while keeping the stream open. 2.) Pause for 100ms after the initial send. 3.) Disable TCP_NODELAY to leverage Nagle’s algorithm, ensuring final frames are batched. 4.) Send a ping to warm up the connection. 5.) Release the withheld frames, ensuring they arrive in a single packet (verifiable via Wireshark). Note: This method is ineffective for static files, as they are not typically involved in race condition exploits. 🔹 Adapting to Server Architecture: Understanding the target’s architecture is key. Front-end servers may route requests differently, affecting timing. Sending harmless pre-requests can help normalize request timing by warming up server-side connections. 🔹 Handling Session-Based Locking: Some frameworks, like PHP’s session handler, serialize requests per session, making vulnerabilities harder to detect. Using unique session tokens for each request can bypass this restriction and improve attack timing. 🔹 Overcoming Rate or Resource Limits: If connection warming doesn’t work, intentionally flooding the server with dummy requests can trigger rate or resource limit delays. This can create conditions favorable for a single-packet attack, increasing the chances of a successful race condition exploit. The Attack: 🪓 🔭 The Manual Approach Step-1: Intercept the application’s login request and redirect it to the intruder, then send the request and observe the normal request and its response using valid credentials. Fig 1: Observe the valid request and response. Step-2: Send the request to the Intruder, select the password position, and choose the sniper-attack option. Enter the password combinations, including one valid password and the rest as invalid. Initiate the attack, and upon completion, observe the remaining attempts in the response. Fig 2: Observe the remaining attempts. Step-3: Navigate to the third request in Intruder and examine the response to check the remaining attempts. Fig 3: Observe the remaining attempts: Zero. Step-4: Same step-3, navigate to the 100th request with a valid password and observe its response. Fig 4: Observe the remaining attempts: Zero. Step-5: Now, for the manual Race-Condition attack, create 100 tabs in Repeater, with one valid password and the rest as invalid. Use Burp’s Create Group feature to combine all the tabs into a single group. Fig 5: Screenshot shows the Create/Edit Group feature. Step-6: The first 99 tabs contain incorrect passwords, except for the last one. Now, select Send group in parallel (single-packet attack), and then click the Send button to dispatch all requests in the group. Fig 6: Screenshot shows single-packet attack. Step-7: Observe the remaining attempts in the response. For example, in this case, the first tab shows -40. Fig 7: Screenshot shows the error message with 401 with remaining attempts. Step-8: Navigate to any other tabs and observe that the same remaining attempts value appears in the response. For example, in the screenshot below, the value is -79. Fig 8: Screenshot shows the error message with 401 with remaining attempts. Step-9: Navigate to the last tab and observe that the request containing the correct password received a valid response with a user token. Fig 9: Screenshot shows the success response. 🔭 The Automated Approach 👉 Turbo Intruder 🔹This section demonstrates a method to exploit a race condition vulnerability in a login page by utilizing Turbo Intruder, an advanced HTTP request engine designed for high-speed and parallel attacks. 🔹The provided Python script uses Turbo Intruder’s request engine to perform a brute-force attack by sending multiple login attempts in parallel. These requests are queued and dispatched simultaneously through a single gate mechanism, leveraging a single-packet attack technique to bypass conventional rate limiting and account lockout protections. def queueRequests(target, wordlists): # If the target supports HTTP/2, use Engine.BURP2 to trigger the single-packet attack # If they only support HTTP/1, use Engine.THREADED or Engine.BURP instead # For more information: https://portswigger.net/research/smashing-the-state-machine engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=1, engine=Engine.BURP2 ) # Assign a list of candidate payloads from the clipboard payloads = wordlists.clipboard # Ensure you copy a wordlist to your clipboard # The 'gate' argument withholds part of each request until openGate is invoked # If you see a negative timestamp, the server responded before the request was complete for password in payloads: # Iterating through password wordlist engine.queue(target.req % password, gate='race1') # Injecting the payload into the request # Once every 'race1'-tagged request has been queued, send them in sync engine.openGate('race1') def handleResponse(req, interesting): table.add(req) ⌛ Script 👉 Multiple Gates def queueRequests(target, wordlists): # Use HTTP/2 for a single-packet attack engine = RequestEngine( endpoint=target.endpoint, concurrentConnections=5, # Allow parallel request queuing engine=Engine.BURP2 ) # Get passwords from clipboard passwords = wordlists.clipboard # Use multiple gates to avoid Turbo Intruder's 99-request limit max_per_gate = 50 # Adjust based on testing gate_index = 1 current_gate = "race" + str(gate_index) # Fixed string formatting count = 0 for password in passwords: engine.queue(target.req % password, gate=current_gate) count += 1 # If we reach the max_per_gate limit, switch to a new gate if count % max_per_gate == 0: engine.openGate(current_gate) # Release current batch gate_index += 1 current_gate = "race" + str(gate_index) # Fixed formatting # Ensure the last batch is sent engine.openGate(current_gate) def handleResponse(req, interesting): table.add(req) 🔹This approach targets systems vulnerable to TOCTOU (Time-of-Check to Time-of-Use) flaws, where concurrent requests may result in a successful login even if traditional defenses are in place. When one of the synchronized requests contains valid credentials, the race condition may allow unauthorized access before the system processes invalid attempts. This technique is particularly effective against applications that do not implement proper concurrency control during authentication checks. Step-10: Copy the list of passwords from the Notepad file. Fig 10: Screenshot showing the passwords copied to the clipboard. Step-11: Select the password value from the Repeater tab and send the request to Turbo Intruder. The selected password will automatically be replaced with %s as a placeholder for payload injection. Fig 11: Screenshot shows the python script for the single-packet attack. Step-12: Navigate to any random Queue ID(with Incorrect password) and observe the “remaining attempts” value in the response. Example: In the screenshot below, it shows -21. Fig 12: Screenshot showing the remaining attempts value in negative. Step-13: Navigate to any other Queue ID where the actual remaining attempts are reflected accurately. Example: In the screenshot below, it displays 0. Fig 13: Screenshot shows the actual remaining attempts Step-14: Navigate to the last Queue ID (102) where the actual response is received. This confirms that the brute-force attack was successfully executed using Turbo Intruder through a single-packet attack by sending all requests in parallel. Fig 14: Screenshot shows the successful single-packet attack. ⚠️ Turbo Intruder Limitations & Fixes 1. Internal Request Batching Turbo Intruder appears to have an undocumented batching threshold (typically around 99–100 requests). Requests queued beyond this threshold may be silently dropped, even if engine.openGate() is used. 2. concurrentConnections=1 Causes Queuing Bottlenecks With only a single connection, all requests are funneled sequentially. This may lead to: ❌ Delayed request delivery ❌ Dropped requests ❌ Improper synchronization 3. HTTP/2 Multiplexing Limits Targets using HTTP/2 via Engine.BURP2 may enforce concurrent stream limits. Exceeding those limits results in: 🚫 Stream resets 🚫 Requests not processed or acknowledged 4. Single Gate Overload Assigning all requests to the same gate (e.g., race1) can create internal processing issues. After about 99 requests: ⚠️ The rest may be ignored silently ⚠️ The attack becomes incomplete or ineffective Remediations and Best Practices ✅ 1. Use Multiple Gates to Distribute Load # Example: # gate1 => 0–49 passwords # gate2 => 50–99 passwords Assign a unique gate to each batch: race1, race2, race3, … Call engine.openGate("raceX") after each batch is queued. ✅ 2. Increase concurrentConnections concurrentConnections = 5 # or higher based on testing Enables parallel streams. Helps avoid throttling and improves throughput. ✅ 3. Avoid Assigning >100 Requests Per Gate Try to keep batches around 90–100 requests max per gate to prevent internal drop issues. Turbo Intruder performs better when batches are smaller and structured. ✅ 4. Monitor Request Timing If response timestamps appear before request timestamps: 🕒 It indicates a race failure or premature server response. Suggests the gate was not used correctly or the request was dropped. ✅ 5. Consider Server-Side Throttling Although less likely, some targets may: 🛡️ Rate-limit connections 🛡️ Block excessive request bursts Remediate by: Adding small delays Reducing batch size Switching from BURP2 to THREADED engine if needed. 💡 Summary: Always break requests into smaller groups with unique gates and ensure that you monitor Turbo Intruder’s internal behavior. Tune the configuration based on how the target server handles stream multiplexing and rate limits. Conclusion: It’s crucial not to give up when faced with login rate limiting on applications. If default methods prove ineffective, there are numerous alternative ways to bypass these limitations. By thinking outside the box and creating new methods, it’s possible to overcome even the most robust security measures. This adaptability underscores the importance of continuous vigilance and innovation in the field of security. References: Portswigger
Web
· 2025-04-16
<
>
Touch background to close