刷題記錄:[LCTF]bestphp's revenge
題目復現鏈接:https://buuoj.cn/challenges
參考鏈接:https://xz.aliyun.com/t/3341#toc-22
從LCTF WEB簽到題看PHP反序列化
LCTF2018-bestphp's revenge 詳細題解
這是LCTF的web簽到題??打擾了。現在一天一題已經有點跟不上了。。。。
一、知識點
這幾個知識點環環相扣形成利用鏈,所以我一起講了
session反序列化->soap(ssrf+crlf)->call_user_func激活soap類
1、SoapClient觸發反序列化導致ssrf
2、serialize_hander處理session方式不同導致session注入
3、crlf漏洞
二、解題思路
首先,php反序列化沒有可利用的類時,可以調用php原生類,參考
反序列化之PHP原生類的利用,
貼上源碼和poc講
//index.php
<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET[f],$_POST);
session_start();
if(isset($_GET[name])){
$_SESSION[name] = $_GET[name];
}
var_dump($_SESSION);
$a = array(reset($_SESSION),'welcome_to_the_lctf2018');
call_user_func($b,$a);
?>
//flag.php
session_start();
echo 'only localhost can get flag!';
$flag = 'LCTF{*************************}';
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
$_SESSION['flag'] = $flag;
}
only localhost can get flag!
很容易想到f
傳入extract
覆蓋b為我們想要的函數,問題是后面session的利用。
先說SoapClient
,參考從幾道CTF題看SOAP安全問題
SOAP(簡單對象訪問協議)是連接或Web服務或客戶端和Web服務之間的接口。
其采用HTTP作為底層通訊協議,XML作為數據傳送的格式
SOAP消息基本上是從發送端到接收端的單向傳輸,但它們常常結合起來執行類似於請求 / 應答的模式。
那么如果我們能通過反序列化調用SoapClient
向flag.php
發送請求,那么就可以實現ssrf
接下要解決的問題是:
- 在哪觸發反序列化
- 如何控制反序列化的內容
這里要知道call_user_func()
函數如果傳入的參數是array
類型的話,會將數組的成員當做類名和方法,例如本題中可以先用extract()
將b覆蓋成call_user_func()
,reset($_SESSION)
就是$_SESSION['name']
,我們可以傳入name=SoapClient
,那么最后call_user_func($b, $a)
就變成call_user_func(array('SoapClient','welcome_to_the_lctf2018'))
,即call_user_func(SoapClient->welcome_to_the_lctf2018)
,由於SoapClient
類中沒有welcome_to_the_lctf2018
這個方法,就會調用魔術方法__call()
從而發送請求
那SoapClient
的內容怎么控制呢,貼上大佬的poc
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4\r\n",
'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
這里又涉及到crlf,參考[轉載]CRLF Injection漏洞的利用與實例分析,我的理解是因為http請求遇到兩個\r\n
即%0d%0a
,會將前半部分當做頭部解析,而將剩下的部分當做體,那么如果頭部可控,就可以注入crlf實現修改http請求包。如果我的理解有錯,請大佬指正。
這個poc就是利用crlf偽造請求去訪問flag.php並將結果保存在cookie為PHPSESSID=tcjr6nadpk3md7jbgioa6elfk4
的session中。
最后一點,就是如何讓php反序列化結果可控。這里涉及到php反序列的機制。
php中的session中的內容並不是放在內存中的,而是以文件的方式來存儲的,存儲方式就是由配置項session.save_handler來進行確定的,默認是以文件的方式存儲。
存儲的文件是以sess_sessionid來進行命名的,文件的內容就是session值的序列話之后的內容。
在php.ini中存在三項配置項:
session.save_path="" --設置session的存儲路徑
session.save_handler="" --設定用戶自定義存儲函數,如果想使用PHP內置會話存儲機制之外的可以使用本函數(數據庫等方式)
session.serialize_handler string --定義用來序列化/反序列化的處理器名字。默認是php(5.5.4后改為php_serialize)
PHP內置了多種處理器用於存儲$_SESSION數據時會對數據進行序列化和反序列化,常用的有以下三種,對應三種不同的處理格式:
處理器 | 對應的存儲格式 |
---|---|
php | 鍵名 + 豎線 + 經過serialize()函數反序列化處理的值 |
php_binary | 鍵名的長度對應的ASCII字符 + 鍵名 + 經過serialize()函數反序列化處理的值 |
php_serialize(php>=5.5.4) | 經過serialize()函數反序列處理的數組 |
配置選項 session.serialize_handler,通過該選項可以設置序列化及反序列化時使用的處理器。
如果PHP在反序列化存儲的$_SEESION數據時的使用的處理器和序列化時使用的處理器不同,會導致數據無法正確反序列化,通過特殊的偽造,甚至可以偽造任意數據。
當存儲是php_serialize處理,然后調用時php去處理,如果這時注入的數據時a=|O:4:"test":0:{}
,那么session中的內容是a:1:{s:1:"a";s:16:"|O:4:"test":0:{}";}
,那么a:1:{s:1:"a";s:16:"
會被php解析成鍵名,后面就是一個test對象的注入。
正好我們一開始的call_user_func
還沒用,可以構造session_start(['serialize_handler'=>'php_serialize'])
達到注入的效果。
三、解題步驟
先注入poc得到的session
觸發反序列化使SoapClient發送請求
攜帶poc中的cookie訪問即可得到flag