JSLR

Introduction

Cross-Site Scripting (XSS) has been around for ages – with first incidents being reported in the late nineties. Despite the attack technique not being the most complex of all, XSS is not only still around in 2011 but has gained incidence and gravity. In many real life attacks XSS was used as an entry-door for other exploits, such as the successful source code compromise against the Apache Foundation and several attacks against code management systems and Intranet systems. Despite XSS being a never ending story, defense mechanisms haven’t evolved much during the last decade – and still range from server side filtering to browser based parameter inspection and script execution blocking. This article introduces a novel way of approaching XSS – following the footsteps of previous work such as JSReg, IceShield and “Locking the Throneroom”

In 2006 Eduardo Vela Nava – also known as “sirdarckcat” presented a technique labeled “Active Content Signatures” (ACS); in his ACS sandbox aimed towards preventing a browser from rendering HTML after his script was injected by using a plaintext element. The plaintext element is an HTML element unique in its behavior. All following markup will be rendered as plain text instead of active markup. Plaintext is therefore a HTML based method to force parts of a HTML document to be HTML encoded by the user agent. Additionally plaintext cannot be ended as other HTML elements. Once written to the document, plaintext will do its magic until the end of the document.

This was the point of origin to the techniques that followed. A few years later in 2011 Mario Heiderich demonstrated using the DOM to prevent XSS by using the plaintext element and locking dangerous objects using ECMA Script 5 (ES5) methods and only allowing access via certain trusted events – essentially a mixture of Object freezing and capability control. The result ended in an open challenge allowing participants to inject any given data into the target website with the goal to access the protected property document.cookie. The challenge experienced an overall of 14.000 attempts to break the security installments – of which 50 succeeded. The challenge host hot-fixed the bypasses during the contest – making it harder for the contestants to find further bugs and bypasses. I took these two techniques and built on it further by providing randomization of elements and attributes as a means to protect from injection whilst still allowing normal functionality of a site.

JSLR – derived from the term ASLR – is a way of randomizing certain tags and attributes so the attacker cannot inject malicious HTML without knowing the randomized token. It is easy to implement since any static/dynamic HTML can be rewritten with a special id that enables the element or attribute. The HTML itself is intercepted before it’s rendered by the browser using the plaintext element, the DOM is then used to create a safe copy of the HTML before adding the code back. This enables JSLR to check each attribute and tag and verify it wasn’t added by the attacker. I believe the following technique will ultimately be the death of XSS – requiring just few refinements to be able to cover a wide range of attacks including DOMXSS and similar techniques.

How it works

Three random tokens are provided to the JSLR javascript file a random id (used for elements and attributes), a random single quote id and a random double quote id. The script first uses document.write() to inject a plaintext element in the DOM to prevent everything after the script from being rendered. Then the DOMContentLoaded event is being used to wait for the page to render. DOMContentLoaded is the closest we can get to the moment the DOM has finished loading – but hasn’t necessarily loaded all binary resources. After the event fired, the DOM is being reconstructed inside a new virtual HTML document. This technique was used by the XSSMe² challenge to hinder Iframes using JavaScript URIs to execute before the protective purpose of the client side XSS protection could execute. Gecko based browsers as well as Opera support an event meant to help with this timing and racing problem known as DOMFrameContentLoaded – but the event implementation is as of today too unstable to provide the necessary functionality and safety. Furthermore it’s not considered to be standards conform – thus unlikely to be implemented in other user agents. This virtual HTML document is used to remove any harmful elements or attributes unless they contain the correct token. Each script element is checked and any in-line script has single/double quotes replaced before adding them back to the DOM.

Implementation

JSLR is very easy to implement, there is minimal server side changes to be applied – and frameworks such as CakePHP or symfony make implementation even easier based on their customizable template builders. With a little trickery and and backpack full of knowledge on properly building regular expressions, output buffering can be utilized to inject the necessary data. Developers are required to generate three random ids server side and place a JavaScript file at the highest point of your site. Any attributes that would wish to allow on your site must be given the id token generated server side. Any elements such as object, embed, script, textarea, button, style or svg must be given a randomized attribute to allow it to render at all – imagine an in-line SVG element being injected and messing up the protection by executing JavaScript in ways described in. In-line script would require a randomized token for single or double quotes to protect it.

A typical JSLR protected document would look like the following:

<?php
require('jslr.inc.php');
$JSLR = new JSLR;
?>
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>JSLR</title>
<script src="<?php $JSLR->getWebPath()?>jslr.js" type="text/javascript"></script>
<script type="text/javascript">
try {
JSLR=JSLR('<?php $JSLR->getID()?>','<?php $JSLR->getSingleQuoteID()?>','<?php echo $JSLR->doubleQuote?>');
} catch(e) {
document.write('<plaintext>Error unable to find JSLR. Check the web path.');
}
</script>
</head>
<body>
<iframe <?php $JSLR->getID()?>_src="http://www.microsoft.com/"></iframe>
</body>

</html>

It is important that the X-Frame-Options header is added to allow documents serving JSLR script as well as a correct character set and the X-XSS-Protection: 1;mode=block this ensures the script is run correctly and cannot be modified by an attacker or change it’s behaviour. If no charset were given, an attacker could attempt to The JSLR->id, JSLR->singleQuote and JSLR->doubleQuote are just randomized tokens and can be generated using any server side language. They should be completely random and unlikely to clash with any existing source code.

Limitations

In its current state, JSLR only runs on modern browsers, any browser which doesn’t support the DOMContentLoaded event will not work, this is only a temporary limitation however since every browser supports the plaintext element and the code could be written to support every browser as far back as IE6. JSLR also requires control over DOM attributes, any browser that does not give full control over these objects would fail in JSLR.

Any injections before the JSLR script file would bypass the protection since the vector would be rendered before JSLR could protect it. As with any DOM based protection technique, the order of deployment is essential. In case an arbitrary attacker controlled script is capable of executing before the document can be inspected and sealed, the browser has no control over the methods performing the inspection and sealing process anymore. They could be poisoned, manipulated or deleted and simply deny service in more or less noisy ways. Several DOMXSS based techniques might bypass the protective coat as well; currently any DOM injections that use location.hash, location.search in combination with document.write/innerHTML and alike inside a already white-listed script will bypass JSLR. In future it may be possible to intercept such calls and perform a JSLR request before the HTML is rendered.
Protecting against malicious attributes
The majority of attributes are malicious, in order to protect against XSS and CSRF (within the site outwards to another site) you have mark the attribute as legitimate which enables it. This is done using the special id that is randomly generated.

<iframe <?php echo $JSLR->id?>_src="http://www.microsoft.com/"></iframe>

Here we manually allow the “src” attribute by specifying the JSLR id with an underscore followed by the name of the attribute we wish to allow. The HTML output looks like the following:


<iframe c886c4256f0cd99c99bb4256dc85c53257d213ee0ac49df999dad017d7ab1c805c109143911f9fde71563074bde0f9472a8e4448fcaeb334d7e3e4da97e85bf1_src="http://www.microsoft.com/"></iframe>

JSLR then finds all attributes of the element and only if the attribute contains this token is the attribute enabled. If the attacker injects <iframe src=javascript:alert(1)></iframe>
JSLR will notice that no token is present on the attribute and therefore drop all attributes of the iframe resulting in a harmless element of <iframe></iframe>
being rendered.

Protecting against malicious elements
Certain elements such as textarea, button and style are harmful even without attributes – no to mention the instrumented plaintext element we discussed earlier. To prevent an attacker from injecting a malicious textarea that then can be used to gather all the source code after the injection we can give the textarea a special id that indicates the site itself generated it.

<textarea <?php echo $JSLR->id?>_=1>test textarea</textarea>

JSLR then checks all elements that it does not normally render and if this attribute is encountered it will make an exception for this particular element.

Preventing DOM script injections

If JSLR has a valid token for an attribute or perhaps a inline script element was allowed and an injection occurs within that element, XSS can still be prevented by marking all single and double quotes within that element with a special token that represents single or double quotes. For example an inline script would look like the following:

<script>x=’randomtoken ATTACKER CONTROLLED VALUE randomtoken’</script>

The random token must occur within the source code after the first single quote and before the next single quote. This can be done manually or could automated by pre parsing the source code. This enables JSLR to handle and protect any inline script regardless if an injection is present. It does this by removing all double and single quotes from the source file and using the randomized tokens to put back the correct quotes. If an attacker injects a single or double quote it will be removed from the source code before it’s rendered therefore nullifying the attack.

A example of protecting script injections would look like the following:
<script <?php echo $JSLR->id?>_=1>x='<?php echo $JSLR->singleQuote?><?php echo $_GET['dom']?><?php echo $JSLR->singleQuote?>';</script>

Demonstration

A simple demo can be found at:
JSLR demo

The site contains two XSS injection vulnerabilities that are unfiltered server side, it demonstrates how to use elements such as iframes but preventing an attacker from injecting malicious vectors. The first vector is injected directly in the document and is available here http://www.businessinfo.co.uk/labs/jslr/jslr.php?xss=123 and the second injection occurs within an allowed script element http://www.businessinfo.co.uk/labs/jslr/jslr.php?dom=%27,alert%28123%29,%27

Credits

This isn’t finished work and there is a long way to go but I consider this a stepping stone to the death of XSS for good. Without the work of Eduardo Vela and Mario Heiderich and Stefano Di Paola the concept wouldn’t be possible, I’d like to thank them for their ideas and techniques and hopefully we will destroy XSS once and for all.

Bypasses

@masa141421356 – Masahiro YAMADA

Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?xss=%3Cimg%20name%3DgetElementsByTagName%20src%3D1%20%20onerror%3Dalert(1)%3E

Description: Masahiro used a pretty awesome technique that is “known as DOM clobbering” basically you overwrite a dom function to remove it’s functionality and therefore bypass JSLR. In this instance getElementsByTagName would now call the image instead fo the DOM function and the malicious element wouldn’t be removed. The fix was to call the getElementsByTagName by passing a reference of the document using call. e.g. document.getElementsByTagName.call(html, ‘*’);.

@cgvwzq – Pepe Vila

Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?xss=%3Cform%20name%3D%22body%22%20onmouseover%3D%22alert(1)%22%20style%3D%22height%3A800px%22%3E%3Cfieldset%20name%3D%22attributes%22%3E%3Cform%3E%3C%2Fform%3E%3Cform%20name%3D%22parentNode%22%3E%3Cimg%20id%3D%22attributes%22%3E%3C%2Fform%3E%3C%2Ffieldset%3E%3C%2Fform%3E

Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?xss=%3Cform%20onmouseover%3Dalert(1)%3E%3Cinput%20name%3Dattributes%3E

Description: Pepe used the DOM clobbering technique again but took advantage of how form objects can allow you to overwrite DOM properties like parentNode and attributes. The parentNode would be incorrectly returned as a form element and therefore fail to remove the malicious code. The fix for this was messy because the browsers have a poorly written DOM the only way to test for this type of attack was to inspect the property directly to see if the attributes object was actually the attributes object. e.g. j[i].attributes instanceof NamedNodeMap

@irsdl – Soroush Dalili

Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?xss=%3Cform%20name%3D%22IRSDL%22%20onmouseover%3D%22alert(%2FXSSed-by-IRSDL-nestedForms-TestedInFF8%2F)%22%20style%3D%22position%3Aabsolute%3Btop%3A0px%3Bleft%3A0px%3Bwidth%3A3000px%3Bz-index%3D99999999%3B!important%3B%22%3E%20Over%20me…%3Cbr%2F%3E%3Cbr%2F%3E%3Cbr%2F%3E%3Cbr%2F%3E%3Cbr%2F%3E%3Cbr%2F%3E%3Cfieldset%3E%3Cinput%20name%3D%22attributes%22%3E%3Cinput%20name%3D%22tagName%22%2F%3E%3Cform%20name%3D%22nestedformbug%22%3E%3Cinput%20name%3D%22tagName%22%2F%3E%3C%2Fform%3E%3Cform%20name%3D%22parentNode%22%3E%3Cfieldset%3E%3Cform%20name%3D%22nestedformbug%22%3E%3Cinput%20name%3D%22tagName%22%2F%3E%3C%2Fform%3E%3Cinput%20name%3D%22attributes%22%3E%3C%2Ffieldset%3E%3C%2Fform%3E%3C%2Ffieldset%3E%3C%2Fform%3E

Description: Soroush again uses the DOM clobbering technique this time to overwrite tagName, parentNode etc using nested forms.

@securityshell

Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?xss=%3Ctt%20style%3D%22width%3A100%25%22%20onmousemove%3Djavascript%3Awindow.location%3D%22http%3A%2F%2Fwww.google.com%22%3B%3E
Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?anchor=%22%3E%3Ciframe%20src%3D”%20onload%3Dalert(‘XSS’)%3E

Description: @securityshell came up with a few vectors on Opera that bypassed JSLR because of specific bugs in the Opera DOM. I think it was specifically the remove attribute feature and the workaround was to set the attribute to nothing. E.g. j[i].setAttribute(attrNamesToRemove[k],”);//for opera

@kkotowicz – Krzysztof Kotowicz

Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?anchor=data:text/html,%3Cscript%3Ealert(document.cookie)%3C%2Fscript%3E

Description: Lame error on my part, I was inspecting the DOM object for the JavaScript protocol but missed the data protocol.

@shafigullin – Roman Shafigullin

Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?xss=%3Ctextarea%3E%3C%2Ftextarea%3E%3Cscript%3Ealert(document.cookie)%3C%2Fscript%3E

Description: JSLR had a classic problem where if it removed an element then the current listed nodes would be modified but JSLR would continue which would result in a incorrect number of dom elements traversed. The simple fix was to give a list of elements to remove after all elements have been traversed and then remove each element by object reference from the array.

@disenchant_ch – Sven Vetsch

Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?xss=%3Cimg%20%20src%3Dx%20onerror%3Dalert(1)%3E

Description: Sven took advantage of the DOM by using a backslash to create an exception in JSLR, the next attributes were then allowed without removal.

@kinugawamasato – Masato Kinugawa

Vector: http://www.businessinfo.co.uk/labs/jslr/jslr.php?xss=%3Cscript%3E&dom=%3C/script%3E%3Cscript%3Ealert%28document.cookie%29//

Description: Masato used a combination of dom and html context vectors to break out of the script and execute code. This was solved by using the random start and end of the script variable to escape the code and remove the .

Browser design issues

In their infinite wisdom the various vendors thought it would be a good idea to depreciate the plaintext element and yet provide no other way for client side script to intercept the DOM before it’s rendered. Plaintext offers the only way to force a page source code not to be rendered to enable concepts like JSLR and ACS (by Sirdarckcat). Eduardo first came up with the concept when developing ACS (Active Content Signatures).

By forcing the page into plaintext you can stop JavaScript execution and reuse the DOM to safely parse the code. Of course it isn’t that simple as the vectors have shown, interestingly even using plaintext will not fully stop the DOM from being rendered, using document.write(‘<plaintext>’)
will stop events from firing but not http requests, this is because the HTML is rendered first and so the http requests are sent and then the plaintext element forces the DOM to text.

Mario’s vectors also used Java in this way to execute XSS since the http requests were sent to the class file and the plugin was executed before the HTML was rendered. Our solution to this problem was a horrible hack where we look for applets within the source code and remove them.

The http requests also present a problem too since a partial img request to an external url can disclose the source code of the page and in effect cause info disclosure without scripts. The only workaround would be to directly use the plaintext element within the markup without using document.write which makes the whole solution less elegant.

Browser future features

We really need a elegant way to intercept all HTML and DOM executions before they are rendered by the browser. With this one feature we can use the browser itself to protect against malicious content and filter directly in the correct context. I think Mario suggested a DOM event such as beforedomrendered which would be an ideal way to intercept the content. We also need improvements in “virtual doms” at the moment we can use iframes to get a complete render of the page but document.implementation should be the ideal solution and we need control over what exactly is rendered and how or if http requests are made.

Related work

Preventing XSS with Data Binding by Stefano Di Paola

ACS By Eduardo Vela

Locking the Throne Room

Script keys

Comments are closed :( too much spam. If you want to contact me about any article please email or tweet me.