Web应用安全 · kp-012
SQL 注入:原理与参数化防御
一句话定义
SQL 注入是把用户输入拼进 SQL 语句,使数据被当成代码执行的漏洞;根治方法是参数化查询(预编译占位符),让数据与代码在语法层面永远分离。
为什么重要
注入长期位列 OWASP Top 10(A03),且后果常是全库级:数据被批量导出、篡改,甚至在权限配置不当时执行系统命令。它是"一个根因、一类家族"的原型——命令注入、LDAP 注入、XPath 注入、模板注入共享同一原理,学会 SQL 注入的防御范式即可迁移到整个注入家族。
前置知识
kp-011(Web 攻击面):理解输入如何到达服务端。
核心概念
- 根因:字符串拼接使"数据"与"代码"共享同一语法通道,攻击者用引号闭合原语句并附加自己的逻辑。概念示例(仅说明原理):
-- 服务端把输入直接拼进查询(错误写法)
SELECT * FROM users WHERE name = '输入值'
-- 输入为 ' OR '1'='1 时,条件恒真,查询返回全表- 参数化查询(预编译):SQL 结构与参数分开发送,数据库先解析语句骨架、再把参数绑定进数据槽位,参数永不参与语法解析:
# 正确写法:占位符 + 参数绑定
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))- 纵深控制:数据库账号最小权限(kp-003)、错误信息不外泄(防数据库指纹)、WAF 作为旁路缓解(不替代修复)。
- ORM 边界:ORM 默认参数化,但拼接查询(如字符串拼 WHERE 子句)仍会引入注入,"用了 ORM"不等于免疫。
公式、模型或图示
拼接路径:输入 ──字符串拼接──▶ SQL 解析器 ──▶ 数据=代码(漏洞)
参数化路径:输入 ──参数通道──▶ 已解析语句骨架 ──▶ 数据≠代码(安全)
▲ 骨架在参数到达前已编译,输入无法改变语法结构原理与机制
参数化之所以是根治而非缓解,在于它把安全下沉到协议层:SQL 文本与参数走不同通道,无论输入是什么字符序列都不可能改变语句结构。相比之下,"过滤特殊字符/黑名单关键字"是语境猜测——数据库方言、编码变换、嵌套语境都会造成过滤遗漏,维护成本高且必然出错,只能作为遗留系统的过渡方案。完整防御链还包含权限收缩:即使注入发生,只读账号与库表白名单把爆炸半径从"全库沦陷"压到"局部泄露";错误页统一处理则移除攻击者的反馈信道(基于报错的探测依赖详细错误回显)。
实例或案例
真实事故模式:某后台检索接口拼接排序字段名(开发者以为"列名不是值所以直接拼"),攻击者控制列名注入子查询导出用户表。教训:参数化覆盖的是"值",凡是拼接进语句的任何成分(列名、表名、ORDER BY 方向)都必须改用服务端白名单映射,而不是转义。
直观类比
拼接 SQL 像把乘客的话直接写进航班指令:"飞往__"。乘客说"北京,然后改降我指定的地方",指令就被劫持;参数化像填写标准运单——目的地只能写在指定栏位,系统只把它当地址数据,永远不会当成新指令。
常见误区
- 黑名单过滤=防御:语境与编码组合使过滤必然存在缺口,且维护成本随方言增长。
- ORM 全自动安全:拼接式查询 API、原生 SQL 片段仍可注入,需同样使用参数绑定。
- 存储过程一定安全:存储过程内部若动态拼接 SQL(EXEC + 拼接),照样可注入。
与其他知识点的关系
- kp-013(XSS):同根因的另一种语境(HTML),防御范式同为"语境化处理"。
- kp-016(OWASP Top 10):注入属 A03,权限与配置类问题(A01/A05)常放大其后果。
自测题
- 参数化查询为什么能根治注入?要点:语句骨架先编译、参数走数据通道,输入无法参与语法解析。
- ORDER BY 的列名参数为什么不能简单参数化?正确做法是什么?要点:列名是标识符不是值;应做服务端白名单映射。
- 数据库账号最小权限如何限制注入后果?要点:只读/库表白名单使注入无法写数据或删表,爆炸半径被压到局部。
延伸阅读
《OWASP Top 10:2021》(OWASP Foundation)A03 注入条目:分类与防御概览。