DVWA靶场(一、暴力破解)

Brute Force(low)

Brute Force即为暴力破解,通过枚举获取管理员的账号和密码,一般用于破解后台管理系统的登录。
需要准备以下工具

  • 密码字典
  • Burp Suite

Burp环境设置好后,浏览器开启代理就可以进行抓包了
首先打开搭建好的DVWA,选择low级别,选择暴力破解模块,输入用户名,密码这里随便输入,点击login

这时抓到相应的http包,可以看到为GET请求,username为admin,password为123456,点击右键发送到intruder模块

选择攻击类型Attack type为Sniper,攻击类型有以下

  • Sniper:狙击手,可以指定多个变量同时进行破解,但只设置一个字典文件,将指定的变量挨个用字典内容进行替换
  • Batteringram:攻城锤,可以指定多个变量,但仍只设置一个字典文件,将所有的变量一起用字典文件内容进行替换
  • Ptichfork:草叉子,可以指定多个变量,但需要每个变量分别设置一个字典文件,然后用对应的字典内容对变量同时进行替换
  • Cluster bomb:集束炸弹,可以指定多个变量,并为每个变量分别设置一个字典文件,然后用字典内容组合对变量进行替换

在positions中清除变量并双击密码变量后选择添加,在payloads中选择payload type为Runtime fiel,常用载荷类型有以下

  • Simple list:可以手动添加字典列表,也可以导入Brup自带的字典,或者导入自定义字典
  • Runtime file:只能加载自定义的字典
  • Numbers:设定一个数值范围,从范围内依次或随机取值进行测试
  • Brute forcer:自己定义字符范围来生成字典文件


这里特别注意密码字典命名和路径不能包含汉字或者汉字符号,否则加载不出字典

在Options里可以设置请求引擎,加快破解速度,进程数设置为10,网络失败重试次数为0,重试前暂停(毫秒)为0

设置Grep匹配为登录错误时的提示

点击Start attack后开始暴力破解,得到以下结果

通过长度以及Grep匹配可以看出密码为password,暴力破解结束。

Brute Force(medium)

中等等级的暴力破解和低等级的相同,只是低等级的暴力破解可以进行sql注入,而中等级的把其中的字符串过滤了,操作还是一样的,在此我就不再演示。
中级的暴力破解相对来说比较慢,因为有个sleep函数,在破解失败后会使程序停止运行两秒。附上低级和中级的代码对比:
可以看到低等级的代码,服务器只是验证了参数Login是否被设置(isset函数在php中用来检测变量是否设置,该函数返回的是布尔类型的值,即true/false),没有任何的防爆破机制,且对参数username、password没有做任何过滤,存在明显的sql注入漏洞。

 <?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);
}

?>

相比Low级别的代码,Medium级别的代码主要增加了mysql_real_escape_string函数,这个函数会对字符串中的特殊符号(x00,n,r,,’,”,x1a)进行转义,基本上能够抵御sql注入攻击,说基本上是因为查到说 MySQL5.5.37以下版本如果设置编码为GBK,能够构造编码绕过mysql_real_escape_string 对单引号的转义(因实验环境的MySQL版本较新,所以并未做相应验证);同时,$pass做了MD5校验,杜绝了通过参数password进行sql注入的可能性。但是,依然没有加入有效的防爆破机制(sleep(2)实在算不上)。

 <?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);
}

?>

Brute Force(higt)

按照之前的方法进行破解,对higt级别不管用,因为代码中增加了user_token参数

可以看到破解出来的密码长度都是一样的

首先我们正常抓包,得到完整数据包之后,我们把需要猜解的参数范围选中user_token和password,选择Pitchfork测试类型。

找到Redirections选中always允许重定向,最后在Options中找到Grep-Extract模块,点击Add,并设置筛选条件,得到user_token。
然后设置密码本,点击payload,选择第一项的密码本与低等级的相同,第二项的时候选择Recursive grep 并且把之前得到的token值粘贴到方框中。
分析higt级别代码:

<?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攻击,同时也增加了爆破的难度,通过抓包,可以看到,登录验证时提交了四个参数:username、password、Login以及user_token。每次服务器返回的登陆页面中都会包含一个随机的user_token的值,用户每次登录时都要将user_token一起提交。服务器收到请求后,会优先做token的检查,再进行sql查询。
同时,High级别的代码中,使用了stripslashes(去除字符串中的反斜线字符,如果有两个连续的反斜线,则只去掉一个)、 mysql_real_escape_string对参数username、password进行过滤、转义,进一步抵御sql注入。
由于加入了Anti-CSRFtoken预防无脑爆破,还有一种办法是用python写个脚本:

import  requests
from bs4 import BeautifulSoup

header={
     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0'
     'Cookie': 'PHPSESSID=o8fani7hijfi1g42oo0opnhbl7; security=high'
}

url="http://192.168.115.130:89/vulnerabilities/brute/index.php"

def get_token(url,heders):
    r=requests.get(url,heders=header)
    #print(r.status code,end=' ')
    html = r.content.decode()
    #print(len(html),end=' ')
    soup = BeautifulSoup(html,"html.parser")
    user_token = soup.find_all('input')[3]['value']
    return user_token

user_token = get_token(url,headers)
i=0
for line in open("test.txt"):
	url = "http://192.168.115.130:89/vulnerabilities/brute/index.php"+"?username=admin&password="+line.strip()+"&Login=Login&user_token="+user_token
	i = i+1
	print i,'admin',line.strip(),user_token,
	user_token = get_token(url,headers)
	if (i == 30):
		break

Brute Force(impossible)

Impossible级别的代码加入了可靠的防爆破机制,当检测错误登录3次后,账户将锁定15分钟,爆破也就无法继续。
同时采用了更为安全的PDO(PHP Data Object)机制防御sql注入。

<?php

if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
    // 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 "<pre><br />This account has been locked due to too many incorrect logins.</pre>";

        // 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) . "<br />";
        print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
        print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
        */

        // 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<br />";
        }
    }

    // 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 "<p>Welcome to the password protected area <em>{$user}</em></p>";
        echo "<img src=\"{$avatar}\" />";

        // Had the account been locked out since last login?
        if( $failed_login >= $total_failed_login ) {
            echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
            echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
        }

        // 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 "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";

        // 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();

?> 

防止暴力破解措施

1、增加密码复杂度
2、设置Anti-CSRF token,设置验证码
3、设置登录失败次数和锁定时间
4、使用双因素认证,如密码+证书
5、过滤用户输入,防止SQL注入

posted @ 2020-11-20 15:08  tonywell  阅读(1797)  评论(0编辑  收藏  举报