必须用白名单硬校验动态表名,${}原样拼接高危,#{}无法用于表名;校验须在Java层代码写死枚举或常量,禁用配置中心动态加载,且需正则兜底校验格式。

动态表名无法参数化,任何试图用 ? 或 #{} 包裹表名的写法都无效,直接拼接就是漏洞入口——必须用白名单硬校验,且校验必须落在 SQL 构造之前。
MyBatis 里 ${tableName} 拼表名一定会触发 SQL 注入
MyBatis 的 ${} 是字符串原样替换,不经过预编译。哪怕只放一个 ${tableName},攻击者传入 users; DROP TABLE orders -- 就能执行多语句。
-
SELECT * FROM ${tableName}→ 用户输logs WHERE 1=1 UNION SELECT password FROM users,结果查出密码 - 常见错误补救:加
mysql_real_escape_string()或正则过滤union|select|;—— 全部可绕过(大小写、编码、注释干扰) - 正确做法:白名单必须在 Java 层完成,比如
if (!List.of("user_logs", "order_logs", "pay_logs").contains(tableName)) throw new IllegalArgumentException(); - 校验通过后,再用
String.format("SELECT * FROM %s WHERE id = ?", tableName)拼接,?只留给真正可参数化的值
存储过程调用中传动态表名仍是高危点
很多人以为“进了存储过程就安全了”,但若 Java 层用 CALL proc_name('${tableName}') 调用,${} 仍在 MyBatis 层就被替换了,存储过程根本没机会做校验。
- 错误示例:
<select id="xxx" statementType="CALLABLE">CALL get_data_by_table('${tableName}');</select> - 即使存储过程内部做了
IF @table NOT IN ('t1','t2') RAISERROR,也晚了——SQL 已被解析执行 - 正确链路:Java 层白名单校验 → 校验通过后以
#{safeTableName}传参 → 存储过程内仅作字符串拼接(此时输入已可信) - 额外加固:存储过程账号禁用
EXECUTE AS、禁止跨库访问、拒绝对sys和information_schema的 SELECT 权限
Node.js mysql2 库 execute() 也不能救动态表名
execute() 确实强制服务端预编译,但它只认 ? 占位符对应的数据值;表名、列名、ORDER BY 字段属于 SQL 结构,数据库根本不允许参数化。
- 错误写法:
conn.execute("SELECT * FROM ? WHERE id = ?", [tableName, id])→ MySQL 报错ER_PARSE_ERROR - 危险写法:
conn.execute(`SELECT * FROM ${tableName} WHERE id = ?`, [id])→ 模板字符串提前拼接,execute失效 - 必须白名单 + 正则双校验:
const allowed = ["events_2024", "events_2025"]; if (!allowed.includes(tableName)) throw ...; - 再加一层兜底:
if (!/^[a-zA-Z0-9_]+$/.test(tableName)) throw new Error("Invalid table name");(拒绝点号、反引号、空格、连字符)
白名单不能从配置中心或数据库动态加载
白名单一旦可被用户间接修改(比如通过管理后台更新配置项、或从用户可控的数据库表读取),就等于把校验逻辑交给了攻击者。
- 典型破防场景:配置表里存了
{"allowed_tables": ["users", "orders"]},但该表本身可通过 SQL 注入增删改 - 正确姿势:白名单必须是代码里写死的常量或枚举,如
public enum ReportTable { USER_LOG, ORDER_LOG } - 如果业务真要支持“运营人员自定义报表表名”,那就得走审批流+人工上线,不能 runtime 加载
- 最容易被忽略的一点:排序字段白名单也要独立维护,
sort=created_at和sort=created映射不同物理字段,不能只校验 key 是否存在
动态结构的校验不是加一层 if 就完事——它必须不可绕过、不可延迟、不可降级。哪怕接口只开放给内部系统,只要输入来源不可信,就必须按白名单硬控。漏掉一个字段映射,就可能成为整个数据层的突破口。

















