67

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.

Totor
  • 2,876
  • 3
  • 22
  • 31
  • 1
    You might answer your own question by understanding the difference between PATH_INFO and PATH_TRANSLATED: http://blogs.msdn.com/b/david.wang/archive/2005/08/04/what-is-path-translated.aspx – Giovanni Tirloni Sep 12 '14 at 13:38

3 Answers3

94

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.

phsource
  • 105
  • 3
user109322
  • 1,281
  • 12
  • 15
  • 6
    Nice answer! To clarify: if, as you say, PHP determines what `SCRIPT_FILENAME` is, why is there a `fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;` line in my `nginx` conf? Does it overrides PHP's effort to discover the value of `SCRIPT_FILENAME` by itself? – Totor Nov 05 '16 at 01:09
  • Is there a function to get the value of `security.limit_extensions`? I tried `phpinfo()`, `ini_get(security.limit_extensions)`, and `ini_get_all()` with no success. – elbowlobstercowstand Jun 28 '18 at 13:31
  • Thank you, if recent versions of PHP-FPM (~5.3.9+?) have this as default, why php7.1 needs it? Or is [this](https://www.rosehosting.com/blog/install-php-7-1-with-nginx-on-an-ubuntu-16-04-vps/) article wrong? – Yevgeniy Afanasyev Nov 27 '18 at 04:48
  • 1
    location ~* ^/uploads/.*\.php.* { return 400 – Bob Jan 13 '21 at 22:48
15

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.

Hrvoje Špoljar
  • 5,162
  • 25
  • 42
  • 1
    You only repeat what can be read on the links I provided. You don't explain the real mechanism. Your answer needs added value IMHO. – Totor Sep 12 '14 at 13:23
  • 7
    That may be true, but your title has question and answer to that question is in my response. If you want it explicitly; yes it is dangerous; very dangerous. – Hrvoje Špoljar Sep 12 '14 at 13:43
  • 1/ My answer is not limited to its title: it has a body. 2/ *user109322* has proven you wrong: whatever value used for `cgi.fix_pathinfo` is *not* dangerous, because the default conf of `php-fpm` is safe (it will only execute files with the `.php` extension). – Totor Nov 05 '16 at 00:56
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.

user2372674
  • 203
  • 2
  • 5