序列化与反序列化
seriallization 序列化 : 将对象转化为便于传输的格式, 常见的序列化格式:二进制格式,字节数组,json字符串,xml字符串。
deseriallization 反序列化:将序列化的数据恢复为对象的过程。
PHP反序列化
serialize()将对象序列化成字符串。
unserialize()将字符串反序列化回对象。
序列化
对象转换成字符串
方便传输
反序列化
字符串转换成对象
O:object
//创建类
class Stu{
public $name;
public $age;
public $sex;
}
//创建对象
$stu1 = new Stu();
$stu1->name = "wuhu";
$stu1->age = 18;
$stu1->sex = true;
var_dump($stu1);
echo "
";
echo serialize($stu1);
?>
类中的魔术方法,在特定情况下会自动调用。即使魔术方法在类中没有被定义,也是真实存在的。
两个下划线:
以_开头的函数,是PHP中的魔术方法,是为了更快的响应。__wakeup()和__sleep()方法在互联网上会被频繁调用。
反序列化漏洞不能通过黑盒测试来查找漏洞。只能通过白盒测试,也就是代码审计。
反序列化漏洞攻击者是不可控的,因为功能代码全是开发者自己写的。
class animal{
public $name;
public $age;
public function __sleep(){
if(@$_GET['cmd']=="abc"){
a();
}
}
}
$an1=new animal;
$an1->name="dog";
$an1->age=3;
//var_dump($an1);
@serialize($an1);
?>
序列化和反序列化本身是为了实现数据在网络上完整高效的传输,但是由于反序列化过程中,对象的魔术方法会自动调用,魔术方法本身调用了别的方法,最终呈现一种链式调用,直到执行任意的代码或者命令。并且危险函数的参数可控。
为了满足一定的条件以后实现链式调用。
序列化:
ObjectOutputStream --> writeObject()
反序列化:
ObjectInputStream --> readObject()
实验
Person类
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
public int age;
public String name;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Runtime.getRuntime().exec("calc");
// 默认的反序列化操作
in.defaultReadObject();
}
}
注意:想让某个类执行序列化或者反序列化必须实现Serializable接口。
Test类
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person p=new Person();
p.age=18;
p.name="wuhu";
//将Person这个类的对象序列化后存放到wuhu.bin文件中
//没有指定路径就表示存放到当前路径中
serialize(p,"wuhu.bin");
System.out.println("反序列化结果:" + deserialize("xiu.bin"));
}
//序列化
public static void serialize(Object obj, String filePath) throws IOException {
try (FileOutputStream fileOut = new FileOutputStream(filePath);
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut)) {
objectOut.writeObject(obj);
}
}
//反序列化
public static Object deserialize(String filePath) throws IOException, ClassNotFoundException {
try (FileInputStream fileIn = new FileInputStream(filePath);
ObjectInputStream objectIn = new ObjectInputStream(fileIn)) {
return objectIn.readObject();
}
}
}
序列化后的字符串
说明:不同的编程语言的序列化和反序列化后的数据格式不一样。
将Person类中的readObject方法注释了执行的时候,输出结果就是输出了反序列化后的结果。
那么如果重写readObject方法后,在方法中添加一个Runtime.getRuntime().exec("calc");
这行代码使用 Java 的 Runtime
类执行操作系统命令,即在 Windows 系统上运行计算器应用程序(calc)。执行结果如下:
在实际的开发环境中,需要重写readObject()方法,例如:从前端传递的值是base64编码的,而系统的方法不识别base64,所以需要在重写的readObject()方法中先解码,将解码完的对象再去调用系统的方法。
Java反序列漏洞的成因是函数的链式调用,满足链式调用的全部条件,并且参数可控制。以及重写了readObject()方法,才能造成Java反序列漏洞。(条件苛刻)。当然如果没有重写readObject()方法,执行的就是系统的反序列化方法,也就没有了反序列化漏洞了。
PHP的反序列化和java的反序列化是两种不同的类型,序列化和反序列化本身没有漏洞点,只是为了实现数据的完整高效的传输。
PHP反序列漏洞是由于类里面的魔术方法调用了某个函数,该危险函数又调用了别的函数,最终执行到了危险函数的位置。
JAVA反序列化漏洞是由于开发者重写了readObject方法,该readObject方法方法调用了别的方法,最终执行到了例如Transfrom方法的危险方法(链式调用)。
shiro :爆破密钥,找寻CC利用链