跳至正文

SQL注入原理与预防(一)

参数化查询

<?php
// 获取用户输入的关键字
$keyword = $_GET['keyword'];

// 执行参数化查询
$query = "SELECT * FROM articles WHERE title LIKE ?";
$stmt = mysqli_prepare($connection, $query);
mysqli_stmt_bind_param($stmt, "s", "%" . $keyword . "%");
mysqli_stmt_execute($stmt);      # 执行拼接好的查询语句
$result = mysqli_stmt_get_result($stmt);    # 查询结果集存储,以便后用
?php>

*这段代码是使用参数化查询来执行SQL查询语句,并从数据库中获取结果。
具体解释如下:

  1. $query = "SELECT * FROM articles WHERE title LIKE ?";:定义了SQL查询语句,使用占位符 ? 表示待填入的参数。
  2. $stmt = mysqli_prepare($connection, $query);:使用 mysqli_prepare() 函数准备查询语句,传入数据库连接对象 $connection 和查询语句 $query
  3. mysqli_stmt_bind_param($stmt, "s", "%" . $keyword . "%");:使用 mysqli_stmt_bind_param() 函数将参数绑定到查询语句中的占位符。"s" 表示参数的类型为字符串,"%".$keyword."%" 表示待填入的参数值,即用户输入的关键字。
  4. mysqli_stmt_execute($stmt);:执行准备好的查询语句,通过 mysqli_stmt_execute() 函数执行。
  5. mysqli_stmt_get_result($stmt);:从执行的语句中获取结果集,使用 mysqli_stmt_get_result() 函数将结果集存储在变量 $result 中。这样可以进一步处理结果集,例如遍历结果、获取数据等操作。
    使用参数化查询的方式可以确保用户输入的关键字被正确地处理,避免了宽字节注入等安全风险。通过将参数绑定到查询语句中的占位符,可以确保输入的数据被视为数据而不是可执行的代码,提高了应用程序的安全性。

预编译

<?php
// 获取用户输入的关键字
$keyword = $_GET['keyword'];
// 准备预编译语句
$query = "SELECT * FROM articles WHERE title LIKE ?";
$stmt = $connection->prepare($query);
$stmt->bind_param("s", $keywordParam);
// 设置参数并执行查询
$keywordParam = "%" . $keyword . "%";
$stmt->execute();
// 获取结果集
$result = $stmt->get_result();
// 处理结果集
while ($row = $result->fetch_assoc()) {
    // 处理每行数据
    // ...
}
// 关闭预编译语句和数据库连接
$stmt->close();
$connection->close();

*在这个例子中,我们使用了预编译语句和参数绑定来执行 SQL 查询。具体解释如下:

  1. $query = "SELECT * FROM articles WHERE title LIKE ?";:定义了 SQL 查询语句,使用占位符 ? 表示待填入的参数。
  2. $stmt = $connection->prepare($query);:使用 $connection 对象的 prepare() 方法准备预编译语句。
  3. $stmt->bind_param("s", $keywordParam);:使用 $stmt 对象的 bind_param() 方法将参数绑定到查询语句中的占位符。"s" 表示参数的类型为字符串,$keywordParam 是参数的变量。
  4. $keywordParam = "%" . $keyword . "%";:设置参数的值为 %关键字%,即用户输入的关键字。
  5. $stmt->execute();:执行预编译语句,通过 $stmt 对象的 execute() 方法执行查询。
  6. $result = $stmt->get_result();:从执行的语句中获取结果集,通过 $stmt 对象的 get_result() 方法将结果集存储在变量 $result 中。
  7. 使用 while 循环遍历结果集并处理每行数据。在这个例子中,我们使用 fetch_assoc() 方法获取每行数据,并进行相应的处理。
  8. 最后,通过 $stmt->close() 关闭预编译语句,通过 $connection->close() 关闭数据库连接。
    使用预编译语句和参数绑定可以防止 SQL 注入攻击,并提高应用程序的安全性。预编译语句会将参数视为数据而不是代码,避免了输入的关键字被误解为可执行的语句。

预编译和参数化的区别

  • 我的理解:预编译就是把语句存储到变量中,可以重复调用,参数化是直接使用,一次性的。
  • AI解释:
    预编译语句和参数化查询的概念和实现方式非常相似,并且在实际使用中经常结合使用。它们都是为了解决相同的安全问题,即SQL注入攻击。
    参数化查询是通过使用占位符(例如?)来表示查询语句中的参数,并将参数值与查询语句分开发送给数据库。这样可以确保参数被正确地解释为数据而不是代码,从而预防了SQL注入攻击。
    预编译语句是一种使用参数化查询的技术实现方式。在预编译过程中,应用程序将查询语句发送给数据库,并告诉数据库该查询语句包含参数。数据库会对查询语句进行编译和优化,并准备一个可重复使用的执行计划。然后,应用程序可以通过绑定参数的方式多次执行该预编译语句,避免了每次执行查询都需要重新编译查询语句的开销,提高了查询的效率。
    因此,预编译语句在实现上与参数化查询非常相似。它们都使用占位符来表示参数,并将参数值与查询语句分开处理,以防止输入数据被误解为可执行的代码,从而提高了应用程序的安全性。预编译语句可以看作是对参数化查询的一种优化,可以提供更好的性能和可维护性。

    预编译和参数化为什么可以预防

*参数化查询是通过使用占位符(例如?)来表示查询语句中的参数,并将参数值与查询语句分开发送给数据库。这样可以确保参数被正确地解释为数据而不是代码,从而预防了SQL注入攻击。

宽字节注入例子

*常出现再双字节编码的场景下

<?php
// 获取用户输入的关键字
$keyword = $_GET['keyword'];
// 执行SQL查询
$query = "SELECT * FROM articles WHERE title LIKE '%" . $keyword . "%'";
$result = mysqli_query($connection, $query);

*攻击者可以利用宽字节注入来绕过输入过滤和验证。例如,如果攻击者输入 %bf%27 OR 1=1 -- 作为关键字,那么最终的查询语句将变成:

SELECT * FROM articles WHERE title LIKE '%¿' OR 1=1 -- %'

*这里也可以称作单引号绕过的一种方式(编码绕过 %bf%27

最常见的sql注入方法

<?php
$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";
$result = mysqli_query($connection, $query);
// ...
?>

*这是一个登录验证的例子
如果客户端输入

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

*在双字节编码(如UTF-8)中,单引号'被表示为%bf%27,注释符号--被表示为%bf%23%bf%23。把后面的密码验证注释掉了,不用验证了

常见的编码

%bf%27           单引号编码 (')  

%bf%23%bf%23     注释符编码(--)