Partial construction race conditions

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 trivially exploitable vulnerabilities | Karthikeyan Nagaraj

Karthikeyan Nagaraj
3 min readJul 8, 2024

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:

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 to the attacker.

Note

For this attack to work, the different operations performed by each process must occur in just the right order. It would likely require multiple attempts, or a bit of luck, to achieve the desired outcome.

Email address confirmations, or any email-based operations, are generally a good target for single-endpoint race conditions. Emails are often sent in a background thread after the server issues the HTTP response to the client, making race conditions more likely.

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 trivially 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 applications 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 uninitialized 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:

params = {"param"=>{"key"=>nil}}

In the example above, this means that during the race window, you could potentially make authenticated API requests as follows:

GET /api/user/info?user=victim&api-key[]= HTTP/2
Host: vulnerable-website.com

Note

It’s possible to cause similar partial construction collisions with a password rather than an API key. However, as passwords are hashed, this means you need to inject a value that makes the hash digest match the uninitialized value.

--

--

Karthikeyan Nagaraj

Security Researcher | Bug Hunter | Web Pentester | CTF Player | TryHackme Top 1% | AI Researcher | Blockchain Developer