反序列化初探
# PREFACE:
# 概念
序列化:把对象转换为字节序列的过程,即把对象转换为可以存储或传输的数据的过程。例如将内存中的对象转换为二进制数据流或文件,在网络传输过程中,可以是字节或是 XML 等格式。
反序列化:把字节序列恢复为对象的过程,即把可以存储或传输的数据转换为对象的过程。例如将二进制数据流或文件加载到内存中还原为对象。
# 漏洞成因
在身份验证,文件读写,数据传输等功能处,在未对反序列化接口做访问控制,未对序列化数据做加密和签名,加密密钥使用硬编码(如 Shiro 1.2.4),使用不安全的反序列化框架库(如 Fastjson 1.2.24)或函数的情况下,由于序列化数据可被用户控制,攻击者可以精心构造恶意的序列化数据(执行特定代码或命令的数据)传递给应用程序,在应用程序反序列化对象时执行攻击者构造的恶意代码,达到攻击者的目的。
# 漏洞可能出现的位置
1. 解析认证 token、session 的位置
2. 将序列化的对象存储到磁盘文件或存入数据库后反序列化时的位置,如读取 json 文件,xml 文件等
3. 将对象序列化后在网络中传输,如传输 json 数据,xml 数据等
4. 参数传递给程序
5. 使用 RMI 协议,被广泛使用的 RMI 协议完全基于序列化
6. 使用了不安全的框架或基础类库,如 JMX 、Fastjson 和 Jackson 等
7. 定义协议用来接收与发送原始的 java 对象
询问了 web 手相关事宜,反序列化大概考点是 python、php、java 三种语言
在 Python 和 PHP 中,一般通过构造一个包含魔术方法(在发生特定事件或场景时被自动调用的函数,通常是构造函数或析构函数)的类,然后在魔术方法中调用命令执行或代码执行函数,接着实例化这个类的一个对象并将该对象序列化后传递给程序,当程序反序列化该对象时触发魔术方法从而执行命令或代码。
在 Java 中没有魔术方法,但是有反射(reflection)机制:在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法,这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。一般利用反射机制来构造一个执行命令的对象或直接调用一个具有命令执行或代码执行功能的方法实现任意代码执行。
python:pickle 模块
php:serialize 函数
# php
php class 序列化后:
public:属性被序列化的时候属性值会变成 属性名
protected:属性被序列化的时候属性值会变成 \\x00*\\x00属性名
private:属性被序列化的时候属性值会变成 \\x00类名\\x00属性名
php 魔术方法:
__construct() //类的构造函数,创建对象时触发 | |
__destruct() //类的析构函数,对象被销毁时触发 | |
__call() //在对象上下文中调用不可访问的方法时触发 | |
__callStatic() //在静态上下文中调用不可访问的方法时触发 | |
__get() //读取不可访问属性的值时,这里的不可访问包含私有属性或未定义 | |
__set() //在给不可访问属性赋值时触发 | |
__isset() //当对不可访问属性调用 isset() 或 empty() 时触发 | |
__unset() //在不可访问的属性上使用unset()时触发 | |
__invoke() //当尝试以调用函数的方式调用一个对象时触发 | |
__sleep() //执行serialize()时,先会调用这个方法 | |
__wakeup() //执行unserialize()时,先会调用这个方法 | |
__toString() //当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用 |
serialize () 函数会检查类中是否存在一个魔术方法。如果存在,该方法会先被调用,然后才执行序列化操作。
从序列化到反序列化这几个函数的执行过程是:
__construct()` ->`__sleep()` -> `__wakeup()` -> `__toString()` -> `__destruct()
_sleep
方法在一个对象被序列化时调用, _wakeup
方法在一个对象被反序列化时调用
__tostring () 触发时机:
1. echo($obj)/print($obj)打印时会触发 | |
2. 反序列化对象与字符串连接时 | |
3. 反序列化对象参与格式化字符串时 | |
4. 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型) | |
5. 反序列化对象参与格式化SQL语句,绑定参数时 | |
6. 反序列化对象在经过php字符串处理函数,如strlen()、strops()、strcmp()、addslashes()等 | |
7. 在in_array()方法中,第一个参数时反序列化对象,第二个参数的数组中有__toString()返回的字符串的时候__toString()会被调用 | |
8. 反序列化的对象作为class_exists()的参数的时候 |
PHP 序列化需注意以下几点:
1、序列化只序列属性,不序列方法 2、因为序列化不序列方法,所以反序列化之后如果想正常使用这个对象的话我们必须要依托这个类要在当前作用域存在的条件 3、我们能控制的只有类的属性,攻击就是寻找合适能被控制的属性,利用作用域本身存在的方法,基于属性发动攻击
4、PHP 对象是存放在内存的堆空间段上的,PHP 文件在执行结束的时候会将对象销毁。
# CVE-2016-7124
CVE-2016-7124:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup 的执行
# java 反序列化
Java 中通常使用 Java.io.ObjectOutputStream
类中的 writeObject
方法进行序列化, java.io.ObjectInputStream
类中的 readObject
方法进行反序列化。使用下面代码将字符串进行序列化和反序列化:
package com.company;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;
public class Main{
public static void main(String args[]) throws Exception {
String obj = "hello";
// 将序列化后的数据写入文件a.ser中,当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名
FileOutputStream fos = new FileOutputStream("a.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(obj);
os.close();
// 从文件a.ser中读取数据
FileInputStream fis = new FileInputStream("a.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
// 通过反序列化恢复字符串
String obj2 = (String)ois.readObject();
System.out.println(obj2);
ois.close();
}
}
程序执行后生成 a.ser 文件
以十六进制查看 a.ser 文件内容,如图:
ac ed 00 05
是 java 序列化内容的特征,如果经过 base64 编码,那么相对应的是 rO0AB
:
一个 Java 类的对象要想序列化成功,必须满足两个条件:
1. 该类必须实现 java.io.Serializable
接口。
2. 该类的所有属性必须是可序列化的,如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
# java 反序列化漏洞成因
暴露或间接暴露反序列化 API ,导致用户可以操作传入数据,攻击者可以精心构造反序列化对象并执行恶意代码
反序列化时会调用 readObject () 函数,如果重写了 readObject 函数,并且里面含有恶意代码,那么在反序列化时调用这个函数就会直接执行恶意代码。
import java.io.*; | |
class MyObject implements Serializable{ | |
public String name; | |
//重写readObject()方法 | |
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException, IOException { | |
//执行默认的readObject()方法 | |
in.defaultReadObject(); | |
//执行打开计算器程序命令 | |
Runtime.getRuntime().exec("calc.exe"); | |
} | |
} | |
public class testSerialize { | |
public static void main(String args[]) throws Exception{ | |
//定义myObj对象 | |
MyObject myObj = new MyObject(); | |
myObj.name = "hi"; | |
//创建一个包含对象进行反序列化信息的”object”数据文件 | |
FileOutputStream fos = new FileOutputStream("object"); | |
ObjectOutputStream os = new ObjectOutputStream(fos); | |
//writeObject()方法将myObj对象写入object文件 | |
os.writeObject(myObj); | |
os.close(); | |
//从文件中反序列化obj对象 | |
FileInputStream fis = new FileInputStream("object"); | |
ObjectInputStream ois = new ObjectInputStream(fis); | |
//恢复对象 | |
MyObject objectFromDisk = (MyObject)ois.readObject(); | |
System.out.println(objectFromDisk.name); | |
ois.close(); | |
} | |
} |
这里定义了一个 Myobject 类并继承了 Serializable 接口,并且重写了 readObject 方法。在反序列化时会执行 readObject 方法。在 readObject () 方法中写入了 Runtime.getRuntime ().exec (“calc.exe”),在反序列化时就会执行相应的命令。
这么看起来原理挺简单的,不过实际上就没有这种运气了,都得靠自己构造链子,不过这里先不探讨了,这里只是入个门…
这篇打算来看看,用工具比较多:CTFSHOW web 入门 java 反序列化篇(更新中)_ctfshow java 反序列化 - CSDN 博客
参考:
PHP 反序列化漏洞详解(万字分析、由浅入深)_php 反序列化漏洞原理 - CSDN 博客