[toc] # Race conditions :::info :bulb: **Race conditions** occur when websites process requests concurrently without adequate safeguards. This can lead to multiple distinct threads interacting with the same data at the same time, resulting in a "collision" that causes unintended behavior in the application. ::: A race condition attack uses carefully timed requests to cause intentional collisions and exploit this unintended behavior for mailicious purposes The period of time during which a collision is possible is known as the **"race window"** # Limit overrun race conditions For example, consider an online store that lets you enter a promotional code during checkout to get a one-time discount on your order. To apply this discount, the appliaction may perform the following high-level steps: 1. Check that you haven't already used this code 2. Apply the discount to the order total 3. Update the record in the database to reflect the fact that you've now used this code If you later attempt to reuse this code, the intial checks performed at the start of the process should prevent you from doing this. ![image](https://hackmd.io/_uploads/r1KNY2lE6.png) Now consider what would happen if a user who has never applied this discount code before tried to apply it twice at almost exactly the same time: ![image](https://hackmd.io/_uploads/H1rOFnlNT.png) This introduces a small race window during which you can repeatedly clainm the discount as many times as you like. There are many variations of this kind of attack, including: - Redeeming a gift card multiple times - Rating a product multiple times - Withdrawing or transferring cash in excess of your account balance - Reusing a single CAPTCHA solution - Bypassing an anti-brute-force rate limit ## Detecting and exploiting limit overrun race conditions with Burp Repeater The process of detecting and exploiting limit overrun race conditions is relatively simple. In high-level terms, all you need to do is: 1. Identify a single-use or rate-limited endpoint that has some kind of security impact or other useful purpose 2. Issue multiple requests to this endpoint in quick succession to see if you can overrun this limit. The primary challenge is timing the requests so that at least two race windows line up, causing a collision. **This window is often just milliseconds and can be even shorter** Even if you send all of the requests at exactly the same time, in practice there are various uncontrollable and unpredictable external factors that affect when the server processes each request and in which other. ![image](https://hackmd.io/_uploads/r1_mahxET.png) Burp Suite 2023.9 adds powerful new capabilities to Burp Repeater that enable you to easily send a group of parallel requests in a way that greatly reduces the impact of one of these factors, namely **network jitter**. Burp automatically adjusts the technique it uses to suit the HTTP version supported by the server: - For HTTP/1, it uses the classic last-byte synchronization technique. - For HTTP/2, it uses the single-packet attack technique The single-packet attack enables you to completely neutralize interference from network jitter by using a single TCP packet to complete 20-30 requests simultaneously. ![image](https://hackmd.io/_uploads/rJujJaeET.png) ### APRENTICE Lab: Limit overrun race conditions **Target:** Purchase a Lightweight L33t Leather Jacket which costs $1337.00 ![image](https://hackmd.io/_uploads/S1tBqxM4a.png) But wiener's store credit is only $50.00 ![image](https://hackmd.io/_uploads/rJHnqeGEa.png) **Application flow:** 1. Send GET request to `/cart` to check the product in cart and ready to purchase ![image](https://hackmd.io/_uploads/Hku9ybGNa.png) 2. Apply `coupon` (if have) ![image](https://hackmd.io/_uploads/HJMze-MNp.png) 3. Use `Place order` function, and because our store credit is not enough, it return *"Not engough store credit for this purchase"* ![image](https://hackmd.io/_uploads/SkqqgbMV6.png) **Take a closer loot at `Apply coupon` function** 1. It a POST request to `/cart/coupon` ![image](https://hackmd.io/_uploads/H1d5b-MVT.png) 2. Server will check if `coupon=PROMO20` was already used or not. 3. If it is not applied then applied, caculate and discount the price, then save somewhere in database that this coupon is applied and return *"Coupon applied"* ![image](https://hackmd.io/_uploads/BkjQVWzNa.png) 4. If it checks with somewhere in the database and finds that the coupon is already applied, then nothing happen, return *"Coupon already applied"* ![image](https://hackmd.io/_uploads/Bk9IXbM46.png) Here is where race condition can arise. We will test it by using Burp Repeater to send multiple requests in parallel First, remove the coupon and make sure no coupon is applied ![image](https://hackmd.io/_uploads/S197PbM4T.png) Next, send request add coupon to **Repeater**, right click on the request choose **Add tab to group** > **Create tab group** ![image](https://hackmd.io/_uploads/Hk4l_bfET.png) Then click **Create** ![image](https://hackmd.io/_uploads/SkFrOZMNT.png) Now in the request add coupon, right click and choose **Send it to Repeater** or **Ctrl + R** to have 10 or 15 requests add coupon in the tab like this ![image](https://hackmd.io/_uploads/HJGrTEQ4a.png) In **Group send option** choose **Send group in parallel** ![image](https://hackmd.io/_uploads/SJuCTV7N6.png) Then click **Send group (parallel)** ![image](https://hackmd.io/_uploads/SJofCNQVa.png) This time we will see that all request in the `add coupon` group were sent in the same time and some of them return *"Coupon applied"* and others return *"Coupon already applied"* Send GET request to /cart to check `Total` ![image](https://hackmd.io/_uploads/H1aRJrm46.png) We can see that the reduction is more than 20% :smiling_face_with_smiling_eyes_and_hand_covering_mouth: this means it's vulnerable to race conditions. Due to some external influences, requests may be sent a few miliseconds apart. Therefore we need to repeat this attack a few times until the number of requests we send actually at the same time and the coupon applied by the server is enough for us to be able to buy the product for $50 ![image](https://hackmd.io/_uploads/SkkpMHmEa.png) Perfect!!! Now we can purchase this :3 ![image](https://hackmd.io/_uploads/rJqE7SXVT.png) :::success **Solved** :+1: ::: # Single-packet attack **Sub-state** :::info :bulb: A **sub-state** is a short-lived state that an application transitions through while processing a single request ::: **Race window** :::info :bulb: Sub-states are only occupied for a brief time window - often around 1ms(0.001s). I'll fefer to this time window as the **'race window'** ::: To discover a sub-state, you need an initial HTTP request to trigger a tranistion through the sub-state, and a second request that interacts with the same resource during the race window. **Network jitter** Ping and jitter are most important during streaming media usage like video streaming, online gaming, or voice over internet. Web browsing isn't very affected by response times and the variation therein, but if you need real-time data, **ping and jitter are important measures of your connection quality** As mentioned earlier, jitter is a variance in latency, or the time delay between when a signal is transmitted and when it is received. This variance is mearsured in miliseconds (ms) and is described as the disruption in the normal sequence of sending data packets Vulnerabilities with small race windows have historically been extremely difficult to discover thanks to network jitter. Jitter erractically delays the arrival of TCP packets, making it tricky to get multiple requests to arrive close togetherm even when using techniques like **last-byte** sync: ![image](https://hackmd.io/_uploads/SJjI4LmEp.png) In search of a solution, I've developed the **'single-packet attack'**. Using this technique, you can make 20-30 requests arrive at the server simultaneously - regardless of network jitter: ![image](https://hackmd.io/_uploads/Skxh48Q46.png) **Developing the single-packet attack** The single-packet attack was inspired by the 2020 USENIX presentation [Timeless Timing Attacks](https://www.usenix.org/conference/usenixsecurity20/presentation/van-goethem) In that presentation, they place **two entire HTTP/2 requests into a single TCP packet**, then look at the response order to compare the server-side processing time of the two requests: ![image](https://hackmd.io/_uploads/rJ7Z887VT.png) This is a novel possibility with HTTP/2 because it allows HTTP requests to be sent over a single connection concurrently, whereas in HTTP/1.1 they have to be sequential. The use of a single TCP packet completely **eliminates the effect of network jitter**, wo this clearly has potential for race condition attacks too. However, **two requests isn't enough for a reliable race attack thanks to server-side jitter** - variations in the application's request-processing time caused by uncontrollable variables like CPU contention I spotted an opportunity to adapt a trick from the **HTTP/1.1 'last-byte sync' technique**. Since servers only process a request once they regard it as complete, maybe by withholding a tiny fragment from each request we could pre-send the bulk of the data, then 'complete' 20-30 requests with a single TCP packet: ![image](https://hackmd.io/_uploads/HycQtUQ4p.png) To **pre-send the bulk of each request:** - If the request has no body, send all the headers, but don't set the END_STREAM flag. Withhold an empty data frame with END_STREAM set. - If the request has a body, send the headers and all the body data except the final byte. Withhold a data frame containing the final byte. Next, **prepare to send the final frames:** - Wait for 100ms to ensure the initial frames have been sent. - Ensure TCP_NODELAY is disabled - it's crucial that Nagle's algorithm batches the final frames. - Send a ping packet to warm the local connection. If you don't do this, the OS network stack will place the first final-frame in a separate packet. Finally, send the withheld frames. You should be able to verify that they landed in a single packet using Wireshark. ## Detecting and exploiting limit overrun race conditions with Turbo Intruder In addition to providing native support for the single-packet attack in Burp Repeater, we've also enhanced the Turbo Intruder extension to support this technique. Turbo Intruder requires some proficiency in Python, but is suited to more complex attacks, such as ones that require multiple retires, staggred request timing, or and extremely large number of request. To use the single-packet attack in Turbo Intruder: 1. Ensure that the target supports HTTP/2. The single-packet attack is incompatible with HTTP/1. 2. Set the `engine=Engine.BURP2` and `concurrentConnections=1` configuration options for the request engine. 3. When queueing your requests, group them by assigning them to a named gate using the `gate` argurment for the `engine.queue()` method. 4. To send all of the requests in a given group, open the respective gate with the `engine.openGate()` method. ```python= def queueRequests(target, wordlists): engine = RequestEngine(endpoint=targe.endpoint, concurrentConnections=1, engine=Engine.BURP2) # queue 20 requests in gate '1' for i in range (20): engine.queue(target.req, gate'1') # send all requests in gate '1' in parallel engine.openGate('1') ``` For more details, see the `race-single-packet-attack.py` template provided in Turbo Intruder's default examples directory. ### PRACTITIONER Lab: Bypassing rate limits via race conditions This lab's login mechnism uses rate limiting to defend against brute-force attacks. However, this can be bypassed due to a race condition **Application flow:** The first time we `GET /`, server will response with header Set-Cookie like this ``` Set-Cookie: session=wn1TE9lfU2oxiNNUgQpC9xUNwbvbI5YA ``` Then, when we `GET /login`, server will return to us the login form, with a csrf token in it ```htmlmixed= <form class=login-form method=POST action="/login"> <input required type="hidden" name="csrf" value="z6wkwlhD7uMLDiIDPu5rYCLmlTtKpL0F"> <label>Username</label> <input required type=username name="username" autofocus> <label>Password</label> <input required type=password name="password"> <button class=button type=submit> Log in </button> </form> ``` When we `POST /login`, we will send the cookie `session`, `csrf` token, `username`, `password` like this ``` POST /login HTTP/2 Host: 0ada00b004b7e49f80ed3f580089007f.web-security-academy.net Cookie: session=wn1TE9lfU2oxiNNUgQpC9xUNwbvbI5YA ... csrf=z6wkwlhD7uMLDiIDPu5rYCLmlTtKpL0F&username=admin&password=admin ``` And here is the flow I predict in sever ![image](https://hackmd.io/_uploads/SJItG9hNp.png) Check credentials is the step we can attack race condition In this lab I will use `Turbo Intruder` function in Burp to brute force + race condition this server - Step 1: Send username and password in Browser and use Proxy to catch the request to get the newest `session` and `csrf token` - Step 2: Right click on that request > **Extensions** > **Turbo Intruder** > **Send to turbo intruder** - Step 3: In the request, change username to carlos and password to %s like this ![image](https://hackmd.io/_uploads/ryB9P5nEa.png) - Step 4: And the script below will look like this ```python= 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 ) # 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 word in open('c:/path/to/burp-passwd.txt'): engine.queue(target.req, word.rstrip(), 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) ``` - Step 5: Click **Attack** and wait for the result. We will see there is one request return with status 302 ![image](https://hackmd.io/_uploads/SySiE52Na.png) Use that password to login to carlos account and we can solve the lab ![image](https://hackmd.io/_uploads/HkFRFY2Na.png) :::success **Solved** :+1: ::: # Methodology Classic limit-overrun vulnerabilities can be discovered using a trivial methodology: identify a limit, and try to overrun it. Discovering exploitable sub-states for more advanced attacks is not quite so simple. Over months of testing, I've developed the following black-box methodology to help. I recommend using this approach even if you have source-code access; in my experience it's extremely challenging to identify race conditions through pure code analysis ![image](https://hackmd.io/_uploads/SkMXWO746.png) ## 1 - Predict potential collisions Testing every endpoint is impractical. After mapping out the target site as normal, you can reduce the number of endpoints that you need to test by asking yourself the following questions: - **Is this endpoint security critical?** Many endpoints don't touch critical functionality, so they're not worth testing. - **Is there any collision potential?** For a successful collision, you typically need two or more requests that trigger operations on the same record. For example, consider the following variations of a password reset implementation: ![image](https://hackmd.io/_uploads/B1QmH0ZHT.png) With the first example, requesting parallel password resets for two different users is unlikely to cause a collision as it results in changes to two different records. However, the second implementation enables you to edit the same record with requests for two different users ## 2 - Probe for clues To recognize clues, you first need to benchmark how the endpoint behaves under normal conditions. You can do this in Burp Repeater by grouping all you your requests and using the **Send group in sequence (separate connections)** option. Next, send the same group of requests at once using the single-packet attack to minimize network jiter. You can do this in Burp Repeater by selecting the **Send group in parallel** option. Anything at all can be a clue. Just look for some form of deviation from what you obseved during benchmarking. This includes a change in one or more responses, but don't forget second-order effects like different email contens or a visible change in the application's behavior after ward. ## 3 - Prove the concept Try to understand what's happening, remove superfluous requests, adn make sure you can still replicate the effects ... Referer: https://portswigger.net/research/smashing-the-state-machine#methodology https://book.hacktricks.xyz/pentesting-web/race-condition # Multi-endpoint race conditions Perhaps the most intuitive form of these race conditions are those that involve sending requests to multiple endpoints at the same time. Think about the classic logic flaw in online stores where you add an item to your basket or cart, pay for it, then add more items to the cart before force-browsing to the order comfirmation page A variation of this vulnerability can occur when payment validation and order confirmation are performed during the processing of a single request. The state machine for the order status might look something like this: ![image](https://hackmd.io/_uploads/SJn-TaRIp.png) In this case, you can potentially add more items to your basket during the race window between when the payment is validated and when the order is finally confirmed ## Aligning multi-endpoint race windows When testing for multi-endpoint race conditions, you may encounter issues trying to line up the race windows for each request, even if you send them all at exactly the same time using the single-packet technique. ![image](https://hackmd.io/_uploads/S1L4ATAI6.png) This common problem is primarily caused by the following two factors: - **Delays introduced by network architecture** - For example, there may be a delay whenever the front-end server establishes a new connection to the back-end. The protocol used can also have a major impact. - **Delays introduced by endpoint-specific processing** - Different endpoints inherently vary in their processing times, sometimes significantly so, depending on what operations they trigger. Fortunately, there are potential workarounds to both of these issues ## Connection warming Back-end connection delays don't usually interfere with race condition attacks because they typically delay paralle request equally, so the requests stat in sync. It's essential to be able to distinguish these delays from those caused by endpoint-specific fators. One way to do this is by "warming" the connection with one or more consequential rquests to see if this smoothes out the remaining processing times. In Burp Repeater, you can try adding a `GET` rquest for the homepage to the start of your tab group, then using the **Send group in sequence (single connection)** option If the first rquest still has a longer processing time, but the rest of the rquests are now processed within a short window, you can ignore the apparent delay and continue testing as normal ### PRACTITIONER Lab: Multi-endpoint race conditions **Predict a potential collision** 1. Log in and purchase a gift card so you can study the purchasing flow ![image](https://hackmd.io/_uploads/H1b9UflvT.png) 2. Consider that the shopping cart mechanism and, in particular, the restrictions that determine what you are allowed to order, are worth trying to bypass. 3. From the proxy history, identify all endpoints that enable you to interact with the cart. For example, a `POST /cart` request adds items to the cart and a `POST /cart/checkout` request submits your order ![image](https://hackmd.io/_uploads/SkMAPGlDp.png) 4. Add another gift card to your cart, then send the `GET /cart` request to Burp Repeater 5. In Repeater, try sending the `GET /cart` request both with and without your session cookie. - With session cookie: ![image](https://hackmd.io/_uploads/SJhQFzlwa.png) - Without session cookie: ![image](https://hackmd.io/_uploads/ryQ8FfgD6.png) Confirm that without the session cookie, you can only access an empty cart. Form this you can infer that: - The state of the cart is stored server-side in your session. - Any operations on the cart are keyed on your session ID or the associated user ID This indicates that there is potential for a collision 6. Notice that submitting and receiving confirmation of a successful order takes place over a single request/response cycle 7. Consider that there may be a race window between when your order is validated and when it is confirmed. This could enable you to add more items to the order after the server checks whether you have enough store credit **Benchmark the behavior** 1. Send both the `POST /cart` and `POST /cart/checkout` request to Burp Repeater ![image](https://hackmd.io/_uploads/rkQe6GeDa.png) 2. In Repeater, add the two tabs to a new group ![image](https://hackmd.io/_uploads/SySSpzlwa.png) 3. Send the two requests in sequence over a single connection a few times. ![image](https://hackmd.io/_uploads/Byyy0zgvT.png) Notice from the response times that the first request consistently takes significantly longer than the second one ![image](https://hackmd.io/_uploads/H1HYkmeDa.png) 4. Add a `GET` request for the homepage to the start of you tab group ![image](https://hackmd.io/_uploads/ByXVlXgDp.png) 5. Send all three requests in sequence over a single connection. Observe that the first request still takes longer, but by "warming" the connection in this way, the second and third requests are now completed within a much smaller window. ![image](https://hackmd.io/_uploads/H1fe-Xxvp.png) 6. Deduce that this delay is caused by the back-end network architecture rather than the respective processing time of the each enpoint. Therefore, it is not likely to interfere with your attack 7. Remove the `GET` request for the homepage from you tab group 8. Make sure you have a single gift card in your ![image](https://hackmd.io/_uploads/BJfAMXxDp.png) 9. In Repeater, modify the `POST /cart` request in your tab group so that the `productId` parameter is set to `1`, that is, the ID of the **Lightweight L33t Leather Jacket** ![image](https://hackmd.io/_uploads/r1brQ7xPa.png) 10. Send the requests in sequence again 11. Observe that the order is rejected due to insufficient funds, as you would expect. ![image](https://hackmd.io/_uploads/rkFXN7xDa.png) **Prove the concept** 1. Remove the jacket from your cart and add another gift card 2. In Repeater, try sending the requests again, but this time in parallel. ![image](https://hackmd.io/_uploads/rkqrrQxvT.png) 3. Look at the response to the `POST /cart/checkout` request: - If you received the same "insufficient funds" response, it means the jacket was added before the cheking phase. Remove the jacket from your cart and repeat the attack. This may take several attempts. Maybe you can add more `POST /cart` request in group like this. - If you received a 200 response, check whether you successfully purchased the leadter jacket. If so, the lab is solved ![image](https://hackmd.io/_uploads/HyJv9Xxv6.png) **Summary** Here is the idea ![image](https://hackmd.io/_uploads/BJMR3mxvp.png) Remember to check the response timers, and if it take more time than order response, try using `"warming" the connection` technique. If the first request still has a longer processing time, but the rest of the requests are now processed within a short window, you can ignore the apparent delay and continue testing as normal. ![image](https://hackmd.io/_uploads/rJ2d1Vgv6.png) :::success **Solved** :+1: ::: ## Abusing rate or resource limits If connection warming doesn't make any difference, there are various solutions to this problem Using Turbo Intruder, you can introduce a short client-side delay. However, as this involves splitting your actual attack requests across multiple TCP packets, you won't be able to use the single-packet attack technique. As a result, on high-jitter targets, the attack is unlikely to work reliably regardless of what delay you set. ![image](https://hackmd.io/_uploads/HknqzNlPa.png) Instead, you may be able to solve this problem by abusing a common security feature. Web servers often delay the processing of requests if too many are sent too quickly. By sending a large number of dummy requests to intentionally trigger the rate or resource limit, you may be able to cause a suitable server-side delay. This makes the single-packet attack viable even when delayed execution is required ![image](https://hackmd.io/_uploads/S1q_dUlDp.png) # Single-endpoint race conditions Sending parallel requests with different values to a single endpoint can sometimes trigger powerful race conditions Consider a password reset mechanism that stores the user ID and reset token in the user's session In this scenario, sending two parallel password reset requests from the same session, but with two different usernames, could potentially cause the following collision: ![image](https://hackmd.io/_uploads/Bkm75IgPa.png) Note the final state when all operations are complete: - `session['reset-user'] = victim` - `session['reset-token'] = 1234` The session now contains the victim's user ID, but the valid reset token is sent ### PRACTITIONER Lab: Single-endpoint race conditions **PREDICT A POTENTIAL COLLISION** 1. Log in and attempt to change your email to `anything@exploit-<YOUR-EXPLOIT-SERVER-ID>.exploit-server.net`. ![image](https://hackmd.io/_uploads/SJ96-IZPT.png) Observe that a confirmation email is sent to your intended new address, and you're prompted to click a link containing a unique token to confirm the change ![image](https://hackmd.io/_uploads/r1L7z8ZwT.png) After click the link, a GET request contains the unique token is sent to the server ![image](https://hackmd.io/_uploads/rJ2G7IZPa.png) 2. Complete the process and confirm that your email address has been updated on your account page ![image](https://hackmd.io/_uploads/Hy9wQLbDp.png) 3. Try submitting two different email addresses which have the same domain is your exploit domain, then go to the email client ![image](https://hackmd.io/_uploads/rJUbrLZwa.png) 4. Notice that if you try to use the first confirmation link you received, this is no longer valid ![image](https://hackmd.io/_uploads/ry6KBIZPa.png) From this, you can infer that the website only stores one pending email address at a time. As submitting a new email address edits this entry database rather than appending to it, there is potential for a collision. **BENCHMARK THE BEHAVIOR** 1. Send the `POST /my-account/change-email` request to Repeater 5 times. 2. In Repeater, add all 5 tabs to a new group. 3. In each tab, modify the first part of the email address so that it is unique to each request, for example, `test1@...` and so on 4. Send the group of requests in sequence over separate connections. ![image](https://hackmd.io/_uploads/B1ulFLbPa.png) 6. Go back to the email client and observe that you have received a single confirmation email for each of the email change requests ![image](https://hackmd.io/_uploads/BJ8XqUZDT.png) **PROBE FOR CLUES** 1. In Repeater, send a group of requests again, but this time in paralle, effectively attempting to change the pending email address to multiple different values at the same time. 2. Go to the email client and study the new set of confirmation emails you've received. When I tried with only 5 request -> nothing different, but when I increased the number of request to 11 -> something happended. ![image](https://hackmd.io/_uploads/B1MdyPZPa.png) Notice that, this time, the recipient address doesn't always match the pending new email address 3. Consider that there may be a race window between when the website: 1. Kicks off a task that eventually sends and email to the provided address 2. Retrieves data from the database and uses this to render the email template 4. Deduce that when a parallel request changes the pending email address stored in the database during this window, this resuls in confirmation emails being sent to the wrong address ![image](https://hackmd.io/_uploads/HJFBdPWPa.png) If we can receive the content of the other request -> we can read the content that send to orthers' email which we may don't have the right to access -> We can change own email adress to anyone mail even those we don't have the right to access that email, because we can read the content of the mail and take the token through this vulnerable **PROVE THE CONCEPT** Target is to change wiener's email to `carlos@ginandjuice.shop` 1. In Repeater, create a new group containing two copies of the `POST /my-account/change-email` 2. Change the `email` parameter of one request to `anything@exploit-<YOUR-EXPLOIT-SERVER-ID>.exploit-server.net` 3. Change the `email` parameter of the other request to `carlos@ginandjuice.shop` 4. Send the requests in parallel 5. Check your inbox: - If you received a confirmation email in whic the address in the body matches your own address, resend the requests in parallel and try again. - If you received a confirmation email in which the address is `carlos@ginandjuice.shop`, click the confirmation link to update your address accordingly. ![image](https://hackmd.io/_uploads/BkgWWubvp.png) 6. Go to your account page and notice that you now see a link for accessing the admin panel ![image](https://hackmd.io/_uploads/SJ2BW_bwT.png) Visit the admin panel and delete the user `carlos` and solve the lab ![image](https://hackmd.io/_uploads/BJkAWuWvp.png) :::success **Solved** :+1: ::: ## Session-based locking mechanisms Some frameworks attempt to prevent accidental data corruption by using some form of request locking. For example, PHP's native session handler module only processes one request per session at a time It's extremely important to spot this kind of behavior as it can otherwise mask trivally exploitable vulnerabilities. If you notice that all of your requests are being processed sequentially, try sending each of them using a different session token ## Partial construction race conditions Many applicaitons create objects in multiple steps, which may introduce a temporary middle state in which the object is exploitable. For example, when registering a new user, an application may create the user in the database and set their API key using two separate SQL statements. This leaves a tiny window in which the user exists, but their API key is uninitialized. This kind of behavior paves the way for exploits whereby you inject an input value that returns something matching the unitialized database value, such as an empty string, or `null` in JSON, and this is compared as part of a security control. Frameworks often let you pass in arrays and other non-string data structures using non-standard syntax. For example, in PHP: - `param[]=foo` is equivalent to `param = ['foo']` - `param[]=foo&param[]=bar` is equivalent to `param = ['foo', 'bar']` - `param[]` is equivalent to `param = []` Ruby on Rails lets you do something similar by providing a query or `POST` parameter with a key but no value. In other words `param[key]` results in the following server-side object: ```ruby= params = {"param"=>{"key"=>nil}} ``` In the example above, this means that during the race window, you could potentially make authenticated API request as follows ``` GET /api/user/info?user=victim&api-key[]= HTTP/2 Host: vulnerable-website.com ``` ### EXPERT Lab: Partial construction race conditions This lab contains a user registration mechanism. A race condition enables you to bypass email verification an register with an arbitrary email address that you do not own **PREDICT A POTENTIAL COLLISION** 1. Study the user registration mechaism. Observe that: - You can only register using `@ginandjuice.shop` email addresses. - To complete the registration, you need to visit the confirmation link, which is sent via email - As you don't have access to an `@ginandjuice.shop` email account, you don't appear to have a way to access a valid confirmation link 2. In Burp, from the proxy history, notice that there is a request to fetch `/resources/static/users.js` ![image](https://hackmd.io/_uploads/Sk1d2ubvp.png) 3. Study the JavaScript and notice that this dynamically generates form for the confirmation page, which is presumably linked from the confirmation email. This leaks the fact that the final confirmation is submitted via `POST` request to `/confirm`, with the token provided in the query string ![image](https://hackmd.io/_uploads/SJBvCdWDa.png) 4. In Burp Repeater, create an equivalent request to what your browser might send when clicking the confirmation link. You can do this by try to send a POST request to `/confirm` ![image](https://hackmd.io/_uploads/B1DfgKZPT.png) Now add parameter `token` ![image](https://hackmd.io/_uploads/SynvlKZDp.png) 5. Experiment with the `token` parameter in your newly crafted confirmation request. Observe that: - If you submit an arbitrary token, you receive an `Incorrect token` - If you remove the parameter altogether, you receive a `Missing parameter token` - If you submit an empty token parameter, you receive a `Forbidden` response. 6. Consider that this `Forbidden` response may indicate that the developers have patched a vulnerability that could be exploited by sending an empty token parameter. 7. Consider that there may be a small race window between: 1. When you submit a request to register a user 2. When the newly generated registration token is actually stored in the database If so, there may be a temporary sub-state in which `null` (or equivalent) is a valid token for confirming the user's registration 8. Experiment with different ways of submitting a token parameter with a value equivalent to `null`. For example, some frameworks let you to pass an empty array as follows: ``` POST /confirm?token[]= ``` 9. Observe that this time, instead of the `Forbidden` response, you receive an `Invalid token: Array` response. This shows that you've successfully passed in an empty array, which could potentially match an uninitialized registration token ![image](https://hackmd.io/_uploads/BJHGEY-wa.png) **BENCHMARK THE BEHAVIOR** 1. Send the `POST /register` request to Burp Repeater 2. In Burp Repeater, experiment with the registration request. Observe that if you attempt to register the same username more than once, you get a different response ![image](https://hackmd.io/_uploads/Hkgf2NKZPT.png) 3. In a separate Repeater tab, use what you've learned from the JavaScript import to construct a confirmation request with an arbitrary toke. For example: ``` POST /confirm?token=1 HTTP/2 Host: YOUR-LAB-ID.web-security-academy.net Cookie: phpsessionid=YOUR-SESSION-ID Content-Type: application/x-www-form-urlencoded Content-Length: 0 ``` 4. Add both requests to a new tab group 5. Try sending both requests sequentially and in parallel several times, making sure to change the username in the registration request each time to avoid hitting the separate `Account already exists with this name` code path. 6. Notice that the confirmation response consistently arrives much quicker than the response to the registration request ![image](https://hackmd.io/_uploads/ByNAwtWv6.png) **PROVE THE CONCEPT** Here is the idea: ![image](https://hackmd.io/_uploads/SJM5apMDa.png) 1. Note that you need the server to begin creating the pending user in the database, then compare the token you send in the confirmation request before the user creation is complete 2. Consider that as the confirmation response is always processed much more quickly, you need to delay this so that it falls within the race window 3. In the `POST /register` request, highlight the value of the `username` parameter, then right-click and select Extensions > Turbo Intruder > Send to turbo intruder ![image](https://hackmd.io/_uploads/B1xt_qzvT.png) 4. In Turbo Intruder, in the request editor: 1. Notice that the value of the `username` parameter is automatically marked as a payload position with the `%s` placeholder. 2. Make sure the `email` parameter is set to an arbitrary `@ginandjuice.shop` address that is not likely to already be registered on the site 3. Make a note of the static value of the `password` parameter. You'll need this later 5. From the drop-down menu, select the `examples/race-single-packet-attack.py` templates ![image](https://hackmd.io/_uploads/HkdztcMwp.png) 6. In the Python editor, modify the main body of the template as follows: 1. Define a variable containing the confirmation request you've been testing in Repeater. ![image](https://hackmd.io/_uploads/SJbA6iMPa.png) 2. Create a loop that queues a single registration request using a new username for each attempt ![image](https://hackmd.io/_uploads/rypyCjGvp.png) 3. Create a nested loop that queues a large number of confirmation requests for each attempt. These should also use the same release gate ![image](https://hackmd.io/_uploads/r1YWAofva.png) 4. Open the gate for all the requests in each attempt at the same time ![image](https://hackmd.io/_uploads/rk4X0ofPa.png) The resulting script should look something like this ```python= 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 ) comfirmationReq = ''' POST /confirm?token[]= HTTP/2 Host: 0a8d00b6046524099ac4bf5900ff00e2.web-security-academy.net Cookie: phpsessionid=Ry16pt9QanyoF2pNOdlq7iiAQLwKqKVx Content-Length: 0 ''' for attempt in range(20): currentAttempt = str(attempt) username = 'uname' + currentAttepmt # 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 queue requests for this attempt engine.openGate(currentAttempt) def handleResponse(req, interesting): table.add(req) ``` 7. Launch the attack 8. In the results table, sort the results by the Length column 9. If the attack was successful, you should see one or more 200 responses to your confirmation request containing the message `Account registration for user <USERNAME> successful` ![image](https://hackmd.io/_uploads/B1bntsGDa.png) 10. Make a note of the username from one of these responses. If you used the example script above, this will be something like `unumber6` 11. In the browser, log in using this username and the static password you used in the registration request. ![image](https://hackmd.io/_uploads/ByLO9jfPT.png) 12. Access the admin panel and delete `carlos` to solve the lab ![image](https://hackmd.io/_uploads/BJ8o5jfPp.png) :::success **Solved** :+1: ::: ## Time-sensitive attacks Sometimes you may not find race conditions, but the techniques for delivering requests with precise timing can still reveal the presence of other vulnerabilities One such example is when high-resolution timestamps are used instead of cryptographically secure random strings to generate security tokens. Consider a password reset token that is only randomized using a timestamp. In this case it might be possible to trigger two password resets for two different users, which both use the same token. All you need to do is time the requests so that they generate the same timestamp. ### PRACTITIONER Lab: Exploiting time-sensitive vulnerabilities This lab contains a password reset mechanism. Although it doensn't contain a race condition, you can exploit the mechanism's broken cryptography by sending carefully timed requests **Study the behavior** 1. To complete resetting password you need to visit the confirmation link, which is sent via email 2. The confirmation link is a `GET` request to `/forgot-password` with paramerter `token` 3. If we request reset password twice, there will be 2 tokens are sent to our mail. But the first one will be invalid ![image](https://hackmd.io/_uploads/S1IP3TzD6.png) 4. If we sent a punch of `POST` request to `/forgot-password`, we still get a different token in each confirmation email. Infer that our requests are still being processed in sequence rather than concurrently **Bypass the per-session locking restriction** 1. Notice that your session cookie suggetsts that the website uses a PHP back-end. This could mean that the server only processes one request at a time per session ![image](https://hackmd.io/_uploads/HJEAvAUvp.png) 2. Send the `GET /forgot-password` request to Burp Repeater, remove the session cookie from the request the send it, to get new session cookie and csrf token ![image](https://hackmd.io/_uploads/SyZ8_RLPT.png) 3. Fromn the response, copy the newly issued session cookie and csrf token and use them to replace the respective values in one of the two `POST /forgot-password` requests. You now have a pair of password reset requests from two different sessions 4. Send the two `POST` requests in parallel a few times and observe that the processing times are now much more closely aligned, and sometimes identical. ![image](https://hackmd.io/_uploads/SkxItCIvp.png) 6. Go back to your inbox and notice that when the response times match for the pair of reset request, this result in two confirmation emails that use an identical token. This confirms that a timestamp must be one of the inputs for the hash. ![image](https://hackmd.io/_uploads/ryaxcALwp.png) 7. Notice the separate `username` parameter. This suggests that the username might not be included in the hash, which means that two different usernames could theoretically 8. Try send two request with different session, csrf token and username. One is wiener and one is carlos 9. Check inbox again and observe that, this time, you've only received one new confirmation email. Infrer that the other email, hopefully containing the same token, has been sent to Carlos ![image](https://hackmd.io/_uploads/B1dmoC8wp.png) 10. Copy the link from the email and change the username in the querry string to carlos ![image](https://hackmd.io/_uploads/SkC_jAUvT.png) 11. Set the password to something you'll remember and submit the form ![image](https://hackmd.io/_uploads/B1aynRIP6.png) 13. Try logging as `carlos using the password you just set` and delete the user `carlos` to solve the lab ![image](https://hackmd.io/_uploads/rJZX3C8vT.png) :::success **Solved** :+1: :::