As usual, I opened Twitter to scroll news and waste some time. And first what I met was new Intigriti XSS challenge. It meant only one thing — I will not rest till I manage to solve it 🌚 Nevertheless, lets begin!
Challenge analysis
Lets see what do this page exactly do. It has three text fields, one of them pretends to be a captcha. The other ones is just some fields to put your data to. Lets try to input something there and hit ‘Submit’.
Once we hit it, we will see that new element appeared — ‘this link’. It shows that we can control both inputs’ values. The link look like this: https://challenge-0221.intigriti.io/?assignmentTitle=something&assignmentText=something (you can notice that captcha doesn’t matter when the text length is < 50)
Naively, but lets check what if we will put special characters there..
Not surprised, this thing is screened. But this article wouldn’t exist if it were that simple? :)
The code
Now lets take a look at the code of the challenge:
No innerHTML
there. But.. eval? This cant be truth, pretty sure its fake.. Looking closer one more time — there’s no entry points besides eval(). Okay, lets trust it for now. Wait, what’s about result
variable, is it global? Sure, it is, JavaScript ¯\_(ツ)_/¯. What can we do then? Lots of code, which is useless for the solution, lets throw it out. Imagine that we already have some DOM clobbering or attribute injection on the page, what can we control then?
window.result
looks pretty suspicious, its screaming that we must use clobbering there. Okay, lets remember this. We need to get control over the result.questionAnswer.value
to insert our code into argument for eval().
Looks simple, but we have no HTML injection in this code, what should we do then?
First hint was critical
Unicode character code and the character itself.. Nothing special about ℋ meaning in code as I see.. What if just to paste it in challenge fields?
Nothing there, but what if we will open ‘this link’?
Bingo! It is not the character we entered… So..? Lets investigate what is this text is closely. The hint contain Unicode code (\u210B
) too, it should mean something I guess. Lets try to understand what happens on the server so it returning Unicode characters different way.
Character code: \u210B
, returned text: !0b
There’s ‘0b’ in both codes, doesn’t it mean that the first two numbers of unicode code must be decoded as ‘!’ somehow?
Yep, that’s it. I think its obvious what we should test next, lets do it!
Exactly. So it seems like unicode is being processed just by replacing ‘\u’ to ‘%’ on the server
Alright, we know how this thing work. But all data is being filtered, as we understood at the start. What If we will put something prohibited in two first characters of the unicode? Lets write a function to reverse this algorithm:
'ℋ'.charCodeAt(0).toString(16) // => "210b" => \u210b => “ℋ” => !0b'a'.charCodeAt(0).toString(16) // => "61" => \u6100 => “愀” => a00
Now we can test this. Lets paste some characters there to see if it will be screened or not:
Quotes and angle brackets will be enough. Payload: \u2200 \u2700 \u3e00 \u3c00
Paste it in the console as a text to decode characters, then copy the result. Here we go. ∀ ✀ 㸀 㰀
Submit, this link, lets see what we got:
On the page its clear visible that angle brackets is not being filtered in title. Looking on the page source, you can see that quotes is not filtered too. It means we can use any character, bypassing filter, great news.
Looks pretty easy, just add something like <img onerror=alert() src=a>
.. Don’t you think its that easy again? CSP is here to break your dreams:
script-src 'strict-dynamic' 'nonce-qHRZi3kUfZx2MLwW/T4qxYMslOk=' 'unsafe-eval' http: https: ; object-src 'none'; base-uri 'none';
So this means that we can exploit this page only using existing script somehow, no ways to create new one for us.
HEX code
Wait a minute.. But there’s still two last characters of unicode, which will not let us to create tags or something, right? Nope, we should use it for our own purposes :P
As the Unicode code is a hex string, we can add any 16-bit numbers we want. I will show an example, don’t worry :)
I said hex numbers? But we cant create tags using only numbers… You can guess that we can, as this challenge can be solved :)
Hex numbers consist of this: 01234567890ABCDEF
(from 0 to 16)
And ABCDEF
is a very important part, as its not a numbers, when mentioned outside of HEX-context. This means we can create tags, using this letters as first two letters of a tag name.
Now we can inject some HTML on the page, means we can make a DOM Clobbering and attribute injection, as I predicted already :) Let investigate code again to understand what (and where) we need to spoof.
Last steps
When we found the injection point and saw some DOM sinks, which can be exploited thru DOM clobbering, we can remove all parts we don’t need, using all knowledge we gained for now:
Lets return to result = window.result || { ... }
What will be if we will create a tag with id=result
?
Pretty right, window.result
will return the tag itself. This is how the DOM clobbering works.
Okay, we have replaced the result
variable to the tag. What can we do then? We need to replace result.questionAnswer.value
, not result
itself.
If you thought the same way, then you don’t know a lot about DOM Clobbering :) Sorry, I will not explain every detail of how clobbering works, you can read about it on PortSwigger or somewhere else, this topic may be larger than all this write-up. Lets find an element, which will support ‘value’ and ‘name’ attributes, where first two letters of tagname is from HEX letters. Looks terrible, right?
Googling some time, I met <data>
element. Everything is good, lets create protentional clobbering scheme:
<data id=result name=a> <data id=result name=questionAnswer value=”something”>
Isn’t that exactly what we needed? Using this code, we have control over result.questionAnswer.value
text, which is a part of an argument for ‘eval()’. We need it to be something like alert(origin)//
to make magic alert box appear.
The Payload
Escaping a current element and open new ones, as our injection is in the attribute of title field. Then our payload looks like:
"><data id=result name=a><data id=result name=questionAnswer value=alert(origin)//
We need to encode quotes and angle brackets only. But don’t forget about <data>
tag, we need to put ‘da’ as the last two letters for one of codes. Just watch my hands:
Finally, lets build our ‘this link’ to see the alert box! Don’t forget the ‘autosubmit’ URL param, we will need it. So the URL looks like this:
https://challenge-0221.intigriti.io/?autosubmit=1&assignmentTitle=∀㸀㳚ta id=result name=a㸀㳚ta id=result name=questionAnswer value=alert(origin)//
That’s all folks! Thanks for reading, I hope you enjoyed this writeup :)
Thanks @intigriti and @holme for a great XSS challenge!