Skip to content

SQL 注入漏洞详解:原理、利用与防御

一、漏洞原理

SQL 注入(SQL Injection,简称 SQLi)是一种将恶意 SQL 语句插入到应用查询参数中,从而欺骗后端数据库执行非预期指令的攻击手法。它长期位居 OWASP Top 10 榜单前列,是 Web 安全中最经典、危害最大的漏洞之一。

其根本原因在于:用户输入被当成了 SQL 代码来执行,而不是被当作数据处理

开发者在拼接 SQL 语句时,如果直接把用户可控的参数(如 GET/POST 参数、Cookie、请求头)嵌入语句字符串,攻击者就可以通过构造特殊输入,改变原语句的结构和语义。例如原本的查询条件被单引号"提前闭合",攻击者接续写入的任意 SQL 片段就会被数据库当作合法语法执行,进而实现:

  • 绕过登录认证(如万能密码)
  • 读取、篡改、删除任意数据库数据
  • 在某些配置下读写服务器文件,甚至执行系统命令

二、漏洞代码示例

以下是一段典型的存在注入漏洞的 PHP + MySQL 代码:

php
<?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 时,执行的语句为:

sql
SELECT id, username, email FROM users WHERE id = '1'

但当攻击者访问 ?id=1' OR '1'='1 时,语句变为:

sql
SELECT id, username, email FROM users WHERE id = '1' OR '1'='1'

条件恒为真,数据库会返回全表数据。类似的 Java、Python、C# 代码只要使用字符串拼接而非参数化查询,均存在同样问题。

三、三种典型 Payload

1. 认证绕过(万能密码)

针对登录场景,在密码字段构造永真条件:

text
用户名:admin' --
密码:任意值

拼接后的语句:

sql
SELECT * FROM users WHERE username='admin' --' AND password='xxx'

-- 注释掉后续密码校验,直接以 admin 身份登录。

2. UNION 联合查询注入(数据窃取)

利用 UNION SELECT 将攻击者想查询的表数据附加到正常结果集中。常配合 ORDER BY 探测列数后:

text
?id=-1 UNION SELECT 1, username, password FROM admin_users

原 id 传入 -1(保证原查询为空,页面上只显示注入结果),从而拖取 admin_users 表的账号密码。

3. 报错注入(盲环境提取数据)

当页面不回显查询结果但会显示数据库报错时,利用报错函数带出数据:

text
?id=1' AND extractvalue(1, concat('~', (SELECT database()))) -- +

extractvalue 的第二个参数若不是合法 XPath 会报错,并SELECT database() 的结果打印在错误信息中,攻击者借此逐位猜解库名、表名和字段内容。

四、修复建议

  1. 使用参数化查询(预编译语句)——最核心的修复手段。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)

  2. 使用 ORM 框架:如 MyBatis(注意 ${} 依然是拼接,应改用 #{})、Hibernate、GORM 等,规范写法天然规避注入。

  3. 严格的输入校验:对确定类型或格式的参数做白名单校验,如 id 强制转整数、排序字段限定枚举值。

  4. 最小权限原则:应用连接数据库的账号只授予必要权限,禁止使用 root 或 sa 等高权限账户,即使被注入也能大幅缩小损失。

  5. 关闭错误回显:生产环境禁用详细数据库报错输出,统一返回友好错误页,削弱报错注入的利用条件。

  6. 部署 WAF 与定期审计:Web 应用防火墙可拦截常见攻击特征,同时配合代码安全审计与渗透测试,在上线前发现遗留风险。

五、总结

SQL 注入的防御本质只有一句话:让代码与数据彻底分离。参数化查询能解决 95% 以上的注入场景,再辅以输入校验、最小权限和错误处理加固,即可将风险降到可接受范围。开发人员在任何涉及数据库操作的代码中,都应把"禁止字符串拼接 SQL"作为铁律。

仅用于学习交流的防守型安全知识库