Skeptikal.org

Tuesday, August 11, 2009

PHP Casting Errors in Wordpress

Did you see the Wordpress vulnerability that was published last night?

While Wordpress vulns aren't really news, this one was fascinating. All it does is allow an attacker to reset the adminsitrator's password- locking him out of the account. Annoying, but not fatal. I'm interested in the vuln itself, as it stems from PHP's flexible casting. In particular, the following lines of code (paraphrased for context and clarity):

$key = preg_replace('/[^a-z0-9]/i', '', $_GET['key']);

if ( empty( $key ) ){
return new WP_Error('invalid_key', __('Invalid key'));
}

$user = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->users WHERE user_activation_key = %s", $key));

This code can be exploited by passing the vulnerable script an empty array rather than a string for $_GET['key']. You can pass an array in a query string using the [] syntax:

wp-login.php?action=rp&key[]=

Go back and step through the vulnerable code again. The value of $_GET['key'] would look like this in PHP form:

$_GET['key'][0]='';

The array is handed to preg_replace, which, when given an array to work with, will perform the regex substitution on each element of the array, then return that array. Again, this array contains one element, an empty string, so when it hits the "if(empty($key))" line, it returns false (as the array is not empty). This, in turn, is important because the wpdb->prepare() statement receives an array when it is expecting a string, flattens that array out, and gives you the following query:

SELECT * FROM `users` WHERE user_activation_key = ''

Since the admin user is the first row in the database, and his activation key is blank (assuming nobody has performed a password reset on the account yet), that user is returned, and his password is changed.

That probably wasn't the best explanation, but if you're still with me, that brings us to the question of "what did they do wrong?" The first problem was assuming that $_GET['key'] was a string. While the programmer checked that string for malicious characters (in the preg_replace), he didn't check the type of the variable. Flexible casting of variables is common in PHP apps, but as this example demonstrates, this can be dangerous. As part of your input validation process, you should be either checking or forcibly casting the type of that input. I suspect that the programmer thought he was forcibly casting the input with preg_replace(), but was not aware of how it handles arrays.

Here is the official fix:

if ( empty( $key ) || ( is_array( $key )){
return new WP_Error('invalid_key', __('Invalid key'));
}


Can you see the problem here? This is a classic bad approach to fixing security bugs- patching the exploit, not the vulnerability. Instead of checking whether the input is what you expect (is_string), they are making sure that it isn't what they don't expect (is_array). While this does fix the issue, it is a blacklist approach, not the whitelist approach that would be recommended. What will happen if an attacker figures out how to pass another data type to this function? It may break, it may not. Either way, it's not very fault-tolerant coding.

The reason I'm bringing it up is that this is an excellent case study of an underappreciated programming bug. As useful as PHP's loose casting and overloaded functions can be, you still need to validate your inputs and explicitly verify that they are what you expected.

Thanks to Rafal Los for making me look at this, and working through the mess that is the Wordpress codebase with me.

Labels: , ,

Wednesday, May 20, 2009

Thoughts on HTTP Parameter Pollution

Luca Carettoni and Stefan di Paola presented at OWASP Appsec Poland, speaking about HTTP Parameter Pollution. The short version is this- by mangling the inputs for a HTTP request, you can cause all kinds of strange behaviors. On a high level, this isn't too different from SQLi, XSS, and plenty of other attacks. What sets HPP apart is that the quirks you can generate aren't all data sanitization issues- they are often logic bugs, which are much harder to test for, easier to introduce into an application, and more fun to exploit.

The presentation involved a few very different attacks, which makes it a bit difficult to summarize, but essentially, it involves overwriting HTTP parameters. A few examples:

PHP applications (and others) primarily get their user inputs from COOKIE, GET, and POST. If the application reads input from the generic REQUEST parameter, the data could conceivably come from any of these, and developers don't always have a clear understanding of how that decision is made. By abusing the order in which inputs are handled, an attacker can generate unexpected behavior in the application- expecting data to come from POST but instead reading it from GET, for example.

This really isn't new- the register_globals mess in PHP stems from a very similar issue. While register_globals is dying a well-deserved death, similar attacks can still be made against the $_REQUEST parameter, which many developers (usually the same ones that used register_globals) rely on. Java, perl, and ASP have all seen the same issues, and are at varying levels of fixed-ness.

The more interesting and sneaky attack: If you pass a query string with multiple copies of the same parameter (e.g. index.php?foo=bar&foo=baz), the application may handle it in unexpected ways. PHP will use the last occurence of that parameter, JSP will use the first occurence, mod_perl converts it to an array, and ASP concatenates the parameters into a comma-separated string. Custom-buit parsers on embedded hardware can be even more surprising.

Now, if the developer is doing his job right- being paranoid about where his inputs come from, validating, and escaping, this should not be a major problem. However, no developer is perfect, and this being a fairly unknown behavior, it is quite likely to pop up. Quirks are edge cases, and edge cases are where you find security holes.

I recommend you go over the slides, and the whitepaper when it comes out (which should soon). Some very interesting, very cool real-world attacks are outlined there (I especially like the ASP SQL reconstruction), but I won't go into them here. In summary, you can leverage these parameter processing quirks against both the server and the client, in a lot of interesting ways:

  • Bypassing input validation

  • Bypassing WAFs

  • Manipulating application flow

  • Manipulating mod_rewrites

  • Forcing/spoofing cookies

  • Manipulating client-side applications

  • Manipulating and polluting page content

As a pentester, I absolutely love these kinds of attacks- they exploit seemingly-benign quirks in the application. They rely on the attacker knowing more about how the application works than the person who wrote it. Besides that, they're wicked fun to play with. I see HPP as yet-another useful tool in my arsenal. Like CSRF and XSS, it is (and will continue to be) extremely common in poorly built applications. Well-designed apps will have minimal attack surface, defense in depth will further limit their exposure, and what holes do exist will be quickly remediated.

HTTP Parameter Pollution isn't an entirely new attack, but it is a much more in-depth revisiting of older flow-control and input parsing issues, specifically at the border between the application and its back-end engine. I expect to see a surge of HPP-related vulnerability reports in the future. From the defender's perspective, though, it is the same story: Know your inputs, know where data flows through the app, validate before you use it, and escape it on the way out.

Labels: , ,

Friday, March 20, 2009

cPanel, mod_userdir, and Shared Hosting

I know I've made my views on shared hosting clear in the past, but I keep coming up with more reasons to not use it.

To begin with, let me start by saying that yes, I know that mod_userdir isn't recommended by its creators, cPanel, or Apache for secure applications. I may be grabbing at low-hanging fruit here, but that doesn't change the fact that its use is hugely popular for mass-hosted webservers. It is a convenient way to get a lot of sites onto a server, to get valid SSL certificates for each, and for accessing/managing content on all of them.

With that out of the way...

For those that aren't in the know, mod_userdir is an Apache module that allows you to create a separate website for each user on a server. These sites can all be accessed by going to http://servername.com/~username. The module is installed on most Apache setups by default, but isn't necessarily always enabled. Most mass-hosted cPanel servers make extensive use of it for a variety of reasons.

I should note that in the following examples, 'victim' is the username and 'victim.com' the domain of the targeted web site. 'attacker' and 'attacker.com', respectively, are used to compromise victim, but do not have to be run by the attacker- they can simply be accounts with poor log management, PHP or XSS holes, depending on the exploit. With hundreds of sites on a server, finding such a site is usually trivial.

cPanel's default Apache configuration has a handful of flaws in it that make mod_userdir suprisingly useful to the wily attacker. To begin with, the attacker can attack one site, but have the logs deposited in another's log directory. To do this, hit the server through the domain of the site that you want to logs to show up on, but use the username of the target site:

http://attacker.com/~victim/admin.php

With this url format, we can scan, probe, and bruteforce logins without the target site hearing a peep from his apache logs. Even if he does suspect foul play, he will have a hard time getting the logs, as (theoretically, at least) both the server's administrator and the attacker's site will be rather resistant to turning over their own site's logs without a court order.

If you can't find a decent user account on the server, you can always use the server's IP address instead of a domain and run the scripts as apache rather than attacker. This way, only root will be able to access the logs, and surprisingly few administrators ever review the global apache logs.

The one caveat here is that cPanel does allow you to set up a "default" account which may end up catching the logs, but if you go directly to the server's address and see something like this, you have a winner.

Due to poor default Apache configuration, directory listings on /cgi-bin are enabled when you access it in similar fashion:

http://victim.com/~victim/cgi-bin

This alone has been enough for me to crack accounts in past penetration tests. While it has been fixed recently, past versions of cPanel would also print out cgi scripts' contents rather than executing them. Still, included files without a .cgi, .pl, .plx, .ppl or .perl extension can be easily located and downloaded with this method.

By going to http://attacker.com/~victim, we can execute PHP scripts from the user "victim", with the server thinking that we're "attacker". This can be handy for a few things. First off, printing out $_SERVER['DOCUMENT_ROOT'] tells me that I'm running in /usr/home/attacker/. By contrast, the server knows that $_SCRIPT['FILENAME'] is /usr/home/victim/public_html/script.php. The script is executed with victim's privileges, so you can't read or write anything that the user doesn't normally have access to, but if victim is including files or locating resources based on the $_SERVER['DOCUMENT_ROOT'] variable, we can manipulate it, forcing the application to load your own PHP code.

Let's say you don't even want to bother with PHP. If you can find a single XSS hole on any user's site on the server (and you can, I promise), you just have to get people to hit http://victim.com/~attacker/XSSedPage.php.

Let me repeat that- You can XSS every domain on an entire server. Many of these servers contain hundreds of users. In cases where an entire server is owned by one web development company, that server's php applications are usually homogenous. The implications of this are huge. While many are certified as such, no site on a cPanel server should be considered PCI compliant. Many people downplay the importance of XSS exploits, because they have to be individually found and have custom exploit code written. With mod_userdir and a bit of fancy coding, an attacker can compromise hundreds of accounts, all performing hundreds of ecommerce transactions per day.

That's just good economics.

Labels: , , , ,

Wednesday, December 3, 2008

Redirecting Safely

I don't think this is news to the other security people, but as a developer, I had never heard of this issue. As far as I know, none of the developers I regularly work with had heard of it either (though using the code as recommended by the php.net manual will prevent it).

In many PHP applications, there is an admin side and a public side. It is common for developers to have an "admin check" in the code header of the admin side, which essentially evaluates whether a user is authorised to view a resource and either redirects them to the login page, the public page, or lets them through to the resource.

What many developers don't realize is that a browser doesn't have to terminate a connection as soon as it receives a 302 redirect. If you are relying on the following call to keep the riffraff out of your admin pages, you may be vulnerable:

header('Location: login.php');

I can't tell you how many times I've been able to access reports full of user logins, order information, or even account modification functionality, simply by asking my browser to continue loading a page after receiving a redirect. If you need a testing tool to see if you are vulnerable, I whipped up this perl script one day to allow me to simply snag admin pages or post content to them during a penetration test.

In case it isn't obvious, you can prevent this hole simply by halting the flow of the application. If you aren't using more advanced templating and flow control (which I'd recommend anyways), this will work:

header('Location: login.php');
die('Kindly piss off.');


While obvious, this issue is incredibly widespread, and I have to wonder how many developers are unaware of it.

Labels: , , ,

Tuesday, October 21, 2008

PHP's Most Useless Security Bug

I found this a few months ago and have been racking my brain for a way to use it in a pentest. Maybe somebody else can find a practical exploit for this; so far, I have nothing. I do think it's kind of funny though.

With display_errors on, PHP will send errors to the browser, along with the location of the buggy code. This is often useful for debugging, and helpful to malicious users. Interestingly, it doesn't filter the filename for HTML characters.

If you were able to, say, create a folder named "<script>alert(1337)<", and within it, place a file called "script>.php", which in turn threw an error, you'd get something like this.

Labels: , ,