利用 PHP7 的 OPcache 執行 PHP 程式碼

wyzsk發表於2020-08-19
作者: Her0in · 2016/04/29 11:45

from:http://blog.gosecure.ca/2016/04/27/binary-webshell-through-opcache-in-php-7/

在 PHP 7.0 釋出之初,就有不少 PHP 開發人員對其效能提升方面非常關注。在引入 OPcache 後,PHP的效能的確有了很大的提升,之後,很多開發人員都開始採用 OPcache 作為 PHP 應用的加速器。OPcache 帶來良好效能的同時也帶來了新的安全隱患,下面的內容是 GoSecure 部落格發表的一篇針對 PHP 7.0 的 OPcache 執行 PHP 程式碼的技術博文。

本文會介紹一種新的在 PHP7 中利用預設的 OPcache 引擎實施攻擊的方式。利用此攻擊向量,攻擊者可以繞過“Web 目錄禁止檔案讀寫”的限制 ,也可以執行他自己的惡意程式碼。

0x00 OPcache 利用方式簡介


OPcache 是 PHP 7.0 中內建的快取引擎。它透過編譯 PHP 指令碼檔案為位元組碼,並將位元組碼放到記憶體中。

使用 PHP7 加速 Web 應用

OPcache 快取檔案格式請看這裡

同時,它在檔案系統中也提供了快取檔案。
在 PHP.ini 中配置如下,你需要指定一個快取目錄:

opcache.file_cache=/tmp/opcache

在指定的目錄中,OPcache 儲存了已編譯的 PHP 指令碼檔案,這些快取檔案被放置在和 Web 目錄一致的目錄結構中。如,編譯後的 /var/www/index.php 檔案的快取會被儲存在 /tmp/opcache/[system_id]/var/www/index.php.bin 中。

system_id 是當前 PHP 版本號,Zend 擴充套件版本號以及各個資料型別大小的 MD5 雜湊值。在最新版的 Ubuntu(16.04)中,system_id 是透過當前 Zend 和 PHP 的版本號計算出來的,其值為 81d80d78c6ef96b89afaadc7ffc5d7ea。這個雜湊值很有可能被用來確保多個安裝版本中二進位制快取檔案的相容性。當 OPcache 在第一次快取檔案時,上述目錄就會被建立。
在本文的後面,我們會看到每一個 OPcache 快取檔案的檔案頭裡面都儲存了 system_id
有意思的是,執行 Web 服務的使用者對 OPcache 快取目錄(如:/tmp/opcache/)裡面的所有子目錄以及檔案都具有寫許可權。

#!shell
$ ls /tmp/opcache/
drwx------ 4 www-data www-data 4096 Apr 26 09:16 81d80d78c6ef96b89afaadc7ffc5d7ea

正如你所看到的,www-data 使用者對 OPcache 快取目錄有寫許可權,因此,我們可以透過使用一個已經編譯過的 webshell 的快取檔案替換 OPcache 快取目錄中已有的快取檔案來達到執行惡意程式碼的目的。

0x01 OPcache 利用場景


要利用 OPcache 執行程式碼,我們需要先找到 OPcache 的快取目錄(如:/tmp/opcache/[system_id])以及 Web 目錄(如:/var/www/)。
假設,目標站點已經存在一個執行 phpinfo() 函式的檔案了。透過這個檔案,我們可以獲得 OPcache 快取目錄, Web 目錄,以及計算system_id 所需的幾個欄位值。我寫了一個指令碼,可以利用 phpinfo() 計算出 system_id

另外還要注意,目標站點必須存在一個檔案上傳漏洞。
假設 php.ini 配置 opcache 的選項如下:

opcache.validate_timestamp = 0   ; PHP 7 的預設值為 1
opcache.file_cache_only = 1      ; PHP 7 的預設值為 0
opcache.file_cache = /tmp/opcache

此時,我們可以利用上傳漏洞將檔案上傳到 Web 目錄,但是發現 Web 目錄沒有讀寫許可權。這個時候,就可以透過替換 /tmp/opcache/[system_id]/var/www/index.php.bin 為一個 webshell的二進位制快取檔案執行 webshell。

  1. 在本地建立 webshell 檔案 index.php ,程式碼如下:

    #!php
    <?php 
       system($_GET['cmd']); 
    ?>
    
  2. 在 PHP.ini 檔案中設定 opcache.file_cache 為你所想要指定的快取目錄

  3. 執行 PHP 伺服器(php -S 127.0.0.1:8080) ,然後向 index.php 傳送請求(wget 127.0.0.1:8080),觸發快取引擎進行檔案快取。

  4. 開啟你所設定的快取目錄,index.php.bin 檔案即為編譯後的 webshell 二進位制快取檔案。

  5. 修改 index.php.bin 檔案頭裡的 system_id 為目標站點的system_id。在檔案頭裡的簽名部分的後面就是system_id的值。

  6. 透過上傳漏洞將修改後的 index.php.bin 上傳至 /tmp/opcache/[system_id]/var/www/index.php.bin ,覆蓋掉原來的 index.php.bin

  7. 重新訪問 index.php ,此時就執行了我們的 webshell

針對這種攻擊方式,在 php.ini 至少有兩種配置方式可以防禦此類攻擊。

  • 禁用 file_cache_only
  • 啟用 validate_timestamp

0x02 繞過記憶體快取(file_cache_only = 0)


如果記憶體快取方式的優先順序高於檔案快取,那麼重寫後的 OPcache 檔案(webshell)是不會被執行的。但是,當 Web 伺服器重啟後,就可以繞過此限制。因為,當伺服器重啟之後,記憶體中的快取為空,此時,OPcache 會使用檔案快取的資料填充記憶體快取的資料,這樣,webshell 就可以被執行了。

但是這個方法比較雞肋,需要伺服器重啟。那有沒有辦法不需要伺服器重啟就能執行 webshell 呢?

後來,我發現在諸如 WordPress 等這類框架裡面,有許多過時不用的檔案依舊在釋出的版本中能夠訪問。如: registration-functions.php

由於這些檔案過時了,所以這些檔案在 Web 伺服器執行時是不會被載入的,這也就意味著這些檔案沒有任何檔案或記憶體的快取內容。這種情況下,透過上傳 webshell 的二進位制快取檔案為 registration-functions.php.bin ,之後請求訪問 /wp-includes/registration-functions.php ,此時 OPcache 就會載入我們所上傳的 registration-functions.php.bin 快取檔案。

0x03 繞過時間戳校驗(validate_timestamps = 1)


如果伺服器啟用了時間戳校驗,OPcache 會將被請求訪問的 php 原始檔的時間戳與對應的快取檔案的時間戳進行對比校驗。如果兩個時間戳不匹配,快取檔案將被丟棄,並且重新生成一份新的快取檔案。要想繞過此限制,攻擊者必須知道目標原始檔的時間戳。
如上面所說的,在 WordPress 這類框架裡面,很多原始檔的時間戳在解壓 zip 或 tar 包的時候都是不會變的。

注意觀察上圖,你會發現有些檔案從2012年之後從沒有被修改過,如:registration-functions.php 和 registration.php 。因此,這些檔案在 WordPress 的多個版本中都是一樣的。知道了時間戳,攻擊者就可以繞過 validate_timestamps 限制,成功覆蓋快取檔案,執行 webshell。二進位制快取檔案的時間戳在 34位元組偏移處。

0x04 總結


OPcache 這種新的攻擊向量提供了一些繞過限制的攻擊方式。但是它並非一種通用的 PHP 漏洞。隨著 PHP 7.0 的普及率不斷提升,你將很有必要審計你的程式碼,避免出現上傳漏洞。並且檢查可能出現的危險配置項。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章