什么是 SQL 注入以及如何在 PHP 应用程序中进行预防?

所以您认为您的 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 看起来很简单,对吧?

  9个魔兽世界(WOW)私人服务器加入

错误的!

在我们的天真中,我们已经打开了立即破坏我们数据库的大门。 为此,攻击者必须满足以下条件:

  • 应用程序在 SQL 数据库上运行(今天,几乎每个应用程序都是)
  • 当前数据库连接对数据库有“编辑”和“删除”权限
  • 重要表名可猜

第三点意味着现在攻击者知道您正在经营一家电子商务商店,您很可能将订单数据存储在订单表中。 有了这一切,攻击者需要做的就是提供这个作为他们的名字:

乔; 截断订单;? 是的先生! 让我们看看查询在被 PHP 脚本执行后会变成什么:

SELECT * FROM messages WHERE name = Joe; 截断订单;

好的,查询的第一部分有一个语法错误(“Joe”周围没有引号),但是分号迫使 MySQL 引擎开始解释一个新的部分:截断订单。 就这样,一口气,整个订单记录消失了!

现在您已经了解了 SQL 注入的工作原理,是时候看看如何阻止它了。 SQL注入成功需要满足的两个条件是:

  • PHP 脚本应该对数据库具有修改/删除权限。 我认为所有应用程序都是如此,您无法将您的应用程序设置为只读。 🙂 猜猜看,即使我们删除所有修改权限,SQL 注入仍然可以允许某人运行 SELECT 查询并查看所有数据库,包括敏感数据。 换句话说,降低数据库访问级别是行不通的,而您的应用程序无论如何都需要它。
  • 正在处理用户输入。 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();
    ?>

    我知道如果您不熟悉准备好的语句,这个过程听起来会不必要地复杂,但是这个概念非常值得付出努力。 这是 一个很好的介绍。

      如何计算 Google 表格中两个日期之间的天数

    对于那些已经熟悉 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 框架的安全性,因此最好注意此类攻击,不要上当受骗。

      使用 Otter 更好地运行您的在线会议