DVWA靶场通关笔记-SQL Injection Blind(SQL盲注 Impossible级别)
目录
一、源码分析
1、index.php
2、impossible.php
二、SQL注入防范分析
1、CSRF Token 验证
2、输入数字强制校验
3、PDO 预处理语句
本系列为通过《DVWA靶场通关笔记》的SQL Injection Blind关卡(low,medium,high,impossible共4关)渗透集合,通过对相应关卡源码的代码审计找到讲解渗透原理并进行渗透实践。本文为SQL Injection Blind impossible关卡的原理分析部分,讲解相对于low、medium和high级别,为何对其进行渗透测试是Impossible的。
一、源码分析
1、index.php
进入DVWA靶场源目录,找到index.php源码。
这段 PHP 代码是 Damn Vulnerable Web Application (DVWA) 中 SQL盲注演示页面的主控制器,根据用户设置的安全级别(低 / 中 / 高 / 安全)加载不同级别安全成都程度的代码,通过表单让用户输入或选择用户 ID,动态生成页面并展示查询结果,同时检测 PHP 不安全配置并提供 SQL 注入学习资源链接,用于演示不同防护等级下的盲注攻击场景及防御方式。
经过注释后的详细代码如下所示。
<?php
// 定义网站根目录路径常量,用于后续文件引用
define( 'DVWA_WEB_PAGE_TO_ROOT', '../../' );
// 引入DVWA页面基础功能库
require_once DVWA_WEB_PAGE_TO_ROOT . 'dvwa/includes/dvwaPage.inc.php';// 启动页面,验证用户是否已认证并初始化PHPIDS(入侵检测系统)
dvwaPageStartup( array( 'authenticated', 'phpids' ) );// 创建新页面实例
$page = dvwaPageNewGrab();
// 设置页面标题
$page[ 'title' ] = 'Vulnerability: SQL Injection (Blind)' . $page[ 'title_separator' ].$page[ 'title' ];
// 设置页面ID,用于导航和标识
$page[ 'page_id' ] = 'sqli_blind';
// 添加帮助按钮和源代码按钮
$page[ 'help_button' ] = 'sqli_blind';
$page[ 'source_button' ] = 'sqli_blind';// 连接数据库
dvwaDatabaseConnect();// 设置HTTP请求方法(默认为GET)
$method = 'GET';
// 初始化不同安全级别对应的源文件
$vulnerabilityFile = '';// 根据安全级别Cookie值选择不同的实现文件
switch( $_COOKIE[ 'security' ] ) {case 'low':// 低安全级别:存在明显SQL注入安全风险$vulnerabilityFile = 'low.php';break;case 'medium':// 中安全级别:部分防御措施,使用POST方法和下拉菜单$vulnerabilityFile = 'medium.php';$method = 'POST';break;case 'high':// 高安全级别:增强防御,但仍可能存在安全风险$vulnerabilityFile = 'high.php';break;default:// 安全模式:使用预处理语句,理论上无SQL注入风险$vulnerabilityFile = 'impossible.php';break;
}// 引入选定的实现文件
require_once DVWA_WEB_PAGE_TO_ROOT . "vulnerabilities/sqli_blind/source/{$vulnerabilityFile}";// 检查PHP配置中的安全风险
$WarningHtml = '';
// 检测Magic Quotes是否启用(已弃用的安全机制)
if( ini_get( 'magic_quotes_gpc' ) == true ) {$WarningHtml .= "<div class=\"warning\">The PHP function \"<em>Magic Quotes</em>\" is enabled.</div>";
}
// 检测Safe Mode是否启用(已弃用的安全机制)
if( ini_get( 'safe_mode' ) == true ) {$WarningHtml .= "<div class=\"warning\">The PHP function \"<em>Safe mode</em>\" is enabled.</div>";
}// 构建页面主体内容
$page[ 'body' ] .= "
<div class=\"body_padded\"><h1>Vulnerability: SQL Injection (Blind)</h1>{$WarningHtml}<div class=\"vulnerable_code_area\">";// 根据不同安全级别显示不同的用户交互界面
if( $vulnerabilityFile == 'high.php' ) {// 高级别安全:通过JavaScript弹窗设置用户ID$page[ 'body' ] .= "Click <a href=\"#\" onclick=\"javascript:popUp('cookie-input.php');return false;\">here to change your ID</a>.";
}
else {// 低、中级别安全:显示表单让用户输入ID$page[ 'body' ] .= "<form action=\"#\" method=\"{$method}\"><p>User ID:";// 中级别安全:使用下拉菜单限制用户选择范围if( $vulnerabilityFile == 'medium.php' ) {$page[ 'body' ] .= "\n <select name=\"id\">";// 查询用户数量用于生成下拉选项$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>' );$num = mysqli_fetch_row( $result )[0];$i = 0;// 动态生成下拉选项(1到用户总数)while( $i < $num ) { $i++; $page[ 'body' ] .= "<option value=\"{$i}\">{$i}</option>"; }$page[ 'body' ] .= "</select>";}else// 低级别安全:允许用户自由输入$page[ 'body' ] .= "\n <input type=\"text\" size=\"15\" name=\"id\">";$page[ 'body' ] .= "\n <input type=\"submit\" name=\"Submit\" value=\"Submit\"></p>\n";// 安全模式:添加CSRF防护令牌if( $vulnerabilityFile == 'impossible.php' )$page[ 'body' ] .= " " . tokenField();$page[ 'body' ] .= "</form>";
}// 显示查询结果(由引入include的指定文件生成)
$page[ 'body' ] .= "{$html}</div><h2>More Information</h2><ul>// 提供SQL注入相关的参考链接<li>" . dvwaExternalLinkUrlGet( 'http://www.securiteam.com/securityreviews/5DP0N1P76E.html' ) . "</li><li>" . dvwaExternalLinkUrlGet( 'https://en.wikipedia.org/wiki/SQL_injection' ) . "</li><li>" . dvwaExternalLinkUrlGet( 'http://ferruh.mavituna.com/sql-injection-cheatsheet-oku/' ) . "</li><li>" . dvwaExternalLinkUrlGet( 'http://pentestmonkey.net/cheat-sheet/sql-injection/mysql-sql-injection-cheat-sheet' ) . "</li><li>" . dvwaExternalLinkUrlGet( 'https://www.owasp.org/index.php/Blind_SQL_Injection' ) . "</li><li>" . dvwaExternalLinkUrlGet( 'http://bobby-tables.com/' ) . "</li></ul>
</div>\n";// 输出最终HTML页面
dvwaHtmlEcho( $page );?>
2、impossible.php
进入DVWA靶场SQL Injection Blind的source源码目录,找到impossible.php源码,分析其为何能让这一关卡名为不可能实现SQL盲注渗透。
打开源码impossible.php,分析可知这段代码实现了用户 ID 查询功能,通过登录校验、CSRF Token、数字输入验证及 PDO 预处理,彻底防范注入与 CSRF 攻击,保障安全,如下所示。
详细注释后的impossible.php源码如下所示。
<?php// 检查是否通过GET方式提交了表单
if( isset( $_GET[ 'Submit' ] ) ) {// 验证CSRF令牌,防止跨站请求伪造攻击// 比较用户请求中的令牌与会话存储的令牌是否一致checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );// 从GET参数中获取用户输入的ID$id = $_GET[ 'id' ];// 验证输入是否为数字(防止非数字类型的SQL注入)if(is_numeric( $id )) {// 准备SQL查询:从users表中查询指定ID的用户信息// 使用预处理语句防止SQL注入$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );// 将用户输入的ID绑定为整数类型参数// 这一步确保即使输入包含非数字字符也会被转换为整数$data->bindParam( ':id', $id, PDO::PARAM_INT );// 执行SQL查询$data->execute();// 检查查询结果是否为1条记录if( $data->rowCount() == 1 ) {// 用户存在时的反馈信息$html .= '<pre>User ID exists in the database.</pre>';}else {// 用户不存在时返回404状态码header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );// 用户不存在时的反馈信息$html .= '<pre>User ID is MISSING from the database.</pre>';}}
}// 生成新的CSRF令牌并存储到会话中
generateSessionToken();?>
二、SQL注入防范分析
impossible.php
是 DVWA 中 SQL 盲注模块的最高防护级别页面,核心功能是在实现 “根据用户 ID 查询姓名” 业务的同时,通过多重机制杜绝安全风险。它先校验用户登录状态与 CSRF Token,确保请求合法;再对输入的 id
进行数字强制校验,过滤非预期输入;最后用 PDO 预处理语句执行数据库查询,彻底隔离用户输入与 SQL 逻辑。此外,页面还限制查询结果仅一条,避免数据泄露,通过 “请求校验 - 输入过滤 - 安全查询” 的闭环,既实现了用户数据查询的基础功能,又完全抵御 SQL 注入、CSRF 等攻击,是 Web 安全中数据库操作的标准安全实现。相对于Low级别、Medium级别、High级别,四个级别的防范措施对比如下所示。
级别 | 输入方式 | 防护措施 | 注入难度 | 核心特点 |
---|---|---|---|---|
Low | GET 参数 | 无任何防护 | 极低 | 直接拼接 SQL 语句,可通过页面反馈判断注入结果 |
Medium | POST 参数(下拉) | 字符串转义(数字型查询无引号) | 低 | 转义无法防御数字型注入,可直接构造逻辑表达式 |
High | Cookie 参数 | 无过滤但有随机延迟 | 中等 | 依赖时间盲注,输入位置隐蔽,需通过延迟判断结果 |
Impossible | GET 参数 + Token | 数字验证 + PDO 预处理 + CSRF 防护 | 无 | 彻底隔离用户输入与 SQL 逻辑,无注入风险,完全防御各类攻击 |
impossible.php
并非仅实现 “查询用户数据” 的简单功能,而是通过 三重防护机制 构建了完整的安全闭环,彻底杜绝 SQL 注入及相关攻击:
防护机制 | 作用场景 | 核心目标 |
---|---|---|
CSRF Token 验证 | 请求合法性校验 | 防止第三方伪造用户请求 |
输入数字强制校验 | 输入源过滤 | 阻断非预期格式的恶意输入 |
PDO 预处理语句 | 数据库查询安全 | 彻底隔离用户输入与 SQL 逻辑 |
1、CSRF Token 验证
CSRF 攻击利用用户已登录的会话伪造恶意请求,impossible.php
通过 Token 验证 确保请求由用户主动发起,而非第三方伪造。
// 5. 若用户点击「Submit」提交请求,先验证 CSRF Token
if (isset($_GET[ 'Submit' ])) {// 核心:验证请求中的 Token 与会话中存储的 Token 是否一致checkToken($_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php');// 后续业务逻辑...
}// 6. 在表单中嵌入 CSRF Token 隐藏字段(generateSessionToken() 已生成会话 Token)
$page[ 'body' ] .= '' . tokenField() . ' <!-- 生成 <input type="hidden" name="user_token" value="随机字符串"> --></form>
';
generateSessionToken()
:在用户会话($_SESSION['session_token']
)中生成一个随机、唯一的 Token(如a3f2d4e5b1
),每次页面刷新或请求后可重新生成(短期有效);tokenField()
:将会话中的 Token 嵌入表单隐藏字段,随用户请求一起提交;checkToken(...)
:对比请求参数user_token
与会话session_token
,若不一致则判定为恶意请求,跳转至首页并阻断后续操作。
2、输入数字强制校验
即使通过 CSRF 验证,代码仍对用户输入的 id
参数进行 强制数字校验,从源头阻断非预期格式的输入(如 SQL 注入片段)。
if (isset($_GET[ 'Submit' ])) {checkToken(...); // 先过 Token 验证// 7. 获取用户输入的 id 参数,并进行数字类型强制校验$id = $_GET[ 'id' ];// 核心:仅允许数字输入(整数/浮点数),非数字直接返回错误提示if (is_numeric($id)) {// 合法输入:进入数据库查询流程} else {// 非法输入:渲染错误提示,不执行任何数据库操作$page[ 'body' ] .= '<pre>ERROR: Invalid ID. Please enter a numeric value.</pre>';}
}
- 若用户输入恶意字符串(如
1' OR '1'='1
、UNION SELECT...
),is_numeric($id)
会判定为false
,直接阻断后续查询,避免非法输入进入数据库层; - 仅允许
1
、2
、3.5
等数字格式,符合「用户 ID 为数字」的业务逻辑,实现 “输入格式与业务逻辑匹配” 的安全原则。
3、PDO 预处理语句
对于合法的数字输入,代码使用 PDO 预处理语句(参数化查询) 执行数据库查询,这是杜绝 SQL 注入的核心机制。
if (is_numeric($id)) {// 8. 初始化 PDO 数据库连接(DVWA 核心配置中已确保 PDO 连接安全)$pdo = dvwaDbConnect();// 9. 准备预处理 SQL:使用占位符 (:id) 代替直接拼接用户输入$data = $pdo->prepare('SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;');// 10. 绑定参数:明确指定参数类型为整数(PDO::PARAM_INT),强制类型匹配$data->bindParam(':id', $id, PDO::PARAM_INT);// 11. 执行查询(用户输入仅作为参数传递,不参与 SQL 语法解析)$data->execute();// 12. 获取查询结果(仅返回一条数据,避免批量泄露)$row = $data->fetch();// 13. 渲染查询结果:有数据则显示用户名,无数据则提示“未找到”if ($row) {$page[ 'body' ] .= "<pre>ID: {$id}<br>First name: {$row[ 'first_name' ]}<br>Surname: {$row[ 'last_name' ]}</pre>";} else {$page[ 'body' ] .= '<pre>User ID not found in the database.</pre>';}
}