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 数据库上运行(今天,几乎每个应用程序都是)
- 当前数据库连接对数据库具有“编辑”和“删除”权限
- 可以猜到重要表的名字
第三点意味着,既然攻击者知道您正在经营一家电子商务商店,那么您很可能会将订单数据存储在一个orders
表中。有了这一切,攻击者需要做的就是提供这个作为他们的名字:
Joe; truncate orders;
? 是的先生!让我们看看查询被 PHP 脚本执行后会变成什么:
SELECT * FROM messages WHERE name = Joe; truncate orders;
好的,查询的第一部分有一个语法错误(“Joe”周围没有引号),但是分号强制 MySQL 引擎开始解释一个新的:truncate orders
. 就这样,一口气,整个订单历史都消失了!
现在您已经了解了 SQL 注入的工作原理,是时候看看如何阻止它了。SQL注入成功需要满足的两个条件是:
- PHP 脚本应该对数据库具有修改/删除权限。我认为这适用于所有应用程序,您将无法将您的应用程序设为只读。🙂 你猜怎么着,即使我们删除所有修改权限,SQL 注入仍然可以允许某人运行 SELECT 查询并查看所有数据库,包括敏感数据。换句话说,降低数据库访问级别是行不通的,您的应用程序无论如何都需要它。
- 正在处理用户输入。SQL 注入起作用的唯一方法是当您接受来自用户的数据时。再次强调,仅仅因为担心 SQL 注入就停止应用程序的所有输入是不切实际的。
防止 PHP 中的 SQL 注入
现在,鉴于数据库连接、查询和用户输入是生活的一部分,我们如何防止 SQL 注入?值得庆幸的是,它非常简单,有两种方法可以做到:1)清理用户输入,2)使用准备好的语句。
清理用户输入
如果您使用的是较旧的 PHP 版本(5.5 或更低版本,并且在共享主机上经常发生这种情况),明智的做法是通过一个名为mysql_real_escape_string()
. 基本上,它的作用是删除字符串中的所有特殊字符,以便它们在数据库使用时失去意义。
例如,如果您有一个类似 的字符串I'm a string
,攻击者可以使用单引号字符 (') 来操纵正在创建的数据库查询并导致 SQL 注入。通过 products 运行它,这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 = "john@example.com";
$stmt->execute();
$firstname = "Mary";
$lastname = "Moe";
$email = "mary@example.com";
$stmt->execute();
$firstname = "Julie";
$lastname = "Dooley";
$email = "julie@example.com";
$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。🙂
无论如何,我的观点是,现代网络开发人员不必考虑 SQL 注入,因此,他们甚至没有意识到这种可能性。因此,即使他们在应用程序中打开了一个后门(可能是 $_GET 查询参数和启动脏查询的旧习惯),结果也可能是灾难性的。因此,花时间深入了解基础总是更好。
结论
SQL 注入是对 Web 应用程序的一种非常恶劣的攻击,但很容易避免。正如我们在本文中看到的,在处理用户输入(顺便说一下,SQL 注入并不是处理用户输入带来的唯一威胁)和查询数据库时要小心。也就是说,我们并不总是致力于 Web 框架的安全性,因此最好注意这种类型的攻击,不要上当。