sql注入防护必须在应用层实现,通过预编译参数化所有用户输入,动态表名列名需白名单校验,分库分表及分布式事务中每条sql均须绑定参数,物理节点权限须单独精细化配置。

SQL 注入防护必须在应用层做,不能只靠数据库权限控制
分布式数据库本身不拦截恶意 SQL 语句,GRANT 和 REVOKE 只能限制用户能访问哪些表或字段,但对拼接进 WHERE 条件里的恶意字符串完全无效。一旦应用用 String.format 或 + 拼接用户输入,攻击者就能绕过所有数据库侧权限。
- 典型错误:把
user_id = '1' OR '1'='1'当成普通查询发给分库分表中间件,结果扫出全表数据 - 正确做法:所有用户输入必须走预编译(
PreparedStatement/pg.Prepare/sqlx.Query绑定参数),让驱动层处理类型校验和转义 - 特别注意:分库分表路由规则里如果用了用户输入(比如
shard_key = ?),也必须参数化——否则中间件可能把恶意值直接透传到后端节点
动态表名和列名无法参数化,得靠白名单硬校验
select * FROM <strong>{table_name}</strong> 这种写法,? 占位符根本不起作用,数据库驱动明确报错 Error: syntax error at or near "?"。这类元数据操作只能靠代码层强约束。
- 列出所有合法表名(如
["order_2024", "order_2025"]),用in_array/includes()/enum校验,不匹配就 400 - 列名同理:排序字段
ORDER BY <strong>{sort_col}</strong>必须从预设集合(如["created_at", "amount"])中取,禁止自由输入 - 别信“正则过滤”:
/^[a-zA-Z0-9_]+$/挡不住user_id; DROP TABLE users;这类注入,因为分号在列名上下文中是合法的
分布式事务中的 SQL 安全容易被忽略
跨库更新时,应用常把多个 UPDATE 包进一个 XA 或 Seata 事务,但每个语句仍独立执行。只要其中一条用了拼接,整笔事务就成攻击入口。
- 检查每个
UPDATE的SET和WHERE子句是否都用了绑定参数,不能只查主语句 - 分库中间件(如
ShardingSphere)的日志里如果出现actual SQL含未转义变量,说明某处漏了参数化 - 避免在事务内调用存储过程并传入用户输入——多数分布式数据库(如
CockroachDB、tidb)不支持带参数的跨节点 SP 调用,容易退化为客户端拼接
权限最小化要落到每个物理节点,不是逻辑库
分库分表后,一个逻辑库名(如 shop_db)背后可能是几十个物理库(shop_db_001 到 shop_db_096)。如果只在中间件配了 shop_db 的读权限,攻击者可能直连某个物理库绕过管控。
- 运维必须给每个物理库单独建账号,用
CREATE USER+GRANT SELECT ON shop_db_001.orders精确授权 - 应用连接串不能写
host=proxy就完事,要确认底层是否允许直连物理节点;禁用allowAllFiles=true(mysql)、enable_citus_ddl(Citus)等高危配置 - 定期审计:查
information_schema.TABLE_PRIVILEGES或pg_roles,确认没有ALL PRIVILEGES泛授权
最麻烦的是多租户场景下按 tenant_id 分库,此时租户隔离实际依赖应用层路由逻辑——哪怕数据库权限再严,路由代码写错一行,数据就串了。