PHP `SQL Injection` `Blind SQL Injection` 与 `Time-Based SQL Injection`

咳咳,各位听众朋友们,晚上好!我是今晚的“安全夜市”摊主,专门贩卖各种SQL注入的“独家秘方”。别害怕,这些秘方不是教你搞破坏,而是教你如何保护自己,避免被黑客“深夜投毒”。

今天我们要聊的是PHP环境下的三种SQL注入“黑暗料理”:SQL注入,Blind SQL注入,以及Time-Based SQL注入。我会用最通俗易懂的语言,搭配美味的代码,让你吃得开心,学得放心。

第一道菜:SQL注入(SQL Injection)——“明明白白的告诉你我要搞事”

SQL注入,顾名思义,就是把SQL代码“注入”到你的查询语句里,让数据库执行原本不该执行的操作。这就像往你的咖啡里加了敌敌畏,喝下去的后果不堪设想。

原理:

应用程序在构建SQL查询语句时,直接使用了用户输入的数据,没有进行任何过滤或转义。这就给黑客留下了可乘之机,他们可以在输入框里输入恶意SQL代码,改变查询逻辑,甚至执行任意SQL命令。

举个栗子:

假设我们有一个登录页面,PHP代码如下:

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

$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

$result = mysqli_query($conn, $sql);

if (mysqli_num_rows($result) > 0) {
    // 登录成功
} else {
    // 登录失败
}
?>

这段代码看起来很正常,但实际上存在严重的SQL注入漏洞。如果用户在username输入框里输入以下内容:

' OR '1'='1

那么,SQL语句就会变成:

SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '$password'

由于'1'='1'永远为真,所以这条SQL语句会返回所有用户的信息,黑客就可以绕过密码验证,直接登录系统。

代码实战:

我们来模拟一次简单的SQL注入攻击。假设数据库里有一张名为users的表,结构如下:

列名 数据类型
id INT
username VARCHAR(255)
password VARCHAR(255)

我们可以通过SQL注入来获取数据库里的所有用户名和密码。

在username输入框里输入:

' UNION SELECT username, password FROM users --

注意后面的--,这是SQL注释符,用来注释掉后面的AND password = '$password',防止语法错误。

如果应用程序没有做任何过滤,那么SQL语句就会变成:

SELECT * FROM users WHERE username = '' UNION SELECT username, password FROM users --' AND password = '$password'

这条SQL语句会返回两部分数据:

  1. WHERE username = ''的结果,这部分结果为空。
  2. UNION SELECT username, password FROM users的结果,这部分结果是users表里的所有用户名和密码。

最终,黑客就可以从返回结果中获取到数据库里的所有用户名和密码。

防御措施:

  • 永远不要信任用户输入的数据! 这是防御SQL注入的第一原则。
  • 使用参数化查询或预编译语句。 参数化查询会将用户输入的数据作为参数传递给SQL语句,而不是直接拼接到SQL语句里。这样可以避免SQL注入攻击。

    <?php
    $username = $_POST['username'];
    $password = $_POST['password'];
    
    $stmt = mysqli_prepare($conn, "SELECT * FROM users WHERE username = ? AND password = ?");
    mysqli_stmt_bind_param($stmt, "ss", $username, $password);
    mysqli_stmt_execute($stmt);
    $result = mysqli_stmt_get_result($stmt);
    
    if (mysqli_num_rows($result) > 0) {
        // 登录成功
    } else {
        // 登录失败
    }
    ?>
  • 对用户输入的数据进行过滤和转义。 可以使用mysqli_real_escape_string()函数来转义特殊字符。

    <?php
    $username = mysqli_real_escape_string($conn, $_POST['username']);
    $password = mysqli_real_escape_string($conn, $_POST['password']);
    
    $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
    
    $result = mysqli_query($conn, $sql);
    
    if (mysqli_num_rows($result) > 0) {
        // 登录成功
    } else {
        // 登录失败
    }
    ?>
  • 使用最小权限原则。 数据库用户只应该拥有执行必要操作的权限,不要授予过多的权限。
  • 定期更新数据库和应用程序。 及时修复已知的安全漏洞。
  • 使用Web应用防火墙(WAF)。 WAF可以检测和防御SQL注入攻击。

第二道菜:Blind SQL注入(Blind SQL Injection)——“悄无声息的试探”

Blind SQL注入,顾名思义,就是“盲注”。和普通的SQL注入不同,Blind SQL注入不会直接返回数据库里的数据,而是通过一些间接的方式来判断SQL语句的执行结果。这就像一个医生给病人做检查,但是看不到病灶,只能通过病人的反应来判断病情。

原理:

应用程序不会直接显示SQL查询的结果,而是根据查询结果显示不同的页面或者返回不同的状态码。黑客可以通过构造不同的SQL语句,观察应用程序的反应,来推断数据库里的数据。

两种类型:

  • Boolean-based Blind SQL Injection: 通过SQL语句的真假来判断结果。
  • Time-based Blind SQL Injection: 通过SQL语句的执行时间来判断结果。

先说说 Boolean-based Blind SQL Injection:

举个栗子:

假设我们有一个搜索功能,PHP代码如下:

<?php
$keyword = $_GET['keyword'];

$sql = "SELECT * FROM products WHERE name LIKE '%$keyword%'";

$result = mysqli_query($conn, $sql);

if (mysqli_num_rows($result) > 0) {
    echo "搜索结果:";
    // 显示搜索结果
} else {
    echo "没有找到相关商品。";
}
?>

这段代码也存在SQL注入漏洞。但是,它不会直接显示数据库里的数据,而是根据搜索结果显示不同的提示信息。

我们可以利用这个漏洞来判断数据库里是否存在某个用户。

在keyword输入框里输入:

' AND (SELECT 1 FROM users WHERE username = 'admin') IS NOT NULL --

如果数据库里存在用户名为admin的用户,那么SQL语句就会变成:

SELECT * FROM products WHERE name LIKE '%' AND (SELECT 1 FROM users WHERE username = 'admin') IS NOT NULL --%'

AND (SELECT 1 FROM users WHERE username = 'admin') IS NOT NULL 这部分会返回真,所以搜索结果会显示“搜索结果:”。

如果数据库里不存在用户名为admin的用户,那么SQL语句就会变成:

SELECT * FROM products WHERE name LIKE '%' AND (SELECT 1 FROM users WHERE username = 'admin') IS NOT NULL --%'

AND (SELECT 1 FROM users WHERE username = 'admin') IS NOT NULL 这部分会返回假,所以搜索结果会显示“没有找到相关商品。”。

通过这种方式,我们就可以判断数据库里是否存在某个用户。

代码实战:

我们来模拟一次Boolean-based Blind SQL注入攻击。假设我们要判断数据库里是否存在用户名为admin的用户。

我们可以使用以下SQL语句:

' AND (SELECT 1 FROM users WHERE username = 'admin') IS NOT NULL --

如果返回“搜索结果:”,那么说明数据库里存在用户名为admin的用户。

如果返回“没有找到相关商品。”,那么说明数据库里不存在用户名为admin的用户。

再说说 Time-based Blind SQL Injection:

原理:

应用程序不会直接显示SQL查询的结果,而是根据查询结果的执行时间来判断结果。黑客可以通过构造不同的SQL语句,让数据库执行一些耗时的操作,观察应用程序的响应时间,来推断数据库里的数据。

举个栗子:

还是上面的搜索功能,我们可以利用Time-based Blind SQL注入来获取数据库里的版本信息。

在keyword输入框里输入:

' AND IF(version() LIKE '5%', SLEEP(5), 0) --

如果数据库版本以5开头,那么SQL语句就会变成:

SELECT * FROM products WHERE name LIKE '%' AND IF(version() LIKE '5%', SLEEP(5), 0) --%'

IF(version() LIKE '5%', SLEEP(5), 0) 这部分会让数据库休眠5秒。

如果数据库版本不是以5开头,那么SQL语句就会变成:

SELECT * FROM products WHERE name LIKE '%' AND IF(version() LIKE '5%', SLEEP(5), 0) --%'

IF(version() LIKE '5%', SLEEP(5), 0) 这部分会让数据库休眠0秒。

通过观察应用程序的响应时间,我们就可以判断数据库的版本信息。

代码实战:

我们来模拟一次Time-based Blind SQL注入攻击。假设我们要判断数据库的版本是否以5开头。

我们可以使用以下SQL语句:

' AND IF(version() LIKE '5%', SLEEP(5), 0) --

如果应用程序的响应时间超过5秒,那么说明数据库版本以5开头。

如果应用程序的响应时间很短,那么说明数据库版本不是以5开头。

防御措施:

Blind SQL注入的防御措施和SQL注入类似,但是需要更加严格的过滤和转义。

  • 永远不要信任用户输入的数据!
  • 使用参数化查询或预编译语句。
  • 对用户输入的数据进行严格的过滤和转义。
  • 不要在错误信息中显示敏感信息。
  • 限制数据库用户的权限。
  • 使用Web应用防火墙(WAF)。

第三道菜:Time-Based SQL Injection(基于时间的SQL注入)——“比的就是耐心”

Time-Based SQL Injection 是 Blind SQL Injection 的一种特殊形式,它依赖于数据库执行某些操作所需的时间来推断信息。 就像你在玩一个猜数字游戏,对方不告诉你答案,但是会根据你的猜测让你等不同的时间,你根据等待时间来判断数字的大小。

原理:

攻击者通过注入 SQL 代码,使数据库执行一个耗时的操作(比如 SLEEP() 函数),然后根据响应时间来判断条件是否成立。 如果响应时间超过预设的阈值,就说明条件为真;否则,条件为假。

举个栗子:

假设我们有一个页面,可以根据 ID 查询用户信息:

<?php
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$result = mysqli_query($conn, $sql);

if ($result) {
    // 显示用户信息
} else {
    echo "Error!";
}
?>

这个代码看起来很简单,但是 id 参数没有经过任何处理,存在 SQL 注入的风险。 我们可以利用 Time-Based SQL Injection 来获取数据库的版本信息。

代码实战:

攻击者可以构造如下 URL:

http://example.com/user.php?id=1 AND IF(substring(version(),1,1)='5',sleep(5),0)

这个 URL 的含义是:

  • 如果数据库版本的第一位是 ‘5’,就执行 sleep(5) 函数,让数据库休眠 5 秒。
  • 否则,执行 sleep(0) 函数,让数据库休眠 0 秒。

如果页面响应时间超过 5 秒,就说明数据库版本的第一位是 ‘5’。 我们可以通过不断改变 substring() 函数的参数,来逐位获取数据库的版本信息。

更复杂的例子:

假设我们要获取 users 表中第一个用户的用户名长度。 可以构造如下 SQL 注入语句:

id=1 AND IF((SELECT LENGTH(username) FROM users LIMIT 1) = 5, SLEEP(5), 0)

这条语句的意思是:如果 users 表中第一个用户的用户名长度等于 5,就休眠 5 秒。 我们可以通过二分法来猜测用户名的长度,每次改变等号后面的数字,直到找到正确的长度。

防御措施:

Time-Based SQL Injection 的防御措施和 Blind SQL Injection 类似,但更强调对响应时间的监控。

  • 参数化查询/预编译语句: 这是最有效的防御手段,可以完全避免 SQL 注入。
  • 严格的输入验证和过滤: 过滤掉用户输入中的特殊字符和关键字。
  • 最小权限原则: 数据库用户只拥有必要的权限。
  • Web 应用防火墙(WAF): WAF 可以检测和防御 SQL 注入攻击。
  • 监控响应时间: 监控应用程序的响应时间,如果发现异常的延迟,及时报警。
  • 禁用 SLEEP() 函数: 如果不需要使用 SLEEP() 函数,可以禁用它,防止被攻击者利用。 (但是注意,有些应用程序可能需要使用SLEEP函数,所以需要根据实际情况来决定是否禁用)

总结:

攻击类型 特点 防御措施
SQL Injection 直接返回数据库信息,攻击效果明显。 参数化查询/预编译语句,严格的输入验证和过滤,最小权限原则,Web 应用防火墙(WAF),定期更新数据库和应用程序。
Blind SQL Injection 不直接返回数据库信息,需要通过间接方式(Boolean-based 或 Time-based)来推断信息。 参数化查询/预编译语句,更严格的输入验证和过滤,最小权限原则,Web 应用防火墙(WAF),不要在错误信息中显示敏感信息。
Time-Based SQL Injection 是 Blind SQL Injection 的一种特殊形式,依赖于数据库执行耗时操作的时间来推断信息。 参数化查询/预编译语句,更严格的输入验证和过滤,最小权限原则,Web 应用防火墙(WAF),监控响应时间,禁用 SLEEP() 函数(如果不需要)。

好了,今天的“安全夜市”就到这里。希望这些“黑暗料理秘方”能帮助你更好地保护你的应用程序。记住,安全无小事,防患于未然。祝各位晚安!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注