Bugcrowd & Vullnerability XSS Challenge 2020

Greetings!
This is my very first write-up. I have been working in this field since April 2020 (about 4 months) and I am fascinated by what this area has to offer.

If you would like to get in touch with me, I would be happy to receive a message on Twitter from you. Also if you enjoyed the write-up, please follow me on Twitter. Thanks a lot and enjoy reading :)

This write-up will present the solutions to the five different XSS vulnerabilities in this challenge.

Please follow the creators on Twitter, who have put so much effort into this challenge :)

Let’s get right into it!

“Invalid User”

This is the first XSS vulnerability I have found. In my opinion, it is also the most obvious and easiest one of all.

After you have successfully registered and then logged in, you will land on the profile page. While browsing the profile page I noticed the parameter “link” in the URL.

Since the given value of the parameter starts with “user_”, I immediately assumed that the parameter contains a UserID. So I thought that if I want to view another profile, I have to change the user ID. And this appeared:

As you can see the wrong UserID is reflected in the error message.

So I created a simple payload and appended it to the UserID to see if the input is sanitiesed.

The payload must be appended to the UserID. The UserID itself should not be removed. A check has been implemented in the Javascript for this as you can see above.

BOOM. The input was not sanitized! But why didn’t the XSS Payload trigger an alert?

This happened because of the Content Security Policy (CSP), which is used to prevent cross-site scripting and other attacks by smuggling data into websites. The CSP is reported to the browser via a response header.

In this case the CSP has the format: script-src ‘nonce-<base64-value>’. This means that script elements are only allowed if the attribute ‘nonce’ is specified together with its value.

For this I used the <iframe> tag along with the “srcdoc” attribute to create a custom HTML website and insert it into the source code.

Iframe successfully added to the DOM.

Now only the Javascript has to be added together with the CSP bypass.

Wow! The first XSS vulnerability has just been successfully identified!

“Forgot Password”

As the name suggests, I was able to find the second XSS vulnerability in the “Forgot Password” function. This time it is a bit more challenging than the previous one.

You are asked to enter an email. So I entered a random, valid email and was redirected to another page. The following appeared:

The entered email was reflected in the URL and on the website! As soon as you change the email in the URL, it was reflected on the site!

So once again I added a simple XSS payload to the email. But now an error message appeared.

Obviously an email validation has been implemented. Probably using the standard syntax. So I used a search engine to find out more about email validation and its criteria. This is what I learned:

Based on this information I adjusted the payload accordingly and attempted it again.

Awesome! The payload did work and the alert has been successfully triggered. The second XSS vulnerability has just been successfully identified!

“Github […]”

Github. Some people love it, some hate it and others use it for their XSS. And that is exactly what we are doing now. The 3rd XSS vulnerability has a unique concept and requires more creative approaches. Let’s get started!

After successful login you will be forwarded to the “Devplanet Apply Form”.

There you enter sample data and send it. Then click on “My Profile”.

The sample data is reflected on the profile page! You should immediately be aware that this could be a possible entry point for an XSS.

If you take a look at the source code, you will notice the following part.

The profileLoad function is called in the api.js with two different parameters. The first thing we are interested in is how the values given before are reflected on the page. Therefore we look for the corresponding part in the api.js.

As you can see all these values are set with the .text() function. But what does this mean for us? Here is a short explanation:

“The text() method sets or returns the text content of the selected elements. When this method is used to return content, it returns the text content of all matched elements (HTML markup will be removed).“ [https://www.w3schools.com/jquery/html_text.asp]

Since the HTML markup is removed, there is no way for us to use this input for an XSS. But haven’t I seen a particularly interesting sentence on the Apply Form page?

“We love to see live examples. Especially GitHub.”

This is it! Let’s take a closer look at how the given github profile is reflected on the page.

Javascript
HTML

We can see that the website is loading additional information from the linked github page. The attribute “title” seems to be the only entry point for our XSS in this case.

Sourcecode on github.com/nahamsec

As you can see, the title of the github page is reflected exactly! So what if I create my own HTML document on Github with a Javascript payload as title? The payload already contained the content security bypass using the ‘nonce’ attribute in the script element.

With a little “trick” the raw files on Github can be accessed via “github.com” instead of “raw.githubusercontent.com”. We will use this trick here, because we can only manipulate the part after “github.com” in our profile page.

I updated my profile and this happened….

Wow! The alert was successfully triggered when I accessed the profile page. The third XSS vulnerability has just been successfully identified!

“Line Break”

Oh, my goodness! This was quite difficult. As it turned out, however, it was only the preparation for the final XSS…

Before we start, I have a question for you. When you see a picture of the profile page in a moment. Behind which functionality do you expect no XSS?

extract from the profile page

Of course a “Logout” function has no XSS. Am I right? NOPE.

As soon as I logged out, I noticed that you are redirected to the homepage via an additional separate page. That’s what it looks like:

Not really interesting. So let’s have a look at the source code.

Oh, that’s new!

What is the purpose of the javascript?

  • It searches for the parameter “next” in the search bar and stores its value in the variable “get_parameter_next”.
  • The value of the parameter “next” is checked whether it matches a certain pattern (Regex).
  • If it fits, the user is redirected to the page specified in the parameter.

Important! The redirect is done by assigning the value to “window.location.href”. This can be exploited to execute Javascript code.

The default approach for this is: javascript:alert(document.domain);

But this will not work because it does not match the given pattern. A detailed explanation of the pattern would be very extensive. That’s why I try to keep it simple.

The simplified regex: (n)://(x).(x)

  • (n) = Any letters, numbers and the underscore
  • (x) = Any numbers, letters, signs
  • Everything not in brackets must be present exactly in that order. Everything in brackets does not necessarily have to be present.

If you would like to know more about the pattern you can look at the following resource: https://regexr.com/59po0

Precisely because we want to execute Javascript, we are dependent on “javascript:”. If we also want to match the pattern, we have to start our payload with “javascript://”. Probably you will now think about the following payload:

javascript://alert(document.domain);

Sorry to disappoint you. The payload won’t work. Why not? In Javascript, “//” represents a single line comment and therefore ensures that any subsequent code is ignored by the browser.

In fact, I have never encountered such a problem before and it took me quite a while to find useful information. But then I came across this:

“For a single-line comment, you have to write two forward slashes: // . Anything that goes after // up until a line break is treated as a JavaScript comment and not executed as code.” [https://www.bitdegree.org/learn/javascript-comment]

“This gotta be it. It’s the only way.” — My first thought when I saw this.

A line break can be achieved with “%0a%0d”. So I adjusted the payload and tested it.

The result: I was forwarded to the homepage.

It took me a long time to realize that the parameter will probably still be decoded. So I tried again. This time with “%250a%250d”.

Finally! The fourth XSS vulnerability has just been successfully identified!

“Callback”

Welcome to hell. In my opinion, this was by far the most severe XSS vulnerability. You will soon know why :) Let’s get straight into it!

It is often worthwhile to have a look at the “Robots.txt” of the website. It can be used to determine which areas of a domain may be crawled by a web crawler and which may not. This is useful, as other end points can be listed there, thus extending the scope of the attack. You can often find them at: https://[URL]/robots.txt

The check seems to have been worth it. We just discovered the URL path to a devloper login. Let’s have a look at it.

To save you time I have already looked through the sourcecode for you. Except for a clearly outdated jQuery version I haven’t found anything interesting.

Also on the “Forgot Password” page I could not find anything else.

I ended up trying to use the IDs of the login form elements as parameters and possibly achieve an HTML injection.

And look, it worked! It was now possible to inject any HTML into the source code. Next, I looked to see if a CSP was being used.

And indeed they did. This CSP means that the specified source in script elements must have the same URL Scheme and Port Number as the page.

Consequently, due to the CSP we can only use scripts from “http://lab.takeover.host" for our payload. Fortunately, the site has its own API, which we can use for this purpose. It is not uncommon that there is a “callback” parameter. This is the case here as well. So we set our adapted Javascript code as value for the parameter. Let’s see what happens.

Unfortunately some characters like [ , ] , [ “ ] and [ ‘ ] are blocked and therefore not reflected on the page. So, we will use a payload which is not including parantheses. Already encoded we put this into the actual XSS payload. This way we have included the JSONP endpoint and thus bypassed the CSP. Now it is time to hope and pray that an alert will be triggered.

Congratulations! The alert has been triggered and thus the last XSS vulnerability of this challenge has just been successfully identified!

Conclusion

I enjoyed this challenge incredibly! It was sometimes a bit nerve-wracking, but as soon as the solution was found, I was extremely proud and pleased. Especially I like this creative approach to this challenge. I hope to be able to solve further challenges of this kind in the future.

Takeaways

  • Always check for a Content-Security-Police (CSP) in the Response-Header.
  • Check if the input must match a certain format (e.g. email)
  • Check whether you can make use of third party servies (e.g. github).
  • The XSS Payload: javascript://%0a%0dalert(document.domain)

In General:

  • Try to understand how the application works!
  • When something does not work, find the cause and use it to your advantage!
  • Take notes!

“Smart people learn from everything and everyone, average people from their experiences, stupid people already have all the answers.” — Socrates