java安全-CC1进阶
java安全-CC1进阶
一、官方CC1
ysoserial官方链为
1 | https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java |
1 | /* |
二、主要逻辑
官方CC1没有使用我们一开始的类,而是用到了LazyMap.java这个类文件
- 入口类
readObject类没有变,依旧是sun.reflect.annotation.AnnotationInvocationHandler这个类下的readObject方法 - 最终危险调用结束也没有改变,依旧是
ChainedTransformer.transform()这个链式方法执行命令
改变的是中间逻辑lazyMap的调用
具体过程如下(危险到入口)
1.ChainedTransformer#transform方法的调用
这里,官方选取了org/apache/commons/collections/map/LazyMap.java这个类中的get方法

这个方法的条件一般都是容易满足,只要没有key对应的value,如果满足这个条件就会接着调用 factory 的 transform 方法,而这个transform类是可控的

利用此方法创建LazyMap对象时,传入第二个参数便可以控制为new ChainedTransformer(chuan) 这个恶意类
1 | Map<Object,Object> lazyMap = LazyMap.decorate(map,new ChainedTransformer(chuan)); |
2.lazyMap#get方法的调用
通过追溯,找到了sun/reflect/annotation/AnnotationInvocationHandler.java类下invoke方法会调用传入类(可控)的get 方法

这里我们通过反射构造 sun.reflect.annotation.AnnotationInvocationHandler 类时,构造器传第二个参数为 lazyMap 对象即可
1 | InvocationHandler h = (InvocationHandler) constructor.newInstance(Override.class,lazyMap); |
3.AnnotationInvocationHandler#invoke方法的调用
注意到此类实现了InvocationHandler接口,于是可以作为动态代理时,处理被代理对象的类

1 | InvocationHandler h = (InvocationHandler) constructor.newInstance(Override.class,lazyMap); |
我们在调用 lazyMap 中的方法时,便会先调用 InvocationHandler 中的逻辑,也就会触发 invoke 方法中的 get 方法
具体逻辑
创建恶意Map集合
1
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ChainedTransformer(chuan));
创建代理利用时的重写 invoke 方法的继承 InvocationHandler 的类
1
InvocationHandler h = (InvocationHandler) constructor.newInstance(Override.class,lazyMap);
这个与我们之前创建代理类时相同,不同的是我们不需要手动创建,这里直接使用
AnnotationInvocationHandler#invoke类直接作为创建代理对象时的第三个参数也就是拦截器创建代理类
这里我们使用
lazyMap的类加载器作为第一个参数,因为我们要代理它嘛,实现的接口为 Map ,最终第三个参数为处理类1
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
代理触发点

AnnotationInvocationHandler 类在反序列化时,会调用 readObject 方法,进而调用 memberValues.entrySet() 方法,随后变走到了动态代理里面,因此我们构造使得 memberValues 为mapProxy 对象
1
Object obj2 = constructor.newInstance(Override.class,mapProxy);
由此,链式调用结束,实现恶意类的调用
三、poc
1 | Class r = Runtime.class; |
1.链路回顾
1 | readObject() |
2.额外
官方在最后才修改了 ChainedTransformer(chuan)#transform(key) 中的 chuan 参数,防止,在序列化之前调用Map的某些方法导致先行执行命令,造成干扰

**官方在chain链的最后加入了一个 new ConstantTransformer(1) } **

这么做的目的,理论上是为了防止恶意类被检测到
没做之前异常信息为
1
Exception in thread "main" java.lang.ClassCastException: java.lang.ProcessImpl cannot be cast to java.util.Set
这里有一个java.lang
添加之后

变为了 Integer
3.完美
1 | Class r = Runtime.class; |
4.JDK8u71
在 JDK 8u71 这个版本中,Oracle 修改了 AnnotationInvocationHandler 的源码
Oracle 在 8u71(以及 7u95, 6u111)中重写了 readObject 方法。原本是为了解决另一个无关的 bug,但副作用是彻底改变了数据的读取方式,大概如下
1 | // JDK >= 8u71 |
LazyMap
这个不能用的原因是因为
jdk8u71重写了AnnotationInvocationHandler的readObject方法
这里我们传入的 LazyMapProxy 不在是通过默认的defaultreadfield方法去构建,而是通过自定义流的情况下获取我们传入的恶意类 LazyMapProxy 并赋值为 streamVals ,之后调用entrySet方法,进到了我们重写的 invoke 方法
可能性1
此时,readObject逻辑没有执行完, memberValues 这个成员变量当然也还没有赋值,所以会产生空指针异常,爆炸
可能性2(更加稳妥)
但是此时,我们创建代理对象恶意类的时候
1
InvocationHandler h = (InvocationHandler) constructor.newInstance(Override.class,lazyMap);
这个,新版本重写了 readobject 逻辑,导致最后把新生成的、干净的 Map 赋给 this.memberValues,而不是我们最开始的恶意代理类LazyMapProxy
进而在 invoke 里调用put方法时,不会触发我们的恶意方法链
新版代码处理后,
AnnotationInvocationHandler对象在内存里复活时,它的memberValues变成了一个普通的LinkedHashMap
TransformerMap
这个就更别说了,新版本直接删除了这个类的触发方法 setValue
jdk8u71

jdk8u65
