php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #68053 PHP-FPM with cgi.fix_pathinfo 0 loads script from PATH_TRANSLATED
Submitted: 2014-09-19 10:44 UTC Modified: -
Votes:4
Avg. Score:4.2 ± 0.8
Reproduced:2 of 2 (100.0%)
Same Version:0 (0.0%)
Same OS:1 (50.0%)
From: devel at jasonwoods dot me dot uk Assigned:
Status: Open Package: FPM related
PHP Version: 5.5.17 OS: CentOS 6.5
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: devel at jasonwoods dot me dot uk
New email:
PHP Version: OS:

 

 [2014-09-19 10:44 UTC] devel at jasonwoods dot me dot uk
Description:
------------
Looking at the nginx example config here:

http://wiki.nginx.org/HttpFcgiModule#fastcgi_split_path_info
(or even here: http://wiki.nginx.org/PHPFcgiExample)

During a request, PATH_TRANSLATED is set to the full path to the PATH_INFO on the filesystem as defined by the CGI specification: http://www.ietf.org/rfc/rfc3875

Now we have something like this:

    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^((?U).*[^/]\.php)(/.*)$;
        include "fastcgi.conf"; # To get the other standard variables
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
        if (!-f $document_root$fastcgi_script_name) {
            return 404;
        }
        fastcgi_pass unix:/var/run/php-fpm/{{ id }}.sock;
    }

This works OK with cgi.fix_pathinfo=1 in the PHP-FPM configuration. Scripts run fine as expected with PATH_INFO populated and PATH_TRANSLATED populated. For example, requesting www.test.com/script.php/images would load script.php.

Now, a lot of blogs (maybe incorrectly) say to disable cgi.fix_pathinfo for security, and reading the docs it makes sense... I've done the splitting in Nginx, why should FPM do any... However, setting it to 0 will generally result in "Access is denied!" and error_log such as the following:

FastCGI sent in stderr: "Access to the script '<root>/images' has been denied (see security.limit_extensions)" while reading response header from upstream, client: X.X.X.X, server: www.test.com, request: "GET /test.php/images HTTP/1.1", upstream: "fastcgi://unix:/var/run/php-fpm/test.sock:", host: "www.test.com"

If you change the request to www.test.com/script.php/images/anotherphpscript.php, and actually create that script in <root>/images/anotherphpscript.php, it actually loads that script! It seems that when PATH_TRANSLATED is present and fix_pathinfo is 0, PHP loads the script from the path pointed to by PATH_TRANSLATED and ignores SCRIPT_FILENAME completely! Yet from what I can gather, PATH_TRANSLATED is the filesystem path to the PATH_INFO stem - this is not meant to be the script to run!

Looking into it further though, it seems PHP documentation kind of states this:
http://php.net/manual/en/reserved.variables.server.php
'PATH_TRANSLATED' - Filesystem- (not document root-) based path to the current script, after the server has done any virtual-to-real mapping.

Yet looking elsewhere you get a completely different definition:
http://oreilly.com/openbook/cgi/ch02_04.html
Meanwhile, the variable PATH_TRANSLATED is also set, which maps the information stored in PATH_INFO to the document root directory (e.g., /usr/local/etc/httpd/ public/cgi/cgi-doc.txt).

Have I misunderstood this completely or PHP completely loading the wrong script?

The thing that concerns me is it appears that using configs on the Nginx website and following a common security recommendation on the web, essentially makes one potentially INSECURE, as it means someone could run a PHP file in /images/ even if you have a match in nginx on /images/ that returns 401

FYI - Adding cgi.discard_path = 1 (with cgi.fix_pathinfo = 0) seems to fix the problem though appears to be sparsely mentioned, but introduces a new issue in that PHP_SELF remains the same and ends up the PATH_INFO (/images/anotherphpscript.php - which would be right if it was still running that script)
Compare to cgi.discard_path = 0 (default) and cgi.fix_pathinfo = 1 where PHP_SELF is correct (/script.php/images/anotherphpscript.php)

Test script:
---------------
This is a really good example, who

Setup nginx with php-fpm and use following block for requests:

    location ~ [^/]\.php(/|$) {
        fastcgi_split_path_info ^((?U).*[^/]\.php)(/.*)$;
        include "fastcgi.conf"; # To get the other standard variables
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
        if (!-f $document_root$fastcgi_script_name) {
            return 404;
        }
        fastcgi_pass unix:/var/run/php-fpm/{{ id }}.sock;
    }

1. Create <root>/images/anotherphpscript.php
<?php
echo "ANOTHERPHPSCRIPT.PHP";
phpinfo();
?>

2. Create <root>/script.php to output "SCRIPT.PHP"
<?php
echo "SCRIPT.PHP";
?>

3. Request www.test.com/script.php/images/anotherphpscript.php

Expected result:
----------------
When requesting www.test.com/script.php/images/anotherphpscript.php
Receive output "SCRIPT.PHP"

Actual result:
--------------
When requesting www.test.com/script.php/images/anotherphpscript.php
Receive output "ANOTHERPHPSCRIPT.PHP"
Receive PHP info dump with following _SERVER:

_SERVER["USER"]	test
_SERVER["HOME"]	/home/test
_SERVER["FCGI_ROLE"]	RESPONDER
_SERVER["SCRIPT_FILENAME"]	/home/access/site/script.php
_SERVER["QUERY_STRING"]	no value
_SERVER["REQUEST_METHOD"]	GET
_SERVER["CONTENT_TYPE"]	no value
_SERVER["CONTENT_LENGTH"]	no value
_SERVER["SCRIPT_NAME"]	/script.php
_SERVER["REQUEST_URI"]	/script.php/images/anotherphpscript.php
_SERVER["DOCUMENT_URI"]	/script.php/images/anotherphpscript.php
_SERVER["DOCUMENT_ROOT"]	/home/access/site
_SERVER["SERVER_PROTOCOL"]	HTTP/1.1
_SERVER["GATEWAY_INTERFACE"]	CGI/1.1
_SERVER["SERVER_SOFTWARE"]	nginx/1.6.0
_SERVER["REMOTE_ADDR"]	X.X.X.X
_SERVER["REMOTE_PORT"]	XXXXX
_SERVER["SERVER_ADDR"]	X.X.X.X
_SERVER["SERVER_PORT"]	80
_SERVER["SERVER_NAME"]	www.test.com
_SERVER["REDIRECT_STATUS"]	200
_SERVER["PATH_INFO"]	/images/anotherphpscript.php
_SERVER["HTTPS"]	no value
_SERVER["HTTP_X_FORWARDED_FOR"]	X.X.X.X
_SERVER["PATH_TRANSLATED"]	/home/access/site/images/anotherphpscript.php
_SERVER["HTTP_HOST"]	www.test.com
_SERVER["HTTP_DNT"]	1
_SERVER["HTTP_COOKIE"]	no value
_SERVER["HTTP_CONNECTION"]	keep-alive
_SERVER["HTTP_ACCEPT"]	text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
_SERVER["HTTP_USER_AGENT"]	Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14
_SERVER["HTTP_ACCEPT_LANGUAGE"]	en-us
_SERVER["HTTP_ACCEPT_ENCODING"]	gzip, deflate
_SERVER["HTTP_CACHE_CONTROL"]	max-age=0
_SERVER["PHP_SELF"]	/images/anotherphpscript.php
_SERVER["REQUEST_TIME_FLOAT"]	1398935271.6949
_SERVER["REQUEST_TIME"]	1398935271

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2014-09-19 10:45 UTC] devel at jasonwoods dot me dot uk
Originally raised as #67164 but was private, so we can delete/close/duplicate #67164 and keep this public one - I'm keen if anyone else experience this or knows what is "right"
 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Mon Oct 26 02:01:23 2020 UTC