PostgreSQL多租户Schema隔离中,SET search_path本身不防SQL注入,若切换逻辑被注入篡改(如; SET search_path TO tenant_b),后续未限定Schema的查询将全部落入错误租户,导致隔离彻底失效。

Schema切换本身不防SQL注入,但切换逻辑若被绕过或污染,会让注入攻击直接击穿租户隔离层——这不是“有没有防住”的问题,而是“一破全破”的连锁失效。
PostgreSQL中SET search_path被SQL注入篡改的后果
当应用用SET search_path TO tenant_a切换Schema后,若后续SQL未显式指定Schema前缀(如写SELECT * FROM users而非SELECT * FROM tenant_a.users),攻击者只要在参数里注入; SET search_path TO tenant_b,就能让接下来所有未限定Schema的查询落到错误租户下。
- 这种注入在
ORDER BY、GROUP BY、LIMIT等子句中极易得手,因为这些位置常被开发者忽略参数化 -
search_path是会话级变量,一旦被污染,整个连接生命周期内都生效,线程复用时风险放大 - PostgreSQL默认允许
search_path被客户端任意设置,除非DBA显式禁用set_search_path权限
MyBatis拦截器无法拦截动态SQL中的租户条件绕过
当使用@SelectProvider或<script>拼接SQL时,MyBatis的TenantLineInnerInterceptor这类插件根本不会介入——它只解析Mapper XML或注解里的静态SQL结构,对运行时拼出的字符串无能为力。
- 典型场景:搜索功能里用
WHERE 1=1 <if test="name != null"> AND name LIKE CONCAT('%', #{name}, '%') </if>,但没强制加AND tenant_id = #{tenantId} - 修复必须双管齐下:禁用
@SelectProvider,全部迁移到XML或@Select注解;同时在拦截器里加白名单校验,拒绝任何不含tenant_id =的SELECT语句执行 - 别信“加个全局WHERE就行”——动态SQL里
WHERE可能被UNION或子查询绕过
Spring Boot中@DS注解与SQL注入的耦合风险
@DS("tenant_a")这类数据源路由注解,只控制连接从哪个物理库/Schema取,**不参与SQL构造过程**。如果SQL本身存在拼接漏洞,路由再准也挡不住注入。
- 常见误用:以为加了
@DS就安全,结果DAO层仍用String.format("SELECT * FROM users WHERE id = %s", id) - 更隐蔽的问题:
@DS作用于方法,但异步调用(如@Async)或定时任务里若未显式传入租户上下文,@DS可能路由到默认数据源,导致跨租户查询 - 验证方式很简单:在SQL注入点触发后,抓包看实际执行的SQL是否还带
tenant_id = ?条件——没有就是漏了
真正卡住风险的地方,从来不是“该不该建Schema”,而是每次SQL执行前,是否确保了三件事同时成立:租户ID已绑定、Schema已锁定、WHERE条件已注入。少一个,隔离就形同虚设。

















