声明:文中涉及到的技术和工具,仅供学习使用,禁止从事任何非法活动,如因此造成的直接或间接损失,均由使用者自行承担责任。
一、开篇:为什么“过滤关键字”没用?
在前七篇中,我们用各种注入技巧突破了应用的防御——但你有没有发现:所有注入的根源都是同一件事:应用把“用户输入”直接当成了“SQL代码”执行。$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password';";
用户输入的' OR 1=1--直接拼进了SQL语句,变成了“可执行代码”。PortSwigger原路径明确指出:“过滤关键字(如拦截OR、SELECT)是最无效的防御”——因为攻击者可以用大小写混淆(Or)、编码绕过(%27,即'的URL编码)、甚至注释分割(SE/**/LECT)。二、终极防御:参数化查询
PortSwigger将参数化查询列为“SQL注入的唯一根治方案”——它的核心是:把“代码”和“数据”彻底分开。1. 原理:数据库“认得出”什么是数据
参数化查询会用占位符(?)代替用户输入,然后把用户输入作为“纯数据”传递给数据库——数据库永远不会把数据当成代码执行。$sql = "SELECT * FROM users WHERE username = '$username'";mysqli_query($conn, $sql);
$sql = "SELECT * FROM users WHERE username = ?"; // 占位符$stmt = mysqli_prepare($conn, $sql);mysqli_stmt_bind_param($stmt, "s", $username); // 绑定用户输入($username是“数据”)mysqli_stmt_execute($stmt);
语言/框架 | 参数化代码示例 |
|---|
Java (JDBC) | String sql = "SELECT * FROM users WHERE username = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username);
|
Python (SQLite) | sql = "SELECT * FROM users WHERE username = ?"; cursor.execute(sql, (username,))
|
Node.js (mysql2) | const sql = "SELECT * FROM users WHERE username = ?"; connection.execute(sql, [username]);
|
PHP (PDO) | $sql = "SELECT * FROM users WHERE username = :username"; $stmt = $pdo->prepare($sql); $stmt->execute(['username' => $username]);
|
三、辅助防御:多层防护体系
参数化查询是“根基”,但PortSwigger原路径强调:“没有绝对的安全,需要多层防御”——以下是3道“补漏防线”:1. 最小权限原则(数据库账号)
给应用账号仅授予必要权限(比如SELECT、INSERT,禁止DROP、DELETE);CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'StrongPassword123!';GRANT SELECT, INSERT ON test_db.* TO 'app_user'@'localhost';
2. 输入验证(白名单优于黑名单)
对固定格式的输入(如邮箱、手机号),用正则表达式验证:if(!preg_match("/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/", $email)) { die("非法邮箱"); }
对自由格式的输入(如评论),用htmlspecialchars()转义HTML特殊字符(防止XSS,间接减少注入风险)。3. 错误处理:绝不暴露数据库细节
不要向用户显示“MySQL syntax error”“ORA-00933”等详细错误;try { $stmt->execute();} catch(Exception $e) { error_log("Database error: " . $e->getMessage()); // 记录到服务器日志 echo "操作失败,请重试";}
四、常见误区
错!如果用PDO时还拼接用户输入(比如$sql = "SELECT * FROM users WHERE username = '$username'"),依然会注入——必须要用占位符。WAF是“外围防御”,可能被绕过(比如用/**/分割关键字);参数化是“代码层防御”,永不失效。错!如果存储过程中拼接用户输入(比如CREATE PROCEDURE getUser @username VARCHAR(50) AS SELECT * FROM users WHERE username = @username),依然会注入——存储过程也要用参数化。五、防御 checklist
✅ 定期审计代码(用工具如SonarQube扫描SQL注入漏洞)。六、互动与总结
小问题:你在项目中用过参数化查询吗?是用PDO还是JDBC?遇到过什么坑?欢迎评论区分享!系列总结:从“原理→实战→特殊场景→防御”,我们用8篇文章走完了PortSwigger SQL注入学习路径——记住:SQL注入的本质是“代码与数据不分”,参数化查询是唯一根治方法。(注:文中防御方法均基于PortSwigger Web Security Academy原路径,原文链接:https://portswigger.net/web-security/sql-injection/preventing)