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