SQL 注入漏洞详解:原理、利用与防御
一、漏洞原理
SQL 注入(SQL Injection,简称 SQLi)是一种将恶意 SQL 语句插入到应用查询参数中,从而欺骗后端数据库执行非预期指令的攻击手法。它长期位居 OWASP Top 10 榜单前列,是 Web 安全中最经典、危害最大的漏洞之一。
其根本原因在于:用户输入被当成了 SQL 代码来执行,而不是被当作数据处理。
开发者在拼接 SQL 语句时,如果直接把用户可控的参数(如 GET/POST 参数、Cookie、请求头)嵌入语句字符串,攻击者就可以通过构造特殊输入,改变原语句的结构和语义。例如原本的查询条件被单引号"提前闭合",攻击者接续写入的任意 SQL 片段就会被数据库当作合法语法执行,进而实现:
- 绕过登录认证(如万能密码)
- 读取、篡改、删除任意数据库数据
- 在某些配置下读写服务器文件,甚至执行系统命令
二、漏洞代码示例
以下是一段典型的存在注入漏洞的 PHP + MySQL 代码:
<?php
// 存在 SQL 注入漏洞的登录验证代码
$id = $_GET['id']; // 用户输入未经过滤,直接拼入 SQL
$sql = "SELECT id, username, email FROM users WHERE id = '" . $id . "'";
$result = mysqli_query($conn, $sql);
while ($row = mysqli_fetch_assoc($result)) {
echo $row['username'] . " | " . $row['email'];
}
?>当用户正常访问 ?id=1 时,执行的语句为:
SELECT id, username, email FROM users WHERE id = '1'但当攻击者访问 ?id=1' OR '1'='1 时,语句变为:
SELECT id, username, email FROM users WHERE id = '1' OR '1'='1'条件恒为真,数据库会返回全表数据。类似的 Java、Python、C# 代码只要使用字符串拼接而非参数化查询,均存在同样问题。
三、三种典型 Payload
1. 认证绕过(万能密码)
针对登录场景,在密码字段构造永真条件:
用户名:admin' --
密码:任意值拼接后的语句:
SELECT * FROM users WHERE username='admin' --' AND password='xxx'-- 注释掉后续密码校验,直接以 admin 身份登录。
2. UNION 联合查询注入(数据窃取)
利用 UNION SELECT 将攻击者想查询的表数据附加到正常结果集中。常配合 ORDER BY 探测列数后:
?id=-1 UNION SELECT 1, username, password FROM admin_users原 id 传入 -1(保证原查询为空,页面上只显示注入结果),从而拖取 admin_users 表的账号密码。
3. 报错注入(盲环境提取数据)
当页面不回显查询结果但会显示数据库报错时,利用报错函数带出数据:
?id=1' AND extractvalue(1, concat('~', (SELECT database()))) -- +extractvalue 的第二个参数若不是合法 XPath 会报错,并把 SELECT database() 的结果打印在错误信息中,攻击者借此逐位猜解库名、表名和字段内容。
四、修复建议
使用参数化查询(预编译语句)——最核心的修复手段。SQL 结构与数据彻底分离,用户输入永远只会被当作参数值处理:
php$stmt = $conn->prepare("SELECT id, username, email FROM users WHERE id = ?"); $stmt->bind_param("i", $id); $stmt->execute();Java 中对应
PreparedStatement,Python 中对应cursor.execute(sql, params)。使用 ORM 框架:如 MyBatis(注意
${}依然是拼接,应改用#{})、Hibernate、GORM 等,规范写法天然规避注入。严格的输入校验:对确定类型或格式的参数做白名单校验,如 id 强制转整数、排序字段限定枚举值。
最小权限原则:应用连接数据库的账号只授予必要权限,禁止使用 root 或 sa 等高权限账户,即使被注入也能大幅缩小损失。
关闭错误回显:生产环境禁用详细数据库报错输出,统一返回友好错误页,削弱报错注入的利用条件。
部署 WAF 与定期审计:Web 应用防火墙可拦截常见攻击特征,同时配合代码安全审计与渗透测试,在上线前发现遗留风险。
五、总结
SQL 注入的防御本质只有一句话:让代码与数据彻底分离。参数化查询能解决 95% 以上的注入场景,再辅以输入校验、最小权限和错误处理加固,即可将风险降到可接受范围。开发人员在任何涉及数据库操作的代码中,都应把"禁止字符串拼接 SQL"作为铁律。