DynamicTableNameInnerInterceptor 存在上下文污染风险导致数据写错

Author: QiuYucheng2003Created Feb 21, 2026Updated Aug 6, 2026

确认

  • 我使用的版本是最新版, 并且使用插件确认过项目里无依赖版本冲突
  • 我已经在 issue 中搜索过, 确认问题没有被提出过
  • 我已经修改标题, 将标题中的 描述 替换为遇到的问题(不得删除 描述 前面的部分)

当前程序版本

3.4.0

问题描述

核心缺陷: TableNameHandler#dynamicTableName 接口设计仅包含 sql 和 tableName 参数。在多租户或动态分表场景下,开发者被迫使用 ThreadLocal 传递业务标识。

漏洞机理(类型 III:上下文污染):

  1. 清理机制不健壮:尽管拦截器提供了 hook 变量,但它依赖用户手动配置清理逻辑。若未配置或业务流程因复杂异常中断,分表标识将残留在线程池线程中。

  2. 身份错乱:当该线程处理后续请求时,会错误读取上一个请求的残留标识,导致 SQL 被拦截并重定向至错误的物理表(如本该写入 A 租户的数据写入了 B 租户)。

详细堆栈日志

bash
由于本发现基于静态代码审计,以下为模拟漏洞触发的逻辑路径:

1. 入口阶段 (Entry):

业务方在 Service 层通过自定义 ContextHolder(底层为 ThreadLocal)绑定租户 ID。

线程池中的 Thread-1 开始处理该请求。

2. 拦截阶段 (Interception):

DynamicTableNameInnerInterceptor 被触发,进入 processTableName 方法。

方法内部调用 tableNameHandler.dynamicTableName。

此时 TableNameHandler 实现类从 Thread-1 的 ThreadLocal 中读取到租户 ID,并完成 SQL 改写。

3. 异常/未清理阶段 (Leak):

SQL 执行后,由于业务代码未在 finally 中显式 remove(),或拦截器配置的 hook 为空,导致 Thread-1 残留了上一个请求的租户标识。

4. 触发错乱 (Pollution):

Thread-1 被归还线程池并被下一个不同租户的新请求复用。

新请求在进入 DynamicTableNameInnerInterceptor 时,拦截器错误地读取到了残留的旧租户标识。

后果:新请求的 SQL 被指向了错误的物理表名,导致数据物理性写错。