> For the complete documentation index, see [llms.txt](https://0x1int.gitbook.io/blogs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0x1int.gitbook.io/blogs/intigriti-xss-challenge-april-2022.md).

# Intigriti XSS Challenge April 2022

Hello guys I am back again. So let’s start talking rn bc this writeup will be long.

![](https://miro.medium.com/max/700/0*MXvuldqmaPX0ag5U.png)

So this is challenge description. Let’s access to the given URL.

![](https://miro.medium.com/max/700/0*0x0n-5Q6WnF21bnI.png)

So we got a Windows XP style UI generator. Nostalgia hits. So let’s enter some values and see what happens.

![](https://miro.medium.com/max/700/0*ZWUF3U2G4amJ1sWL.png)

![](https://miro.medium.com/max/700/0*2745UuQUqtQAWpQM.png)

So we submitted input and got this result. But let’s take a look at the query parameters.

![](https://miro.medium.com/max/700/0*npMXszRdUtFU6U1s.png)

Oof there’s a bunch of them. Let’s take a look at the source code.

In the first part of the `body` tag we can see that the page is made by using [Mithril](https://mithril.js.org/index.html). If we take a look at the official documentation for Mithril we can see that it’s a JavaScript framework for building single page app’s. Now let’s take a look at the script, this part will be interesting.

![](https://miro.medium.com/max/700/0*Hz3VS-Al9kvZDNPn.png)

![](https://miro.medium.com/max/700/0*lxkm2yp0uuKefBeX.png)

![](https://miro.medium.com/max/700/0*OdPYq7zcYTTlQq5y.png)

![](https://miro.medium.com/max/700/0*e93aLiY-vh5HXpt8.png)

This is long code tho, so let’s start from the top and see what is interesting.

![](https://miro.medium.com/max/700/0*wJEOwfG15NVQom_c.png)

We can see the usage of the function `m`, and let’s take a look at the documentation and this is the way to create DOM elements using Mithril. If we check all components we will see that all DOM elements are created using constants. So we are not able to inject any elements. After the components we have the function `main`.

![](https://miro.medium.com/max/700/0*UPF2wKfHIKtniNsz.png)

Hmm interesting. We have the call to `parseQueryString` which will parse the query string and turn it into an object. If we use the data from the query string we got when sending some values in the initial field, we get the following object from `parseQueryString`.

> *{*
>
> *“config”: {*
>
> *“window-name”: “Test name”,*
>
> *“window-content”: “Test content”,*
>
> *“window-toolbar”: \[“min”, “max”, “close”],*
>
> *“window-statusbar”: true*
>
> *}*
>
> *}*

After the query string is parsed, a dictionary object named `appConfig` is created an initialized with some default values. Then a check to see if the `config` prop exists on the parsed query string object, if it exists a call to the function `merge` is made with the `appConfig` and the supplied query string object. Then another dictionary object is created named `devSettings`, which contains a `main` element and some other settings. Then a call to `checkHost` is made and if it returns `true` a call to the `merge` function with the `devSettings` and the settings object from the query string is made.After this there’s a check to see if `isTestHostOrPort` or `isDebug` on the `devSettings` object is true, if one of them are true the `appConfig` and `devSettings` objects are printed to the JavaScript konsole. If the `customMode` on the `appConfig` is false, the default `App` component is rendered using the `main` element from `devSettings`. Let’s take a look at the `merge` function.

![](https://miro.medium.com/max/700/0*zCrE_BLN8zATi_9M.png)

This function will iterate over each key on an object and assigning each value to the target object. For each key a check is made using the `isPrimitive` function.

![](https://miro.medium.com/max/700/0*VdU0Os562a4jqtof.png)

It will check if the type of the value is a primitive type and if it is a primitive type. The result of the call to `sanitize` using the new value is assigned to the target object. If the current key is a complex type, a recursive call to the `merge` function is made to make a deep copy of the data structure. Let’s take a look at the `sanitize` function.

![](https://miro.medium.com/max/660/0*JGZPq_Ss3l1bgRa0.png)

This function replace the characters `<`, `>`, `%`, `&`, `$`, space, `\` and the word `script` with underscores if the data type is `string`, hence the modified output when we submitted the form earlier. Let’s take a look at the `checkHost` function.

![](https://miro.medium.com/max/516/0*cMBH_HrFyA2EjuCN.png)

This function takes the `location.host` variable and checks if the `hostname` part is `localhost` or the `port` part is `8080` and return `true` if either is a match. Now let’s bypass checkhost. Let’s take a look again at the `checkHost` function. So, we need to verify the hostname and port, the `location.host` variable is split by the port-separator, `:`. But since the `location.host` variable won’t have any port-separator on the challenge page, we will only get the hostname in the `temp` array.

![](https://miro.medium.com/max/560/0*AOg9iLxA0NRy67Jk.png)

This explains that the `port` constant will be set to `443`. Btw It’s not possible to modify the `location.host` variable since this will redirect us from the challenge page, so we need to find a way to get the `port` constant to be `8080`. Let’s start by looking at the `merge` function again, since it’s the only place our input is being used at the moment.

![](https://miro.medium.com/max/700/0*j7HMweph3K_0LoeS.png)

We know that this function will assign each value from the parsed query string to the target object, which in this case is `appConfig`. This means that we should be able to assign our own keys and values to the `appConfig` object. To verify this we can enter the following query string, **?config\[test]=test**, set a breakpoint after the `merge` call using the debugger and finally checking the values of the `appConfig` object. So let’s set the breakpoint.

![](https://miro.medium.com/max/700/0*cthUKEAPrgUwwGEA.png)

Let’s reload the page and use console to examine the object.

![](https://miro.medium.com/max/700/0*oZQBiRiqP3St0hEU.png)

We can see in the output we have the key `test` with the value `test`. This means that we might be able to use prototype pollution to be able to bypass the `checkHost` check. But since the key `__proto__` is in the `protectedKeys` array in the `merge` function, I got idea to do this so, the `__proto__` key isn’t the only way to achieve prototype pollution, we could also use the `constructor.prototype` property to achieve this. And since none of those keys are in the `protectedKeys` array we can use those keys instead. If we enter the following query string **?config\[constructor]\[prototype]\[test]=test** to set the `test` property of the `object` prototype and checking the `appConfig` object we get the following.

![](https://miro.medium.com/max/700/0*jBxEvCVQZoT6uFhg.png)

But if we try to verify the prototype pollution it will show us that it failed.\\

![](https://miro.medium.com/max/700/0*a323uQeHL0rghyrB.png)

This is bc the creation of the `appConfig` object is done by `Object.create(null)` which will create a constructor-less object. So we have to find another way to abuse this. If we take a look at the `appConfig` object again we can see that the `window-toolbar` property is an array. Let’s see if we can pollute the array prototype instead. If we send the following query string **?config\[window-toolbar]\[constructor]\[prototype]\[test]=test** to set the `test` property on the `Array` prototype. We have the property `test` in the `Array` prototype. And when we verify that the prototype pollution works we get the following.

![](https://miro.medium.com/max/700/0*BmJq657esb9ygKrC.png)

We have successfully polluted the `Array` prototype. Now we need to find way to use this to bypass checkhost. So `const temp = location.host.split(':')` will result in an array with only one element and the `const port = Number(temp[1]) || 443` will try to get the non existent second element of the array, we can try to set the property `1` on the `Array` prototype to the value `8080`. To verify that this should work we can try this out in the console.

![](https://miro.medium.com/max/700/0*bSWC0XNu_ZzEigV3.png)

To verify that it works, we create another array and try to access the second element.

![](https://miro.medium.com/max/675/0*kDYNbmrDg-KD6Lm3.png)

If we send the query string **?config\[window-toolbar]\[constructor]\[prototype]\[1]=8080** we will get the following output in the console.

![](https://miro.medium.com/max/700/0*CpwCzrJuoUtFkj0O.png)

Let’s take a look at the `checkHost` check in the `main` function again.

![](https://miro.medium.com/max/630/0*Emhh0Hv9bFDzDSB8.png)

So the output in the console indicates that we have successfully bypassed the check.

Now let’s inject the payload.

So, we are able to modify both the `appConfig` and `devSettings` object. Let’s try to modify the `innerHTML` property with our payload. To set the values on `devSettings` the object `qs.settings` is used, which means that our query string parameter should look **like this settings\[root]\[innerHTML]**. So let’s try it out with the payload **\<img src=x onerror=alert(document.domain) />** and see what happens.Using the query string **?config\[window-toolbar]\[constructor]\[prototype]\[1]=8080\&settings\[root]\[innerHTML]=\<img%20src%3Dx%20onerror%3Dalert(document.domain)%20%2F>** nothing happens lol. But if we set a breakpoint right after the `merge(devSettings, qs.settings)` call and taking a look at the `innerHTML` property we will see **\_img\_src=x\_onerror=alert(document.domain)\_/\_**. Now we have left to do is to bypass the `sanitize` function.

Now bypassing sanitaze function comes. Let’s take a look at the sanitaze function again. So the only data type that will be sanitized is `string`. But how to bypass it? Let’s take a look at the `merge` function again. So if the `target[key]` value is a primitive, `sanitize` with the `source[key]` value is called. So this means that if a property on the existing object is a primitive we could assign another type to that property and thus bypassing the sanitization. `Array` would probably do the trick, since the `toString` function will basically call `array.join(',')` and return the result.

![](https://miro.medium.com/max/700/0*eAMEwgpK5Q-ooaUu.png)

But how can we change the type to an array? We can find a section on [Deep Data Structures](https://mithril.js.org/parseQueryString.html#deep-data-structures).

“Querystrings that contain bracket notation are correctly parsed into deep data structures”

`m.parseQueryString("a[0]=hello&a[1]=world")`

`// data is {a: ["hello", "world"]}`

If we add **\[]** to the end of the **settings\[root]\[innerHTML]** parameter, we should get an array. Using the query string **?config\[window-toolbar]\[constructor]\[prototype]\[1]=8080\&settings\[root]\[innerHTML]\[]=\<img%20src%3Dx%20onerror%3Dalert(document.domain)%20%2F>** we will get our XSS working.

![](https://miro.medium.com/max/700/0*IxER6ALdSADTIa8q.png)

So we can confirm XSS. This challenge was pretty hard ngl.

![](https://miro.medium.com/max/640/0*XGq557YVw2hzWI7x.gif)

## README.md <a href="#b8da" id="b8da"></a>

Hope you enjoy reading this writeup, hope you learn something, take a care and see you in a next one writeup.&#x20;
