Remember register globals? Remember how you had to code as if it was off, because it might be? Remember how you had to consider the security implications of it being on, because it might be? The might be and might not be is something which has plagued a lot of early PHP features. Register globals is in no way alone in this, in the effort of making things versatile the PHP developers managed to introduce the worst of both worlds and the best of none. At least for code where you can’t guarantee 100% control over the environment your code would be running in.

We see this even today with things such as short open tags. Ever been told you shouldn’t use them? Yeah, that’s primarily because they could be turned off thus leaking your source code into the document. (To a lesser degree it’s also about XML incompatibility)

Today I want to cover a very known feature, which many people don’t often think of as being in the same group as register globals and short open tags. Namely path info. The idea of path info is brilliant enough, it is most often used as a way to have SEO “friendly” URIs in cases where one might not be able to rewrite the URL. In short, you can have a URI like so:

/index.php/user/dashboard

In standard Unix this would read as “file dashboard in directory /index.php/user/”. Of course, /index.php/user/ is not a directory and there’s no file called dashboard. Instead, PHP sees this and translates it into /index.php with /user/dashboard as the path info. In case you’re shaking your head already, this is actually in the CGI spec so it’s not really a fault of PHP, there is literally an RFC specifying this behaviour.

And for the longest time this was perfectly fine. The web server model used with Apache made this a non-issue. PHP was embedded in Apache and as such would only be called for actual files configured as such. But these days people aren’t just using Apache any more, however, they still think most things function like they do in Apache. Since I’m an Nginx person and this is primarily an Nginx and PHP blog, lets look at how path info works with Nginx.

The issue is that where Apache sees files Nginx sees URIs. Nginx is at the heart of it a reverse proxy, it does not embed scripting languages and it does not execute code. Instead, it sees a URI and either try to serve a static file or pass it onto a backend. What this means is that when using PHP we see locations like the following:

location ~ \.php$ {
	fastcgi_pass upstream;
}

This location actually does not allow for normal path info to work as the location defines the URI as having to end in .php. However, lets look at what happens when we reverse the path info request URI like so:

/uploads/avatar32.jpg/index.php

In this case PHP will see that there is no index.php file in /uploads/avatar32.jpg/ and as such will instead execute /uploads/avatar32.jpg with /index.php as the path info. We are essentially allowing PHP to execute any arbitrary file in our defined nginx root by just appending /index.php to the URI!

What makes this scary is that there’s a ton of ways to hide PHP code in file uploads. For instance if you run forum software like VB you can embed PHP code inside an EXIF tag and upload it as an avatar without VB ever batting a virtual eyelash. I trust I don’t need to tell you how bad it is to allow attackers to execute arbitrary PHP code on your server.

And the best thing is that this is not even a security vulnerability in either Nginx or PHP, Nginx is doing exactly what a reverse proxy should be doing and PHP is simply following the CGI specification. As such there won’t be a “fix” for this, it’s solely up to the developers and server admins to educate themselves and understand the tools they’re actually using.

With all that dire info out of the way, the good news is that you can secure yourself very easily. The simplest way is to tell PHP not to translate the path info by setting the php.ini variable cgi.fix_pathinfo to 0. This means that PHP will instead try to execute the /index.php file which doesn’t exist and thus return 404 and “no input file specified”

The best way, in my opinion, is to use the fastcgi_split_path_info directive in Nginx to handle the path info translation in Nginx. This means that Nginx will handle the path translation instead of PHP. Combining the two is also possible, though doesn’t provide any more security than just one of them.

So why is this like register globals and short open tags? Because it’s a php.ini setting. You can turn the behaviour on and off. Your code has to consider the security implications in case it might be on, but it cannot take advantage of it in case it’s off, you’re getting the worst of both worlds. In practice this is a dangerous feature that should be deprecated and set to off in PHP by default.

Related posts:

  1. “No input file specified” With PHP and Nginx
  2. File Uploading With PHP & Nginx
  3. Implementing Full-Page caching with Nginx and PHP
  4. 12,000 Requests per second with Nginx, PHP and Memcached
  5. WordPress Performance Benchmarks

flattr this!