Intigriti - XSS Challenge 1121

Before everything, I should say that it was a very hard Challenge, I spent many hours but I managed to  solve it, here we go!


Summary

The XSS challenge  hosted by Intigirti occurs once a month, you can follow them on Twitter to stay tuned about upcoming challenges and other nice infosec contents, I really recommend you to try those challenges as it is a good source for learning and they are always very challenging.

The challenge created by @IvarsVids was available on https://challenge-1121.intigriti.io/

challenge instructions page



Context

As usual, in the XSS challenge we need to find a way to execute the XSS alert(document.domain). This time the scenario was a bit different, there was a Vuejs page with php extension which receives text input from users in the challenge page.


challenge page


Reconnaissance


The Challenge page receives a request parameter called s with the input value, and it results in the vuejs.php page being loaded which forwards some redirects, let's check it better

http requests list


Assessing the implementation we can see some important aspects.

In a loading sequence we have:

Line 3: The user input is stored in the document in the <title> tag, in this way, we can close the </title> tag and insert new html tags from the user input.

Line 5: A variable called isProd is created and set to True.

document - line 3, line 5


Line 46: a variable called delimiters is set with an array value which seems to be the double mustache Vue interpolation format.
Line 47: the method AddJS() is called receiving a string that seems to be the url path loaded, and another argument called initVUE, looking at those parameters they seem obvious, let's continue.


document - line 46, line 47


Line 8-14:  The method is creating a <script> element to load the ./vuejs.php and calling initVUE in the onload event.

document, line 8, addJS function


Line 16-42:  the initVUE() method  fill the data using Vue js.

document, line 16-42, initVUE function


Line 48-63: last but not least, a piece code never being executed (the variable isProd is set to true) on the line 50, something to be explored.

document, line 48-63, code not executed



Exploitation


On the very beginning, we can see the page using  CSP and all the script tags using nonce hashes, due to that we cannot simply include a script tag.

csp nonce

At this stage, I had to find a way to change this variable isProd, but how? In the meantime the first Intigriti hint was released on Twitter




This hint opened a door to me, but the wrong one, due to that I spent days to solve the challenge...
When I saw this hint, saying that we need to make our enemy stronger, I wrongly thought that I had to trigger the CSP adding script tags. After a lot of payloads, I got the first (wrong) door opened to me, which release me to jump to the next phase.

I realized that keeping an opened <script> tag was wrapping the script block which sets the variable isProd and then the variable was not set thanks to CSP!! So, in my mind, the enemy was the CSP and it got stronger blocking my payload...

wrapping  script tag with another script tag



Let's move to the part 2, now we can exercise the code as of line 50:

inside the not executed code



At this stage, we have two new request parameters to be included in the payload version and vueDevtools.
The version variable is being sliced to 12 digits, and the variable is always typeof String. That being said, the first IF will always be evaluated to false because it is using strict equality (===) and String and numbers will not match.

On the second condition we have the solution for the part 2, this is the else if (version === 999999999999)) but there is a problem here...

The condition expects that version is greater than 1 trillion (13 digits), but version variable can have only 12 digits and it is a String being compared to a Number, how to evaluate it to true?

To answer that we need to understand Javascript, more details can be found here, but to summarize, the javascript will convert the string to a number when performing the comparison.
Now, the string should have a value bigger than 1 trillion but the variable can receive only 12 digits.
To achieve the condition, we can declare the numeric string in a different way, we can use hexadecimal format or exponential format, more details here.

On my case I used exponential, like that:


version bigger than 1 trillion




Due to that, we have now the ability to create a new <script> tag in the document but what we should do with that?

I took a lot of time thinking about this vueDevtools variable because of the regex being applied on it.

regex, vueDevtools


After sometime, I concluded that I could only do the same that the normal flow is doing,  which is initialize the Vue, and the Hint shared on Twitter had confirmed that to me.



At this point, I had the following payload in hand, but no way to execute the XSS as the script tag was blocked by CSP.

payload 1



I GOT STUCK HERE, FOR DAYS!!


During those days, I found that the only way to call the alert(document.domain) would be by Vue, searching on the internet I found this payload from portswigger: v-{{_c.constructor(%27alert(document.domain)%27)()}} but due to the <script> tag created in stage 1, I was not being able to make ./vuejs.php be executed in the page.


At this point I was in trouble , I had to  remove the <script> tag from stage 1, but remove that makes the variable isProd be set, how to manage that? I decided to re-think about that hint again:






After some time thinking about that tip, I thought; alright... make the enemy stronger... What if I try to,  instead of add a script tag, try to add a meta tag to change the CSP? what if I make this CSP stronger ? and then I found the solution...


Solution



My solution was a bit strange, I could do it simpler but I liked how I did it and I don't want to make it simpler.
During the test, I fould that I could use parameter polution on the search parameter (s).

In the first parameter I set the payload to trigger the Xss:
?s=v-{{_c.constructor(%27alert(document.domain)%27)()}}

In the second payload, I added what I need to deal with the isProd variable:
&s=</title><meta http-equiv="Content-Security-Policy" content="script-src>

A lot of things here, let me explain that.

From Vue side, the first s value is taken, but the second is loaded in the page. When the second  payload containing the <meta> tag payload is loaded, it is messing the html and the declarion from variable isProd is moved out from <script> tag.

after meta tag insertion


And thanks to this change, the XSS is triggered:
the glory

The final payload:

https://challenge-1121.intigriti.io/challenge/index.php?s=v-{{_c.constructor('alert(document.domain)')()}}&version=9e9999&s=</title><meta http-equiv="Content-Security-Policy" content="script-src>&vueDevtools=.. /challenge/vuejs.php



Thanks


I'd like to thank you the owners for that challenge, I got really tired doing that as I spent a lot of days to solve that (I'm not a pro on it) but I could learn a lot from it!! Thank you, I hope I can solve it faster next time.

Bye.


Extra


There is a second solution that many people found, I also found that, but it works on chrome but not in firefox(78.13.0esr (64-bit)) 
Payload:

https://challenge-1121.intigriti.io/challenge/index.php?s=%27v-{{_c.constructor(%27alert(document.domain)%27)()}}%3C/title%3E%3Cscript%3E&version=9e9999&vueDevtools=./vuejs.php

Comments