已经有A 很多 的 说关于cgi.fix_pathinfo
与 Nginx 一起使用的 PHP 选项(通常是 PHP-FPM、快速 CGI)相关的安全问题。
因此,默认的 nginx 配置文件是这样的:
# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
然而,现在,“官方” Nginx wiki指出无需禁用上述 PHP 选项,PATH_INFO 即可正确处理。那又怎么样?
问题
- 你能清楚地解释一下
cgi.fix_pathinfo
做什么吗?(官方文件只是说:“有关 PATH_INFO 的更多信息,请参阅 CGI 规范”) PATH_INFO
PHP 实际上会用这些和变量做什么SCRIPT_FILENAME
?- Nginx 为何以及如何会带来危险?(详细的例子)
- 这些程序的最新版本中是否仍然存在该问题?
- Apache 是否存在漏洞?
我试图理解每一步的问题。例如,我不明白为什么使用 php-fpm Unix 套接字可以避免这个问题。
答案1
TL;DR - 修复(您甚至可能不需要)非常简单,并且位于本答案的末尾。
我会尽力解答您的具体问题,但是您对 PATH_INFO 的误解使得问题本身有点错误。
第一个问题应该是“这个路径信息业务是什么?”
路径信息是 URI 中脚本后面的内容(应以正斜杠开头,但在查询参数之前结束,查询参数以 开头
?
)。概述部分的最后一段维基百科关于 CGI 的文章总结得很好。下面PATH_INFO
是“/THIS/IS/PATH/INFO”:http://example.com/path/to/script.php/THIS/IS/PATH/INFO?query_args=foo
您的下一个问题应该是:“PHP 如何确定什么是
PATH_INFO
和SCRIPT_FILENAME
是什么?”- 早期版本的 PHP 很幼稚,从技术上讲甚至不支持
PATH_INFO
,所以本来应该PATH_INFO
被混杂在一起SCRIPT_FILENAME
,是的,在许多情况下是错误的。我没有足够老的 PHP 版本来测试,但我相信它认为它是SCRIPT_FILENAME
完整的:上例中的“/path/to/script.php/THIS/IS/PATH/INFO”(像往常一样以 docroot 为前缀)。 - 启用 cgi.fix_pathinfo 后,PHP 现在可以正确找到上述示例中的“/THIS/IS/PATH/INFO”并将其放入
PATH_INFO
并SCRIPT_FILENAME
获取指向所请求脚本的部分(当然以 docroot 为前缀)。 - 注意:当 PHP 开始真正支持时
PATH_INFO
,他们必须为新功能添加配置设置,以便运行依赖于旧行为的脚本的用户可以运行新版本的 PHP。这就是为什么甚至有一个配置开关。它应该从一开始就内置(具有“危险”行为)。
- 早期版本的 PHP 很幼稚,从技术上讲甚至不支持
但是 PHP 如何知道哪个部分是脚本以及它的路径信息是什么?如果 URI 是这样的:
http://example.com/path/to/script.php/THIS/IS/PATH/INFO.php?q=foo
- 在某些环境中,这可能是一个复杂的问题。在 PHP 中,它会发现 URI 路径的第一部分与服务器的 docroot 下的任何内容都不对应。对于此示例,它会发现您的服务器上没有“/docroot/path/to/script.php/THIS”,但您肯定有“/docroot/path/to/script.php”,因此现在
SCRIPT_FILENAME
已经确定并PATH_INFO
获取其余部分。 - 现在,Nginx 文档和Hrvoje Špoljar 的回答(你不能对这样一个清晰的例子吹毛求疵)变得更加清晰:给出 Hrvoje 的例子(“http://example.com/foo.jpg/nonexistent.php"),PHP 在您的 docroot 上看到一个文件“/foo.jpg”,但是它没有看到任何名为“/foo.jpg/nonexistent.php”的东西,因此
SCRIPT_FILENAME
获取“/foo.jpg”(再次以 docroot 为前缀)并PATH_INFO
获取“/nonexistent.php”。
- 在某些环境中,这可能是一个复杂的问题。在 PHP 中,它会发现 URI 路径的第一部分与服务器的 docroot 下的任何内容都不对应。对于此示例,它会发现您的服务器上没有“/docroot/path/to/script.php/THIS”,但您肯定有“/docroot/path/to/script.php”,因此现在
现在我们应该已经清楚它为何以及如何会造成危险了:
- Web 服务器其实没有错——它只是将 URI 代理到 PHP,PHP 无意中发现“foo.jpg”实际上包含 PHP 内容,因此执行它(现在你被黑了!)。这是不是特别是 Nginx 本身。
- 这真实的问题在于,您允许不受信任的内容在未经清理的情况下被上传到某处,并且允许其他任意请求到达同一位置,而 PHP 在可以时会愉快地执行这些请求。
Nginx 和 Apache 可以构建或配置为阻止使用此技巧的请求,并且有很多关于如何做到这一点的示例,包括user2372674 的回答。这篇博客文章很好地解释了问题,但缺少正确的解决方案。
但是,最好的解决方案是确保 PHP-FPM 配置正确,这样它就不会执行以“.php”结尾的文件。值得注意的是,PHP-FPM 的最新版本(~5.3.9+?)将此作为默认设置,因此这种危险不再是大问题。
解决方案
如果您拥有最新版本的 PHP-FPM(~5.3.9+?),那么您不需要执行任何操作,因为下面的安全行为已经是默认的。
否则,找到 php-fpm 的www.conf
文件(可能/etc/php-fpm.d/www.conf
,取决于您的系统)。确保您有这个:
security.limit_extensions = .php
再说了,如今这在很多地方都是默认的。
请注意,这并不能阻止攻击者将“.php”文件上传到 WordPress 上传文件夹并使用相同技术执行该文件。您仍然需要为您的应用程序提供良好的安全性。
答案2
本质上没有这个,你可以将名为“foo.jpg”的php代码文件上传到网络服务器;然后像http://domain.tld/foo.jpg/nonexistent.php并且 Web 服务器堆栈会错误地说“哦;这是一个 PHP;我需要处理它”,它将无法找到 foo.jpg/nonexistent.php,因此它将返回到 foo.jpg 并将 foo.jpg 作为 php 代码处理。这很危险,因为它使系统很容易受到入侵;例如,任何允许上传图像的 Web 应用程序都会成为上传后门的工具。
关于使用带有 unix 套接字的 php-fpm 来避免这个问题;在我看来它并不能解决问题。
答案3
在里面Nginx 维基百科作为安全措施
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
包含在位置块中。在其他教程中
try_files $uri =404;
,它应该做同样的事情,但根据 Nginx wiki 的说法,它会产生问题。使用这些选项,cgi.fix_pathinfo=1
应该不会再有问题了。更多信息可以找到这里。