声明:本公众号文章大部分来源个人日常网安知识学习笔记总结,请勿利用文章内设计技术内容从事非法行为,如因此产生一切不良后果与文章作者与公众号无关。
0x00 Preface 前言
反序列化入门笔记,根据网上学习资料进行的学习总结。本文还不够完善,措辞可能有误,或者个人理解有误,若有大佬发现错误,烦请指出,非常感谢。
0x01 序列化/反序列化原理
一些当初学习的笔记,属于知识概括,简单来说就是:
writeObject()方法序列化
readObject()方法反序列化
一个对象要想被序列化,该对象所属的类必须实现Serializable接口
在这里插入代码片
序列化:对象 -> 字符串
反序列化:字符串 -> 对象
0x02 序列化/反序列化
根据上面所介绍的,这里是一个小案例
静态成员变量是不能被序列化,序列化是针对对象属性,而静态成员变量是属于类的(transient标识的对象成员变量不参与序列化)
1、一个对象要想被序列化,该对象所属的类必须实现Serializable接口。
一个实体类,用于实例化一个Person对象,可以看到这个实体类实现了Serializable接口。
import java.io.Serializable;
public class Person implements Serializable{
private String name;
private int age;
public Person(){
}
public Person(String name,int age){
this.name=name;
this.age=age;
}
public String toString(){
return "Person{" +
"name=" +name +'\''+
",age=" +age + '}';
}
}
2、writeObject()方法序列化
序列化一个对象,其中存放姓名与年龄
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class SerializationTest {
//将序列化功能封装进了 serialize这个方法里面,在序列化当中
//通过这个FileOutputStream输出流对象,将序列化的对象输出到ser.bin当中。再调用 oos 的writeObject方法,将对象进行序列化操作。
public static void serialize(Object obj)throws IOException{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));//文件输出流
oos.writeObject(obj);//序列化
}
public static void main(String[] agrs)throws Exception{
Person person =new Person("aa",22);
//System.out.println(person);
serialize(person);
}
}
3、readObject()方法反序列化
反序列化一个对象文件,取出其中存放的个人数据
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UnSerializeTest {
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));//文件输入流
Object obj=ois.readObject();//反序列化
return obj;
}
public static void main(String[] args) throws Exception{
Person person=(Person)unserialize("ser.bin");
System.out.println(person);
}
}
反序列化结果:
0x03 反序列化攻击情况
可能的形式:
1、入口的readObject直接调用危险方法(基本不可能)
2、入口类参数中包含可控类,该类有危险方法,readObject时调用。(也不多见)
3、入口类参数中包含可控类,该类又调用其他危险方法的类,readObject时调用。
比如类型定义为Object,调用equals();hashcode();toString()
(重点 相同类型 同名函数)
4、构造函数/静态代码块等类加载隐式执行。
第一种
最简单,很多时候也意味着几乎遇不到
在实体类里面重写(也许应该叫自定义)readObject()方法
如下,在实体类里面写了一个readObject(),其中写了一个执行系统命令的语句(由以上Person进行小改造),与前面步骤保持一致先序列化,再反序列化,在反序列化过程中弹出计算器。
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable{
private String name;
private int age;
public Person(){
}
public Person(String name,int age){
this.name=name;
this.age=age;
}
public String toString(){
return "Person{" +
"name=" +name +'\''+
",age=" +age + '}';
}
//类里面写了一个readObject()其中写了一个执行系统命令的语句,弹计算器
private void readObject(ObjectInputStream ois)throws IOException,ClassNotFoundException{
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
}
当将其序列化后,再反序列化时,执行了其中的语句
0x04 实现反序列化攻击路线
:
首先的攻击前提:继承Serializable
1、入口类 source(重写readObject 调用常见函数 参数类型宽泛 最好jdk自带)
2、调用链gadget chain 相同名称 相同类型
3、执行类 sink (rce/ssrf写文件等等)
以 HashMap 为例说明一下,仅仅只是说明如何找到入口类
以HashMap为例进行说明。
首先,攻击前提,那必然是要继承了Serializable这个接口,要不然谈何序列化与反序列化对吧。
HashMap 确实继承了Serializable这个接口。
入口类这里比较难懂,还是以HashMap为例吧,这些步骤是要自己动手实操一下。
打开 “Structure”,找到重写的readObject,往下分析。
我们看到第 1407 行与 1409 行中,Key 与 Value 的值执行了readObject的操作,再将 Key 和 Value 两个变量扔进hash这个方法里,重新计算key的hash值,我们再跟进(ctrl+鼠标左键即可) hash 方法当中。
若传入的参数 key 不为空,则h = key.hashCode(),于是乎,继续跟进hashCode当中。
hashCode 位置处于 Object 类当中,满足我们 调用常见的函数这一条件。
重写了hashCode 、equals、toString之类的方法并存在潜在的危险函数,并且这个类还可以反序列化,那么可能就出现在利用链上的类
关注:卡比兽网安笔记 精彩内容 等你发现