XSS Challenge by Holme (on Intigriti)

RootEval
7 min readFeb 22, 2021

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..

https://challenge-0221.intigriti.io/?assignmentTitle=something%27%22%3E%3Casd&assignmentText=something%27%22%3E%3Casd

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:

script.js

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

\u210B is ℋ

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?

decodeURIComponent(‘%21’)

Yep, that’s it. I think its obvious what we should test next, lets do it!

decodeURIComponent(‘%210b’)

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

magic trick

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”>
htmledit.squarefree.com

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!

--

--