前言css
关于java反序列化漏洞的原理分析,基本都是在分析使用Apache Commons Collections这个库,形成的反序列化问题。然而,在下载老外的ysoserial工具并仔细看看后,我发现了许多值得学习的知识。html
至少能学到以下内容:java
不一样反序列化payload玩法灵活运用了反射机制和动态代理机制构造POCgit
java反序列化不只是有Apache Commons Collections这样一种玩法。还有以下payload玩法:github
CommonsBeanutilsCollectionsLogging1所需第三方库文件: commons-beanutils:1.9.2,commons-collections:3.1,commons-logging:1.2 CommonsCollections1所需第三方库文件: commons-collections:3.1 CommonsCollections2所需第三方库文件: commons-collections4:4.0 CommonsCollections3所需第三方库文件: commons-collections:3.1(CommonsCollections1的变种) CommonsCollections4所需第三方库文件: commons-collections4:4.0(CommonsCollections2的变种) Groovy1所需第三方库文件: org.codehaus.groovy:groovy:2.3.9 Jdk7u21所需第三方库文件: 只需JRE版本 <= 1.7u21 Spring1所需第三方库文件: spring框架所含spring-core:4.1.4.RELEASE,spring-beans:4.1.4.RELEASEweb
上面标注了payload使用状况下所依赖的包,诸位能够在源码中看到,根据实际状况选择。spring
经过对该攻击代码的分析,能够学习java的一些有意思的知识。并且,里面写的java代码也很值得学习,巧妙运用了反射机制去解决问题。老外写的POC仍是很精妙的。shell
准备工做apache
在github上下载ysoserial工具。使用maven进行编译成Eclipse项目文件,mvn eclipse:eclipse。要你联网下载依赖包,请耐心等待。若是卡住了,中止后再次执行该命令。api
导入后,能够看到里面有8个payload。其中ObjectPayload是定义的接口,全部的Payload须要实现这个接口的getObject方法。下面就开始对这些payload进行简要的分析。
payload分析
1. CommonsBeanutilsCollectionsLogging1
该payload的要求依赖包挺多的,可能碰到的状况不会太多,但用到的技术是极好的。对这个payload执行的分析,请阅读参考资源第一个的分析文章。
先直接看代码:
第一行代码final TemplatesImpl templates = Gadgets.createTemplatesImpl(command);建立了TemplatesImpl类的对象,里面封装了咱们须要的命令执行代码。并且是使用字节码的形式存储在对象属性中。
下面就具体分析下这个对象的产生过程。
(1) 利用TemplatesImpl类存储危险的字节码
在产生字节码时,用到了JDK中javassist类。具体了解能够参考这篇博客http://www.cnblogs.com/hucn/p/3636912.html。
下面是我编写的一个简单的样例程序,便于理解:
上述代码首先获取到class定义的容器ClassPool,并找到了我自定义的Point类,由今生成了cc对象。这样就能够开始对类进行修改的任意操做了。并且这个操做是直接写字节码。这样能够绕过许多安全机制,正像工具中注释说的:
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
后面的操做即是利用我自定义的模板类Point,生成新的类名,并使用insertAfter方法插入了恶意java代码,执行命令。有兴趣的能够再详细了解这个类的用法。这里再也不赘述。
这段代码运行后,会在当前目录生成字节码(class文件)。使用java反编译器可看到源码,在原始模板类中插入了恶意静态代码,并且以字节码的形式直接存储。命令行直接运行,能够执行弹出计算器的命令:
如今看看老外工具中,生成字节码的代码为:
根据以上样例分析,能够清楚看见:前面几行代码,即生成了咱们须要的插入了恶意java代码的字节码数据。该字节码其实能够看作是一个类(.class)文件。final byte[] classBytes = clazz.toBytecode();将其转成了二进制数据进行存储。
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes,ClassFiles.classAsBytes(Foo.class)});这里又来到了一个有趣知识,那就是java反射机制的强大。ysoserial工具封装了使用反射机制对对象的一些操做,能够直接借鉴。
具体能够看看其源码,这里在工具中常用的Reflections.setFieldValue(final Object obj, final String fieldName, final Object value);方法,即是使用反射机制,将obj对象的fieldName属性赋值为value。反射机制的强大之处在于:
能够动态对对象的私有属性进行改变赋值,即:private修饰的属性。动态生成任意类对象。
因而,咱们便将com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类生成的对象templates中的_bytecodes属性,_name属性,_tfactory属性赋值成咱们但愿的值。
重点在于_bytecodes属性,里面存储了咱们的恶意java代码。如今的问题即是:如何触发加载咱们的恶意java字节码?
(2) 触发TemplatesImpl类加载_bytecodes属性中的字节码
在TemplatesImpl类中存在执行链:
这在ysoserial工具中的注释中是能够看到的。在源码中,咱们从TemplatesImpl.getOutputProperties()开始跟踪,不难发现上面的执行链。最终会在getTransletInstance方法中看到以下触发加载自定义ja字节码部分的代码:
#!javaprivate Translet getTransletInstance()throws TransformerConfigurationException { ............. if (_class == null) defineTransletClasses();//经过ClassLoader加载字节码,存储在_class数组中。 // The translet needs to keep a reference to all its auxiliary // class to prevent the GC from collecting them AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();//新建实例,触发恶意代码。 ............
在defineTransletClasses()方法中,会加载咱们以前存储在_bytecodes属性中的字节码(能够看作类文件),进而返回类的Class对象,存储在_class数组中。下面是调试时候的截图:
能够看到在defineTransletClasses()后,获得类的Class对象。而后会执行newInstance()操做,新建一个实例,这样便触发了咱们插入的静态恶意java代码。若是接着单步执行,便会弹出计算器。
经过以上分析,能够看到:
只要可以自动触发TemplatesImpl.getOutputProperties()方法执行,咱们就能达到目的了。 (3) 利用BeanComparator比较器触发执行
咱们接着看payload的代码:
很简单,将PriorityQueue(优先级队列)插入两个元素,并且须要一个实现了Comparator接口的比较器,对元素进行比较,并对元素进行排队处理。具体能够看看PriorityQueue类的readObject()方法。
从对象反序列化过程原理,能够知道会首先调用该对象readObject()。固然在序列化过程当中会首先调用该对象的writeObject()方法。这两个方法能够对比着看,方便理解。
首先,在序列化PriorityQueue类实例时,会依次读取队列中的对象,并放到数组中进行存储。queue[i] = s.readObject();而后,进行排序操做heapify();。最终会到达这里,调用比较器的compare()方法,对元素间进行比较。
这里传进去的,即是BeanComparator比较器:位于commons-beanutils包。
因而,看看比较器的compare方法。
o1,o2即是要比较的两个对象,property即咱们须要比较对象中的属性(可控)。一开始property赋值为lowestSetBit,后来改为真正须要的outputProperties属性。
PropertyUtils.getProperty( o1, property )顾名思义,即是取出o1对象中property属性的值。而实际上会去调用o1.getProperty()方法获得property属性值。
到这里,能够画上完美的一个圈了。咱们只需将前面构造好的TemplatesImpl对象添加到PriorityQueue(优先级队列)中,而后设置比较器为BeanComparator("outputProperties")便可。
那么,在反序列化过程当中,会自动调用TemplatesImpl.getOutputProperties()方法。执行命令了。
我的总结观点:
只须要想办法:自动调用TemplatesImpl的getOutputProperties方法。或者TemplatesImpl.newTransformer()即能自动加载字节码,触发恶意代码。这也在其余payload中常常用到。 触发原理:提供会自动调用比较器的容器。如:将PriorityQueue换成TreeSet容器,也是能够的。
为了在生成payload时,可以正常运行。在代码中,先象征性地加入了两个BigInteger对象。
后面使用反射机制,将comparator中的属性和queue容器存储的对象都改为咱们须要的属性和对象。
不然,在生成payload时,便会弹出计算器,抛出异常,没法正常执行了。测试以下:
2. Jdk7u21
该payload实际上是JAVA SE的一个漏洞,ysoserial工具注释中有连接:https://gist.github.com/frohoff/24af7913611f8406eaf3。该payload不须要使用任何第三方库文件,只需官方提供的JDK便可,这个很方便啊。 不知Jdk7u21之后怎么补的,先来看看它的实现。
在介绍完上面这个payload后,再来看这个能够发现:CommonsBeanutilsCollectionsLogging1借鉴了Jdk7u21的利用方法。
一样,Jdk7u21开始便建立了一个存储了恶意java字节码数据的TemplatesImpl类对象。接下来就是怎么触发的问题了:如何自动触发TemplatesImpl的getOutputProperties方法。
这里首先就有一个有趣的hash碰撞问题了。
(1) "f5a5a608"的hash值为0
类的hashCode方法是返回一个独一无二的hash值(int型),去表明这个惟一对象。若是类没有重写hashCode方法,会调用原始Object类中的hashCode方法返回一个hash值。
String类的hashCode方法是这么实现的。
因而,就有了有趣的值:
能够看到"f5a5a608"字符串,经过hashCode方法生成的hash值为0。这在以后的触发过程当中会用到。
(2) 利用动态代理机制触发执行
Jdk7u21中使用了HashSet容器进行触发。添加了两个对象,一个是存储了恶意java字节码数据的TemplatesImpl类对象templates,一个是代理了Templates接口的proxy对象,使用了动态代理机制。
以下是Jdk7u21生成payload时的主要代码:
HashSet容器,就能够当作是一个HashMap<key,new>,key即是咱们存储进去的数据,对应的value都只是静态的Object对象。
一样,来看看HashSet容器中的readObject方法。