SQL 注入原理与防御:从一行拼接说起
一、什么是 SQL 注入
SQL 注入(SQL Injection)是最经典、危害最广的 Web 漏洞之一。它的本质只有一句话:程序把用户输入当成了 SQL 代码,而不是数据。
当应用用字符串拼接的方式构造 SQL 语句时,如果没有对用户输入做严格处理,攻击者就能让自己输入的内容"越界"成为查询逻辑的一部分,从而读取、篡改甚至删除数据库中的数据。
二、漏洞是怎么产生的
设想一个最常见的登录查询,程序这样拼接:
SELECT * FROM users WHERE name = '输入的用户名' AND pass = '输入的密码'
正常情况下,用户名 alice、密码 123 拼出的语句是符合预期的。但如果用户名输入框里填的是一段精心构造的字符串,使得引号闭合后接上 OR 1=1 这类"永真条件",原本的过滤条件就被绕过了 —— 数据库会返回所有用户,登录校验形同虚设。
关键点在于:单引号让"数据"提前结束,后面的内容被当作了"代码"。 这就是注入的核心。
三、注入的几种典型类型
- 联合查询注入(UNION):利用
UNION把额外的查询结果拼接到原结果里,常用于直接读取数据。 - 报错注入:故意构造让数据库报错,从错误信息里带出数据。适用于页面会回显数据库错误的场景。
- 布尔盲注:页面不回显数据,但会因条件真假呈现不同状态(如"登录成功/失败"),逐位猜测数据。
- 时间盲注:连真假状态都看不出来时,用
SLEEP之类的延时函数,通过响应时间长短判断条件真假。
盲注效率虽低,但只要存在注入点,数据迟早能被一位一位"问"出来。
四、为什么过滤关键词不靠谱
很多开发者第一反应是"把 select、union、单引号都过滤掉"。这是治标不治本的:
- 大小写、编码、注释可绕过:
SeLeCt、URL 编码、内联注释/**/都能躲过简单的关键词黑名单。 - 场景千变万化:数字型注入根本不需要引号;不同数据库语法各异。
- 黑名单永远不全:你能想到的过滤规则,攻击者总能找到漏网之鱼。
安全的思路从来不是"猜测并封堵坏输入",而是从根上让数据无法变成代码。
五、正确的防御:参数化查询
防御 SQL 注入的根本方案是参数化查询(Prepared Statement / 预编译)。
它的原理是:SQL 语句的结构先发给数据库编译好,用户输入只作为参数单独传入,永远被当作纯数据处理,绝不会改变语句结构。
-- 结构先定好,? 是占位符
SELECT * FROM users WHERE name = ? AND pass = ?
-- 再把用户输入作为参数绑定进去
无论用户输入什么内容(哪怕是一堆单引号和 OR 1=1),它都只是 name 字段要匹配的字符串,不可能改变查询逻辑。这是机制层面的免疫,而不是"过滤层面"的封堵。
六、纵深防御:多做一层总没错
参数化查询是地基,但生产环境建议叠加多层防御:
- 最小权限原则:应用连接数据库的账号只给必要权限,即使被注入,也读不到不该读的库表。
- 输入校验:对格式明确的字段(邮箱、手机号、数字 ID)做白名单校验。
- ORM 框架:成熟 ORM 默认使用参数化,大幅降低手写拼接出错的概率。
- WAF:作为补充防线拦截常见 payload,但不能替代代码层修复。
- 错误信息脱敏:不要把数据库原始报错暴露给用户。
七、小结
SQL 注入的根因是"数据与代码混淆",防御的根本是"用参数化查询把二者彻底分开"。记住三句话:
- 永远不要用字符串拼接构造 SQL。
- 永远使用参数化查询 / 预编译。
- 最小权限 + 输入校验 + 错误脱敏做纵深防御。
做到这三点,这个困扰了 Web 世界二十多年的漏洞,在你的应用里就基本绝迹了。
本文为 KHack 社区原创教学内容,仅供安全研究与学习。
评论
登录 后参与讨论。
还没有评论,来说两句。