SQL 注入是什么
SQL 注入的根源是「数据被当成了代码」:把用户输入拼进 SQL 语句后,输入中的引号、注释符或 UNION 等结构会改变原语句的语义,让数据库执行攻击者构造的逻辑。它的危害随数据库账号权限放大,从越权读取数据,到批量篡改、删除,再到通过数据库的文件读写能力进一步触达主机。防护的正确顺序是先做参数化让输入永远只是数据,再用最小权限、统一错误处理和 WAF 收敛剩余风险。
基本信息
| 风险类别 | OWASP Top 10 2021 A03 注入类;CWE-89 |
|---|---|
| 成因 | 字符串拼接 SQL,输入与语句结构未分离 |
| 直接危害 | 数据越权读取、篡改、删除,认证绕过 |
| 扩大危害 | 数据库具备文件读写或命令扩展能力时可威胁主机 |
| 根治手段 | 参数化查询 / 预编译语句 |
| 兜底措施 | 最小权限账号、统一错误页、输入白名单、WAF |
常见出问题的地方
- 登录与检索接口:name、keyword 等参数直接拼接进 WHERE 条件
- 排序与分页:ORDER BY、LIMIT 后接字段名无法用占位符,被当成可拼接位置
- ORM 的原生查询口子:框架里用原生命令拼 SQL,绕过了自动参数化
- 报表与数据导出:动态筛选条件多,拼接逻辑复杂且少有安全评审
- 存储过程与动态 SQL:过程内部再拼语句执行,权限往往偏高
- 二次注入:写入时做了转义,读取后再拼进新语句时转义失效
拼接与参数化对比
拼接与参数化对比
危险:"SELECT * FROM users WHERE name = '" + name + "'"
安全(Python/DB-API):cursor.execute("SELECT * FROM users WHERE name = %s", (name,))
安全(Java):PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?"); ps.setString(1, name);
安全(PHP PDO):$st = $pdo->prepare('SELECT * FROM users WHERE name = ?'); $st->execute([$name]);
# 关键点:占位符只绑定值,不参与语句结构解析
# 表名、排序字段等无法参数化的位置,用白名单映射到固定字符串数据库最小权限示例
数据库最小权限示例
CREATE USER 'app'@'10.%' IDENTIFIED BY '<强随机口令>';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.%';
-- 业务账号不给 FILE、DROP、GRANT 等能力,避免注入被放大为主机控制
-- 只允许应用网段访问,数据库实例不监听公网地址
FLUSH PRIVILEGES;注入类型与观测特征
| 类型 | 触发条件 | 常见观测特征 |
|---|---|---|
| 联合查询注入 | 页面回显查询结果 | 响应中出现额外数据行或列错位 |
| 报错注入 | 错误信息回显到页面 | URL 参数含函数调用,页面返回数据库报错 |
| 布尔盲注 | 仅能区分真伪两种响应 | 同参数多次请求返回长短或内容差异 |
| 时间盲注 | 真假无差异 | 请求耗时规律性增加,且与参数相关 |
| 堆叠查询 | 驱动允许多语句执行 | 单次请求触发意料外的写操作 |
判断是否真正修复,看的是代码里的拼接点是否消灭,而不是页面是否报错消失——把报错藏起来只是减少线索,不改变可利用性。
WAF 规则能挡住大量自动化扫描,但对编码变形与人工构造的语句经常失效,定位只能是缓冲时间,不是整改手段。
验证与回归要点
- 用与发现时相同的参数路径复测,确认该参数已不改变语句结构
- 检查同类接口是否一起修复:同一模板生成的接口往往成批存在同一问题
- 回归正常业务路径:参数化改造后要确认原有查询、排序、模糊匹配结果不变
- 看日志与监控:修复后异常 SQL 报错与大量相似请求应同步下降
- 留存证据:请求与响应摘要、代码提交记录一并归档,便于后续审计
参数化之后还有一处细节:LIKE 查询里的百分号与下划线属于通配符,需要在应用层转义,否则用户输入仍然能改变匹配范围,这是逻辑差异而非注入,但同样影响结果正确性。