From 649ad736c53f27fa8c44f9258c74b85a3008b4fe Mon Sep 17 00:00:00 2001 From: Jing Ling Date: Tue, 11 Aug 2020 18:29:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0fuzz=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=AD=97=E5=85=B8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- brute.py | 117 +++++++++++++++++++++++++++++----------------- config/default.py | 3 +- 2 files changed, 77 insertions(+), 43 deletions(-) diff --git a/brute.py b/brute.py index 13489d5..2f0f3e1 100644 --- a/brute.py +++ b/brute.py @@ -77,39 +77,16 @@ def detect_wildcard(domain, authoritative_ns): wildcard = do_query_a(random_subdomain, resolver) except Exception as e: logger.log('DEBUG', e.args) - logger.log('ALERT', f'Multiple detection errors, so temporarily {domain} does not use wildcard dns record') + logger.log('ALERT', f'Multiple detection errors, so temporarily {domain} ' + f'does not use wildcard dns record') return False else: return wildcard -def gen_fuzz_subdomains(expression, rule): +def gen_subdomains(expression, path): """ - Generate subdomains based on fuzz mode - - :param str expression: generate subdomains's expression - :param str rule: regexp rule - :return list subdomains: list of subdomains - """ - subdomains = list() - fuzz_count = exrex.count(rule) - if fuzz_count > 10000000: - logger.log('ALERT', f'The dictionary generated by this rule is too large:{fuzz_count} > 10000000') - logger.log('DEBUG', f'Dictionary size based on fuzz mode: {fuzz_count}') - for fuzz_string in exrex.generate(rule): - fuzz_string = fuzz_string.lower() - if not fuzz_string.isalnum(): - continue - fuzz_domain = expression.replace('*', fuzz_string) - subdomains.append(fuzz_domain) - random_domain = random.choice(subdomains) - logger.log('ALERT', f'Please check whether {random_domain} is correct or not') - return subdomains - - -def gen_word_subdomains(expression, path): - """ - Generate subdomains based on word mode + Generate subdomains :param str expression: generate subdomains's expression :param str path: path of wordlist @@ -129,9 +106,56 @@ def gen_word_subdomains(expression, path): word = word[:-1] subdomain = expression.replace('*', word) subdomains.append(subdomain) - random_domain = random.choice(subdomains) + size = len(subdomains) + logger.log('DEBUG', f'The size of the dictionary generated by {path} is {size}') + if size == 0: + logger.log('ALERT', 'Please check the dictionary content!') + else: + random_domain = random.choice(subdomains) + logger.log('ALERT', f'Please check whether {random_domain} is correct or not') + return subdomains + + +def gen_fuzz_subdomains(expression, rule, fuzzlist): + """ + Generate subdomains based on fuzz mode + + :param str expression: generate subdomains's expression + :param str rule: regexp rule + :param str fuzzlist: fuzz dictionary + :return list subdomains: list of subdomains + """ + subdomains = list() + if fuzzlist: + fuzz_domain = gen_subdomains(expression, fuzzlist) + subdomains = subdomains.extend(fuzz_domain) + if rule: + fuzz_count = exrex.count(rule) + if fuzz_count > 10000000: + logger.log('ALERT', f'The dictionary generated by this rule is too large: ' + f'{fuzz_count} > 10000000') + for fuzz_string in exrex.generate(rule): + fuzz_string = fuzz_string.lower() + if not fuzz_string.isalnum(): + continue + fuzz_domain = expression.replace('*', fuzz_string) + subdomains.append(fuzz_domain) + random_domain = random.choice(subdomains) + logger.log('ALERT', f'Please check whether {random_domain} is correct or not') + logger.log('DEBUG', f'Dictionary size based on fuzz mode: {len(subdomains)}') + return subdomains + + +def gen_word_subdomains(expression, path): + """ + Generate subdomains based on word mode + + :param str expression: generate subdomains's expression + :param str path: path of wordlist + :return list subdomains: list of subdomains + """ + subdomains = gen_subdomains(expression, path) logger.log('DEBUG', f'Dictionary based on word mode size: {len(subdomains)}') - logger.log('ALERT', f'Please check whether {random_domain} is correct or not') return subdomains @@ -172,7 +196,8 @@ def query_domain_ns(domain): @tenacity.retry(stop=tenacity.stop_after_attempt(2)) def get_wildcard_record(domain, resolver): - logger.log('INFOR', f'Query {domain} \'s wildcard dns record in authoritative name server') + logger.log('INFOR', f"Query {domain} 's wildcard dns record " + f"in authoritative name server") try: answer = resolver.query(domain, 'A') # 如果查询随机域名A记录时抛出Timeout异常则重新查询 @@ -186,7 +211,8 @@ def get_wildcard_record(domain, resolver): return None, None except Exception as e: logger.log('ERROR', e.args) - logger.log('ERROR', f'Query {domain} wildcard dns record in authoritative name server error') + logger.log('ERROR', f'Query {domain} wildcard dns record in ' + f'authoritative name server error') exit(1) else: if answer.rrset is None: @@ -444,6 +470,7 @@ class Brute(Module): brute.py --target domain.com --word True --wordlist subnames.txt run brute.py --target domain.com --word True --recursive True --depth 2 run brute.py --target d.com --fuzz True --place m.*.d.com --rule '[a-z]' run + brute.py --target d.com --fuzz True --place m.*.d.com --fuzzlist subnames.txt run Note: --alive True/False Only export alive subdomains or not (default False) @@ -455,14 +482,15 @@ class Brute(Module): :param int process: Number of processes (default 1) :param int concurrent: Number of concurrent (default 10000) :param bool word: Use word mode generate dictionary (default False) - :param str wordlist: Dictionary path used in word mode (default use ./config/setting.py) + :param str wordlist: Dictionary path used in word mode (default use ./config/default.py) :param bool recursive: Use recursion (default False) :param int depth: Recursive depth (default 2) - :param str nextlist: Dictionary file path used by recursive (default use ./config/setting.py) + :param str nextlist: Dictionary file path used by recursive (default use ./config/default.py) :param bool fuzz: Use fuzz mode generate dictionary (default False) :param bool alive: Only export alive subdomains (default False) :param str place: Designated fuzz position (required if use fuzz mode) :param str rule: Specify the regexp rules used in fuzz mode (required if use fuzz mode) + :param str fuzzlist: Dictionary path used in fuzz mode (default use ./config/default.py) :param bool export: Export the results (default True) :param str format: Result format (default csv) :param str path: Result directory (default None) @@ -471,8 +499,8 @@ class Brute(Module): def __init__(self, target, process=None, concurrent=None, word=False, wordlist=None, recursive=False, depth=None, nextlist=None, - fuzz=False, place=None, rule=None, export=True, alive=True, - format='csv', path=None): + fuzz=False, place=None, rule=None, fuzzlist=None, export=True, + alive=True, format='csv', path=None): Module.__init__(self) self.module = 'Brute' self.source = 'Brute' @@ -487,6 +515,7 @@ class Brute(Module): self.fuzz = fuzz or settings.enable_fuzz self.place = place or settings.fuzz_place self.rule = rule or settings.fuzz_rule + self.fuzzlist = fuzzlist or settings.fuzz_list self.export = export self.alive = alive self.format = format @@ -516,7 +545,7 @@ class Brute(Module): # set可以合并list dict_set = dict_set.union(word_subdomains) if self.fuzz: - fuzz_subdomains = gen_fuzz_subdomains(self.place, self.rule) + fuzz_subdomains = gen_fuzz_subdomains(self.place, self.rule, self.fuzzlist) dict_set = dict_set.union(fuzz_subdomains) count = len(dict_set) logger.log('INFOR', f'Dictionary size: {count}') @@ -532,8 +561,11 @@ class Brute(Module): if len(self.domains) > 1: self.bulk = True if self.fuzz: - if self.place is None or self.rule is None: - logger.log('FATAL', f'No fuzz position or rules specified') + if self.place is None: + logger.log('FATAL', f'No fuzz position specified') + exit(1) + if self.rule is None and self.fuzzlist is None: + logger.log('FATAL', f'No fuzz rules or fuzz dictionary specified') exit(1) if self.bulk: logger.log('FATAL', f'Cannot use fuzz mode in the bulk brute') @@ -614,7 +646,7 @@ class Brute(Module): return self.subdomains def run(self): - logger.log('INFOR', f'Start runing {self.source} module') + logger.log('INFOR', f'Start running {self.source} module') if self.check_env: utils.check_env() self.domains = utils.get_domains(self.target) @@ -622,7 +654,8 @@ class Brute(Module): for self.domain in self.domains: self.check_brute_params() if self.recursive_brute: - logger.log('INFOR', f'Start recursively brute the first layer subdomain of {self.domain}') + logger.log('INFOR', f'Start recursively brute the 1 layer subdomain' + f' of {self.domain}') valid_subdomains = self.main(self.domain) all_subdomains.extend(valid_subdomains) @@ -631,8 +664,8 @@ class Brute(Module): if self.recursive_brute: for layer_num in range(1, self.recursive_depth): # 之前已经做过1层子域爆破 当前实际递归层数是layer+1 - logger.log('INFOR', f'Start recursively brute' - f'the {layer_num + 1} layer subdomain of {self.domain}') + logger.log('INFOR', f'Start recursively brute the {layer_num+1} layer' + f' subdomain of {self.domain}') for subdomain in all_subdomains: self.place = '*.' + subdomain # 进行下一层子域爆破的限制条件 diff --git a/config/default.py b/config/default.py index d3964ae..32e197f 100644 --- a/config/default.py +++ b/config/default.py @@ -73,7 +73,8 @@ only_save_valid = True # 是否在处理爆破结果时只存入解析成功的 check_time = 10 # 检查字典配置停留时间(默认10秒) enable_fuzz = False # 是否使用fuzz模式枚举域名 fuzz_place = None # 指定爆破的位置 指定的位置用`@`表示 示例:www.@.example.com -fuzz_rule = None # fuzz域名的正则 示例:'[a-z][0-9]' 表示第一位是字母 第二位是数字 +fuzz_rule = None # fuzz域名使用的正则表达式 示例:'[a-z][0-9]' 表示第一位是字母 第二位是数字 +fuzz_list = None # fuzz域名使用的字典路径 brute_ip_blacklist = {'0.0.0.0', '0.0.0.1'} # IP黑名单 子域解析到IP黑名单则标记为非法子域 ip_appear_maximum = 100 # 多个子域解析到同一IP次数超过100次则标记为非法(泛解析)子域