In this post I'll describe how OWASP Top 10: A2-Cross Site Scripting applies to javascript based applications. Cross site Scripting - or XSS - is probably one of the most common and one of the most difficult problems to fully mitigate. At first mitigation seems simple, but as contexts grow in complexity and the amount of code grows, it get's harder to discover all the different sinks.
This is the risk rating from OWASP:
Threat Agents | Attack Vectors | Security Weakness | Technical Impacts | Business Impacts | |
---|---|---|---|---|---|
______ | Exploitability AVERAGE |
Prevalence VERY WIDESPREAD |
Detectability EASY |
Impact MODERATE |
______ |
Consider anyone who can send untrusted data to the system, including external users, internal users, and administrators. | Attacker sends text-based attack scripts that exploit the interpreter in the browser. Almost any source of data can be an attack vector, including internal sources such as data from the database. | XSS is the most prevalent web application security flaw. XSS flaws occur when an application includes user supplied data in a page sent to the browser without properly validating or escaping that content. There are three known types of XSS flaws: 1) Stored, 2) Reflected, and 3) DOM based XSS. Detection of most XSS flaws is fairly easy via testing or code analysis. |
Attackers can execute scripts in a victim's browser to hijack user sessions, deface web sites, insert hostile content, redirect users, hijack the user's browser using malware, etc. | Consider the business value of the affected system and all the data it processes. Also consider the business impact of public exposure of the vulnerability. |
We traditionally talk about two types of XSS - reflected and stored. In reflected XSS, the attack is a part of the URL like this one:
http://www.insecurelabs.org/Search.aspx?query="><script>alert(1)</script>
You can test that one here. In reflected XSS, the attacker has to trick the user into opening the URL somehow - typically by employing iframes, phishing or shortened URLs.
In stored, or persistent, XSS, the attacker is able to store the attack string in the database. Since the attack is not dependent on the exact URL being visited anymore, the attacker can simply wait for a victim to visit the otherwise legit page. An example could be the commenting section here: http://www.insecurelabs.org/Talk/Details/5.
Single page webapps are usually loading a static HTML-layout which is exactly the same for all users, and then data and private information is loaded using JSON services. If there is HTML-tags inside our JSON-data we would normally expect the browsers not to render this content, but rather treat application/json as, well, JSON. However this is not always the case. Some browser, and especially IE, tends to try to second guess the content-type given by the server. This is called content-sniffing, and is in place because at some point in time servers tended not to send the correct content-type, and thus browsers could help the developers and end user by checking "what the content really was".
I built a small test bed for showing how this works. You will see a green check if the browser treats the content as HTML (and thus allows scripting). It is expected that this happens if we return the JSON with Content-Type text/html, but not with any of the others. Well maybe an empty Content-Type could be excused, but if the browser says application/json, the browser should definitely not treat the contents as HTML.
It's expected that DOM-based XSS will be more commons in apps reying heavily on JavaScript, than what has been seen in traditional apps. DOM-based XSS occurs because user input is unsafely handled in javascript running on the page. We will address a few examples on this type of XSS. Some of these attacks never reach the server side, and thus cannot be detected by server side code.
Insecure writes happens when user input is written to the DOM without first being sanitized. Data can come in through user input in the browser, or it can be loaded through JSON from the server (which would mean it was a stored DOM-based XSS attack). Insecure writes means we are either outputting the data directly in the DOM using .innerHTML
or through unsafe jQuery functions like:
As mentioned in A1 - Injection the use of insecure functions in JavaScript can cause insecure code execution. More specifically this means using functions that build JavaScript from strings:
$.globalEval(string)
. As we can easily imagine dynamically building code containing user input is probably not a good idea, and is very likely to lead to problems down the road. JSHint, which is a common tool for quality checks on JavaScript, agrees: eval is evil.
Insecure use of jQuery can also lead to unexpected XSS. As mentioned in jQuery XSS something as simple as this can lead to XSS:
$(location.hash)
This can be exploited with:
http://example.com/some/page#<img src=insecure onerror=alert(1)>
The next one is a bit more subtle (courtesy of An overview of DOM XSS):
hash = location.hash.substring(1);
if (!$('a[name|="' + hash + '"]')[0]) {
// not important
}
An input of "] <img src=insecure onerror=alert(1)>
would actually work here.
While the examples above jQuery specific, it is very likely that there are similar problems in other frameworks.
Allowing user input to be directly assigned to document.location
, can easily lead to XSS in the form of javascript:
URLs. The following example is from twitter back in september 2010 (I highly recommend you read the full blog post here: Minded Security Blog: A Twitter DomXss, a wrong fix and something more):
(function(g){var a=location.href.split("#!")[1];if(a){g.location=g.HBR=a;}})(window);
The code above, extracts whatever comes after #!
in the url, and asigns it to window.location
. The intention was to allow a url of http://twitter.com/#!/webtonull
to be redirected to http://twitter.com/webtonull
. However consider the following url:
http://twitter.com/#!javascript:alert(1)
If the XSS vector is persisted in Web Storage, and later rendered in an insecure way, we call it a persistent client side XSS. As the name implies this attack will never reach the server, but still trigger every time the vulnerable page is loaded in the given browser.
Templating from frameworks like Mustache.js and underscore.js etc. allow javascript frameworks to clearly seperate view from data. They normally provide a set of tags for allowing coding and data output. The set from underscore.js looks like:
<% %>
- view logic (JavaScript code)<%= %>
- data output (no escaping)<%- %>
- data output (HTML escaping)&, <, >, ", '
and /
. I don't intend to pick on underscore.js. This is one of the better escaping functions I've seen. But we need to remember to use the HTML escaping tag. And there are contexts where this will not work, simply because HTML escaping is not the correct escaping.
Quoteless HTML attributes
<img title=<%- title %> ...>
<img title=something onclick=alert(1) ...>
HTML comments - courtesy of http://html5sec.org/#133
<!-- <%- title %> -->
<!-- ` I'm now outside the comment in IE6-8 -->
Inside script tags, but you are not using those in a javascript app, right?
<script>
var id = <%- id %>
</script>
<script>
var id = 1;alert(/XSS/.source);
</script>
Inside javascript event handlers, but you are not using those in a javascript app, right?
<img onmouseover="showToolTip('<%- title %>')" ...>
<img onmouseover="showToolTip('');alert('XSS')" ...>
Insecure use of URLs
<a href="<%- url %>" ...>
<a href="javascript:alert(1)" ...>
The above list is not complete. There are a lot more weird contexts. See http://html5sec.org/ and the OWASP cheat sheets listed below.
When you are adding direct links to third-party javascript files, instead of putting the files locally on your server, you have to remember that you are now allowing XSS from the domain hosting that file. That's regardless of whether you are loading a javascript library or "just" using JSONP to load data. Consider a script
tag like this:
<script src="http://example.com/example.js"></script>
Now if example.com goes rougue or is hacked, example.js could start delivering malware to your users or silently stealing their credentials when they log in.
Another thing to consider any security vulnerabilities in downloaded libraries. When you are pulling in some third party script, you should also make sure you later stay up to date, and follow any announcements from the developers of that library.
$.text()
instead of $.html()
<%- %>
instead of <%= %>
eval
Contact me on twitter and I'll be happy to add (with attribution).