一、前置知识
阅读本文前应该先去了解,什么是类,什么是对象,推荐搜索关键词,php对象和类,java对象和类
二、反序列化
用大白话来讲,序列化就是把(类的实例化对象)对象序列化成字符串,反序列化就是把字符串又转化回对象。打个比方,序列化就是把你洗菜,做菜,炒菜最后做出一盘红烧排骨的一系列动作,写成菜谱。而反序列化就是你拿着菜谱,按照菜谱的步骤又做出一盘红烧排骨。
1.php反序列化
serialize():序列化函数 unserialize():反序列化函数
下面我先用php代码举个栗子。
代码语言:javascript复制
//序列化
<?php
//类meat
class meat
{
var $say = "tastes delicious!";
var $cost = 198;
function welcome()
{
echo 'nice to meet you.';
}
}
$pork = new meat();
echo serialize($pork);
?>
访问这个php文件,就得出了序列化之后的字符串
代码语言:javascript复制O:4:"meat":2:{s:3:"say";s:17:"tastes delicious!";s:4:"cost";i:198;}
//说明如下
O:4:"meat":2:
O:object 说明我们序列化的是一个对象,后面的4代表类名占四位字符,类的名字为meat,2代表类有两个变量
s:3:"say";s:17:"tastes delicious!";
第一个变量名为字符串,占三个字符,为say,第一个变量值为字符串,占17个字符为tastes delicious!
s:4:"cost";i:198;
第二个变量名为字符串占四个字符,为cost,第二个变量值为整形,为198
从序列化的字符串中我们可以看出来,序列化的字符串中只含有类名还有类中变量的信息,没有类中函数的信息,之所以这么做的原因是同一类对象中每个对象都具备相同的函数,但是每个对象的变量值却不一定相同,所以我们序列化时只要保存对象的变量就可以了。
思考一下,是不是我们如果能控制序列化后的字符串,我们就能控制反序列化后的对象。下面将刚刚序列化后的字符串反序列化回去。
代码语言:javascript复制<?php
//类meat
class meat
{
var $say = "tastes delicious!";
var $cost = 198;
function welcome()
{
echo 'nice to meet you.';
}
}
//注意反序列化时当前类的定义一定要在当前文件中
$str = 'O:4:"meat":2:{s:3:"say";s:17:"tastes delicious!";s:4:"cost";i:198;}';
$pork = unserialize($str);
$pork->welcome();
echo '<br/>';
var_dump($pork);
?>
代码运行结果如下,这就是一整个序列化和反序列化的过程
2.php魔术方法
从上文看到,我们能控制的只有序列化后的字符串,序列化的字符串只能控制对象中的变量,反序列化后,能不能调用对象中的函数,不是我们能控制的,源码中对象的成员函数有没有调用,是在源码中写死的,所以此时就引出了魔术方法。
魔术方法,之所以被冠名为魔术方法,就因为它们很神奇,神奇的地方就是,它们不需要手动调用,只要满足了触发条件,就能自动被调用。
代码语言:javascript复制__construct():当一个对象创建时被调用
__destruct():当一个对象销毁时被调用
__toString():当对象被当作字符串时被调用
__sleep():当对象被序列化时被调用
__wakeup():当对象被反序列化时被调用
__get():当调用一个未定义的属性时被调用
__set():给一个未定义的属性赋值时调用
__invoke():以调用函数的方式调用一个对象时被调用。
如果我们控制的序列化字符串中的变量,在这些魔术方法中的话,魔术方法被触发,我们的恶意参数就会生效,这就是反序列化漏洞。
下面我们简单演示一下魔术方法是如何被触发的。
代码语言:javascript复制<?php
//类meat
class meat
{
var $say = "tastes delicious!";
var $cost = 198;
function welcome()
{
echo 'nice to meet you.';
}
//当对象被反序列化时被调用
function __wakeup()
{
echo $this->cost;
echo '<br/>';
}
}
$str = 'O:4:"meat":2:{s:3:"say";s:17:"tastes delicious!";s:4:"cost";i:99999;}';
$pork = unserialize($str); 、
$pork->welcome();
echo '<br/>';
var_dump($pork);
?>
此时我们就是修改了序列化字符串中变量的值,将它从198修改成了99999,刚好该参数在魔术方法__wakeup中,在反序列化时此魔术方法被触发,这样子我们的恶意参数就生效了,原本程序想要输出198,结果被我们篡改成了99999。
3.java反序列化
java反序列化和php反序列化根本原理都是一样的,这不过其中的函数有不同。
java规定如果一个对象想要进行序列化操作,那么这个对象对应的类必须实现序列化接口,也就是Serializable
接口。一个可以被序列化的对象的类定义如下:
代码语言:javascript复制import java.io.Serializable;
public class Person implements Serializable {
private String name;
}
java的序列化使用的是ObjectOutputStream(对象输出流)类的writeObject()方法。
java的反序列化使用的是ObjectInputStream(对象输入流)类的readObject()方法。
3.1序列化过程
代码语言:javascript复制public class Person implements Serializable
{
private String name;
private int age;
Person(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public String toString()
{
return "姓名:" name "年龄:" age;
}
}
public static void main(String[] args) throws IOException
{
//实例化Person对象
Person jack = new Person("jack", 12);
//生成一个文件对象,文件不存在将自动创建文件
File f = new File("F:" File.separator "serTest.txt");
//构造一个对象输出流oos
ObjectOutputStream oos = null;
//构造一个文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(f);
//构造对象输出流
oos = new ObjectOutputStream(fileOutputStream);
//序列化一个对象到文件变成二进制内容,二进制字节流文件,直接打开是乱码
oos.writeObject(jack);
oos.close();
}
3.2反序列化过程
代码语言:javascript复制public class Person implements Serializable
{
private String name;
private int age;
Person(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public String toString()
{
return "姓名:" name "年龄:" age;
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException
{
//生成一个文件对象
File f = new File("F:" File.separator "serTest.txt");
//构建对象输入流对象
ObjectInputStream oos = null;
//构建文件输入流对象
FileInputStream fileOutputStream = new FileInputStream(f);
oos = new ObjectInputStream(fileOutputStream);
//读取序列化
Person jack=(Person)oos.readObject();
System.out.println(jack);
}
3.3反序列化漏洞demo
如果类是这么写的,在反序列化的时候就会弹出计算器了。
代码语言:javascript复制public class Person implements Serializable
{
private String name;
private int age;
Person(String name, int age)
{
this.name = name;
this.age = age;
}
@Override
public String toString()
{
return "姓名:" name "年龄:" age;
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
// 执行默认的 readObject() 方法
in.defaultReadObject();
// 执行打开计算器程序命令
Runtime.getRuntime().exec("calc");
}
}