Nginx – Is the PHP option ‘cgi.fix_pathinfo’ really dangerous with Nginx + PHP-FPM

fastcginginxphp-fpmSecurity

There has been a lot of talking about a security issue relative to the cgi.fix_pathinfo PHP option used with Nginx (usually PHP-FPM, fast CGI).

As a result, the default nginx configuration file used to say:

# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

However, now, the "official" Nginx wiki states that PATH_INFO can be handled correctly without disabling the above PHP option. So what?

Questions

  • Can you explain clearly what does cgi.fix_pathinfo do? (official doc just says: "For more information on PATH_INFO, see the CGI specs")
  • What will PHP really do with these PATH_INFO and SCRIPT_FILENAME variables?
  • Why and how can it be dangerous with Nginx? (detailed examples)
  • Does the issue still exist in recent versions of these programs?
  • Is Apache vulnerable?

I'm trying to understand the issue at each step. For example, I don't understand why using the php-fpm Unix socket could avoid this problem.

Best Answer

TL;DR - the fix (which you may not even need) is VERY SIMPLE and at the end of this answer.

I'll try to address your specific questions, but your misunderstanding of what PATH_INFO is makes the questions themselves a little bit wrong.

  • First question should be "What is this path info business?"

  • Your next question should have been: "How does PHP determine what PATH_INFO and SCRIPT_FILENAME are?"

    • Earlier versions of PHP were naive and technically didn't even support PATH_INFO, so what was supposed to be PATH_INFO was munged onto SCRIPT_FILENAME which, yes, is broken in many cases. I don't have an old enough version of PHP to test with, but I believe it saw SCRIPT_FILENAME as the whole shebang: "/path/to/script.php/THIS/IS/PATH/INFO" in the above example (prefixed with the docroot as usual).
    • With cgi.fix_pathinfo enabled, PHP now correctly finds "/THIS/IS/PATH/INFO" for the above example and puts it into PATH_INFO and SCRIPT_FILENAME gets just the part that points to the script being requested (prefixed with the docroot of course).
    • Note: when PHP got around to actually supporting PATH_INFO, they had to add a configuration setting for the new feature so people running scripts that depended on the old behavior could run new PHP versions. That's why there's even a configuration switch for it. It should have been built-in (with the "dangerous" behavior) from the start.
  • But how does PHP know what part is the script and what it path info? What if the URI is something like:

    http://example.com/path/to/script.php/THIS/IS/PATH/INFO.php?q=foo

    • That can be a complex question in some environments. What happens in PHP is that it finds the first part of the URI path that does not correspond to anything under the server's docroot. For this example, it sees that on your server you don't have "/docroot/path/to/script.php/THIS" but you most certainly do have "/docroot/path/to/script.php" so now the SCRIPT_FILENAME has been determined and PATH_INFO gets the rest.
    • So now the good example of the danger that is nicely detailed in the Nginx docs and in Hrvoje Å poljar's answer (you can't be fussy about such a clear example) becomes even more clear: given Hrvoje's example ("http://example.com/foo.jpg/nonexistent.php "), PHP sees a file on your docroot "/foo.jpg" but it does not see anything called "/foo.jpg/nonexistent.php" so SCRIPT_FILENAME gets "/foo.jpg" (again, prefixed with docroot) and PATH_INFO gets "/nonexistent.php".
  • Why and how it can be dangerous should now be clear:

    • The web server really isn't at fault - it's merely proxying the URI to PHP, which innocently finds that "foo.jpg" actually contains PHP content, so it executes it (now you've been pwned!). This is NOT particular to Nginx per se.
  • The REAL problem is that you let untrusted content be uploaded somewhere without sanitizing and you allow other arbitrary requests to the same location, which PHP happily executes when it can.
  • Nginx and Apache could be built or configured to prevent requests using this trickery, and there are plenty of examples for how to do that, including in user2372674's answer. This blog article explains the problem nicely, but it's missing the right solution.

  • However, the best solution is to just make sure PHP-FPM is configured correctly so that it will never execute a file unless it ends with ".php". It's worth noting that recent versions of PHP-FPM (~5.3.9+?) have this as default, so this danger isn't so much problem any more.

The Solution

If you have a recent version of PHP-FPM (~5.3.9+?), then you need to do nothing, as the safe behaviour below is already the default.

Otherwise, find php-fpm's www.conf file (maybe /etc/php-fpm.d/www.conf, depends on your system). Make sure you have this:

security.limit_extensions = .php

Again, that's default in many places these days.

Note that this doesn't prevent an attacker from uploading a ".php" file to a WordPress uploads folder and executing that using the same technique. You still need to have good security for your applications.