Is the PHP option 'cgi.fix_pathinfo' really dangerous with Nginx + PHP-FPM?

Solution 1:

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?"

    • Path info is stuff after the script in a URI (should start with a forward slash, but ends before the query arguments, which start with a ?). The last paragraph in the overview section of the Wikipedia article about CGI sums it up nicely. Below the PATH_INFO is "/THIS/IS/PATH/INFO":

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

  • 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.

Solution 2:

In essence without this you can upload file with php code named like 'foo.jpg' to web server; then request it like http://domain.tld/foo.jpg/nonexistent.php and web server stack will mistakenly say oh; this is a PHP; I need to process this, it will fail to find foo.jpg/nonexistent.php so it will fall back to foo.jpg and process foo.jpg as php code. That is dangerous as it opens system to very easy intrusion; any web application allowing image uploads for instance becomes tool to upload backdoor.

Regarding using php-fpm with unix socket to avoid it; IMO it will not solve the problem.


Solution 3:

In the Nginx wiki as security measure

if (!-f $document_root$fastcgi_script_name) {
    return 404;
}

is included in the location block. In other tutorials

try_files $uri =404;

is used, which should do the same, but can gives problems according to the Nginx wiki. With these options, cgi.fix_pathinfo=1 should not be a problem anymore. More infomation can be found here.