You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/pentesting-web/race-condition.md
+45-12Lines changed: 45 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -25,6 +25,41 @@ Here you can find some techniques for Synchronizing Requests:
25
25
26
26
The subsequent sending of withheld frames should result in their arrival in a single packet, verifiable via Wireshark. This method does not apply to static files, which are not typically involved in RC attacks.
27
27
28
+
#### HTTP/3 Last‑Frame Synchronization (QUIC)
29
+
30
+
-**Concept**: HTTP/3 rides over QUIC (UDP). There’s no TCP coalescing or Nagle to rely on, so classic last‑byte sync doesn’t work with off‑the‑shelf clients. Instead, you need to deliberately coalesce multiple QUIC stream‑final DATA frames (FIN) into the same UDP datagram so the server processes all target requests in the same scheduling tick.
31
+
-**How to do it**: Use a purpose‑built library that exposes QUIC frame control. For example, H3SpaceX manipulates quic-go to implement HTTP/3 last‑frame synchronization for both requests with a body and GET‑style requests without a body.
32
+
- Requests‑with‑body: send HEADERS + DATA minus the last byte for N streams, then flush the final byte of each stream together.
33
+
- GET‑style: craft fake DATA frames (or a tiny body with Content‑Length) and end all streams in one datagram.
34
+
-**Practical limits**:
35
+
- Concurrency is bounded by the peer’s QUIC max_streams transport parameter (similar to HTTP/2’s SETTINGS_MAX_CONCURRENT_STREAMS). If it’s low, open multiple H3 connections and spread the race across them.
36
+
- UDP datagram size and path MTU cap how many stream‑final frames you can coalesce. The library handles splitting into multiple datagrams if needed, but a single‑datagram flush is most reliable.
37
+
-**Practice**: There are public H2/H3 race labs and sample exploits accompanying H3SpaceX.
Understanding the target's architecture is crucial. Front-end servers might route requests differently, affecting timing. Preemptive server-side connection warming, through inconsequential requests, might normalize request timing.
@@ -39,7 +74,7 @@ If connection warming is ineffective, triggering web servers' rate or resource l
39
74
40
75
## Attack Examples
41
76
42
-
-**Tubo Intruder - HTTP2 single-packet attack (1 endpoint)**: You can send the request to **Turbo intruder** (`Extensions` -> `Turbo Intruder` -> `Send to Turbo Intruder`), you can change in the request the value you want to brute force for **`%s`** like in `csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s` and then select the **`examples/race-single-packer-attack.py`** from the drop down:
77
+
-**Turbo Intruder - HTTP2 single-packet attack (1 endpoint)**: You can send the request to **Turbo intruder** (`Extensions` -> `Turbo Intruder` -> `Send to Turbo Intruder`), you can change in the request the value you want to brute force for **`%s`** like in `csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s` and then select the **`examples/race-single-packer-attack.py`** from the drop down:
@@ -54,7 +89,7 @@ If you are going to **send different values**, you could modify the code with th
54
89
> [!WARNING]
55
90
> If the web doesn't support HTTP2 (only HTTP1.1) use `Engine.THREADED` or `Engine.BURP` instead of `Engine.BURP2`.
56
91
57
-
-**Tubo Intruder - HTTP2 single-packet attack (Several endpoints)**: In case you need to send a request to 1 endpoint and then multiple to other endpoints to trigger the RCE, you can change the `race-single-packet-attack.py` script with something like:
92
+
-**Turbo Intruder - HTTP2 single-packet attack (Several endpoints)**: In case you need to send a request to 1 endpoint and then multiple to other endpoints to trigger the RCE, you can change the `race-single-packet-attack.py` script with something like:
@@ -235,10 +270,10 @@ while "objetivo" not in response.text:
235
270
236
271
In the original research it's explained that this attack has a limit of 1,500 bytes. However, in [**this post**](https://flatt.tech/research/posts/beyond-the-limit-expanding-single-packet-race-condition-with-first-sequence-sync/), it was explained how it's possible to extend the 1,500-byte limitation of the single packet attack to the **65,535 B window limitation of TCP by using IP layer fragmentation** (splitting a single packet into multiple IP packets) and sending them in different order, allowed to prevent reassembling the packet until all the fragments reached the server. This technique allowed the researcher to send 10,000 requests in about 166ms.
237
272
238
-
Note that although this improvement makes the attack more reliable in RC that requiers hundreds/thousands of packets to arrive at the same time, it might also have some software limitations. Some popular HTTP servers like Apache, Nginx and Go have a strict `SETTINGS_MAX_CONCURRENT_STREAMS` setting to 100, 128 and 250. However, other like NodeJS and nghttp2 has it unlimited.\
239
-
This basically mean that Apache will only consider 100 HTTP connections from a single TCP connection (limiting this RC attack).
273
+
Note that although this improvement makes the attack more reliable in RC that requires hundreds/thousands of packets to arrive at the same time, it might also have some software limitations. Some popular HTTP servers like Apache, Nginx and Go have a strict `SETTINGS_MAX_CONCURRENT_STREAMS` setting to 100, 128 and 250. However, others like NodeJS and nghttp2 have it unlimited.\
274
+
This basically means that Apache will only consider 100 HTTP connections from a single TCP connection (limiting this RC attack). For HTTP/3, the analogous limit is QUIC’s max_streams transport parameter – if it’s small, spread your race across multiple QUIC connections.
240
275
241
-
You can find some examples using this tehcnique in the repo [https://github.com/Ry0taK/first-sequence-sync/tree/main](https://github.com/Ry0taK/first-sequence-sync/tree/main).
276
+
You can find some examples using this technique in the repo [https://github.com/Ry0taK/first-sequence-sync/tree/main](https://github.com/Ry0taK/first-sequence-sync/tree/main).
242
277
243
278
## Raw BF
244
279
@@ -378,7 +413,7 @@ if user.mfa_enabled:
378
413
### OAuth2 eternal persistence
379
414
380
415
There are several [**OAUth providers**](https://en.wikipedia.org/wiki/List_of_OAuth_providers). Theses services will allow you to create an application and authenticate users that the provider has registered. In order to do so, the **client** will need to **permit your application** to access some of their data inside of the **OAUth provider**.\
381
-
So, until here just a common login with google/linkedin/github... where you are prompted with a page saying: "_Application \<InsertCoolName> wants to access you information, do you want to allow it?_"
416
+
So, until here just a common login with google/linkedin/github... where you are prompted with a page saying: "_Application <InsertCoolName> wants to access you information, do you want to allow it?_"
382
417
383
418
#### Race Condition in `authorization_code`
384
419
@@ -404,9 +439,7 @@ Once you have **obtained a valid RT** you could try to **abuse it to generate se
404
439
- [WebSocket Turbo Intruder: Unearthing the WebSocket Goldmine](https://portswigger.net/research/websocket-turbo-intruder-unearthing-the-websocket-goldmine)
0 commit comments