"Curiosity is the very basis of education and if you tell me that curiosity killed the cat, I say only the cat died nobly." - Arnold Edinborough

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”

Changing the above setting means that path info will no longer work. If path info is still needed then nginx offers a way to have this done by using the fastcgi_split_path_info directive This will let legitimate requests through while having bad requests not re-evaluate into a PHP location block.

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.

  • Siros

    Posted: July 8, 2011


    Great I fixed that issue now.

    But what about "no input file specified "?

    could this be translated into an error page like 404 or a Redirect for example.

    Thank you for this great blog. Reply


    • fjordvald

      Posted: July 9, 2011


      Hi! I have a post on that specific issue as well: http://blog.martinfjordvald.com/2011/01/no-input-file-specified-with-php-and-nginx/ Reply


  • Xiaomao

    Posted: July 17, 2011


    Why not this:
    if (!-f $request_filename) {
    return 404;
    }

    This will produce a clear 404 instead of the weird No input file specified. Reply


    • fjordvald

      Posted: July 18, 2011


      That will just produce a Nginx 404 instead of a PHP 404. It's better to catch it with error page than to actively check up front. Also Nginx is a reverse proxy, sometimes the PHP files are on backend server so you cannot check if they exist. Reply


  • Mobin Hosseini

    Posted: February 24, 2012


    can't you tackle this with try_files directive ? Reply


    • fjordvald

      Posted: February 25, 2012


      You can, but at the expense of an extra stat() call on each page load. I personally prefer this method as it avoids a 404 and instead serves up the proper page safely. Reply


  • Steph

    Posted: September 7, 2012


    Another well explained article, Thank you ! Reply


  • sweet

    Posted: September 4, 2014


    Thank you for explaining this all. Lots of different guides out there but none of them explain what pathinfo is all about. It's 2014 now and I don't think wordpress uses pathinfo anymore which is good. Reply


  • eMBee

    Posted: May 12, 2015


    if nginx is responsible to decide what files should be executed as php, then nginx should also handle pathinfo properly, and check if a file exists and is php source code in an authorized location.
    for one that means that the location that is passed to php should never match user uploaded files.

    if nginx is not responsible for making those checks and decisions, then we have no business to even check for the extension. instead, everything in a certain path should be directed to php and then php should be responsible for verifying which files should be executed as php, and again, user uploaded files should never be allowed to run, no matter what filename they have.

    if neither php nor nginx can handle this then i'd rather avoid using them together. i prefer to blame php here though.

    greetings, eMBee. Reply


    • Martin Fjordvald

      Posted: May 12, 2015


      As far as nginx is concerned we are just telling it every file in a certain path needs to be processed by PHP. That path is just given by a regular expression. So yes, PHP is to blame here. It's a legacy feature which doesn't work well with the way things are done today. Reply


  • Kristian

    Posted: June 26, 2015


    This isn't a problem with php-fpm and security.limit_extensions right? Reply


    • Martin Fjordvald

      Posted: June 26, 2015


      I think it should still be a problem. The URL will have .php ending and fix path info will make it map to the actual .php file so security.limit_extensions shouldn't prevent this.

      I highly recommend always turning fix path info off in php.ini Reply



You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>