Summary of our paper for USENIX Sec ’14 on a new DOM-based XSS filter

Things leading up to this point (and obligatory cat picture)

cat_shoe_cutAs discussed in our previous post, our research has shown that while the XSSAuditor in Chrome is able to catch quite a few exploits targeting server-side reflected XSS, it is also prone to being bypassed in situations where the attacker does not need to inject full script tags. One of the underlying problems that the Auditor has to face is the fact that it has no idea how the user-provided data (i.e. the GET or POST parameters) is used on the server side. The strategy therefore is to approximate the flow of data by finding matching pieces that exist both in request and response.

As shown for our BlackHat talk (which is partially taken from the USENIX paper), the Auditor can be bypassed on about 80% of all domains which carry a DOM-based XSS vulnerability. The reasons for that are spread all across the board – from vulnerabilities targeting eval (the Auditor works on HTML, not on JavaScript), via innerHTML (Auditor is off for performance reasons) to issues related to string matching (where the flow of data can no longer be approximated with high certainty).

Before we really get started on our paper, let’s quickly recap our work for CCS 2013. There is a longer post on the topic, but for the sake of this post, let’s briefly summarise: In order to detect DOM-based XSS in a large number of sites, we modified Chromium’s JavaScript engine V8 and rendering engine WebKit (now Blink) such that strings can carry taint information, i.e. we can at all times precisely determine where a given character in a string originated from. Rather than just saying that a given char is coming from an untrusted source, the engine allows us to pinpoint the source (such as the URL, cookies, postMessage, …). Using this precise taint information, we logged all calls to sinks such as document.write and eval and built an exploit generator to verify the vulnerabilities. The exploit generator takes in the string, its taint information and the URL of the page the flow had occurred and subsequently modifies the URL such that our verification function is called (i.e. breaks out of the existing HTML or JavaScript context and executes our function).

Our proposed filter approach

Let’s look at how the Auditor works in slightly more detail. It is located inside the HTML parser and tries to determine snippets of HTML (either tags or attributes) that lead to JavaScript execution. As an example, when it encounters a script tag, it searches the URL for a matching snippet (actually, only up to 100 chars and only until certain patterns, such as //). If one is, the script’s content is set to an empty string, therefore stopping the execution of the injected payload.

Before we go into detail on our filter, let’s briefly abstract what a Cross-Site Scripting attack is. We argue that it actually is an attack in which the attacker provides data to an application. Since the application does not properly sanitize the data, at some point it ends up being interpreted as code. In general, this is the case for all injection attacks (think about SQL injections or command injections).

Our notion in this is slightly different. Seeing that there are numerous ways around the Auditor which are related to both positioning and string matching, we propose that a filter capable of defending against DOM-based XSS should not be located in the HTML parser – but rather inside the JavaScript engine. If the JavaScript parser is able to determine the exact source information for each string it parses, it is able to check that user-provided data is only interpreted as data and never as code. Speaking in JavaScript terms, we want the user-provided data to end up being a Numeric, String or Boolean Literal, but never to be an Identifier or Punctuator. This approach is dependent on one important requirement: exact source information for each character in the parsed string.

Luckily, we had implement the biggest part of that already for CCS! The changes for the current paper mostly required persisting taint deeper into the rendering engine as well as to patch others JavaScript-based parsers, such as the JSON parser. After having done that, we extended classes used inside the JavaScript parser to allow for carrying of taint. Albeit these changes sound slim, they did require a lot of engineering. Different to CCS, where we were only interested in the flow of data to a sink such as eval or document.write, we now needed the taint information deep inside the JavaScript parser.

Also, in order to ensure that an attacker could not inject a script tag pointing to a remote URL (where all the code contained in the response would be untainted), we implemented rules in the HTML parser that did not allow tainted protocols or domains for such scripts. Similarly, we implemented checks in the DOM API that forbid assignment to such dangerous sinks with tainted values.

Evaluation of our approach

After having designed and implemented our filter, we needed to check it for three criteria: false negatives (i.e. not catching exploits), false positives (i.e. blocking legitimate sites) and performance. As discussed, we had a  large number of vulnerabilities to begin with, so evaluating the false negatives was straight forward: turn off the XSSAuditor to ensure that there was no interference and then revisit all verified vulnerable pages with our attack payload. To no big surprise, all exploits were caught. To evaluate the false positives, we conducted another crawl of the Alexa Top10k, this time enabling our filter and report function. The latter would always generate and send back a report of blocked JavaScript execution to our backend server to allow for offline analysis of the block. Operating under the notion that the links contained in the crawled sites usually do not carry an XSS payload, we initially assume that any block is a false positive. In the following, we will go over the results of that crawl.

Blocking legitimate sites

In our compatibility crawl, we analyzed a total of 981,453 URLs, consisting of 9,304,036 frames. The following table shows the results of our crawl, the percentages being relative to the number of frames and total domains crawled, respectively.

bLOCKING COMPONENT Documents DOmains exploitable DOMAINS
JavaScript 5,979 (0.064%) 50 (0,5%) 22
HTML Parser 8,805 (0.095%) 73 (0.73%) 60
DOM API 182 (0.002%) 60 (0.60%) 8
SUM 14,966 (0.016%) 183 (1.83%) 90

As the table clearly shows, there are a number of documents that use programming paradigms which are potentially insecure, such as passing user-provided JSON to eval. In some cases, the flows only occur when a certain criteria is matched. As an example, Google applies a regular expression to JSON-like input to verify that only JSON is passed and an attack is not possible. Interestingly enough, about half of all the domains on which we found policy-violation (our proposed policy, that is) usage of user-provided data could be exploited in exactly those blocked flows. Depending on the definition of a false positive –  blockage of a secure component or blockage of any component – this cuts down our FP rate to 0.9% with respect to the domains we analysed. More importantly, blocking of a single functionality does not mean that the page is no longer usable – it rather means that a single component does not function as intended.

To allow pages that properly check the provided data before passing it to eval to continue using this pattern, our prototyped implementation supports and explicit untainting API. Calling untaint() on a string will return a completely taint-free version of the string. While this sounds dangerous in terms of an attacker being able to untaint a string, he is faced with a bootstrapping problem: in order to call the untaint function, he first has to execute his own code – which is blocked by our filter.

Performance

The last piece of the puzzle is the performance of the filter. Obviously, the added code causes additional operations that need to be carried out and thus, adds some overhead. The following figure shows the results of running the well-known benchmarks Kraken, Dromaeo, Sunspider and Octane against a vanilla Chromium, our patched version as well as Firefox and IE as comparisons. Note that since strings in these benchmarks do not originate from tainted sources, “Patched Chrome” depicts the overhead that occurs if no string is tainted (just by the added logic and memory overhead), whereas “Patched Chrome (worst)” assumes every string to be tainted to give an upper bound of the overhead.


performance1-page-001

 

Summarizing the results of the benchmark, our implementation adds about 7 to 17% overhead. This leaves us miles in front of IE and still faster than Firefox. We do believe that additional performance can be gained quite easily. V8 draws much of its superior speed from using assembler macros rather than C++ code to get “the easy path” on certain functions such as substrings. Changing these is, however, more error-prone which is why we opted to always jump of out the ASM code into the runtime when handling tainted strings. Extending the taint-passing logic into the ASM space would – in our minds – significantly improve the performance of our approach.

Summary

Since we wrote the conclusion already for the paper, let’s just c&p it here:

In this paper we presented the design, implementation and thorough evaluation of a client-side countermeasure which is capable to precisely and robustly stop DOM-based XSS attacks. Our mechanism relies on the combination of a taint-enhanced JavaScript engine and taint-aware parsers which block the parsing of attacker-controlled syntactic content. Existing measures, such as the XSS Auditor, are still valuable to combat XSS in cases that are out of scope of our approach, namely XSS which is caused by vulnerable data flows that traverse the server.

In case of client-side vulnerabilities, our approach reliably and precisely detects injected syntactic content and, thus, is superior in blocking DOM-based XSS. Although our current implementation induces a runtime overhead between 7 and 17%, we believe that an efficient native integration of our approach is feasible. If adopted, our technique would effectively lead to an extinction of DOM-based XSS and, thus, significantly improve the security properties of the Web browser overall.

Resources

Leave a Reply

Your email address will not be published. Required fields are marked *