从布尔盲注到参数化查询:DVWA SQL 盲注模块完整漏洞分析教程

文章目录

📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 09 篇文章。

一、概述

1、什么是 SQL 盲注

SQL 盲注(Blind SQL Injection)是 SQL 注入攻击的一种特殊形式。当应用程序存在 SQL 注入漏洞,但既不返回数据库错误信息,也不直接显示查询结果时,攻击者便无法直接从页面内容中获取数据。此时,攻击者需要借助"盲注"技术,通过观察应用程序的间接反馈来推断数据库中的信息。

盲注与普通 SQL 注入的核心区别如下:

对比维度 普通 SQL 注入 SQL 盲注
错误信息 ✅ 直接回显 ❌ 不返回或隐藏
查询结果 ✅ 直接显示在页面 ❌ 不直接显示
判断依据 页面内容变化 页面"存在/不存在"的差异或响应时间
利用难度 较低 较高(需要逐字符猜解)

2、盲注的两种主要类型

(1)布尔盲注(Boolean-based Blind SQL Injection)

通过构造 SQL 判断语句,观察页面返回的两种不同状态(例如"存在"与"不存在")来推断条件真假。

示例:

  • 条件为真 → 页面显示 "User ID exists in the database."
  • 条件为假 → 页面显示 "User ID is MISSING from the database."

(2)时间盲注(Time-based Blind SQL Injection)

当页面无论条件真假都返回相同内容时,可通过注入延时函数(如 SLEEP()BENCHMARK()),根据页面响应时间的差异来判断条件真假。

示例:

  • 条件为真 → 页面延迟 5 秒响应。
  • 条件为假 → 页面立即响应。

二、LOW 级别 ------ 无过滤的布尔盲注

1、漏洞描述

LOW 级别的代码结构与普通 SQL 注入的 LOW 级别相同,都是直接将用户输入拼接到 SQL 语句中。然而,关键区别在于:服务器不会返回查询结果的具体内容,也不会显示数据库错误信息,而是仅通过页面状态(例如"存在"或"不存在")来反馈查询结果。

核心问题:

  1. 无任何输入验证或过滤:用户输入未经任何安全检查。
  2. 直接拼接用户输入到 SQL 语句:导致 SQL 注入漏洞。
  3. 页面仅返回两种状态:攻击者无法直接获取数据,必须通过逐字符盲猜的方式推断信息。

2、查看网页源代码

php 复制代码
<?php

if( isset( $_GET[ 'Submit' ] ) ) {
    // Get input
    $id = $_GET[ 'id' ];
    $exists = false;

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            // Check database
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
            try {
                $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ); // Removed 'or die' to suppress mysql errors
            } catch (Exception $e) {
                print "There was an error.";
                exit;
            }

            $exists = false;
            if ($result !== false) {
                try {
                    $exists = (mysqli_num_rows( $result ) > 0);
                } catch(Exception $e) {
                    $exists = false;
                }
            }
            ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
            break;
        case SQLITE:
            global $sqlite_db_connection;

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
            try {
                $results = $sqlite_db_connection->query($query);
                $row = $results->fetchArray();
                $exists = $row !== false;
            } catch(Exception $e) {
                $exists = false;
            }

            break;
    }

    if ($exists) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    } else {
        // User wasn't found, so the page wasn't!
        header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }

}

?>

3、分析网页源代码

(1)代码概述

LOW 级别的后端逻辑主要包括以下几个步骤:

  1. 获取用户输入 :从 $_GET 超全局数组中获取 id 参数。
  2. 无过滤拼接 :未对 $id 进行任何过滤或转义处理,直接将其拼接到 SQL 查询语句中。
  3. 抑制错误信息 :使用 @mysqli_num_rows() 抑制数据库错误,避免具体错误信息暴露给用户。
  4. 返回固定状态 :根据查询结果返回两种固定的页面状态:
    • 有匹配数据 → 显示 "User ID exists in the database."
    • 无匹配数据 → 显示 "User ID is MISSING from the database."

(2)漏洞分析

基于以上代码逻辑,可以识别出以下三个关键的安全漏洞:

① 漏洞一 ------ 完全无输入过滤

  • 问题分析$id 参数未经任何验证或过滤,直接拼接到 SQL 查询语句中。这使得攻击者可以构造恶意的 id 值,从而改变 SQL 语句的原始逻辑,实现注入攻击。

② 漏洞二 ------ 页面状态可区分

  • 问题分析:页面会根据查询结果返回"存在"和"不存在"两种截然不同的状态。这种明确的反馈为攻击者提供了判断依据,使其能够通过布尔盲注技术,逐字符推断数据库中的敏感信息。

③ 漏洞三 ------ 字符型注入

  • 问题分析 :查询语句 user_id = '$id' 使用单引号包裹用户输入,构成了典型的字符型注入点。攻击者需要先闭合前面的单引号,才能插入并执行自定义的布尔表达式。

4、操作步骤

(1)正常查询

为了验证漏洞环境并熟悉正常交互流程,请按以下步骤操作:

  1. 登录 DVWA:使用您的凭据登录 Damn Vulnerable Web Application (DVWA)。
  2. 设置安全级别 :在 DVWA 左侧菜单中,将安全级别设置为 Low
  3. 进入目标页面 :点击左侧菜单中的 SQL Injection (Blind),进入 SQL 盲注漏洞测试页面。
  4. 执行查询 :在输入框中输入用户 ID 1,然后点击 Submit 按钮。
  5. 观察结果 :页面应显示 "User ID exists in the database." ,这表明 ID 为 1 的用户存在于数据库中,验证了正常查询功能。

(2)判断注入类型

为了确认漏洞类型为布尔盲注,我们需要构造特定的 SQL 注入 Payload 并观察页面反馈。请按以下步骤操作:

  1. 构造真值条件 Payload

    • Payload1' AND 1=1 #
    • 操作 :在输入框中输入 1' AND 1=1 #,然后点击 Submit 按钮。
    • 预期结果 :页面显示 "User ID exists in the database." 。这表明注入的布尔条件 1=1(恒真)被执行,查询结果为真,页面返回"存在"状态。
  2. 构造假值条件 Payload

    • Payload1' AND 1=2 #
    • 操作 :在输入框中输入 1' AND 1=2 #,然后点击 Submit 按钮。
    • 预期结果 :页面显示 "User ID is MISSING from the database." 。这表明注入的布尔条件 1=2(恒假)被执行,查询结果为假,页面返回"不存在"状态。
  3. 结论

    • 如果两次测试的页面反馈结果不同(一次"存在",一次"不存在"),则证明应用程序对布尔条件做出了不同的响应。
    • 这清晰地表明,存在布尔盲注漏洞 。攻击者可以利用这种页面状态的差异,通过构造更复杂的布尔表达式来逐位推断数据库中的信息。

(3)获取数据库名长度

在确认存在布尔盲注漏洞后,我们可以开始利用该漏洞逐步获取数据库信息。首先,我们需要获取当前数据库的名称长度。

  1. 构造 Payload
    • Payload1' AND LENGTH(DATABASE()) = 4 #
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 预期结果 :页面显示 "User ID exists in the database." 。这表明我们猜测的数据库名长度(4)是正确的。对于 DVWA 默认数据库,其名称 dvwa 的长度即为 4。

(4)逐字符获取数据库名

知道了数据库名长度后,接下来需要逐字符猜解数据库名的具体内容。

  1. 构造 Payload
    • Payload1' AND ASCII(SUBSTRING(DATABASE(), 1, 1)) = 100 #
    • 原理SUBSTRING(DATABASE(), 1, 1) 用于截取数据库名的第一个字符。ASCII() 函数将其转换为 ASCII 码值。我们猜测第一个字符的 ASCII 码为 100(对应小写字母 d)。
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 判断:如果页面返回"存在",则说明猜测正确(ASCII('d') = 100)。如果返回"不存在",则需要尝试其他 ASCII 值(例如 101, 102...)。
  2. 重复猜解
    • 依次改变 SUBSTRING(DATABASE(), N, 1) 中的位置参数 N(从 1 到数据库名长度),并遍历可能的 ASCII 值(通常为 97-122 对应小写字母 a-z,65-90 对应大写字母 A-Z,48-57 对应数字 0-9)。
    • 通过观察页面是"存在"还是"不存在"来判断每个位置的字符。
  3. 预期结果
    • 最终得到数据库名为 dvwa ,其四个字符对应的 ASCII 值依次为:100 (d), 118 (v), 119 (w), 97 (a)。

(5)获取数据库中的表数量

获取数据库名后,下一步是探查该数据库中存在哪些表。

  1. 构造 Payload
    • Payload1' AND (SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = DATABASE()) = 1 #
    • 原理 :通过查询 information_schema.tables 系统表,统计当前数据库(DATABASE())中所有用户表的数量。
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 预期结果 :页面显示 "User ID exists in the database." 。这表明 dvwa 数据库中恰好有 1 个表。

(6)获取表名长度

知道了数据库中只有一个表后,下一步是获取该表的名称长度。

  1. 构造 Payload
    • Payload1' AND LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 0, 1)) = 5 #
    • 原理LIMIT 0, 1 用于选取当前数据库中的第一个(也是唯一一个)表。LENGTH() 函数用于计算该表名的长度。
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 预期结果:页面返回"存在"状态,说明表名的长度为 5 个字符。

(7)逐字符获取表名

获取表名长度后,开始逐字符猜解表名。

  1. 构造 Payload
    • Payload1' AND ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 0, 1), 1, 1)) = 117 #
    • 原理SUBSTR(..., 1, 1) 截取表名的第一个字符,ASCII() 获取其 ASCII 码。我们猜测为 117(对应小写字母 u)。
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 判断:页面返回"存在"则猜对。
  2. 重复猜解
    • 依次改变 SUBSTR(..., N, 1) 中的位置参数 N(从 1 到表名长度 5),并遍历可能的 ASCII 值进行猜解。
    • 例如,第二个字符的 Payload 为:1' AND ASCII(SUBSTR((SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 0, 1), 2, 1)) = 115 #(ASCII('s') = 115)。
  3. 预期结果
    • 最终得到表名为 users

(8)确认 users 表存在

获取表名后,可以进一步确认该表确实存在且包含数据。

  1. 构造 Payload
    • Payload1' AND (SELECT COUNT(*) FROM users) > 0 #
    • 原理 :查询 users 表中的记录数量是否大于 0。
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 预期结果 :页面返回"存在"状态,确认 users 表存在且至少包含一条数据。

(9)获取 users 表的字段数量

确认表存在后,下一步是探查表结构,即获取 users 表包含的字段(列)数量。

  1. 构造 Payload
    • Payload1' AND (SELECT COUNT(column_name) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'users') = 8 #
    • 原理 :查询 information_schema.columns 系统表,统计 dvwa 数据库中 users 表的所有字段数量。
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 预期结果 :页面返回"存在"状态,说明 users 表共有 8 个字段。

(10)逐字符获取字段名

知道了字段数量,接下来需要逐个获取字段的名称。

  1. 构造 Payload
    • Payload1' AND ASCII(SUBSTR((SELECT column_name FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'users' LIMIT 0, 1), 1, 1)) = 117 #
    • 原理LIMIT 0, 1 选取 users 表的第一个字段。猜测其第一个字符的 ASCII 码为 117(对应小写字母 u)。
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 判断:页面返回"存在"则猜对。
  2. 重复猜解
    • 首先,需要为每个字段(通过改变 LIMIT M, 1 中的 M 从 0 到 7)重复上述猜解长度和字符的过程。
    • 对于每个字段,先猜解其名称长度,再逐字符猜解名称。
  3. 预期结果
    • 最终可以依次得到 users 表的字段名,例如:user_id, first_name, last_name, user, password, avatar, last_login, failed_login

(11)时间盲注验证(拓展)

布尔盲注依赖于页面返回"存在"或"不存在"两种明确状态。如果目标应用程序无论查询结果如何都返回相同的页面内容,我们就需要借助时间盲注技术。

  1. 构造真值延时 Payload
    • Payload1' AND IF(1=1, SLEEP(5), 0) #
    • 原理IF(1=1, SLEEP(5), 0) 是一个条件语句。因为 1=1 恒为真,所以会执行 SLEEP(5) 函数,使数据库休眠 5 秒,从而导致页面响应延迟。
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 预期结果:页面响应时间明显延迟(约 5 秒)。
  2. 构造假值无延时 Payload
    • Payload1' AND IF(1=2, SLEEP(5), 0) #
    • 原理 :因为 1=2 恒为假,所以执行 0,不会触发延时函数。
    • 操作 :在输入框中输入上述 Payload,然后点击 Submit 按钮。
    • 预期结果:页面立即响应,无延迟。
  3. 结论
    • 通过观察页面响应时间的差异,可以判断注入的布尔条件是否为真。这种技术适用于那些不返回明确状态差异,但存在 SQL 注入漏洞的应用程序。

5、AI视角下的 SQL 盲注检测

(1)基于页面状态的异常检测

传统的布尔盲注检测主要依赖安全人员人工观察页面状态的差异。借助人工智能(AI)技术,我们可以构建自动化盲注检测模型,从海量请求中精准识别攻击行为。其核心思路是对比正常请求与布尔盲注攻击在多个特征维度上的差异:

特征维度 正常请求 布尔盲注攻击
请求模式 参数值多为单一、离散的数值或自然语言。 出现大量结构高度相似的请求,旨在逐字符爆破数据库信息。
请求频率 频率较低,符合用户正常操作节奏。 频率极高,通常由自动化脚本发起,短时间内产生密集请求。
参数变化 参数值变化无固定规律,呈现自然、随机的分布。 参数值呈现系统化、可预测的变化模式(如 ASCII 码值连续递增)。
响应模式 页面响应内容多样,状态码、页面长度等特征分布广泛。 响应模式高度集中,通常仅在"存在"与"不存在"两种固定状态间交替。

技术实现方案:

  • 请求聚类分析:应用聚类算法(如 K-Means、DBSCAN)对请求参数和响应特征进行聚类,识别出大量高度相似的探测请求簇,这些簇往往对应盲注攻击。
  • 时序分析:监控短时间内(如1分钟内)的请求频率,检测是否存在远超基线的高频请求模式,这是自动化攻击的典型特征。
  • 参数熵值分析:计算请求参数值的熵(信息熵),正常用户输入的熵值较高(随机性强),而盲注攻击的参数值往往呈现低熵、有序的模式(如连续的 ASCII 值探测),易于被识别。

(2)AI 辅助的时间盲注检测

时间盲注不依赖页面内容差异,而是通过观察响应时间的延迟来判断条件真假,这使得传统基于内容特征的检测方法失效。AI 可以从时序和关联维度进行检测:

  • 响应时间基线建模:首先,在业务低峰期收集大量正常请求,建立各接口、各参数组合下的响应时间基线(包括均值、标准差、百分位数等)。任何显著偏离基线的延迟(如突然出现固定5秒延迟)都可能意味着时间盲注攻击。
  • 延时模式识别 :攻击者常使用固定的延时函数(如 SLEEP(5))。AI 模型可以学习识别这种"请求-固定延迟-响应"的周期性或准周期性模式,并与正常网络波动或服务器负载造成的延迟区分开。
  • 参数-延迟关联分析 :将请求参数与对应的响应时间进行关联分析。在时间盲注中,特定的参数值(如包含 SLEEP() 函数的Payload)会与异常的响应时间强相关。通过机器学习模型(如决策树、逻辑回归)可以挖掘这种隐藏的关联特征,从而在攻击参数变化时仍能有效检测。

6、LOW级别------小结

LOW 级别的 SQL 盲注漏洞完全没有任何安全防护措施,攻击者可以利用布尔盲注技术,通过逐字符爆破的方式获取数据库中的任意敏感数据。

核心问题总结如下:

  1. 无任何输入验证或过滤 :用户输入的 id 参数未经任何安全检查或转义处理,直接被拼接到 SQL 查询语句中,为注入攻击敞开了大门。
  2. 页面状态可明确区分:应用程序根据查询结果返回"存在"与"不存在"两种截然不同的页面状态,为攻击者提供了清晰的布尔判断依据。
  3. 可通过逐字符爆破获取完整数据:攻击者利用页面状态的差异,通过构造系统的布尔表达式,能够逐步推断出数据库名、表名、字段名乃至具体数据内容,最终实现完整的数据窃取。

安全启示:

该级别漏洞清晰地展示了"无防护即高风险"的安全原则。在开发过程中,必须对用户输入进行严格的验证、过滤和参数化处理,同时避免在页面上返回过于明确的差异状态,以增加攻击者的盲注难度。

三、MEDIUM 级别 ------ 转义函数与下拉菜单

1、漏洞描述

MEDIUM 级别在 LOW 级别的基础上引入了两种防护措施:后端使用 mysqli_real_escape_string() 函数对用户输入进行转义,前端使用下拉选择框(SELECT)限制用户只能选择预设的 ID 值,同时将请求方法从 GET 改为 POST。

核心防护机制:

  1. 后端转义处理 :使用 mysqli_real_escape_string() 函数对用户输入的 id 参数中的特殊字符(如单引号、反斜杠等)进行转义,旨在防止 SQL 注入。
  2. 前端输入限制 :使用 HTML <select> 下拉菜单,将用户输入限制为预设的数值选项(如 1, 2, 3, 4, 5),试图从源头杜绝恶意输入。
  3. 请求方法变更:将参数提交方式从 GET 改为 POST,使参数不直接暴露在 URL 中,增加攻击者直接修改参数的难度。

核心安全问题:

尽管引入了上述防护,MEDIUM 级别仍然存在以下安全缺陷:

  1. 前端限制可绕过 :前端下拉菜单仅是一种用户体验限制,无法真正阻止攻击者。攻击者可以通过代理工具(如 Burp Suite)拦截并修改 HTTP 请求,将 id 参数替换为任意值,从而绕过前端限制。
  2. 转义函数对数字型注入无效mysqli_real_escape_string() 主要用于转义字符串中的特殊字符,防止字符型注入。然而,如果应用程序的 SQL 查询逻辑本身存在缺陷(例如未对数字型参数进行类型转换或验证),攻击者可以构造数字型 Payload(如 1 OR 1=1),由于数字不需要引号闭合,转义函数将无法起到防护作用。
  3. 页面状态反馈未改变:与 LOW 级别一样,页面仍然根据查询结果返回"存在"或"不存在"两种明确状态。这为布尔盲注攻击提供了清晰的判断依据,攻击者一旦绕过前端和转义,即可利用相同的盲注技术进行信息探测。

2、查看网页源代码

php 复制代码
<?php

if( isset( $_POST[ 'Submit' ]  ) ) {
    // Get input
    $id = $_POST[ 'id' ];
    $exists = false;

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            $id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"],  $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

            // Check database
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
            try {
                $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ); // Removed 'or die' to suppress mysql errors
            } catch (Exception $e) {
                print "There was an error.";
                exit;
            }

            $exists = false;
            if ($result !== false) {
                try {
                    $exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
                } catch(Exception $e) {
                    $exists = false;
                }
            }
            
            break;
        case SQLITE:
            global $sqlite_db_connection;
            
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
            try {
                $results = $sqlite_db_connection->query($query);
                $row = $results->fetchArray();
                $exists = $row !== false;
            } catch(Exception $e) {
                $exists = false;
            }
            break;
    }

    if ($exists) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    } else {
        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }
}

?>

3、分析网页源代码

(1)代码概述

MEDIUM 级别在 LOW 级别的基础上,引入了以下关键变化以增强安全性:

  1. 请求方法变更 :从 $_GET 改为 $_POST 获取 id 参数,使参数不直接暴露在 URL 中,增加了攻击者直接修改参数的难度。
  2. 输入转义处理 :使用 mysqli_real_escape_string() 函数对用户输入的 id 参数进行转义,旨在过滤特殊字符(如单引号 '、双引号 "、反斜杠 \ 等),防止字符型 SQL 注入。
  3. 查询语句结构变化 :SQL 查询语句由 user_id = '$id' 变为 user_id = $id,即 $id 不再被单引号包裹。这意味着此处被设计为数字型查询,而非字符型。
  4. 页面状态反馈未变:与 LOW 级别相同,页面仍然根据查询结果返回"存在"或"不存在"两种固定状态。

(2)漏洞分析

尽管引入了上述防护措施,MEDIUM 级别仍然存在以下三个关键的安全漏洞:

① 漏洞一 ------ 前端下拉菜单可被绕过

  • 问题分析 :前端使用 HTML <select> 下拉选择框,将用户输入限制为预设的数值选项(如 1, 2, 3, 4, 5)。然而,这仅是一种客户端(前端)的用户体验限制,无法真正阻止攻击者。攻击者可以通过代理工具(如 Burp Suite)拦截 HTTP 请求,直接修改 POST 请求体中的 id 参数为任意值,从而完全绕过前端限制。

② 漏洞二 ------ 转义函数对数字型注入无效

  • 问题分析mysqli_real_escape_string() 函数的主要作用是转义字符串中的特殊字符,以防止字符型 SQL 注入。然而,在本级别的查询语句 user_id = $id 中,$id 未被单引号包裹,属于数字型注入 点。对于数字型注入,攻击者构造的 Payload(如 1 OR 1=1)本身不需要使用单引号,因此转义函数无法对其中的特殊字符(如空格、括号)产生有效的防护作用,攻击依然可以成功。

③ 漏洞三 ------ 仍可进行布尔盲注

  • 问题分析 :页面依然根据查询结果返回"User ID exists in the database."或"User ID is MISSING from the database."两种明确状态。这意味着,一旦攻击者绕过前端限制,并利用数字型注入点构造出有效的布尔表达式(例如 1 AND 1=11 AND 1=2),就可以通过观察页面状态的差异来进行布尔盲注攻击,逐步推断数据库信息。

4、操作步骤

(1)抓包修改 id 参数

为了绕过前端下拉菜单的限制并验证数字型注入漏洞,我们需要使用代理工具(如 Burp Suite)拦截并修改 HTTP 请求。具体操作步骤如下:

  1. 开启 Burp Suite 代理拦截

    • 启动 Burp Suite,确保代理监听器(Proxy → Intercept)处于 Intercept is on 状态。
  2. 触发正常请求

    • 在 DVWA 的 SQL Injection (Blind) 页面(Medium 安全级别),从下拉菜单中选择一个有效用户 ID(例如 1),然后点击 Submit 按钮。
  3. 拦截并分析请求

    • Burp Suite 将拦截到浏览器发出的 POST 请求。请求示例如下:

      http 复制代码
      POST /vulnerabilities/sqli_blind/ HTTP/1.1
      Host: dvwa.cc
      Cookie: PHPSESSID=xxx; security=medium
      
      id=1&Submit=Submit
    • 观察请求体,可以看到 id 参数值为 1,这是通过前端下拉菜单选择的正常值。

  4. 修改 id 参数为恶意 Payload

    • 由于后端查询语句为 user_id = $id(数字型),我们构造的 Payload 不需要使用单引号

    • 在 Burp Suite 的拦截界面,将请求体中的 id=1 修改为 id=1 AND 1=1,即:

      http 复制代码
      id=1 AND 1=1&Submit=Submit
    • 此 Payload 利用了数字型注入点,注入了一个恒真的布尔条件 AND 1=1

  5. 放行请求并观察响应

    • 点击 Forward 按钮放行修改后的请求。
    • 观察浏览器页面。如果页面显示 "User ID exists in the database.",则说明注入的布尔条件被执行,页面返回"存在"状态,验证了数字型布尔盲注漏洞的存在。
    • 可以进一步尝试将 Payload 修改为 id=1 AND 1=2,如果页面返回 "User ID is MISSING from the database." ,则进一步确认了漏洞的可利用性。

(2)数字型布尔盲注 Payload

在成功绕过前端限制并确认数字型注入点后,我们可以构造一系列布尔盲注 Payload 来探测数据库信息。由于是数字型注入,Payload 中不需要使用单引号进行闭合。

下表列出了几个关键的 Payload 及其预期效果:

Payload 效果 说明
id=1 AND 1=1&Submit=Submit 返回"存在"(条件为真) 注入恒真条件 AND 1=1,用于验证布尔盲注漏洞是否可利用。
id=1 AND 1=2&Submit=Submit 返回"不存在"(条件为假) 注入恒假条件 AND 1=2,用于确认页面状态会随布尔条件变化。
id=1 AND LENGTH(DATABASE())=4&Submit=Submit 返回"存在"(条件为真) 判断当前数据库名的长度是否为 4。若页面返回"存在",则说明数据库名长度为 4(DVWA 默认数据库 dvwa 的长度即为 4)。

使用说明:

  1. 构造 Payload :在 Burp Suite 拦截的请求中,将 id 参数的值替换为上表中的 Payload。
  2. 发送请求 :点击 Forward 放行修改后的请求。
  3. 观察响应:根据页面返回"存在"或"不存在"的状态,判断注入的布尔条件是否为真,从而逐步推断出数据库名、表名、字段名等敏感信息。

后续利用思路:

  • 获取数据库名 :在确认数据库名长度后,可使用 1 AND ASCII(SUBSTRING(DATABASE(), 1, 1)) = 100&Submit=Submit 等 Payload 逐字符猜解数据库名。
  • 获取表名 :使用 1 AND (SELECT COUNT(table_name) FROM information_schema.tables WHERE table_schema = DATABASE()) = 1&Submit=Submit 判断表数量,再逐字符猜解表名。
  • 获取字段名:类似地,可进一步猜解表结构,获取字段名。

通过以上步骤,攻击者即可在 MEDIUM 级别下,利用数字型布尔盲注漏洞,逐步窃取数据库中的敏感信息。度

(3)使用 Burp Intruder 自动化爆破

手动逐字符猜解数据库信息效率低下,且容易出错。为了提高效率,我们可以利用 Burp Suite 的 Intruder 模块进行自动化爆破。以下是具体操作步骤:

  1. 发送请求到 Intruder

    • 在 Burp Suite 的 Proxy → HTTP history 中,找到之前拦截并修改过的请求(例如 id=1 AND 1=1&Submit=Submit)。
    • 右键点击该请求,选择 Send to Intruder (或使用快捷键 Ctrl+I)。
  2. 设置攻击类型

    • 切换到 Intruder 标签页,然后进入 Positions 子标签。

    • 默认情况下,Burp 会自动标记一些参数。我们需要清除默认标记,然后手动标记两个变量位置:

      • 第一个变量:数据库名中字符的位置(从 1 开始)。
      • 第二个变量:猜测的 ASCII 码值。
    • 在请求体中,将 id 参数的值修改为以下格式,并手动标记两个 § 之间的部分为变量:

      复制代码
      id=1 AND ASCII(SUBSTRING(DATABASE(),§1§,1))=§2§&Submit=Submit
    • Attack type 下拉菜单中选择 Cluster bomb。这种攻击类型适用于两个变量需要组合爆破的场景(即每个位置尝试所有可能的 ASCII 值)。

  3. 配置 Payload 集

    • 切换到 Payloads 子标签。
    • Payload set 1 (对应第一个变量 §1§,即字符位置):
      • Payload type :选择 Numbers
      • From :设置为 1
      • To :设置为 4(因为我们已知数据库名 dvwa 的长度为 4)。
      • Step :设置为 1
      • 这表示将依次尝试第 1、2、3、4 个字符位置。
    • Payload set 2 (对应第二个变量 §2§,即 ASCII 码值):
      • Payload type :选择 Numbers
      • From :设置为 97(对应小写字母 a 的 ASCII 码)。
      • To :设置为 122(对应小写字母 z 的 ASCII 码)。
      • Step :设置为 1
      • 这表示在每个字符位置上,将依次尝试 ASCII 码 97 到 122(即所有小写字母)。
  4. 开始攻击并分析结果

    • 点击 Start attack 按钮,Intruder 将自动发送所有组合的请求。
    • 攻击完成后,观察结果列表。重点关注 Status 列和 Length 列(或您自定义的筛选条件)。
    • 由于我们构造的 Payload 在猜对时会返回"存在"状态(通常对应特定的响应长度或状态码),而在猜错时会返回"不存在"状态。通过筛选响应长度或状态码,可以快速识别出哪些请求返回了"存在"状态。
    • 例如,对于第一个字符位置(§1§=1),遍历 ASCII 值 97-122,当 Payload 为 1 AND ASCII(SUBSTRING(DATABASE(),1,1))=100&Submit=Submit 时,如果响应表明"存在",则说明第一个字符的 ASCII 码是 100,即字母 d
    • 依次分析每个位置的结果,即可快速得到完整的数据库名 dvwa

优势与扩展

  • 高效:自动化爆破避免了手动尝试每个 ASCII 值的繁琐过程。
  • 可扩展 :此方法同样适用于猜解表名、字段名乃至具体数据。只需修改 Payload 中的 SQL 语句部分(例如将 DATABASE() 替换为 (SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() LIMIT 0,1) 来猜解表名),并调整 Payload 集的范围即可。
  • 灵活 :除了 Cluster bomb ,还可以根据场景选择 SniperBattering ramPitchfork 等攻击类型。

5、AI视角下的 SQL 盲注检测

(1)数字型注入的智能检测

MEDIUM 级别虽然使用了 mysqli_real_escape_string() 函数进行转义,但该函数对数字型注入点无效。AI 可以从以下维度辅助检测此类漏洞:

  • 上下文感知检测 :AI 模型可以分析应用程序的 SQL 查询语句结构,识别出那些参数未被引号包裹的查询点(如 user_id = $id)。这类查询点极易受到数字型注入攻击,即使使用了转义函数也无法防护。
  • 参数类型推断:通过分析参数在 SQL 语句中的上下文(例如,是否用于数值比较、是否参与算术运算),AI 可以推断参数本应被处理为数字类型。如果发现攻击者提交的 Payload 中包含了非数字字符(如字母、空格、运算符)且未被引号包裹,则可判定为潜在的数字型注入尝试。
  • 异常模式识别 :AI 可以学习正常数字型参数的输入模式(如纯数字、有限范围内的整数)。当检测到包含 SQL 关键字(如 ANDORSLEEP)、运算符或函数调用,且无需引号闭合的 Payload 时,即可触发警报。

(2)自动化盲注的防御

针对 MEDIUM 级别中演示的 Burp Intruder 自动化爆破攻击,AI 驱动的防御系统可以采取以下策略:

  • 请求频率限制与异常检测:监控短时间内(如1分钟)向同一接口发起的请求频率。自动化爆破工具(如 Intruder)会产生远超正常用户操作节奏的高频、规律性请求。AI 可以建立动态基线,当请求频率异常增高时,自动触发限流或临时封禁。
  • 攻击工具特征识别 :Burp Intruder 等工具在发起攻击时,其 HTTP 请求头(如 User-Agent、特定的 Header 顺序)、请求间隔的规律性以及 Payload 的序列化特征(如连续的 ASCII 值探测)往往与正常浏览器流量不同。AI 可以通过行为分析模型识别这些自动化工具的特征。
  • 动态挑战响应 :当 AI 系统检测到疑似自动化盲注攻击的行为模式时,可以动态插入验证机制,例如:
    • 要求用户完成一次简单的 CAPTCHA 验证。
    • 在会话中注入一个一次性令牌,要求后续请求必须携带该令牌。
    • 临时改变响应逻辑,使布尔盲注依赖的页面状态反馈变得随机或不可预测,从而干扰攻击者的判断。

6、MEDIUM级别------小结

MEDIUM 级别在 LOW 级别的基础上引入了前端下拉菜单和后端转义函数等防护措施,但这些措施仍存在明显缺陷,无法有效阻止 SQL 盲注攻击。

核心防护措施及其局限性:

  1. 前端下拉菜单限制可被绕过 :前端使用 HTML <select> 下拉菜单将用户输入限制为预设的数值选项(如 1, 2, 3, 4, 5)。然而,这仅是一种客户端限制,攻击者可以通过代理工具(如 Burp Suite)拦截并修改 HTTP 请求,将 id 参数替换为任意恶意值,从而完全绕过前端控制。

  2. 后端转义函数对数字型注入无效 :后端使用 mysqli_real_escape_string() 函数对用户输入进行转义,旨在防止字符型 SQL 注入。但本级别的 SQL 查询语句为 user_id = $id(数字型),参数未被单引号包裹。对于数字型注入,攻击者构造的 Payload(如 1 AND 1=1)本身不需要单引号,因此转义函数无法防护此类攻击。

  3. 页面状态反馈未改变:与 LOW 级别一样,页面仍然根据查询结果返回"User ID exists in the database."或"User ID is MISSING from the database."两种明确状态。这为布尔盲注攻击提供了清晰的判断依据。

攻击要点:

  • 请求方法 :MEDIUM 级别使用 POST 方法提交数据,攻击时需通过抓包工具修改 POST 请求体中的 id 参数。
  • Payload 构造 :由于是数字型注入点,Payload 中无需使用单引号 进行闭合。例如:id=1 AND 1=1&Submit=Submit
  • 自动化利用:可借助 Burp Intruder 等工具进行自动化爆破,大幅提高信息猜解效率。

安全启示:

MEDIUM 级别的防护措施"治标不治本"。它错误地依赖前端限制和针对字符型的转义,却忽略了数字型注入的风险。这警示我们,安全防护必须覆盖所有可能的攻击向量(包括数字型注入),并且不能仅依赖客户端或单一维度的防护。真正的安全需要后端进行严格的输入验证、参数化查询,并考虑隐藏或混淆关键的状态反馈信息。

四、HIGH 级别 ------ 参数转移与随机延迟

1、漏洞描述

HIGH 级别在 MEDIUM 级别的基础上,进一步引入了两项关键的安全机制,旨在增加 SQL 盲注的攻击难度和成本。

核心防护机制:

  1. 参数位置转移id 参数不再通过 GET 或 POST 请求体传递,而是被转移到 HTTP Cookie 中。这意味着攻击者无法像之前那样直接修改 URL 或请求体中的参数,必须通过拦截并修改 Cookie 值才能进行注入,这增加了攻击的复杂度和步骤。
  2. 随机延迟干扰:当 SQL 查询执行失败(例如语法错误、条件为假)时,后端会引入一个随机的延迟(例如 0 到 3 秒)。这种延迟旨在干扰自动化爆破工具(如 Burp Intruder)的判断,因为响应时间不再稳定,使得基于时间差异的自动化猜解变得困难且低效。
  3. 查询语句限制 :SQL 查询语句增加了 LIMIT 1 子句,即 SELECT ... WHERE user_id = '$id' LIMIT 1;。这限制了每次查询只返回一条记录,虽然不影响布尔盲注的逻辑判断,但略微增加了信息泄露的难度。

核心安全问题:

尽管 HIGH 级别采取了上述强化措施,但根本性的 SQL 注入漏洞依然存在:

  1. Cookie 参数可被篡改:将参数置于 Cookie 中仅增加了攻击的步骤,但无法阻止攻击者使用代理工具(如 Burp Suite)拦截请求并修改 Cookie 值。熟练的攻击者可以轻松绕过此限制。
  2. 随机延迟仅增加成本,未消除漏洞:随机延迟确实会降低自动化爆破工具的效率和准确性,增加了攻击的时间成本。然而,对于有耐心的攻击者或采用更智能的延时处理脚本,仍然可以通过观察页面状态的差异("存在"或"不存在")来进行布尔盲注攻击。它增加了攻击难度,但并未修复漏洞本身。
  3. 字符型注入点依然存在 :查询语句 user_id = '$id' 表明参数 $id 仍被单引号包裹,属于字符型注入点。只要攻击者能够闭合单引号并注入有效的 SQL 代码,漏洞即可被利用。防护机制并未对用户输入进行充分的验证或使用参数化查询。

总结:

HIGH 级别通过转移参数位置和引入随机延迟,显著提高了自动化攻击的门槛和成本,体现了"增加攻击难度"的防御思路。然而,它未能从根本上解决 SQL 注入的核心问题------未经验证的用户输入被直接拼接到 SQL 语句中。因此,对于具备一定技能的攻击者而言,漏洞仍然存在并可被利用。

2、查看网页源代码

php 复制代码
<?php

if( isset( $_COOKIE[ 'id' ] ) ) {
    // Get input
    $id = $_COOKIE[ 'id' ];
    $exists = false;

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            // Check database
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
            try {
                $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ); // Removed 'or die' to suppress mysql errors
            } catch (Exception $e) {
                $result = false;
            }

            $exists = false;
            if ($result !== false) {
                // Get results
                try {
                    $exists = (mysqli_num_rows( $result ) > 0); // The '@' character suppresses errors
                } catch(Exception $e) {
                    $exists = false;
                }
            }

            ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
            break;
        case SQLITE:
            global $sqlite_db_connection;

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
            try {
                $results = $sqlite_db_connection->query($query);
                $row = $results->fetchArray();
                $exists = $row !== false;
            } catch(Exception $e) {
                $exists = false;
            }

            break;
    }

    if ($exists) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    }
    else {
        // Might sleep a random amount
        if( rand( 0, 5 ) == 3 ) {
            sleep( rand( 2, 4 ) );
        }

        // User wasn't found, so the page wasn't!
        header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }
}

?>

3、分析网页源代码

(1)代码概述

HIGH 级别在 MEDIUM 级别的基础上,进一步引入了两项关键的安全机制,旨在增加 SQL 盲注的攻击难度和成本:

  1. 参数位置转移id 参数不再通过 $_GET$_POST 获取,而是从 $_COOKIE['id'] 中读取。这意味着攻击者无法像之前那样直接修改 URL 或请求体中的参数,必须通过拦截并修改 Cookie 值才能进行注入,这增加了攻击的复杂度和步骤。
  2. 输入转义处理 :与 MEDIUM 级别类似,HIGH 级别也使用了 mysqli_real_escape_string() 函数对用户输入的 id 参数进行转义,旨在过滤特殊字符,防止字符型 SQL 注入。
  3. 查询语句限制 :SQL 查询语句增加了 LIMIT 1 子句,即 SELECT ... WHERE user_id = '$id' LIMIT 1;。这限制了每次查询只返回一条记录,虽然不影响布尔盲注的逻辑判断,但略微增加了信息泄露的难度。
  4. 随机延迟干扰:当查询结果为空(即用户 ID 不存在)时,后端会引入一个随机的延迟(0 到 3 秒)。这种延迟旨在干扰自动化爆破工具(如 Burp Intruder)的判断,因为响应时间不再稳定,使得基于时间差异的自动化猜解变得困难且低效。

(2)漏洞分析

尽管 HIGH 级别引入了上述防护措施,但根本性的 SQL 注入漏洞依然存在,具体分析如下:

① 漏洞一 ------ 参数在 Cookie 中可被修改

  • 问题分析 :虽然 id 参数被转移到了 Cookie 中,但这仅增加了攻击的步骤,无法真正阻止攻击。攻击者仍然可以使用代理工具(如 Burp Suite)拦截 HTTP 请求,直接修改 Cookie 中的 id 值。熟练的攻击者可以轻松绕过此限制,将恶意 Payload 注入到 Cookie 中。

② 漏洞二 ------ 仍为字符型注入

  • 问题分析 :查询语句 user_id = '$id' 使用单引号包裹用户输入,构成了典型的字符型注入点。攻击者需要先闭合前面的单引号,才能插入并执行自定义的 SQL 代码。mysqli_real_escape_string() 函数虽然可以转义特殊字符,但如果攻击者能够正确闭合单引号并构造有效的 Payload,注入依然可能成功。

③ 漏洞三 ------ 随机延迟降低效率但无法阻止

  • 问题分析:随机延迟确实会增加自动化爆破工具的时间成本和不确定性,降低了爆破效率。然而,这并不能从根本上阻止注入攻击。对于有耐心的攻击者,或者采用更智能的脚本(如增加请求间隔、忽略随机延迟),仍然可以通过观察页面状态的差异("存在"或"不存在")来进行布尔盲注攻击。它增加了攻击难度,但并未修复漏洞本身。

总结:

HIGH 级别通过将参数转移到 Cookie 和引入随机延迟,显著提高了自动化攻击的门槛和成本,体现了"增加攻击难度"的防御思路。然而,它未能从根本上解决 SQL 注入的核心问题------未经验证的用户输入被直接拼接到 SQL 语句中。因此,对于具备一定技能的攻击者而言,漏洞仍然存在并可被利用。

4、操作步骤

HIGH 级别将 id 参数存储在 Cookie 中,因此攻击前需要先确认 Cookie 中的参数值。请按以下步骤操作:

  1. 设置安全级别 :在 DVWA 左侧菜单中,将安全级别设置为 High
  2. 打开浏览器开发者工具 :按 F12 键(或右键点击页面,选择"检查"),切换到 Application(或"应用程序")标签页。
  3. 查看 Cookies :在左侧导航栏中,展开 StorageCookies ,选择当前网站的域名(如 dvwa.cc)。在右侧列表中,找到名为 id 的 Cookie,观察其当前值(通常为 1)。

由于参数已转移到 Cookie 中,我们需要使用代理工具(如 Burp Suite)拦截并修改 Cookie 值。具体操作步骤如下:

  1. 步骤一:触发请求

    • 在 DVWA 的 SQL Injection (Blind) 页面(High 安全级别),点击 Submit 按钮,触发一次正常查询。
  2. 步骤二:拦截并观察请求

    • 确保 Burp Suite 的代理拦截功能已开启(Proxy → Intercept 处于 Intercept is on 状态)。

    • Burp Suite 将拦截到浏览器发出的请求。观察请求头中的 Cookie 字段,可以看到 id 参数:

      http 复制代码
      GET /vulnerabilities/sqli_blind/?Submit=Submit HTTP/1.1
      Host: dvwa.cc
      Cookie: PHPSESSID=xxx; security=high; id=1
  3. 步骤三:修改 Cookie 中的 id 值

    • 在 Burp Suite 的拦截界面,找到 Cookie 请求头,将 id=1 修改为包含恶意 Payload 的值,例如:

      http 复制代码
      Cookie: PHPSESSID=xxx; security=high; id=1' AND 1=1 #
    • 此 Payload 利用了字符型注入点,通过闭合单引号并注入布尔条件 AND 1=1 来验证漏洞。

  4. 步骤四:放行请求并观察响应

    • 点击 Forward 按钮放行修改后的请求。
    • 观察浏览器页面。如果页面显示 "User ID exists in the database." ,则说明注入成功,页面返回"存在"状态。如果页面显示 "User ID is MISSING from the database." 并可能伴随随机延迟,则说明条件为假或注入失败。

(3)HIGH 级别布尔盲注 Payload

在 HIGH 级别下,由于参数位于 Cookie 中且存在随机延迟干扰,攻击者需要将 Payload 放入 Cookie 的 id 值中进行测试。以下是一些关键的布尔盲注 Payload 及其预期效果:

Payload(放入 Cookie) 效果 说明
id=1' AND 1=1 # 返回"存在"(条件为真) 注入恒真条件 AND 1=1,用于验证布尔盲注漏洞是否可利用。若页面返回"存在",则说明注入成功。
id=1' AND 1=2 # 随机延迟后返回"不存在"(条件为假) 注入恒假条件 AND 1=2。由于 HIGH 级别在查询失败时会引入随机延迟(0-3秒),页面会在延迟后返回"不存在"状态。
id=1' AND LENGTH(DATABASE())=4 # 返回"存在"(条件为真) 判断当前数据库名的长度是否为 4。若页面返回"存在",则说明数据库名长度为 4(DVWA 默认数据库 dvwa 的长度即为 4)。

使用说明:

  1. 构造 Payload :在 Burp Suite 拦截的请求中,修改 Cookie 头中的 id 值为上表中的 Payload。
  2. 发送请求 :点击 Forward 放行修改后的请求。
  3. 观察响应:根据页面返回"存在"或"不存在"的状态(注意"不存在"时可能有随机延迟),判断注入的布尔条件是否为真,从而逐步推断出数据库信息。

注意事项:

  • 随机延迟干扰:HIGH 级别在查询失败(条件为假)时会引入随机延迟(0-3秒),这会影响自动化爆破工具的判断。攻击者需要增加请求间隔或采用更智能的脚本以应对延迟。
  • 字符型注入 :查询语句为 user_id = '$id',属于字符型注入点。Payload 中需要使用单引号闭合,并以注释符 # 结束后续语句。
  • 后续利用:在确认漏洞可利用后,可参照 LOW 和 MEDIUM 级别的思路,逐步构造 Payload 获取数据库名、表名、字段名等敏感信息,但需考虑随机延迟对自动化工具的影响。

5、AI视角下的 SQL 盲注检测

(1)Cookie 注入的智能检测

HIGH 级别将 id 参数从常规的 GET/POST 请求体转移到了 HTTP Cookie 中,这增加了攻击的隐蔽性和检测难度。AI 可以从以下维度辅助检测此类 Cookie 注入攻击:

  • 完整请求分析:传统的 WAF 或检测系统往往只关注 GET/POST 参数,而 AI 驱动的检测模型可以对整个 HTTP 请求(包括请求头、Cookie、User-Agent 等)进行综合分析。通过检测 Cookie 值中是否包含 SQL 语法特征(如单引号、注释符、SQL 关键字等),可以识别潜在的注入尝试。
  • 多步骤关联分析:Cookie 注入攻击通常涉及两个步骤:第一次请求设置或修改 Cookie,第二次请求携带恶意 Cookie 执行查询。AI 可以通过会话跟踪和时序分析,关联同一会话内的多次请求,识别出"设置异常 Cookie 值 → 执行敏感查询"的攻击模式。
  • Cookie 值异常检测:AI 可以学习正常业务场景下 Cookie 值的模式(如 Session ID 的格式、用户偏好的编码等)。当检测到 Cookie 值长度异常、字符分布不符合预期或包含明显的 SQL 注入特征时,即可触发警报。

(2)随机延迟的对抗

HIGH 级别引入了随机延迟(0-3 秒)来干扰基于响应时间的自动化攻击。AI 可以从以下角度应对这种干扰,提升检测的鲁棒性:

  • 响应时间统计分析:建立每个接口、每个参数组合下的响应时间基线模型(包括均值、标准差、百分位数等)。通过对比实时请求的响应时间与基线,AI 可以识别出异常的延迟模式,即使延迟是随机的,其统计特征也可能与正常波动不同。
  • 多次采样与去噪:针对同一可疑请求,AI 可以控制发起多次采样请求,通过统计方法(如计算中位数、去除离群值)来消除单次随机延迟的干扰,从而更准确地判断是否存在时间盲注攻击。
  • 自适应阈值机制:AI 可以动态学习业务负载和网络状况的变化,自动调整响应时间异常的检测阈值。例如,在业务高峰期,正常响应时间可能本身就会延长,此时 AI 可以相应放宽阈值,避免误报;而在业务低峰期,则收紧阈值,提高检测灵敏度。

6、HIGH级别------小结

HIGH 级别通过将参数转移到 Cookie 和引入随机延迟,显著增加了 SQL 盲注的攻击难度和成本,体现了"增加攻击门槛"的防御思路。然而,这些措施并未从根本上消除漏洞,熟练的攻击者仍可绕过:

  1. Cookie 参数可被篡改:虽然参数转移到了 Cookie 中,但攻击者仍可通过代理工具(如 Burp Suite)拦截并修改 Cookie 值,注入恶意 Payload。
  2. 字符型注入点依然存在 :查询语句 user_id = '$id' 表明漏洞本质仍是字符型注入,只要攻击者能够正确闭合单引号并构造有效 Payload,注入即可成功。
  3. 随机延迟仅增加时间成本:随机延迟确实会干扰自动化爆破工具的判断,降低其效率,但对于有耐心的攻击者或采用更智能的延时处理脚本,布尔盲注仍然可行。

一句话总结:HIGH 级别的防护措施是一种典型的"成本型防御",它通过增加攻击步骤和时间消耗来提高攻击门槛,但并未修复 SQL 注入的核心缺陷------未经验证的用户输入被直接拼接到 SQL 语句中。因此,要实现根本性防护,仍需采用参数化查询、严格的输入验证等安全编码实践。

五、Impossible 级别 ------ 参数化查询与 CSRF Token

1、漏洞描述

Impossible 级别通过引入参数化查询(Prepared Statements)和 CSRF Token 验证等多重安全机制,从根本上消除了 SQL 盲注漏洞的可能性。

核心安全改进如下:

  1. CSRF Token 验证 :通过 checkToken() 函数验证请求的合法性,防止跨站请求伪造攻击。
  2. 数字类型验证 :使用 is_numeric() 函数检查用户输入的 id 参数是否为有效数字,从源头过滤非法输入。
  3. 类型强制转换 :通过 intval($id) 将输入强制转换为整数,确保即使攻击者绕过前端验证,后端也只会处理数字值。
  4. PDO 参数化查询 :采用 prepare() 预处理 SQL 语句,再通过 bindParam() 绑定参数,实现数据与代码的彻底分离,从根本上杜绝 SQL 注入。
  5. 结果数量限制 :使用 rowCount() == 1 确保只有当查询结果恰好为一条记录时才输出用户信息,避免信息泄露。
  6. Token 刷新机制 :每次请求后通过 generateSessionToken() 生成新的 CSRF Token,防止 Token 重用带来的安全风险。

2、查看网页源代码

php 复制代码
<?php

if( isset( $_GET[ 'Submit' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
    $exists = false;

    // Get input
    $id = $_GET[ 'id' ];

    // Was a number entered?
    if(is_numeric( $id )) {
        $id = intval ($id);
        switch ($_DVWA['SQLI_DB']) {
            case MYSQL:
                // Check the database
                $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
                $data->bindParam( ':id', $id, PDO::PARAM_INT );
                $data->execute();

                $exists = $data->rowCount();
                break;
            case SQLITE:
                global $sqlite_db_connection;

                $stmt = $sqlite_db_connection->prepare('SELECT COUNT(first_name) AS numrows FROM users WHERE user_id = :id LIMIT 1;' );
                $stmt->bindValue(':id',$id,SQLITE3_INTEGER);
                $result = $stmt->execute();
                $result->finalize();
                if ($result !== false) {
                    // There is no way to get the number of rows returned
                    // This checks the number of columns (not rows) just
                    // as a precaution, but it won't stop someone dumping
                    // multiple rows and viewing them one at a time.

                    $num_columns = $result->numColumns();
                    if ($num_columns == 1) {
                        $row = $result->fetchArray();

                        $numrows = $row[ 'numrows' ];
                        $exists = ($numrows == 1);
                    }
                }
                break;
        }

    }

    // Get results
    if ($exists) {
        // Feedback for end user
        echo '<pre>User ID exists in the database.</pre>';
    } else {
        // User wasn't found, so the page wasn't!
        header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

        // Feedback for end user
        echo '<pre>User ID is MISSING from the database.</pre>';
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?>

3、分析网页源代码

(1)代码概述

Impossible 级别采用了纵深防御(Defense in Depth)策略,通过多层安全机制协同工作,从根本上杜绝了 SQL 盲注漏洞。其核心防护措施包括:

  1. CSRF Token 验证 :通过 checkToken() 函数验证请求中携带的 Token 是否与会话中的 Token 一致,有效防止跨站请求伪造(CSRF)攻击,确保请求来源的合法性。
  2. 数字类型验证 :使用 is_numeric($id) 函数检查用户输入的 id 参数是否为有效的数字格式,从源头拦截非数字输入。
  3. 类型强制转换 :通过 intval($id) 将输入强制转换为整数。即使攻击者以某种方式绕过了前端的验证,后端也只会处理转换后的整数值,非数字部分将被丢弃。
  4. PDO 参数化查询 :采用 prepare() 方法预处理 SQL 语句,定义查询结构;再通过 bindParam()bindValue() 将用户输入的数据(如 $id)绑定到预定义的参数上。这一机制实现了 SQL 语句与数据的彻底分离,用户输入在任何情况下都被视为数据而非可执行的代码。
  5. 结果数量限制 :使用 rowCount() == 1(对于 MySQL)或检查查询结果行数,确保只有当查询结果恰好为一条记录时才输出用户信息,避免了因意外或恶意构造导致的数据批量泄露风险。
  6. Token 刷新机制 :每次请求处理后,通过 generateSessionToken() 生成新的 CSRF Token 并更新会话,防止 Token 被重放攻击(Replay Attack)利用。

(2)安全分析

下表详细阐述了 Impossible 级别各项安全措施的实现方式及其防御效果:

安全措施 实现方式 防御效果
CSRF Token 验证 checkToken() 函数验证请求中的 user_token 是否与 session_token 匹配。 防止跨站请求伪造攻击,确保提交操作的请求来源于当前已认证的用户会话。
数字类型验证 is_numeric($id) 在绑定前检查输入是否为数字。 在最早阶段拒绝非数字输入,为后续的类型转换和参数化查询提供纯净的数据基础。
类型强制转换 intval($id) 将输入强制转换为整数。 确保最终传递给数据库的参数是一个确定的整数值,消除了字符串拼接可能带来的注入风险。
PDO 参数化查询 prepare() 预处理 SQL 语句,bindParam() 绑定参数。 彻底杜绝 SQL 注入。数据库引擎会严格区分 SQL 指令结构和绑定参数,用户输入的任何内容(包括 SQL 关键字)都将被安全地作为数据处理,而不会被解析执行。
结果数量限制 检查 rowCount() 或结果集行数是否为 1。 防止攻击者通过 UNION SELECT 等方式一次性获取多条记录,限制了单次查询可能泄露的信息量。

(3)为什么 Impossible 级别是安全的?

Impossible 级别的安全性根植于其 参数化查询(Prepared Statements) 机制,这是防御 SQL 注入最有效、最根本的方法。

核心原理:数据与代码分离

在参数化查询中,SQL 语句的模板(结构)与具体的参数值(数据)在发送到数据库前是明确分开的。数据库引擎会先编译 SQL 语句的结构,然后再将参数值代入。这意味着,即使用户输入中包含了 ' OR '1'='1SLEEP(5) 等恶意 SQL 片段,这些内容在绑定后也只会被当作普通的字符串或数字数据来处理,而不会被数据库解析为可执行的 SQL 代码

纵深防御的叠加效应

  • 第一层(输入验证)is_numeric() 确保了输入必须是数字,从逻辑上杜绝了包含字母、符号的注入 Payload。
  • 第二层(类型转换)intval() 进一步将输入强制转换为整数,即使输入是 "1' AND 1=1 #",转换后也只剩下数字 1
  • 第三层(参数化查询) :经过前两层处理后的整数 1,通过 bindParam() 安全地绑定到查询中。此时,任何试图改变 SQL 语句结构的企图都已不可能实现。
  • 辅助层(CSRF Token & 结果限制):CSRF Token 防止了未授权的请求提交,结果数量限制则减少了潜在的信息泄露面。

一句话总结 :Impossible 级别通过 CSRF Token 验证、严格的数字输入验证与转换、以及最关键的 PDO 参数化查询,构建了一个多层次、互补的防御体系,使得 SQL 盲注攻击在此环境下变得真正 "Impossible"(不可能)。

4、操作步骤

Impossible 级别通过多重安全机制彻底防御了 SQL 盲注攻击。以下步骤演示了在该级别下,正常查询与恶意注入尝试的不同结果,直观展示了安全措施的有效性。

(1)正常查询(数字输入)

  1. 设置安全级别 :在 DVWA 左侧菜单中,将安全级别设置为 Impossible
  2. 执行查询 :在输入框中输入数字 1,然后点击 Submit 按钮。
  3. 预期结果 :页面正常显示 "User ID exists in the database."。这表明 ID 为 1 的用户存在于数据库中,且所有安全验证(CSRF Token、数字验证、参数化查询)均已通过。

(2)尝试 SQL 盲注(被拒绝)

  1. 构造注入 Payload :在输入框中输入典型的布尔盲注 Payload:1' AND 1=1 #
  2. 提交请求 :点击 Submit 按钮。
  3. 预期结果 :页面显示 "User ID is MISSING from the database." 。这是因为:
    • is_numeric() 函数检测到输入包含非数字字符(单引号、空格、等号、注释符),验证失败。
    • 即使验证通过,intval() 也会将 1' AND 1=1 # 强制转换为整数 1
    • 最终,参数化查询将 1 作为纯数据绑定,AND 1=1 # 不会被解析为 SQL 代码,因此查询条件不成立,返回"不存在"状态。

(3)尝试非数字输入(被拒绝)

  1. 输入非数字内容 :在输入框中输入纯文本,例如 abc
  2. 提交请求 :点击 Submit 按钮。
  3. 预期结果 :页面显示 "User ID is MISSING from the database." 。这是因为 is_numeric('abc') 返回 false,输入验证阶段就直接拒绝了该请求,后续的 SQL 查询根本不会执行。

结论 :Impossible 级别通过严格的输入验证(is_numeric)、类型转换(intval)和参数化查询,确保了只有合法的数字输入才能被处理,从而从根本上杜绝了 SQL 盲注攻击。任何包含 SQL 语法特征的输入都会被安全机制拦截,无法影响数据库查询逻辑。

5、AI视角下的 SQL 盲注防御

Impossible 级别通过参数化查询等机制已能有效防御 SQL 盲注,但人工智能(AI)技术可在以下方面提供进一步的增强:

(1)智能盲注检测

尽管参数化查询能从根源上杜绝 SQL 注入,但在实际系统迁移或遗留代码改造过程中,仍可能存在部分查询未采用参数化的情况。AI 可辅助检测这些潜在的安全盲点:

  • 查询模式学习:基于历史日志,AI 可学习正常业务场景下的 SQL 查询模式,建立行为基线。一旦出现偏离基线的异常查询(如突然出现大量结构相似、参数值呈现规律性变化的请求),系统即可自动告警。
  • 隐式注入检测:攻击者可能通过编码、混淆、注释拆分等手段隐藏注入特征。AI 模型可通过对请求参数进行多层解码和模式匹配,识别这类经过伪装的注入尝试。
  • 参数值异常检测:AI 可分析参数值的字符分布、信息熵等特征,检测其中是否异常包含 SQL 关键字、特殊字符或明显的注入语法模式。

(2)参数化查询的自动化验证与推广

AI 不仅能检测漏洞,还能积极推动安全编码实践的落地:

  • 代码审计增强:在代码审计阶段,AI 可自动扫描源代码,识别出所有使用字符串拼接方式构建的 SQL 语句,并标记出应迁移为参数化查询的高风险位置。
  • 智能迁移建议:对于识别出的高风险 SQL 语句,AI 可进一步分析上下文,自动生成对应的参数化查询代码建议,辅助开发人员快速、安全地进行代码重构。
  • CI/CD 集成防御:将 AI 检测模型集成到持续集成/持续部署(CI/CD)流水线中,在代码提交、构建或部署阶段自动拦截存在 SQL 注入风险的代码,实现安全左移。

6、Impossible级别------小结

Impossible 级别完整展示了纵深防御(Defense in Depth)的安全设计理念,通过多层防护机制协同工作,构建了坚实的防御体系:

  1. CSRF Token 验证 :通过 checkToken() 验证请求来源的合法性,有效防止跨站请求伪造攻击。
  2. 严格的输入验证 :使用 is_numeric() 函数确保输入为有效数字,从源头拦截非法字符。
  3. 类型强制转换 :通过 intval() 将输入强制转换为整数,确保后端处理的是确定的数值。
  4. 参数化查询 :采用 prepare() 预处理 SQL 语句,再通过 bindParam() 绑定参数,实现 SQL 指令与数据的彻底分离,从根本上杜绝了注入的可能性。
  5. 结果数量限制 :使用 rowCount() == 1 确保仅当查询结果恰好为一条记录时才输出信息,防止数据批量泄露。
  6. Token 刷新机制:每次请求后刷新 CSRF Token,防止 Token 被重放利用。

一句话总结 :Impossible 级别通过 CSRF Token 验证、严格的输入验证与类型转换、以及最关键的 PDO 参数化查询,构建了一个多层次、互补的防御体系,使得 SQL 盲注攻击在此环境下变得真正 "Impossible"(不可能)。

六、LOW vs MEDIUM vs HIGH vs Impossible:防御演进对比

1、防护策略演进对比

防护维度 LOW 级别 MEDIUM 级别 HIGH 级别 Impossible 级别
输入过滤 ❌ 无 ⚠️ 转义函数 ⚠️ 转义函数 ✅ 类型验证 + intval
注入类型 字符型 数字型 字符型 无(仅允许数字)
参数位置 GET 参数 POST 参数 Cookie GET + CSRF Token
错误信息 ❌ 隐藏(固定状态) ❌ 隐藏(固定状态) ❌ 隐藏(固定状态) ✅ 不输出任何状态
随机延迟 ❌ 无 ❌ 无 ⚠️ 0-3 秒 ❌ 无(不需要)
防御策略 无防御 弱防护 成本型防御 参数化查询 + CSRF

2、攻击面变化分析

攻击类型 LOW MEDIUM HIGH Impossible
布尔盲注 ✅ 直接利用 ✅ 数字型绕过 ✅ Cookie 注入 ❌ 参数化查询
时间盲注 ✅ 直接利用 ✅ 数字型绕过 ✅ Cookie 注入 ❌ 参数化查询
字符型注入 ✅ 直接利用 ❌ 转义阻止 ✅ 可绕过 ❌ 类型验证
数字型注入 ✅ 直接利用 ✅ 可绕过 ❌ 字符型 ❌ 类型验证
自动化爆破 ✅ 高效 ✅ 高效 ⚠️ 随机延迟降低效率 ❌ 无法注入

3、SQL 盲注防御的核心原则

原则 说明 实现级别
参数化查询 使用 prepare() + bindParam() 分离数据和代码,从根本上杜绝 SQL 注入。 Impossible
输入验证 对用户输入进行严格的类型、格式验证,从源头拦截非法数据。 Impossible
CSRF Token 使用一次性令牌验证请求合法性,防止跨站请求伪造攻击。 Impossible
不输出状态 不向用户输出"存在/不存在"等可区分的状态信息,增加盲注难度。 Impossible
错误处理 不向用户输出详细的数据库错误信息,避免信息泄露。 架构级

七、总结

1、漏洞全景回顾

下表从四个关键层面(输入层、查询层、输出层、参数层)系统梳理了 DVWA SQL 盲注四个安全级别的防御演进与核心差异:

层级 LOW 级别 MEDIUM 级别 HIGH 级别 Impossible 级别
输入层 🔴 无任何过滤 🟠 使用转义函数(对数字型无效) 🟠 使用转义函数(对数字型无效) 🟢 CSRF Token + 类型验证 + intval 强制转换
查询层 🔴 直接字符串拼接 🔴 直接字符串拼接 🔴 直接字符串拼接 🟢 PDO 参数化查询(prepare + bindParam)
输出层 🟡 固定状态("存在"/"不存在"可区分) 🟡 固定状态("存在"/"不存在"可区分) 🟡 固定状态("存在"/"不存在"可区分) 🟢 不输出任何可区分的状态信息
参数层 🔴 GET 明文传输 🔴 POST 传输 🟠 Cookie 传输 🟢 GET + CSRF Token 验证

2、关键启发

通过对比四个级别的防御措施与攻击面,我们可以得出以下核心安全启示:

  1. 盲注的本质是"信息差":从 LOW 到 HIGH 级别,页面都返回"存在"或"不存在"这两种明确可区分的状态,这正是布尔盲注能够成功的基础。只要攻击者能够通过某种方式(页面内容、响应时间)区分出两种不同的系统反馈,就能逐字符推断出数据库中的敏感信息。

  2. 转移参数位置不等于安全:HIGH 级别将参数从 GET/POST 转移到 Cookie 中,这只增加了攻击的步骤和隐蔽性,并未从根本上解决 SQL 注入漏洞。熟练的攻击者通过代理工具即可轻松修改 Cookie 值,注入恶意 Payload。

  3. 随机延迟是"成本型防御":HIGH 级别引入的随机延迟(0-3秒)确实增加了自动化爆破工具的时间成本和不确定性,降低了攻击效率。但这只是一种"增加攻击成本"的防御思路,对于有耐心的攻击者或更智能的脚本而言,并不能阻止注入本身。

  4. 参数化查询是根本解决方案:Impossible 级别通过 PDO 参数化查询(prepare + bindParam)实现了 SQL 指令与数据的彻底分离,从根源上杜绝了 SQL 注入的可能性。结合 CSRF Token 验证、严格的输入类型验证(is_numeric)和强制类型转换(intval),构建了真正的纵深防御体系。

八、AI增强防御建议

1、智能盲注检测

传统基于规则的 WAF(Web应用防火墙)或人工监控在应对日益复杂的盲注攻击时往往力不从心。人工智能(AI)技术可以通过学习正常业务模式,自动识别异常攻击行为,实现更智能、自适应的盲注检测。

传统方案 AI 增强方案
依赖安全人员人工观察页面状态差异 自动识别请求模式异常,无需人工干预
基于固定规则的频率限制(如每分钟N次) 基于用户/会话行为基线的自适应检测与限流
难以区分正常业务请求与恶意盲注探测 通过机器学习学习正常请求模式,精准识别偏离行为

实现思路:

  • 建立行为基线:在业务低峰期收集大量正常请求,分析其请求频率、参数模式、响应时间等特征,建立多维度的行为基线模型。
  • 时序分析与聚类:使用时序分析技术检测短时间内出现的大量结构高度相似的请求(这是逐字符爆破的典型特征)。应用聚类算法(如 DBSCAN)对请求参数和响应特征进行聚类,识别出异常的请求簇。
  • 识别系统化参数变化:盲注攻击的参数值往往呈现规律性、系统化的变化(如 ASCII 码值连续递增、字符位置固定偏移)。通过分析参数值的熵、变化模式等特征,AI 可以识别这类非自然的、攻击性的参数变化。

2、基于响应时间的盲注检测

时间盲注不依赖页面内容差异,传统基于内容特征的检测方法完全失效。AI 可以从响应时间的统计特征入手,有效识别时间盲注攻击。

特征维度 正常请求 时间盲注攻击
响应时间分布 相对稳定,通常在毫秒级(< 1 秒) 存在固定的异常延迟(如注入 SLEEP(5) 导致固定 5 秒延迟)
响应时间方差 较小,波动主要受网络和服务器负载影响 较大,真值条件(有延迟)与假值条件(无延迟)响应时间差异显著
请求间隔 不规则,符合用户操作习惯 规律性强,通常由自动化脚本以固定频率发起
参数模式 自然变化,无固定规律 系统化、可预测的变化模式(如探测不同 ASCII 值)

实现思路:

  • 建立响应时间统计模型:为每个接口、每个参数组合建立响应时间的基线模型(包括均值、标准差、百分位数等)。
  • 检测固定延迟异常:监控实时请求的响应时间,检测是否出现固定的、异常的延迟模式(如大量请求恰好延迟 5 秒),这很可能是时间盲注攻击的标志。
  • 参数-延迟关联分析:将请求参数与对应的响应时间进行关联分析。通过机器学习模型挖掘"特定参数值"与"异常延迟"之间的隐藏关联,即使攻击者变换 Payload,也能有效识别。

3、实施优先级建议

将 AI 增强防御措施与传统安全实践结合,可以构建更 robust 的防御体系。以下是根据实施难度和防御效果提出的优先级建议:

优先级 措施 实施难度 防御效果 说明
参数化查询 从根本上杜绝 SQL 注入,是必须实施的基础措施。
严格的输入类型验证 在业务逻辑层对输入进行类型、格式、范围校验,拦截非法数据。
CSRF Token 防护 防止跨站请求伪造,保护关键操作。
请求频率限制 针对 IP/会话进行限流,增加自动化攻击成本。
智能盲注检测(AI) 作为监测和告警层,辅助发现绕过传统防护的攻击。
全面的 AI 行为分析 建立完整的用户/实体行为分析(UEBA)体系,实现高级威胁检测。

4、总结

AI 时代的 SQL 盲注防御,其核心思想不是用 AI 替代传统防御,而是用 AI 增强传统防御,构建"基础防护 + 智能监测"的双层防御体系。

  • 传统防御是基石:提供基础的安全基线,包括参数化查询、输入验证、CSRF Token、最小权限原则等。这些措施能从根源上消除大部分漏洞。
  • AI 增强是护城河:提供智能化的异常检测、攻击模式识别和自适应响应能力。AI 能够发现那些绕过传统规则、隐蔽性强的攻击,并在攻击发生时快速预警和响应。

二者紧密结合,才能构建真正 robust、自适应、可持续进化的 SQL 盲注防御体系,从容应对不断演变的网络威胁。

免责声明:本文所述内容仅供安全研究与学习交流使用,所有测试均在本地授权靶场(DVWA)环境中进行。未经授权,严禁将文中技术用于任何非法目的。

📌 本文是专栏「DVWA通关全记录:从漏洞复现到安全防御」的第 09 篇文章。

如果你在阅读过程中有任何疑问,欢迎在评论区留言交流。😊

相关推荐
chuan.bai1 小时前
Java RAG 实战(第 3 篇):从交互式聊天到多轮上下文
java·人工智能·macos·ai
JaydenAI1 小时前
[基于OpenEvals的自动化评估-12]Agent执行轨迹评估[无LLM参与]
ai·langchain·agent·evaluation·openevals
今天AI了吗1 小时前
MCP 协议打通 AI 与国产数据库,SQL 调优全流程一站式闭环
数据库·人工智能·sql
灯澜忆梦2 小时前
【MySQL11】进阶篇 | 索引_#3使用规则
数据库·sql·mysql·性能优化
云技纵横3 小时前
线上接口突然超时,怎么判断卡在 Nginx、线程池、连接池还是 SQL?
后端·sql·mysql
李燚3 小时前
Checkpoint 源码:Agent 执行到一半怎么保存(第80篇-E66)
人工智能·ai·aigc·agent·checkpoint·rag·eino
小白狮ww3 小时前
小模型「扛」住自由运镜:InSpatio-World 开源实时 4D 模拟器
人工智能·ai
吴卫斌3 小时前
Copilot能换成本地吗?—— 本地化部署的优势、限制与最终建议
ai·ai-native
船长@4 小时前
FastAPI 快速上手:从零基础到实操入门
python·ai·fastapi