从直接拼接到参数化查询:DVWA SQL 注入模块完整漏洞分析教程

文章目录

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

一、概述

1、什么是 SQL 注入

SQL 注入(SQL Injection)是一种常见的 Web 安全漏洞。攻击者通过在用户输入中插入恶意的 SQL 代码,破坏原有 SQL 查询的结构,从而欺骗数据库执行非预期的操作。

SQL 注入的发生通常需要满足以下三个必要条件:

  1. 用户输入可控:应用程序接收来自用户的输入数据,并将其直接拼接到 SQL 语句中。
  2. 输入未经处理:用户输入未经过有效的过滤、转义或参数化处理,便直接用于构建 SQL 查询。
  3. 执行结果可回显:数据库执行查询后的结果(如错误信息、查询数据)能够被返回到前端页面,为攻击者提供了信息反馈的渠道。

2、SQL 注入的危害

SQL 注入漏洞一旦被利用,可能造成极其严重的后果,主要包括:

  • 绕过身份验证:攻击者可以构造特殊输入,绕过登录验证,直接获取管理员或其他高权限账户的访问权限。
  • 数据泄露与篡改:攻击者可以读取、修改甚至删除数据库中的敏感数据,如用户信息、交易记录等。
  • 权限提升与横向渗透:通过获取数据库的用户名、密码等凭据,攻击者可能进一步渗透到数据库服务器或关联的其他系统。
  • 服务器沦陷:在特定条件下,攻击者可能利用数据库功能在服务器上执行系统命令,或上传 WebShell,从而完全控制服务器。

二、LOW 级别 ------ 无过滤的 SQL 拼接

1、漏洞描述

LOW 级别未对用户输入进行任何检查或过滤,直接将用户提交的 id 参数拼接到 SQL 查询语句中执行,从而形成了典型的 SQL 注入漏洞。

其核心问题主要体现在以下几个方面:

  1. 缺乏输入验证与过滤:未对用户输入的数据进行任何合法性校验或危险字符过滤。
  2. 直接拼接 SQL 语句:将用户输入直接拼接到 SQL 查询字符串中,使得攻击者可以插入恶意代码。
  3. 可利用 UNION 查询提取数据 :攻击者能够通过构造 UNION SELECT 语句,从数据库中提取任意表、任意字段的敏感信息。
  4. 可通过 ORDER BY 探测字段数量 :攻击者可以利用 ORDER BY 子句逐步试探查询结果集的列数,为后续的 UNION 注入攻击做准备。

2、查看网页源代码

php 复制代码
<?php

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

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            // Check database
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
            $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

            // Get results
            while( $row = mysqli_fetch_assoc( $result ) ) {
                // Get values
                $first = $row["first_name"];
                $last  = $row["last_name"];

                // Feedback for end user
                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
            }

            mysqli_close($GLOBALS["___mysqli_ston"]);
            break;
        case SQLITE:
            global $sqlite_db_connection;

            #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
            #$sqlite_db_connection->enableExceptions(true);

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }

            if ($results) {
                while ($row = $results->fetchArray()) {
                    // Get values
                    $first = $row["first_name"];
                    $last  = $row["last_name"];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    } 
}

?>

3、分析网页源代码

(1)代码概述

LOW 级别的后端逻辑非常简单,主要包含以下几个步骤:

  1. 获取用户输入 :从 $_GET$_REQUEST 中获取用户提交的 id 参数。
  2. 直接拼接 SQL :未对 $id 参数进行任何过滤、转义或验证,直接将其拼接到 SQL 查询字符串中。
  3. 执行查询并输出:执行拼接后的 SQL 语句,并将查询结果输出到页面。
  4. 错误信息回显 :如果 SQL 语句执行出错,会通过 mysqli_error() 将数据库错误信息直接输出到前端。

(2)漏洞分析

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

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

② 漏洞二 ------ 错误信息直接回显

问题分析 :当 SQL 语句执行出错时,mysqli_error() 返回的错误信息会直接输出到页面。这为攻击者提供了宝贵的信息泄露渠道,攻击者可以利用这些错误信息来推断数据库的表结构、字段名等敏感信息,辅助后续的注入攻击。

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

问题分析 :查询语句 user_id = '$id' 使用单引号将变量包裹,这属于典型的字符型 SQL 注入。攻击者需要先闭合前面的单引号,才能插入并执行自己的恶意 SQL 代码。

4、操作步骤

(1)正常查询

  1. 登录 DVWA :使用默认凭据(例如 admin / password)登录 DVWA 后台。
  2. 设置安全级别 :在左侧菜单栏的 "DVWA Security" 页面中,将安全级别设置为 Low
  3. 进入 SQL 注入页面:点击左侧菜单的 "SQL Injection" 进入 SQL 注入测试页面。
  4. 执行查询 :在输入框中输入 1,然后点击 Submit 按钮,观察页面返回的用户信息。

    (2)判断注入类型

(2)判断注入类型

  1. 构造第一个测试 Payload :在输入框中输入 1' AND '1'='1,然后点击 Submit 按钮。

    • 预期结果 :页面正常返回 ID 为 1 的用户信息。这是因为 AND '1'='1' 条件恒为真,查询逻辑未受影响。
  2. 构造第二个测试 Payload :在输入框中输入 1' AND '1'='2,然后点击 Submit 按钮。

    • 预期结果 :页面无数据返回或显示错误信息。这是因为 AND '1'='2' 条件恒为假,导致查询结果为空。
  3. 结果分析 :对比两次测试结果。如果第一次有数据返回而第二次没有,则说明应用程序对用户输入未做有效过滤,存在 SQL 注入漏洞。这种基于布尔逻辑的响应差异是判断注入点存在的典型方法。

(3)获取字段数量

  1. 构造探测 Payload :在输入框中依次输入以下 Payload,并点击 Submit 按钮。

    • 1' ORDER BY 1 #
    • 1' ORDER BY 2 #
    • 1' ORDER BY 3 #
  2. 观察页面响应

    • 如果 ORDER BY n 正常返回数据,说明查询结果集至少有 n 个字段。
    • 如果 ORDER BY n 导致页面报错、无数据返回或显示异常,则说明查询结果集的字段数小于 n
  3. 结果分析

    • 预期结果ORDER BY 2 正常返回用户信息,而 ORDER BY 3 报错或无数据。
    • 结论 :这表明原始查询语句返回的结果集包含 2 个字段 。此信息是后续构造 UNION SELECT 攻击的关键前提。

(4)获取当前数据库和版本

  1. 构造 UNION 查询 Payload :在输入框中输入以下 Payload,并点击 Submit 按钮。

    • 1' UNION SELECT database(), version() #
  2. 观察页面响应

    • 页面应正常返回数据,并在原本显示用户姓名的位置,分别显示当前数据库的名称和数据库的版本信息。
  3. 结果分析

    • 预期结果 :页面会显示类似 ID: 1First name: dvwaSurname: x.x.x 的信息(具体版本号会因环境而异)。
    • 结论 :这表明 UNION SELECT 注入成功。我们利用之前探测到的字段数(2个),成功将 database()version() 函数的查询结果合并到原始查询结果中并回显,从而获取了关键的数据库环境信息。

(5)获取所有数据库名

  1. 构造 UNION 查询 Payload :在输入框中输入以下 Payload,并点击 Submit 按钮。

    • 1' UNION SELECT 1, CONVERT(schema_name USING latin1) FROM information_schema.schemata #
  2. 观察页面响应

    • 页面应正常返回数据,并在原本显示用户姓名的位置,显示一个数字 1 和当前 MySQL 实例中所有数据库的名称(每行一个数据库名)。
  3. 结果分析

    • 预期结果 :页面会显示类似 ID: 1First name: 1Surname: information_schemaSurname: dvwaSurname: mysql 等行(具体数据库列表会因环境而异)。
    • 结论 :这表明我们成功利用 UNION SELECT 查询了 information_schema.schemata 系统表,获取了服务器上所有数据库的列表。这是信息收集阶段的关键一步,为后续选择目标数据库进行深入渗透奠定了基础。

(6)获取当前数据库的所有表名

  1. 构造 UNION 查询 Payload :在输入框中输入以下 Payload,并点击 Submit 按钮。

    • 1' UNION SELECT 1, CONVERT(table_name USING latin1) FROM information_schema.tables WHERE table_schema = database() #
  2. 观察页面响应

    • 页面应正常返回数据,并在原本显示用户姓名的位置,显示一个数字 1 和当前数据库(dvwa)中所有表的名称(每行一个表名)。
  3. 结果分析

    • 预期结果 :页面会显示类似 ID: 1First name: 1Surname: guestbookSurname: users 等行(具体表列表会因环境而异)。
    • 结论 :这表明我们成功利用 UNION SELECT 查询了 information_schema.tables 系统表,并筛选出当前数据库(database() 函数返回 dvwa)的所有表。这是信息收集的进一步深入,为后续提取特定表(如 users 表)的字段和数据做好了准备。

(7)获取 users 表的所有列名

  1. 构造 UNION 查询 Payload :在输入框中输入以下 Payload,并点击 Submit 按钮。

    • 1' UNION SELECT 1, CONVERT(column_name USING latin1) FROM information_schema.columns WHERE table_name = 'users' #
  2. 观察页面响应

    • 页面应正常返回数据,并在原本显示用户姓名的位置,显示一个数字 1users 表中的所有列名(每行一个列名)。
  3. 结果分析

    • 预期结果 :页面会显示类似 ID: 1First name: 1Surname: user_idSurname: first_nameSurname: last_nameSurname: userSurname: passwordSurname: avatar 等行(具体列名列表会因环境而异)。
    • 结论 :这表明我们成功利用 UNION SELECT 查询了 information_schema.columns 系统表,并筛选出 users 表的所有列。这是信息收集的最后一步,至此我们已经掌握了目标表(users)的完整结构,为后续提取具体数据(如用户名和密码)做好了准备。

(8)提取用户名和密码

  1. 构造 UNION 查询 Payload :在输入框中输入以下 Payload,并点击 Submit 按钮。

    • 1' UNION SELECT user, password FROM users #
  2. 观察页面响应

    • 页面应正常返回数据,并在原本显示用户姓名的位置,分别显示 users 表中所有用户的用户名和经过 MD5 加密的密码(每行一个用户)。
  3. 结果分析

    • 预期结果 :页面会显示类似 ID: 1First name: adminSurname: 5f4dcc3b5aa765d61d8327deb882cf99 等行(具体用户名和密码哈希值会因环境而异)。
    • 结论 :这表明我们成功利用 UNION SELECT 直接查询了 users 表的 userpassword 字段,获取了所有用户的登录凭据。至此,我们完成了从漏洞发现、信息收集到最终数据窃取的完整 SQL 注入攻击链。这充分证明了 LOW 级别下无过滤 SQL 拼接漏洞的严重危害性。

(9)使用 group_concat() 一次性提取所有数据

  1. 构造 UNION 查询 Payload :在输入框中输入以下 Payload,并点击 Submit 按钮。

    • 1' UNION SELECT 1, group_concat(user_id, ':', user, ':', password) FROM users #
  2. 观察页面响应

    • 页面应正常返回数据,并在原本显示用户姓名的位置,显示一个数字 1 和一个由所有用户数据拼接而成的长字符串。该字符串的格式为 user_id:user:password,不同用户的数据之间默认由逗号分隔。
  3. 结果分析

    • 预期结果 :页面会显示类似 ID: 1First name: 1Surname: 1:admin:5f4dcc3b5aa765d61d8327deb882cf99,2:gordonb:e99a18c428cb38d5f260853678922e03,3:1337:8d3533d75ae2c3966d7e0d4fcc69216b,4:pablo:0d107d09f5bbe40cade3de5c71e9e9b7,5:smithy:5f4dcc3b5aa765d61d8327deb882cf99 的信息(具体数据会因环境而异)。
    • 结论 :这表明我们成功利用 MySQL 的 group_concat() 函数,将 users 表中所有用户的 user_iduserpassword 字段值一次性拼接并提取出来。这种方法比逐行查询更高效,能在一个请求中获取所有目标数据,进一步展示了 SQL 注入漏洞在数据窃取方面的强大能力。

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

(1)基于语义的 SQL 注入检测

传统的 SQL 注入检测主要依赖正则表达式和关键字黑名单,这种方法容易产生误报和漏报。AI 技术可以辅助构建更智能的语义检测模型,通过分析用户输入的深层语义和结构特征来识别潜在的注入攻击。

核心检测维度对比:

特征维度 正常输入 SQL 注入攻击
输入结构 简单数字或字符串 包含 SQL 关键字(如 UNION、SELECT、DROP 等)
特殊字符 少量或无 包含 '#--/* */ 等 SQL 注释符或分隔符
语法模式 不符合 SQL 语法 符合或部分符合 SQL 语法结构
输入长度 通常较短 可能明显增长,包含复杂的拼接逻辑

技术实现方案:

  1. 语法分析:使用 SQL 语法解析器对用户输入进行分析,判断其是否构成有效的 SQL 语句片段。即使输入经过混淆或编码,解析器也能识别其潜在的 SQL 结构。
  2. 意图识别:利用自然语言处理(NLP)模型分析输入的真实意图。正常查询意图(如"搜索商品")与恶意注入意图(如"提取数据库名")在语义特征上存在显著差异。
  3. 异常检测:基于历史正常请求数据建立基线模型,识别偏离正常输入模式的异常请求。AI 模型可以学习正常用户的行为模式,从而更精准地发现异常。

(2)AI 辅助的 WAF 增强

传统的 Web 应用防火墙(WAF)规则更新滞后,难以应对新型或变种的注入攻击。AI 可以从以下方面增强 WAF 的防御能力:

  1. 动态规则生成:通过分析攻击日志和流量数据,AI 能够自动识别新的注入模式并生成相应的防护规则,实现 WAF 规则的自动化更新和迭代。
  2. 混淆检测:攻击者常对注入 Payload 进行编码(如 URL 编码、Base64)、注释分割等混淆处理以绕过检测。AI 模型可以学习这些混淆手法,有效识别经过处理的恶意输入。
  3. 上下文感知:结合请求的上下文信息(如 User-Agent、Referer、请求频率、访问路径等)进行综合风险评估。单一的输入检测可能被绕过,但结合多维度的上下文分析能显著提升判断准确性。

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

LOW 级别的 SQL 注入漏洞演示了最基础的攻击场景:应用程序未对用户输入进行任何安全防护,攻击者可以轻易地利用 SQL 注入获取数据库中的任意敏感数据。

核心安全问题总结:

  1. 无任何输入验证或过滤 :用户提交的 id 参数未经任何合法性校验或危险字符过滤,直接拼接到 SQL 查询语句中,为攻击者提供了直接的注入入口。
  2. 错误信息直接回显:当 SQL 语句执行出错时,数据库的错误信息会直接输出到前端页面。这为攻击者提供了宝贵的信息泄露渠道,使其能够推断数据库的表结构、字段名等敏感信息,辅助后续的精准攻击。
  3. 可直接通过 UNION 查询提取数据 :由于查询结果会回显到页面,攻击者能够利用 UNION SELECT 语句将任意查询结果合并到原始结果中,从而系统地提取数据库名、表名、列名乃至具体的用户数据(如用户名和密码)。

安全启示:

LOW 级别的案例清晰地展示了"无防护即最大风险"的安全原则。在实际开发中,必须对用户输入进行严格的验证、过滤或使用参数化查询等安全编码实践,从根本上杜绝 SQL 注入漏洞的产生。同时,应避免将详细的数据库错误信息直接暴露给用户,以防止信息泄露。

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

1、漏洞描述

MEDIUM 级别在 LOW 级别的基础上,引入了两种看似有效的防护措施:后端使用 mysqli_real_escape_string() 函数对用户输入进行转义,前端使用下拉选择框(<select>)限制用户只能选择预设的 id 值,并且表单提交方式从 GET 改为 POST。

核心防护机制:

  1. 后端转义 :使用 mysqli_real_escape_string() 函数对用户提交的 id 参数中的特殊字符(如单引号 ')进行转义,旨在防止其破坏 SQL 语句结构。
  2. 前端限制:将输入框替换为下拉菜单,用户只能从预设的选项(如 1, 2, 3, 4, 5)中选择,意图从源头限制恶意输入。
  3. 提交方式:表单使用 POST 方法提交,使得攻击参数不会直接暴露在 URL 中,增加了手动探测的难度。

核心安全问题:

尽管引入了上述防护,MEDIUM 级别仍然存在可被绕过的安全漏洞:

  1. 前端限制可被绕过 :前端下拉菜单仅是一种用户体验限制,并非安全屏障。攻击者可以轻松使用 Burp Suite、Postman 等工具拦截并修改 HTTP 请求,将 id 参数替换为任意值,从而完全绕过前端限制。
  2. 转义函数对数字型注入无效mysqli_real_escape_string() 函数主要用于转义字符串中的特殊字符(如单引号、反斜杠)。然而,如果应用程序的 SQL 查询逻辑是数字型(例如 WHERE user_id = $id,变量未被引号包裹),那么转义函数将不起作用,因为数字本身不需要引号闭合,攻击者可以直接注入数字型 Payload(如 1 OR 1=1)进行攻击。

2、查看网页源代码

php 复制代码
<?php

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

    $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
            $result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

            // Get results
            while( $row = mysqli_fetch_assoc( $result ) ) {
                // Display values
                $first = $row["first_name"];
                $last  = $row["last_name"];

                // Feedback for end user
                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
            }
            break;
        case SQLITE:
            global $sqlite_db_connection;

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }

            if ($results) {
                while ($row = $results->fetchArray()) {
                    // Get values
                    $first = $row["first_name"];
                    $last  = $row["last_name"];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    }
}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query  = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS["___mysqli_ston"]);
?>

3、分析网页源代码

(1)代码概述

MEDIUM 级别的后端逻辑在 LOW 级别的基础上,主要增加了对用户输入的转义处理,其核心流程如下:

  1. 获取用户输入 :从 $_POST 数组中获取用户通过表单提交的 id 参数,提交方式由 GET 改为 POST。
  2. 转义特殊字符 :使用 mysqli_real_escape_string() 函数对 $id 参数中的特殊字符(如单引号 '、双引号 "、反斜杠 \ 等)进行转义,旨在防止这些字符破坏 SQL 语句的结构。
  3. 构建并执行查询 :将转义后的 $id 变量直接拼接到 SQL 查询语句 SELECT first_name, last_name FROM users WHERE user_id = $id; 中并执行。
  4. 输出查询结果 :将查询到的用户信息(first_name, last_name)格式化输出到页面。

关键变化 :与 LOW 级别不同,MEDIUM 级别的查询语句为 user_id = $id,变量 $id 没有被单引号包裹。这意味着此处是数字型 SQL 注入,而非字符型。

(2)漏洞分析

尽管引入了转义函数,MEDIUM 级别仍然存在以下安全漏洞:

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

问题分析 :前端界面使用下拉选择框(<select>)限制用户只能选择预设的 id 值(如 1, 2, 3, 4, 5)。然而,这种限制仅作用于浏览器端,属于用户体验层面的控制 ,而非安全屏障。攻击者可以轻松使用 Burp Suite、Postman、浏览器开发者工具等代理或调试工具,在 HTTP 请求被发送到服务器前拦截并修改 id 参数,将其替换为任意恶意值,从而完全绕过前端限制。

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

问题分析 :这是 MEDIUM 级别最核心的漏洞。mysqli_real_escape_string() 函数的作用是转义字符串中的特殊字符,使其在 SQL 语句中失去特殊含义(例如,将单引号 ' 转义为 \')。然而,MEDIUM 级别的查询语句为 user_id = $id,变量 $id 没有被单引号包裹 。这意味着应用程序期望 $id 是一个数字,直接将其作为数值进行拼接。

  • 数字型注入原理 :在数字型注入中,攻击者注入的 Payload(如 1 OR 1=1)本身就是有效的 SQL 表达式的一部分,不需要通过单引号来"闭合"字符串。因此,即使 mysqli_real_escape_string() 函数被执行,它对数字本身(如 12)或 SQL 运算符(如 OR=)也不起任何转义作用
  • 攻击示例 :攻击者可以提交 id=1 OR 1=1,最终执行的 SQL 语句为 SELECT ... WHERE user_id = 1 OR 1=1。由于 1=1 恒为真,此查询将返回 users 表中的所有记录,成功实现注入。
③ 漏洞三 ------ 错误信息直接回显

问题分析 :与 LOW 级别相同,当 SQL 语句执行出错时(例如,语法错误、表不存在等),mysqli_error() 函数返回的详细数据库错误信息会直接输出到前端页面。这为攻击者提供了宝贵的信息泄露渠道。攻击者可以利用这些错误信息来推断后端数据库的类型、版本、表结构等敏感信息,从而辅助构造更精准的注入 Payload。

4、操作步骤

(1)抓包修改 id 参数

本步骤演示如何绕过前端下拉菜单的限制,通过代理工具(如 Burp Suite)直接修改 HTTP 请求参数,实现数字型 SQL 注入攻击。

  1. 开启代理拦截 :启动 Burp Suite,配置浏览器代理,并确保 Burp Suite 的 Intercept is on(拦截开启)状态。

  2. 触发正常请求 :在 DVWA 的 SQL Injection 页面(安全级别设置为 Medium ),从下拉菜单中选择一个预设值(例如 1),然后点击 Submit 按钮提交查询。

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

    http 复制代码
    POST /vulnerabilities/sqli/ HTTP/1.1
    Host: dvwa.cc
    Cookie: PHPSESSID=xxx; security=medium
    
    id=1&Submit=Submit
  4. 构造并修改 Payload :在 Burp Suite 的拦截界面中,找到 id 参数,将其值从 1 修改为恶意 SQL 注入 Payload。由于 MEDIUM 级别是数字型注入,Payload 中不需要使用单引号 。例如,修改为:

    http 复制代码
    id=1 UNION SELECT user, password FROM users #&Submit=Submit
  5. 放行请求并观察结果 :点击 Forward 按钮放行修改后的请求。观察浏览器返回的页面,如果注入成功,页面将在原本显示用户姓名的位置,返回 users 表中的用户名和密码哈希值。

(2)数字型注入 Payload

由于 MEDIUM 级别是数字型注入,Payload 中不需要使用单引号进行闭合。下表列举了几个常用的数字型注入 Payload 及其攻击效果:

Payload 效果
1 UNION SELECT database(), version()&Submit=Submit 获取当前数据库名称和数据库版本信息。
1 UNION SELECT user, password FROM users #&Submit=Submit 直接提取 users 表中的用户名和密码哈希值。
1 UNION SELECT 1, CONVERT(table_name USING latin1) FROM information_schema.tables WHERE table_schema=database() #&Submit=Submit 获取当前数据库中的所有表名。

使用说明

  1. 将上述任一 Payload 作为 id 参数的值,通过代理工具(如 Burp Suite)修改并发送请求。
  2. 观察页面返回结果,验证注入是否成功,并获取相应的敏感信息。

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

(1)转义函数的智能化增强

传统的 mysqli_real_escape_string() 函数仅能转义特殊字符(如单引号、反斜杠),对于数字型注入或经过编码混淆的攻击往往无能为力。AI 技术可以从以下方面辅助构建更智能的转义与验证机制:

  1. 上下文感知转义 :AI 模型可以分析 SQL 查询语句的上下文结构,动态判断用户输入是否处于字符串引号内。对于数字型上下文(如 WHERE user_id = $id),即使输入包含单引号也无需转义,从而避免误伤正常输入;而对于字符型上下文,则自动启用严格的转义策略。
  2. 注入模式识别 :通过机器学习模型识别输入中是否包含典型的 SQL 注入模式(如 UNION SELECTOR 1=1' OR '1'='1 等关键字组合),即使这些模式经过简单的变形或拆分,AI 也能基于语义特征进行检测。
  3. 编码与混淆检测:攻击者常对注入 Payload 进行 URL 编码、十六进制编码、Unicode 编码等处理以绕过传统检测。AI 模型可以学习这些编码模式,自动解码并分析原始内容,识别隐藏在编码背后的恶意 SQL 片段。

(2)基于请求上下文的检测

MEDIUM 级别将表单提交方式改为 POST,并引入了前端限制,这为基于上下文的 AI 检测提供了新的维度:

  1. 请求方法差异分析:正常用户操作通常通过前端表单(POST)提交,而自动化攻击工具可能直接发送 GET 请求或修改请求方法。AI 可以监控请求方法(GET/POST)与页面功能的匹配度,识别异常的方法使用模式。
  2. 参数值异常检测 :结合请求上下文(如表单字段类型、预期输入范围),AI 可以判断参数值是否异常。例如,对于下拉菜单的 id 参数,正常值应为预设的 1-5,若收到 1 UNION SELECT... 这类明显包含 SQL 语法的值,则可判定为高风险。
  3. 请求-响应行为关联分析 :AI 可以关联分析单个请求与服务器响应的关系。例如,正常查询 id=1 应返回特定用户信息;而注入攻击 id=1 OR 1=1 可能导致返回大量数据或异常错误信息。通过建立正常响应的基线模型,AI 能够识别偏离基线的异常数据泄露行为。

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

MEDIUM 级别在 LOW 级别的基础上引入了两种看似有效的防护措施:后端使用 mysqli_real_escape_string() 函数对用户输入进行转义,前端使用下拉选择框限制用户输入,并将表单提交方式改为 POST。然而,这些防护措施仍存在明显缺陷,无法从根本上阻止 SQL 注入攻击。

核心安全问题总结:

  1. 前端限制可被绕过 :前端下拉菜单仅是一种用户体验层面的限制,并非安全屏障。攻击者可以轻松使用 Burp Suite、Postman 或浏览器开发者工具等代理或调试工具,在 HTTP 请求被发送到服务器前拦截并修改 id 参数,将其替换为任意恶意值,从而完全绕过前端限制。
  2. 转义函数对数字型注入无效mysqli_real_escape_string() 函数的作用是转义字符串中的特殊字符(如单引号、反斜杠)。然而,MEDIUM 级别的查询语句为 user_id = $id,变量 $id 未被单引号包裹,属于数字型 SQL 注入。对于数字型注入,转义函数无法对数字本身或 SQL 运算符(如 OR=)产生任何转义效果,攻击者可以直接注入数字型 Payload(如 1 OR 1=1)进行攻击。
  3. 错误信息直接回显 :与 LOW 级别相同,当 SQL 语句执行出错时,详细的数据库错误信息会通过 mysqli_error() 直接输出到前端页面。这为攻击者提供了宝贵的信息泄露渠道,使其能够推断数据库的类型、版本、表结构等敏感信息,辅助构造更精准的注入 Payload。

攻击注意事项:

由于 MEDIUM 级别使用 POST 方法提交表单,攻击者在构造 Payload 时,必须在 Payload 末尾保留 &Submit=Submit 参数,才能成功触发查询。例如:id=1 UNION SELECT user, password FROM users #&Submit=Submit

安全启示:

MEDIUM 级别的案例表明,单纯依赖转义函数和前端限制无法有效防御 SQL 注入。转义函数仅适用于字符型注入场景,对数字型注入无效;前端限制则完全依赖于客户端的可信性,极易被绕过。在实际开发中,应优先采用参数化查询(预编译语句)等根本性的安全编码实践,并结合严格的输入验证、最小权限原则和错误信息模糊化处理,构建多层次的安全防御体系。

四、HIGH 级别 ------ LIMIT 限制与 SESSION 会话

1、漏洞描述

HIGH 级别在 MEDIUM 级别的基础上,进一步引入了两项看似更严格的防护措施:在 SQL 查询语句末尾添加了 LIMIT 1 限制,并将用户提交的 id 参数存储在 $_SESSION 会话变量中。然而,这些措施依然未能从根本上消除 SQL 注入漏洞。

核心防护机制:

  1. 参数存储于会话 :用户提交的 id 参数不再直接从 $_GET$_POST 获取,而是先存储在 $_SESSION['id'] 中,后续查询从会话中读取该值。这增加了攻击者直接修改请求参数的难度。
  2. 查询结果限制 :SQL 查询语句末尾添加了 LIMIT 1 子句,旨在限制每次查询只返回一条记录,试图阻止攻击者通过 UNION SELECT 等方式一次性提取大量数据。
  3. 错误信息模糊化:当 SQL 语句执行出错时,不再输出详细的数据库错误信息,而是返回通用的错误提示 "Something went wrong.",旨在减少信息泄露。

核心安全问题:

尽管引入了上述防护,HIGH 级别仍然存在可被利用的安全漏洞:

  1. LIMIT 1 限制可被绕过LIMIT 1 子句仅限制查询结果返回的记录数,但无法阻止攻击者执行恶意的 SQL 语句。攻击者可以通过 SQL 注释符(如 #-- )将 LIMIT 1 注释掉,从而绕过该限制。例如,注入 1' UNION SELECT database(), version() # 后,实际执行的查询变为 SELECT ... WHERE user_id = '1' UNION SELECT database(), version() #' LIMIT 1;# 之后的内容被注释,LIMIT 1 失效。
  2. 错误信息模糊化但注入点仍在:虽然错误信息不再泄露具体的数据库错误细节,降低了攻击者利用错误信息进行盲注的难度,但 SQL 注入漏洞本身依然存在。攻击者仍可通过布尔盲注、时间盲注等技术,根据页面返回的正常/异常状态差异来推断信息。
  3. 会话存储并非安全屏障 :将参数存储在 $_SESSION 中只是改变了参数的来源,并未对参数值本身进行任何安全处理(如验证、过滤或参数化)。如果攻击者能够通过其他方式(如会话固定、会话劫持)操纵 $_SESSION['id'] 的值,注入依然可能发生。

攻击影响:

即使有 LIMIT 1 和模糊化的错误信息,攻击者仍然可以:

  • 通过注释符绕过 LIMIT 1 限制,执行 UNION SELECT 查询提取数据库名、表名、列名等敏感信息。
  • 利用布尔逻辑(如 AND 1=1 / AND 1=2)判断注入点是否存在,并进行盲注攻击。
  • 在特定条件下,可能通过子查询、堆叠查询等技术进行更深入的数据窃取或破坏。

安全启示:

HIGH 级别的案例再次证明,单纯依靠添加 LIMIT、模糊错误信息或改变参数存储位置等"外围"防护措施,无法根治 SQL 注入漏洞。最有效的防御手段仍然是采用参数化查询(预编译语句),并结合严格的输入验证、输出编码和最小权限原则,从应用程序逻辑层面杜绝 SQL 注入的可能性。

2、查看网页源代码

php 复制代码
<?php

if( isset( $_SESSION [ 'id' ] ) ) {
    // Get input
    $id = $_SESSION[ 'id' ];

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            // Check database
            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
            $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );

            // Get results
            while( $row = mysqli_fetch_assoc( $result ) ) {
                // Get values
                $first = $row["first_name"];
                $last  = $row["last_name"];

                // Feedback for end user
                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
            }

            ((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;";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }

            if ($results) {
                while ($row = $results->fetchArray()) {
                    // Get values
                    $first = $row["first_name"];
                    $last  = $row["last_name"];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    }
}

?>

3、分析网页源代码

(1)代码概述

HIGH 级别的后端逻辑在 MEDIUM 级别的基础上,主要增加了两项防护措施:将用户提交的 id 参数存储在 $_SESSION 会话变量中,并在 SQL 查询语句末尾添加了 LIMIT 1 限制。其核心流程如下:

  1. 从会话获取输入 :不再直接从 $_GET$_POST 获取用户输入,而是从 $_SESSION['id'] 中读取 id 参数。这要求攻击者必须先通过正常流程提交一次表单,将 id 值存入会话,才能进行后续注入尝试。
  2. 构建并执行查询 :将 $_SESSION['id'] 中的值直接拼接到 SQL 查询语句 SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1; 中并执行。查询语句使用单引号包裹 $id 变量,属于字符型 SQL 注入。
  3. 限制查询结果 :在查询语句末尾添加了 LIMIT 1 子句,旨在限制每次查询只返回一条记录,试图阻止攻击者通过 UNION SELECT 等方式一次性提取大量数据。
  4. 模糊化错误信息:当 SQL 语句执行出错时,不再输出详细的数据库错误信息,而是返回通用的错误提示 "Something went wrong.",旨在减少信息泄露,增加攻击者进行错误型注入的难度。
  5. 输出查询结果 :将查询到的用户信息(first_name, last_name)格式化输出到页面。

关键变化:与 MEDIUM 级别相比,HIGH 级别的主要变化在于:

  • 参数来源 :从 $_POST['id'] 改为 $_SESSION['id'],增加了攻击者直接修改请求参数的难度。
  • 结果限制 :增加了 LIMIT 1 子句,试图限制数据泄露量。
  • 错误处理:错误信息从详细的数据库错误变为通用的 "Something went wrong."。

(2)漏洞分析

尽管引入了会话存储和 LIMIT 1 限制,HIGH 级别仍然存在以下安全漏洞:

① 漏洞一 ------ LIMIT 1 可被注释绕过

问题分析LIMIT 1 子句仅限制查询结果返回的记录数,但无法阻止攻击者执行恶意的 SQL 语句。攻击者可以在注入 Payload 的末尾添加 SQL 注释符(如 #-- ),将 LIMIT 1 注释掉,从而绕过该限制。

攻击示例

  • Payload1' UNION SELECT user, password FROM users #
  • 实际执行的 SQLSELECT first_name, last_name FROM users WHERE user_id = '1' UNION SELECT user, password FROM users #' LIMIT 1;
  • 结果# 之后的内容(包括 ' LIMIT 1;)被注释,UNION SELECT 查询得以完整执行,返回 users 表中的所有用户名和密码,成功绕过 LIMIT 1 限制。

绕过原理 :SQL 注释符(#-- )会使其后的所有内容被数据库引擎忽略。攻击者利用这一特性,在 Payload 末尾添加注释符,使原本用于限制结果的 LIMIT 1 子句失效。

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

问题分析 :查询语句 user_id = '$id' 使用单引号将变量包裹,这属于典型的字符型 SQL 注入。攻击者需要先闭合前面的单引号,才能插入并执行自己的恶意 SQL 代码。虽然 HIGH 级别引入了会话存储和 LIMIT 1,但并未对用户输入进行任何过滤、转义或参数化处理,字符型注入漏洞依然存在。

攻击示例

  • Payload1' OR '1'='1' #
  • 实际执行的 SQLSELECT first_name, last_name FROM users WHERE user_id = '1' OR '1'='1' #' LIMIT 1;
  • 结果 :由于 OR '1'='1' 条件恒为真,查询将返回 users 表中的第一条记录(受 LIMIT 1 限制)。虽然 LIMIT 1 仍然生效,但攻击者已成功注入并改变了查询逻辑。

安全影响 :字符型注入漏洞的存在,使得攻击者仍然可以通过闭合单引号、插入恶意 SQL 代码、并用注释符注释掉后续内容(包括 LIMIT 1)的方式,执行任意 SQL 查询,从而提取敏感数据或进行其他恶意操作。

4、操作步骤

(1)使用注释符绕过 LIMIT 1

HIGH 级别在 SQL 查询语句末尾添加了 LIMIT 1 子句,旨在限制每次查询只返回一条记录,试图阻止攻击者通过 UNION SELECT 等方式一次性提取大量数据。然而,攻击者可以利用 SQL 注释符(如 #-- )将 LIMIT 1 注释掉,从而绕过该限制。

操作步骤:

  1. 正常提交一次查询 :首先,在 DVWA 的 SQL Injection 页面(安全级别设置为 High )中,正常输入一个 id 值(例如 1)并提交。这一步的目的是将 id 参数存入 $_SESSION 会话变量,因为 HIGH 级别从会话中读取该值。

  2. 构造绕过 Payload :在输入框中输入以下 Payload:

    复制代码
    1' UNION SELECT user, password FROM users #
  3. 提交 Payload 并观察结果 :点击 Submit 按钮提交查询。如果注入成功,页面将在原本显示用户姓名的位置,返回 users 表中的所有用户名和经过 MD5 加密的密码哈希值。

原理分析:

  • 原始 SQL 语句SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;

  • 注入后的 SQL 语句SELECT first_name, last_name FROM users WHERE user_id = '1' UNION SELECT user, password FROM users #' LIMIT 1;

  • 绕过效果# 是 MySQL 的单行注释符,它会将其后的所有内容(包括 ' LIMIT 1;)注释掉。因此,实际执行的查询变为:

    sql 复制代码
    SELECT first_name, last_name FROM users WHERE user_id = '1' UNION SELECT user, password FROM users

    该查询会执行 UNION SELECT 操作,返回 users 表中的所有用户数据,成功绕过了 LIMIT 1 的限制。

注意事项:

  • 由于 HIGH 级别从 $_SESSION['id'] 读取参数,攻击者必须先通过正常流程提交一次表单,将 id 值存入会话,才能进行后续注入尝试。
  • 除了 #,也可以使用 -- (注意后面有一个空格)作为注释符,效果相同。
  • 即使有 LIMIT 1 限制,攻击者仍然可以通过注释符轻松绕过,这再次证明了仅依靠外围防护措施无法有效防御 SQL 注入。

(2)其他 HIGH 级别 Payload

除了上述绕过 LIMIT 1 的 Payload 外,攻击者还可以利用其他常见的 SQL 注入 Payload 在 HIGH 级别下进行攻击。下表列举了几个常用的 HIGH 级别 Payload 及其攻击效果:

Payload 效果
1' UNION SELECT database(), version() # 获取当前数据库名称和数据库版本信息。
1' UNION SELECT user, password FROM users # 直接提取 users 表中的用户名和经过 MD5 加密的密码哈希值。
1' UNION SELECT 1, CONVERT(table_name USING latin1) FROM information_schema.tables WHERE table_schema=database() # 获取当前数据库中的所有表名。

使用说明:

  1. 前提条件 :由于 HIGH 级别从 $_SESSION['id'] 读取参数,攻击者必须先通过正常流程提交一次表单(例如输入 1 并提交),将 id 值存入会话,才能进行后续注入尝试。
  2. 构造并提交 Payload :在输入框中输入上述任一 Payload,然后点击 Submit 按钮提交查询。
  3. 观察结果:如果注入成功,页面将在原本显示用户姓名的位置,返回相应的查询结果(如数据库名、版本信息、用户名和密码哈希值、表名等)。

攻击原理:

所有 Payload 均利用了字符型 SQL 注入漏洞,并通过 SQL 注释符 # 注释掉了查询语句末尾的 LIMIT 1 子句,从而绕过结果限制。例如,Payload 1' UNION SELECT database(), version() # 会闭合前面的单引号,插入 UNION SELECT 查询,并用 # 注释掉后续的 ' LIMIT 1;,最终执行完整的 UNION 查询以提取敏感信息。

安全启示:

HIGH 级别的案例再次表明,单纯依靠 LIMIT 限制、会话存储或错误信息模糊化等外围防护措施,无法从根本上消除 SQL 注入漏洞。最有效的防御手段仍然是采用参数化查询(预编译语句),并结合严格的输入验证、输出编码和最小权限原则,从应用程序逻辑层面杜绝 SQL 注入的可能性。

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

(1)LIMIT 绕过的智能检测

HIGH 级别通过 LIMIT 1 子句限制查询结果,但攻击者可通过 SQL 注释符(如 #--/* */)将其注释掉,从而绕过限制。AI 技术可从以下维度辅助检测此类绕过行为:

  1. 注释符检测 :AI 模型可识别输入中是否包含 SQL 注释符及其变体。除了常见的 #--/* */,还需检测经过编码、混淆或嵌套的注释形式(如 %23--+/**/),防止攻击者通过变形绕过简单规则。
  2. 语句完整性分析 :AI 可对最终执行的 SQL 语句进行语法和语义分析,判断其是否被异常截断或篡改。例如,原始查询末尾应有 LIMIT 1,若检测到该子句被注释或删除,且查询结构发生改变(如增加了 UNION SELECT),则可判定为潜在注入。
  3. 结果集异常检测:结合业务逻辑,AI 可监控查询返回的结果数量。对于明确设计为单条记录查询的接口(如根据 ID 查询用户),若返回结果数量超过 1 条,则可能存在注入绕过行为。

(2)基于查询结果的异常检测

AI 可通过分析查询结果的多个维度,识别潜在的 SQL 注入攻击,即使攻击者绕过了传统的防护措施。

  1. 结果数量异常检测 :对于明确使用 LIMIT 1 的查询,AI 可监控返回的记录数。若实际返回多条记录,则可能表明 LIMIT 子句被绕过,存在注入风险。AI 可结合历史基线,动态学习不同查询的预期结果数量范围。
  2. 数据内容异常检测:AI 可分析返回数据的语义和格式是否符合预期。例如,用户查询接口本应返回姓名等基本信息,若结果中出现了密码哈希、数据库版本、系统表名等敏感信息,则可判定为数据泄露异常。模型可通过训练学习正常数据的分布特征,识别偏离该分布的异常内容。
  3. 响应时间异常检测 :针对时间盲注,AI 可监控查询的响应时间。攻击者常利用 SLEEP()BENCHMARK() 等函数构造延迟,以根据响应时间差异推断信息。AI 可建立正常请求的响应时间基线,并识别出显著超出基线的异常请求,即使其不包含明显的恶意关键字。

技术实现路径

  • 多模型融合:结合规则引擎(用于检测已知模式)、机器学习模型(用于识别未知变种)和深度学习模型(用于语义理解),构建分层检测体系。
  • 上下文关联:将单个请求的检测结果与用户会话历史、IP 信誉、访问频率等上下文信息关联,进行综合风险评估。
  • 实时学习与迭代:利用在线学习机制,持续从新的攻击样本和正常流量中学习,动态更新检测模型,以应对不断演变的注入手法。

通过上述 AI 增强的检测手段,可以在传统规则匹配的基础上,更智能、更精准地识别 HIGH 级别中利用注释符绕过 LIMIT、基于结果集和时间延迟的复杂 SQL 注入攻击,提升整体安全防御的深度和适应性。

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

HIGH 级别在 MEDIUM 级别的基础上,进一步引入了 LIMIT 1 限制和错误信息模糊化等防护措施,但这些措施仍存在明显缺陷,无法从根本上消除 SQL 注入漏洞。

核心安全问题总结:

  1. LIMIT 1 限制可被注释符绕过 :虽然查询语句末尾添加了 LIMIT 1 子句以限制返回记录数,但攻击者可以通过 SQL 注释符(如 #-- )轻松将其注释掉,从而绕过该限制,执行完整的 UNION SELECT 查询以提取大量数据。
  2. 字符型注入漏洞依然存在 :查询语句 user_id = '$id' 仍使用单引号包裹变量,属于典型的字符型 SQL 注入。攻击者只需闭合单引号并插入恶意 SQL 代码,即可实施注入攻击。
  3. 错误信息模糊化但注入点仍在:虽然错误信息不再泄露具体的数据库结构细节,降低了攻击者利用错误信息进行盲注的难度,但 SQL 注入漏洞本身并未被修复,攻击者仍可通过布尔盲注、时间盲注等技术进行信息推断。
  4. 会话存储并非安全屏障 :将参数存储在 $_SESSION 中只是改变了参数的来源,并未对参数值本身进行任何安全处理。如果攻击者能够通过其他方式操纵会话数据,注入依然可能发生。

安全启示:

HIGH 级别的案例再次证明,单纯依靠添加 LIMIT 限制、模糊错误信息或改变参数存储位置等"外围"防护措施,无法根治 SQL 注入漏洞。这些措施只能增加攻击难度,但无法消除漏洞本身。最有效的防御手段仍然是采用参数化查询(预编译语句),并结合严格的输入验证、输出编码和最小权限原则,从应用程序逻辑层面彻底杜绝 SQL 注入的可能性。

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

1、漏洞描述

Impossible 级别是 DVWA SQL 注入模块的最高安全级别,它通过多重防护机制从根本上杜绝了 SQL 注入漏洞。与之前级别不同,Impossible 级别不再演示漏洞利用,而是展示了如何通过安全编码实践彻底防御 SQL 注入攻击。

核心安全改进:

  1. CSRF Token 验证 :通过 checkToken() 函数验证请求的合法性,防止跨站请求伪造攻击,确保每个请求都来自合法的用户会话。
  2. 数字类型验证 :使用 is_numeric() 函数对用户输入的 id 参数进行严格检查,确保输入为有效数字,从源头过滤非数字字符。
  3. PDO 参数化查询 :采用 PDO(PHP Data Objects)的预编译语句,通过 prepare() 方法预定义 SQL 查询结构,再使用 bindParam() 方法将用户输入作为参数绑定,实现数据与代码的完全分离。
  4. 结果数量限制 :在执行查询后,通过 rowCount() == 1 检查确保查询结果仅返回一条记录,防止攻击者通过 UNION 查询等方式提取多条数据。
  5. LIMIT 1 子句 :在 SQL 查询语句层面添加 LIMIT 1 限制,进一步确保每次查询最多只返回一条记录。
  6. Token 刷新机制 :每次请求后通过 generateSessionToken() 生成新的会话 Token,防止 Token 重用和会话固定攻击。

安全效果评估:

Impossible 级别通过上述多层防护机制,构建了纵深防御体系。参数化查询从根本上消除了 SQL 注入的可能性,而 CSRF Token 验证、输入类型检查和结果数量限制则提供了额外的安全冗余。即使攻击者尝试注入恶意 SQL 代码,也会被这些防护机制有效拦截,无法对数据库造成任何影响。

2、查看网页源代码

php 复制代码
<?php

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

    // 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();
                $row = $data->fetch();

                // Make sure only 1 result is returned
                if( $data->rowCount() == 1 ) {
                    // Get values
                    $first = $row[ 'first_name' ];
                    $last  = $row[ 'last_name' ];

                    // Feedback for end user
                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                }
                break;
            case SQLITE:
                global $sqlite_db_connection;

                $stmt = $sqlite_db_connection->prepare('SELECT first_name, last_name 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 == 2) {
                        $row = $result->fetchArray();

                        // Get values
                        $first = $row[ 'first_name' ];
                        $last  = $row[ 'last_name' ];

                        // Feedback for end user
                        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
                    }
                }

                break;
        }
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?>

3、分析网页源代码

(1)代码概述

Impossible 级别采用了纵深防御策略,通过多层安全机制协同工作,从根本上杜绝 SQL 注入漏洞:

  1. CSRF Token 验证 :通过 checkToken() 函数验证请求的合法性,防止跨站请求伪造攻击,确保每个请求都来自合法的用户会话。
  2. 数字类型验证 :使用 is_numeric($id) 函数对用户输入的 id 参数进行严格检查,确保输入为有效数字,从源头过滤非数字字符。
  3. 类型转换 :通过 intval($id) 将验证通过的数字输入转换为整数类型,进一步确保数据格式的规范性。
  4. PDO 参数化查询 :采用 PDO(PHP Data Objects)的预编译语句,通过 prepare() 方法预定义 SQL 查询结构,再使用 bindParam() 方法将用户输入作为参数绑定,实现数据与代码的完全分离。
  5. 结果数量限制 :在执行查询后,通过 rowCount() == 1 检查确保查询结果仅返回一条记录,防止攻击者通过 UNION 查询等方式提取多条数据。
  6. LIMIT 1 子句 :在 SQL 查询语句层面添加 LIMIT 1 限制,进一步确保每次查询最多只返回一条记录。
  7. Token 刷新机制 :每次请求后通过 generateSessionToken() 生成新的会话 Token,防止 Token 重用和会话固定攻击。

(2)安全分析

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

安全措施 实现方式 防御效果
CSRF Token checkToken() 函数验证请求中的 Token 与会话 Token 是否匹配 防止跨站请求伪造攻击,确保请求来源的合法性
数字类型验证 is_numeric() 函数检查输入是否为有效数字 从源头拒绝非数字输入,过滤潜在的恶意字符
类型转换 intval() 函数将输入转换为整数 确保输入数据格式规范,消除类型混淆风险
PDO 参数化查询 prepare() 预编译 SQL 语句 + bindParam() 绑定参数 彻底杜绝 SQL 注入,实现数据与代码的完全分离
结果数量限制 rowCount() == 1 检查返回结果数量 防止数据批量泄露,即使有漏洞也只能获取单条记录
LIMIT 1 SQL 语句中添加 LIMIT 1 子句 数据库层面双重限制返回数量,增加攻击难度

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

Impossible 级别的安全性源于其多层次、纵深防御的设计理念,其中最关键的是参数化查询机制

  1. SQL 语句与数据分离 :通过 PDO 的预编译语句,SQL 查询结构在预处理阶段就已确定,用户输入仅作为参数传入,被数据库视为纯数据而非可执行代码。即使输入中包含 'ORUNION 等 SQL 关键字,也不会被数据库解析执行。
  2. 输入验证与类型强制is_numeric()intval() 的组合确保了输入必须是数字,从根本上杜绝了字符型注入的可能性。即使攻击者尝试注入数字型 Payload,参数化查询机制也能确保其仅作为数据值处理。
  3. 多重结果限制LIMIT 1 子句和 rowCount() == 1 检查形成了双重保障,即使存在其他未知漏洞,也能最大程度限制数据泄露的范围。
  4. CSRF 防护:Token 验证机制防止了跨站请求伪造,确保只有合法用户发起的请求才能被处理。

一句话总结:Impossible 级别通过 CSRF Token 验证、严格的输入验证、PDO 参数化查询以及多重结果限制,构建了纵深防御体系,使 SQL 注入攻击在理论上变得"不可能"。

4、操作步骤

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

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

  1. 设置安全级别 :在 DVWA 左侧菜单栏的 "DVWA Security" 页面中,将安全级别设置为 Impossible
  2. 进入 SQL 注入页面:点击左侧菜单的 "SQL Injection" 进入测试页面。
  3. 执行正常查询 :在输入框中输入数字 1,然后点击 Submit 按钮。
  4. 观察结果:页面正常显示 ID 为 1 的用户信息(如 First name: admin, Surname: admin)。这表明在安全参数(数字输入)下,应用程序功能正常。

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

  1. 构造注入 Payload :在输入框中输入以下典型的 SQL 注入 Payload:

    复制代码
    1' UNION SELECT user, password FROM users
  2. 提交并观察 :点击 Submit 按钮提交查询。

  3. 预期结果 :页面无任何数据输出 (或仅显示空白)。这是因为:

    • is_numeric() 函数检测到输入中包含非数字字符(单引号 ' 和字母),判定为无效输入。
    • 程序在数字验证阶段即拒绝执行后续查询,恶意 Payload 根本不会到达数据库执行层。
    • 参数化查询(PDO prepared statement)机制即使输入通过验证,也会将整个 Payload 视为一个完整的字符串参数值,而不会将其解析为 SQL 代码。

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

  1. 输入非数字内容 :在输入框中输入纯字母字符串,例如:

    复制代码
    abc
  2. 提交并观察 :点击 Submit 按钮提交查询。

  3. 预期结果 :页面同样无任何数据输出is_numeric() 函数直接拒绝了非数字输入,程序不会执行任何数据库查询操作。

安全机制总结

通过上述操作可以看出,Impossible 级别的安全设计实现了纵深防御:

  1. 输入验证层is_numeric() 严格限制了输入必须为数字,从源头过滤了绝大多数恶意字符。
  2. 参数化查询层:PDO 预编译语句确保用户输入始终被当作数据值处理,与 SQL 代码逻辑完全分离。
  3. 结果限制层LIMIT 1rowCount() == 1 双重保障,即使出现极端情况,也最大程度限制了数据泄露范围。
  4. 请求验证层:CSRF Token 机制确保了请求的合法性,防止了跨站请求伪造攻击。

因此,在 Impossible 级别下,无论是字符型还是数字型 SQL 注入攻击,都会被上述机制有效拦截,无法对数据库产生任何影响。

5、Impossible级别------小结

Impossible 级别通过多层次、纵深防御的安全设计,从根本上杜绝了 SQL 注入漏洞:

  • CSRF Token 验证:防止跨站请求伪造攻击,确保请求来源的合法性。
  • 严格的输入验证 :使用 is_numeric() 函数检查输入是否为有效数字,并结合 intval() 进行类型转换,从源头过滤非数字字符。
  • 参数化查询(预编译语句) :采用 PDO 的 prepare()bindParam() 方法,实现 SQL 语句结构与用户数据的完全分离,彻底消除 SQL 注入的可能性。
  • 结果数量限制 :通过 rowCount() == 1 检查确保查询仅返回单条记录,防止数据批量泄露。
  • Token 刷新机制:每次请求后生成新的会话 Token,防止 Token 重用与会话固定攻击。

一句话总结:Impossible 级别通过 CSRF Token 验证、严格的输入验证、参数化查询以及多重结果限制,构建了纵深防御体系,使 SQL 注入攻击在理论上变得"不可能"。

6、AI视角下的 SQL 注入防御

Impossible 级别的参数化查询虽然能有效抵御传统 SQL 注入攻击,但结合人工智能技术可以在以下方面实现更深层次的防御增强:

(1)智能 SQL 注入检测

实时流量分析:利用 AI 模型持续监控数据库查询流量,通过模式识别技术自动发现异常查询行为,及时预警潜在的攻击尝试。

查询模式学习:基于历史正常查询数据建立行为基线模型,通过机器学习算法学习应用的正常查询模式,智能检测偏离基线的异常查询请求。

隐式注入检测:针对经过编码、混淆或变形处理的注入攻击,AI 能够识别隐藏在正常输入中的恶意意图,有效检测传统规则引擎难以发现的隐式注入尝试。

(2)自适应查询保护

动态白名单机制:AI 系统通过学习应用的正常查询模式,自动构建动态查询白名单,将符合正常业务逻辑的查询纳入信任范围。

异常查询智能阻断:当检测到不在白名单范围内且具有高风险特征的查询时,系统可自动阻断或进入人工审核流程,防止潜在的攻击执行。

查询复杂度分析:AI 模型能够分析查询语句的结构复杂度、嵌套深度等特征,识别异常复杂的查询模式(可能为精心构造的注入攻击),提供额外的风险预警。)

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

1、防护策略演进对比

防护维度 LOW 级别 MEDIUM 级别 HIGH 级别 Impossible 级别
输入过滤 ❌ 无任何过滤 ⚠️ 使用转义函数(mysqli_real_escape_string() ❌ 无过滤(依赖 SESSION 存储) ✅ 严格的类型验证(is_numeric())与类型转换(intval()
注入类型 字符型(单引号包裹) 数字型(无引号包裹) 字符型(单引号包裹) 无(仅允许数字输入)
请求方法 GET/POST POST SESSION(需先提交一次表单) GET + CSRF Token 验证
查询限制 ❌ 无限制 ❌ 无限制 ⚠️ 使用 LIMIT 1(可被注释绕过) LIMIT 1 + rowCount() == 1 双重限制
错误信息 ❌ 直接回显详细数据库错误 ❌ 直接回显详细数据库错误 ⚠️ 通用错误提示("Something went wrong.") ✅ 不向用户输出任何数据库错误信息
核心防御策略 无任何防护 弱防护(转义函数 + 前端限制) 弱防护(SESSION + LIMIT + 错误模糊化) 参数化查询(PDO prepared statement)+ CSRF Token

2、攻击面变化分析

攻击类型 LOW MEDIUM HIGH Impossible
字符型注入 ✅ 可直接利用(单引号闭合) ❌ 被转义函数阻止 ✅ 可绕过(闭合单引号 + 注释) ❌ 被参数化查询彻底阻止
数字型注入 ✅ 可直接利用 ✅ 可绕过(转义对数字无效) ❌ 不适用(字符型上下文) ❌ 被参数化查询彻底阻止
UNION 查询 ✅ 可直接利用 ✅ 可绕过(数字型注入) ✅ 需注释 LIMIT 1 才能利用 ❌ 被参数化查询彻底阻止
错误信息利用 ✅ 可直接利用(信息泄露) ✅ 可直接利用(信息泄露) ❌ 仅通用提示(信息有限) ❌ 无错误信息输出
CSRF 攻击 ✅ 可行(无防护) ✅ 可行(无防护) ✅ 可行(无防护) ❌ 被 CSRF Token 验证阻止

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

原则 说明 在 DVWA 中的实现级别
参数化查询 使用预编译语句(如 prepare() + bindParam())将 SQL 代码与数据完全分离,从根本上杜绝注入。 Impossible 级别(PDO prepared statement)
输入验证 对用户输入进行严格的类型、格式、范围验证,确保输入符合预期。 Impossible 级别(is_numeric() + intval()
最小权限原则 数据库连接账户应仅具有完成业务所需的最小权限,限制攻击影响范围。 架构级最佳实践(DVWA 未演示)
错误处理 不向用户输出详细的数据库错误信息,防止信息泄露辅助攻击。 HIGH 和 Impossible 级别(通用提示或无输出)
CSRF 防护 使用一次性令牌验证请求合法性,防止跨站请求伪造攻击。 Impossible 级别(checkToken()

七、总结

1、漏洞全景回顾

下表从四个关键防御层级(输入层、查询层、输出层、限制层)对比了 DVWA SQL 注入四个安全级别的防护差异:

防御层级 LOW 级别 MEDIUM 级别 HIGH 级别 Impossible 级别
输入层 🔴 无任何过滤 🟠 使用转义函数(mysqli_real_escape_string() 🟠 无过滤(依赖 SESSION 存储) 🟢 CSRF Token + 类型验证(is_numeric())+ 类型转换(intval()
查询层 🔴 直接拼接 SQL 语句 🔴 直接拼接 SQL 语句 🔴 直接拼接 SQL 语句 🟢 参数化查询(PDO 预编译语句)
输出层 🔴 直接回显详细数据库错误信息 🔴 直接回显详细数据库错误信息 🟡 仅返回通用错误提示("Something went wrong.") 🟢 不向用户输出任何数据库错误信息
限制层 🔴 无任何结果限制 🔴 无任何结果限制 🟠 使用 LIMIT 1(可被注释符绕过) 🟢 LIMIT 1 + rowCount() == 1 双重限制

2、关键启发

通过分析 DVWA SQL 注入四个级别的攻防演进,我们可以得出以下关键安全启示:

  1. 转义函数并非万能 :MEDIUM 级别的案例表明,mysqli_real_escape_string() 只能防御字符型注入,对数字型注入完全无效。依赖单一转义函数无法提供全面的 SQL 注入防护。
  2. 前端限制不可信:MEDIUM 级别的前端下拉菜单限制可被 Burp Suite 等工具轻松绕过。任何仅在前端实施的控制都只是用户体验优化,而非安全屏障。
  3. LIMIT 限制可被绕过 :HIGH 级别的 LIMIT 1 子句可通过 SQL 注释符(如 #)轻松注释掉,无法阻止攻击者通过 UNION SELECT 提取大量数据。
  4. 参数化查询是根本解决方案:Impossible 级别通过 PDO 参数化查询(预编译语句)实现了 SQL 代码与数据的完全分离,从根本上杜绝了 SQL 注入的可能性。结合 CSRF Token 验证和严格的输入验证,构建了纵深防御体系。

八、AI增强防御建议

1、智能 SQL 注入检测

传统的 SQL 注入检测方案主要依赖基于关键词的黑名单和人工维护的规则,这种方法在面对新型或经过混淆的注入攻击时,容易出现误报和漏报。AI 技术可以从以下维度增强检测能力:

传统方案 AI 增强方案
基于关键词的黑名单 基于语义的深度分析
依赖人工维护规则 自动学习正常查询模式
容易被混淆绕过 识别编码、注释等混淆手法

实现思路:

  1. 建立查询模式基线:收集正常业务场景下的 SQL 查询语句,构建查询模式基线,作为判断正常与异常行为的基础。
  2. 意图识别与分析:利用自然语言处理(NLP)模型分析用户输入的真实意图,区分正常的业务查询与潜在的恶意注入意图。
  3. 异常模式识别与阻断:通过机器学习模型识别偏离正常查询模式的异常请求,并实时进行阻断或告警。

2、基于查询结构的异常检测

AI 可以通过分析 SQL 查询的结构特征,更精准地识别潜在的注入攻击。下表对比了正常查询与 SQL 注入攻击在多个维度上的差异:

特征维度 正常查询 SQL 注入攻击
查询复杂度 简单(通常为单表查询) 复杂(常包含多表 UNION、子查询等)
关键字组合 符合业务逻辑的关键字 包含 UNION、INFORMATION_SCHEMA、SELECT 等敏感关键字
注释符使用 无或极少 频繁使用 #--/* */ 等 SQL 注释符
查询结果量 符合业务预期的数量 异常增多(如一次性提取所有用户数据)或减少

3、实施优先级建议

在实际部署 AI 增强的 SQL 注入防御措施时,建议遵循以下优先级,以平衡实施成本与防御效果:

优先级 措施 实施难度 防御效果
参数化查询(预编译语句)
输入类型验证
CSRF Token 防护
智能 SQL 注入检测(基于规则+AI)
AI 语义分析与异常检测

说明:

  • 高优先级措施是防御 SQL 注入的基石,应优先实施。它们技术成熟、成本低,且能从根本上消除大部分注入风险。
  • 中优先级措施可作为传统 WAF 的增强,在已有基础防护上增加智能检测层,提升对新型和变种攻击的识别能力。
  • 低优先级措施代表了更前沿的防御思路,虽然实施难度和成本较高,但能提供更深层次的语义理解和自适应防御能力。

4、总结

AI 时代的 SQL 注入防御,其核心并非用 AI 完全替代传统防御手段,而是利用 AI 技术对传统防御体系进行智能化增强。

  • 传统防御:提供基础的安全基线,如参数化查询、CSRF Token 验证、严格的输入验证等。这些是必须落实的"基本功",能有效抵御绝大多数已知攻击模式。
  • AI 增强:提供智能化的威胁识别、异常行为检测和自适应响应能力。AI 能够学习正常的业务模式,识别偏离基线的异常请求,并对经过混淆、编码的新型攻击手法保持较高的检测率。

只有将扎实的传统防御与灵活的 AI 增强相结合,才能构建起真正健壮(robust)、自适应且可持续演进的 SQL 注入防御体系。

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

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

👉 点击订阅专栏,第一时间收到更新通知!

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

相关推荐
安_2 小时前
hazelcast langchain4j 持久化
ai·vertx
天天有money2 小时前
Claude 中转站用于报告生成:会议纪要、周报和项目复盘怎么提效
gpt·ai·chatgpt
liulilittle2 小时前
反量化:反量化Q8零(q8_0)
人工智能·算法·机器学习·ai·llm
doubt。3 小时前
RAG与Agent智能体项目实战教程跟练与解析(前置准备与OpenAI库基础使用)
python·ai·pip
TechEdu20260613 小时前
[人工智能]TensorFlow深度学习框架工程实践概览
人工智能·深度学习·ai·tensorflow
秃了也弱了。15 小时前
AI-anent:2026火热的agent:Hermes Agent
ai
Tbisnic15 小时前
LangChain的 六大核心组件与 RAG 知识库构建
人工智能·python·ai·langchain·rag·langgraph