TiDB防SQL注入本质与MySQL一致,必须在应用层强制参数化,因TiDB和中间件均不校验原始SQL拼接、无法拦截动态语法且易被绕过。

在 TiDB 中防 SQL 注入,和在 MySQL 里本质一致:TiDB 兼容 MySQL 协议,但不自带任何注入拦截能力;所有防护必须落在应用代码层,中间件或数据库侧的“规则匹配”不可信。
为什么不能依赖 TiDB 自身或 ShardingSphere 等中间件拦截
TiDB Server 本身不解析、不校验用户传入的原始 SQL 字符串是否含恶意拼接;它只执行已送达的语句。ShardingSphere-Proxy 或 MyCat 这类分库分表中间件更危险——它们看到的是拆分后的物理 SQL(如 SELECT * FROM order_2024 WHERE user_id = ?),而原始请求可能是 user_id=1 OR '1'='1,参数还没绑定就已透传。攻击者用 /**/、大小写混写(Or 1=1)、URL 编码(%6f%72%203333%3d3333)轻松绕过特征规则。更关键的是,这些中间件完全不感知 table_name 或 ORDER BY field 这类动态语法是否来自用户输入。
Go 应用连接 TiDB 时必须强制参数化
TiDB 的 Go 驱动(github.com/go-sql-driver/mysql 或 github.com/pingcap/tidb)与标准 database/sql 接口兼容,因此防护逻辑和 MySQL 完全一致:
- 所有查询必须走
db.Query()/db.QueryRow()/stmt.Exec(),且变量一律通过?占位符传入,绝不用fmt.Sprintf、+或${}拼接 - 检查连接字符串中是否启用了
parseTime=true等非安全选项——它们不影响注入,但会掩盖时间类型处理错误,间接增加调试盲区 - 若使用
sqlx,确认没误用sqlx.MustExec("UPDATE users SET name = " + name)这类写法;sqlx.Named是安全的,但命名参数仍需白名单兜底动态字段 - 对
IN (?)场景,不能直接传 slice,要用sqlx.In+sqlx.Rebind生成正确占位符数量
动态表名、排序字段、GROUP BY 字段必须白名单硬校验
TiDB(以及 MySQL、PostgreSQL)不支持 SELECT * FROM ? 或 ORDER BY ?,运行时报错:ERROR: syntax error at or near "?"。这意味着这类动态语法无法靠参数化解决,只能靠白名单:
- 从配置中心加载合法表名列表(如
sharding-tables=["order_2024", "order_2025"]),用in_array(user_table, valid_tables)校验,不匹配直接返回HTTP 400 - 排序字段映射成固定键值对:
map[string]string{"created": "created_at", "amount": "total_amount"},用户只传sort=created,后端查表转成真实字段 - 避免用
reflect.StructTag或json:"-"注解自动映射字段名——反射过程可能绕过白名单校验逻辑
CLI 脚本、定时任务、MQ 消费器这些非 HTTP 路径最容易漏防
Web 请求可能被 WAF 或中间件日志捕获,但 crontab 调起的 Go CLI 工具、Kafka 消费器里的 db.Exec()、Seata 分布式事务补偿逻辑,往往直连 TiDB,完全不走任何网关或代理。这些路径若复用同一套数据库驱动封装(比如统一用重写的 Conn.QueryContext 方法做 AST 解析或字符串扫描),才能真正覆盖盲区。否则,一个未参数化的 DELETE FROM log_ + year 就可能清空整张物理表。


















