所以您认为您的 SQL 数据库性能良好且安全,不会立即遭到破坏? 好吧,SQL 注入不同意!
是的,我们正在谈论的是即时破坏,因为我不想用“加强安全性”和“防止恶意访问”等通常蹩脚的术语来打开这篇文章。 SQL注入是书中的老把戏,每个开发人员都非常了解它,也很清楚如何防止它。 除了那一次他们失误之外,结果简直就是灾难性的。
如果您已经知道什么是 SQL 注入,请随时跳到本文的后半部分。 但对于那些刚刚进入 Web 开发领域并梦想担任更高级角色的人来说,一些介绍是必要的。
目录
什么是 SQL 注入?
理解 SQL Injection 的关键在于它的名字:SQL + Injection。 这里的“注射”一词没有任何医学含义,而是动词“注射”的用法。 这两个词一起传达了将 SQL 放入 Web 应用程序的想法。
将 SQL 放入 Web 应用程序中。 . . 嗯。 . . 这不正是我们正在做的吗? 是的,但我们不希望攻击者驱动我们的数据库。 让我们借助一个例子来理解这一点。
假设您正在为本地电子商务商店构建一个典型的 PHP 网站,因此您决定添加一个联系表单,如下所示:
<form action="record_message.php" method="POST"> <label>Your name</label> <input type="text" name="name"> <label>Your message</label> <textarea name="message" rows="5"></textarea> <input type="submit" value="Send"> </form>
假设文件 send_message.php 将所有内容存储在数据库中,以便店主稍后可以读取用户消息。 它可能有一些这样的代码:
<?php $name = $_POST['name']; $message = $_POST['message']; // check if this user already has a message mysqli_query($conn, "SELECT * from messages where name = $name"); // Other code here
因此,您首先要尝试查看此用户是否已有未读消息。 查询 SELECT * from messages where name = $name 看起来很简单,对吧?
错误的!
在我们的天真中,我们已经打开了立即破坏我们数据库的大门。 为此,攻击者必须满足以下条件:
- 应用程序在 SQL 数据库上运行(今天,几乎每个应用程序都是)
- 当前数据库连接对数据库有“编辑”和“删除”权限
- 重要表名可猜
第三点意味着现在攻击者知道您正在经营一家电子商务商店,您很可能将订单数据存储在订单表中。 有了这一切,攻击者需要做的就是提供这个作为他们的名字:
乔; 截断订单;? 是的先生! 让我们看看查询在被 PHP 脚本执行后会变成什么:
SELECT * FROM messages WHERE name = Joe; 截断订单;
好的,查询的第一部分有一个语法错误(“Joe”周围没有引号),但是分号迫使 MySQL 引擎开始解释一个新的部分:截断订单。 就这样,一口气,整个订单记录消失了!
现在您已经了解了 SQL 注入的工作原理,是时候看看如何阻止它了。 SQL注入成功需要满足的两个条件是:
防止 PHP 中的 SQL 注入
现在,鉴于数据库连接、查询和用户输入是生活的一部分,我们如何防止 SQL 注入? 值得庆幸的是,它非常简单,有两种方法可以做到:1) 清理用户输入,以及 2) 使用准备好的语句。
清理用户输入
如果您使用的是较旧的 PHP 版本(5.5 或更低版本,这种情况在共享主机上经常发生),明智的做法是通过名为 mysql_real_escape_string() 的函数运行所有用户输入。 基本上,它的作用是删除字符串中的所有特殊字符,以便它们在被数据库使用时失去意义。
例如,如果您有一个像 I’m a string 这样的字符串,攻击者可以使用单引号字符 (‘) 来操纵正在创建的数据库查询并导致 SQL 注入。 通过 mysql_real_escape_string() 运行它产生 I’m a string,它向单引号添加反斜杠,将其转义。 结果,整个字符串现在作为无害字符串传递给数据库,而不是能够参与查询操作。
这种方法有一个缺点:它是一种非常非常古老的技术,伴随着 PHP 中较旧的数据库访问形式。 从 PHP 7 开始,这个函数甚至不再存在,这将我们带到下一个解决方案。
使用准备好的语句
准备好的语句是一种使数据库查询更安全可靠的方法。 我们的想法是,我们不是将原始查询发送到数据库,而是首先告诉数据库我们将要发送的查询的结构。 这就是我们所说的“准备”声明的意思。 准备好语句后,我们将信息作为参数化输入传递,以便数据库可以通过将输入插入我们之前发送的查询结构来“填补空白”。 这会带走输入可能具有的任何特殊功能,导致它们在整个过程中仅被视为变量(或有效载荷,如果你愿意的话)。 准备好的语句如下所示:
<?php $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } // prepare and bind $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email); // set parameters and execute $firstname = "John"; $lastname = "Doe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Mary"; $lastname = "Moe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Julie"; $lastname = "Dooley"; $email = "[email protected]"; $stmt->execute(); echo "New records created successfully"; $stmt->close(); $conn->close(); ?>
我知道如果您不熟悉准备好的语句,这个过程听起来会不必要地复杂,但是这个概念非常值得付出努力。 这是 一个很好的介绍。
对于那些已经熟悉 PHP 的 PDO 扩展并使用它来创建准备好的语句的人,我有一个小建议。
警告:设置 PDO 时要小心
当使用 PDO 进行数据库访问时,我们可能会陷入一种错误的安全感。 “啊,好吧,我正在使用 PDO。 现在我不需要考虑其他任何事情”——这就是我们通常的想法。 PDO(或 MySQLi 预处理语句)确实足以防止各种 SQL 注入攻击,但在设置它时必须小心。 通常只从教程或早期项目中复制粘贴代码然后继续,但此设置可以撤消所有内容:
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
此设置的作用是告诉 PDO 模拟准备好的语句,而不是实际使用数据库的准备语句功能。 因此,PHP 会向数据库发送简单的查询字符串,即使您的代码看起来像是在创建准备好的语句和设置参数等等。 换句话说,您和以前一样容易受到 SQL 注入攻击。 🙂
解决方案很简单:确保将此仿真设置为 false。
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
现在 PHP 脚本被迫在数据库级别使用准备好的语句,防止各种 SQL 注入。
防止使用 WAF
您知道您还可以使用 WAF(Web 应用程序防火墙)保护 Web 应用程序免受 SQL 注入攻击吗?
好吧,不仅是 SQL 注入,还有许多其他第 7 层漏洞,如跨站点脚本、身份验证失败、跨站点伪造、数据泄露等。您可以使用像 Mod Security 这样的自托管,也可以像下面这样使用基于云的。
SQL 注入和现代 PHP 框架
SQL 注入是如此常见、如此简单、如此令人沮丧且如此危险,以至于所有现代 PHP Web 框架都内置了应对措施。 例如,在 WordPress 中,我们有 $wpdb->prepare() 函数,而如果您使用的是 MVC 框架,它会为您完成所有脏活,您甚至不必考虑防止 SQL 注入。 在 WordPress 中您必须显式准备语句有点烦人,但是嘿,我们正在谈论的是 WordPress。 🙂
无论如何,我的观点是,现代的 Web 开发人员不必考虑 SQL 注入,因此,他们甚至没有意识到这种可能性。 因此,即使他们在他们的应用程序中留下一个后门(可能是 $_GET 查询参数和启动脏查询的旧习惯),结果也可能是灾难性的。 因此,最好花时间更深入地研究基础知识。
结论
SQL 注入是对 Web 应用程序的一种非常恶劣的攻击,但很容易避免。 正如我们在本文中看到的,在处理用户输入时要小心(顺便说一下,SQL 注入不是处理用户输入带来的唯一威胁)并且查询数据库就是它的全部。 也就是说,我们并不总是致力于 Web 框架的安全性,因此最好注意此类攻击,不要上当受骗。