重构子域爆破模块

This commit is contained in:
Jing Ling
2020-04-04 04:22:22 +08:00
parent 4b1aafa719
commit aee7ce727d
14 changed files with 1903973 additions and 36133 deletions
-328
View File
@@ -1,328 +0,0 @@
#!/usr/bin/python3
# coding=utf-8
"""
OneForAll多进程多协程异步子域爆破模块
:copyright: Copyright (c) 2019, Jing Ling. All rights reserved.
:license: GNU General Public License v3.0, see LICENSE for more details.
"""
import time
import queue
import asyncio
import secrets
import exrex
import fire
import config
import dbexport
from common import resolve, utils
from common.module import Module
from common.database import Database
from config import logger
def detect_wildcard(domain):
"""
探测域名是否使用泛解析
:param str domain: 域名
:return: 如果没有使用泛解析返回False 反之返回泛解析的IP集合和ttl整型值
"""
logger.log('INFOR', f'正在探测{domain}是否使用泛解析')
token = secrets.token_hex(4)
random_subdomain = f'{token}.{domain}'
try:
resolver = resolve.dns_resolver()
answers = resolver.query(random_subdomain, 'A')
# 如果查询随机域名A记录出错 说明不存在随机子域的A记录 即没有开启泛解析
except Exception as e:
logger.log('DEBUG', e)
logger.log('INFOR', f'{domain}没有使用泛解析')
return False, None, None
ttl = answers.ttl
name = answers.name
ips = {item.address for item in answers}
logger.log('ALERT', f'{domain}使用了泛解析')
logger.log('ALERT', f'{random_subdomain} 解析到域名: {name} '
f'IP: {ips} TTL: {ttl}')
return True, ips, ttl
def wildcard_by_compare(ips, ttl, wildcard_ips, wildcard_ttl):
"""
通过与泛解析返回的IP集合和返回的TTL值进行对比判断发现的子域是否是泛解析子域
:param set ips: 子域A记录查询出的IP集合
:param int ttl: 子域A记录查询出的TTL整型值
:param set wildcard_ips: 泛解析的IP集合
:param int wildcard_ttl: 泛解析的TTL整型值
:return: 判断结果
:rtype bool
"""
# 参考:http://sh3ll.me/archives/201704041222.txt
if not ips.issubset(wildcard_ips):
return False
if ttl != wildcard_ttl and ttl % 60 == 0 and wildcard_ttl % 60 == 0:
return False
return True
def wildcard_by_times(ips, ips_times):
"""
对ips出现次数进行判断泛解析
:param set ips: 发现子域的IP集合
:param ips_times: 子域IP集合出现次数统计字典
:return: 判断结果
"""
times = ips_times.get(str(ips))
if times > config.ips_appear_maximum:
return True
return False
def gen_fuzz_domains(domain, rule):
"""
生成fuzz模式下即将用于爆破的子域集合
:param str domain: 待爆破的主域
:param str rule: 用于爆破的正则规则
:return: 用于爆破的子域集合
"""
domains = set()
if '[fuzz]' not in domain:
logger.log('FATAL', f'没有指定fuzz位置')
return domains
if not rule:
logger.log('FATAL', f'没有指定fuzz规则')
return domains
fuzz_count = exrex.count(rule)
if fuzz_count > 2000000:
logger.log('FATAL', f'fuzz规则范围太大:{fuzz_count} > 2000000')
return domains
logger.log('INFOR', f'fuzz字典大小:{fuzz_count}')
for i in range(3):
random_domain = domain.replace('[fuzz]', exrex.getone(rule))
logger.log('ALERT', f'请注意检查随机生成的{random_domain}是否正确')
logger.log('ALERT', f'你有6秒检查时间退出使用`CTRL+C`')
try:
time.sleep(6)
except KeyboardInterrupt:
logger.log('INFOR', '爆破终止')
exit(0)
parts = domain.split('[fuzz]')
for fuzz in exrex.generate(rule):
fuzz_domain = parts[0] + fuzz + parts[1]
domains.add(fuzz_domain)
return domains
def gen_brute_domains(domain, path):
"""
生成基于字典爆破的子域数据
:param str domain: 待爆破的主域
:param str path: 字典路径
:return: 用于爆破的子域集合
"""
domains = set()
with open(path, encoding='utf-8', errors='ignore') as file:
for line in file:
brute_domain = line.strip() + '.' + domain
domains.add(brute_domain)
logger.log('INFOR', f'爆破字典大小:{len(domains)}')
return domains
class AIOBrute(Module):
"""
OneForAll多进程多协程异步子域爆破模块
Example
python3 aiobrute.py --target subdomain.com run
python3 aiobrute.py --target ./subdomains.txt run
python3 aiobrute.py --target example.com --process 4 --coroutine 64 run
python3 aiobrute.py --target example.com --wordlist subnames.txt run
python3 aiobrute.py --target example.com --recursive True --depth 2 run
python3 aiobrute.py --target m.[fuzz].bz --fuzz True --rule '[a-z]' run
Note:
参数segment的设置受CPU性能,网络带宽,运营商限制等限制,默认500个子域为任务组,
当你的环境不受以上因素影响,当前爆破速度较慢,那么强烈建议根据字典大小调整大小:
十万字典建议设置为5000,百万字典设置为50000
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
参数path默认None使用OneForAll结果目录自动生成路径
:param str target: 单个域名或者每行一个域名的文件路径
:param int process: 爆破的进程数(默认CPU核心数)
:param int coroutine: 每个爆破进程下的协程数(默认64)
:param str wordlist: 指定爆破所使用的字典路径(默认使用config.py配置)
:param bool recursive: 是否使用递归爆破(默认False)
:param int depth: 递归爆破的深度(默认2)
:param str namelist: 指定递归爆破所使用的字典路径(默认使用config.py配置)
:param bool fuzz: 是否使用fuzz模式进行爆破(默认False,开启须指定fuzz正则规则)
:param str rule: fuzz模式使用的正则规则(默认使用config.py配置)
:param bool export: 是否导出爆破结果(默认True)
:param bool valid: 只导出有效的子域结果(默认False)
:param str format: 导出格式(默认csv)
:param str path: 导出路径(默认None)
:param bool show: 终端显示导出数据(默认False)
"""
def __init__(self, target, process=None, coroutine=None, wordlist=None,
recursive=False, depth=None, namelist=None, fuzz=False,
rule=None, export=True, valid=None, format='csv', path=None,
show=False):
Module.__init__(self)
self.domains = set()
self.domain = str()
self.module = 'Brute'
self.source = 'AIOBrute'
self.target = target
self.process = process or utils.get_process_num()
self.coroutine = coroutine or utils.get_coroutine_num()
self.wordlist = wordlist or config.brute_wordlist_path
self.recursive_brute = recursive or config.enable_recursive_brute
self.recursive_depth = depth or config.brute_recursive_depth
self.recursive_namelist = namelist or config.recursive_namelist_path
self.fuzz = fuzz or config.enable_fuzz
self.rule = rule or config.fuzz_rule
self.export = export
self.valid = valid
self.format = format
self.path = path
self.show = show
self.nameservers = config.resolver_nameservers
self.ips_times = dict() # IP集合出现次数
self.enable_wildcard = False # 当前域名是否使用泛解析
self.wildcard_check = config.enable_wildcard_check
self.wildcard_deal = config.enable_wildcard_deal
self.wildcard_ips = set() # 泛解析IP集合
self.wildcard_ttl = int() # 泛解析TTL整型值
def gen_tasks(self, domain):
# 如果domain不是self.subdomain,而是self.domain的子域 生成递归爆破字典
if self.domain != domain:
logger.log('INFOR', f'使用{self.recursive_namelist}字典')
domains = gen_brute_domains(domain, self.recursive_namelist)
elif self.fuzz and self.rule: # 开启fuzz模式并指定了fuzz正则规则
logger.log('INFOR', f'正在生成{domain}的fuzz字典')
domains = gen_fuzz_domains(domain, self.rule)
else:
logger.log('INFOR', f'使用{self.wordlist}字典')
domains = gen_brute_domains(domain, self.wordlist)
domains = list(domains)
return domains
def deal_results(self, results):
for result in results:
hostname, answer = result
if answer is None:
continue
if isinstance(answer, Exception):
# logger.log('DEBUG', f'爆破{subdomain}时出错 {str(answers)}')
continue
name, alias, ips = answer
if name.endswith('.'):
name = name[0:-1]
# 取值 如果是首次出现的IP集合 出现次数先赋值0
value = self.ips_times.setdefault(str(ips), 0)
self.ips_times[str(ips)] = value + 1
# 目前域名开启了泛解析
if self.enable_wildcard and self.wildcard_deal:
# 通过对比查询的子域和响应的子域来判断真实子域
# 去掉解析到CDN的情况
if 'cdn' in name or 'waf' in name:
continue
if not name.endswith(self.domain):
continue
# 通过对比解析到的IP集合的次数来判断真实子域
if wildcard_by_times(ips, self.ips_times):
continue
# 只添加没有出现过的子域
if hostname not in self.subdomains:
logger.log('INFOR', f'发现{self.domain}的子域: {hostname} '
f'解析到: {name} IP: {ips}')
self.subdomains.add(hostname)
self.records[hostname] = ','.join(ips)
async def main(self, domain, rx_queue):
if not self.fuzz: # fuzz模式不探测域名是否使用泛解析
if self.wildcard_check:
self.enable_wildcard, self.wildcard_ips, self.wildcard_ttl \
= detect_wildcard(domain)
tasks = self.gen_tasks(domain)
logger.log('INFOR', f'正在爆破{domain}的域名')
results = await resolve.aio_resolve(tasks, self.process, self.coroutine)
self.deal_results(results)
self.save_json()
self.gen_result()
rx_queue.put(self.results)
def run(self, rx_queue=None):
if rx_queue is None:
rx_queue = queue.Queue()
self.domains = utils.get_domains(self.target)
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
for self.domain in self.domains:
start = time.time()
db = Database()
db.create_table(self.domain)
logger.log('INFOR', f'开始执行{self.source}模块爆破域名{self.domain}')
logger.log('INFOR', f'使用{self.process}进程乘{self.coroutine}协程')
# fuzz模式不使用递归爆破
if self.recursive_brute and not self.fuzz:
logger.log('INFOR', f'开始递归爆破{self.domain}的第1层子域')
loop.run_until_complete(self.main(self.domain, rx_queue))
# 递归爆破下一层的子域
# fuzz模式不使用递归爆破
if self.recursive_brute and not self.fuzz:
for layer_num in range(1, self.recursive_depth):
# 之前已经做过1层子域爆破 当前实际递归层数是layer+1
logger.log('INFOR', f'开始递归爆破{self.domain}'
f'{layer_num + 1}层子域')
for subdomain in self.subdomains.copy():
# 进行下一层子域爆破的限制条件
if subdomain.count('.') - self.domain.count('.') \
== layer_num:
loop.run_until_complete(self.main(subdomain,
rx_queue))
# 队列不空就一直取数据存数据库
while not rx_queue.empty():
results = rx_queue.get()
# 将结果存入数据库中
db.save_db(self.domain, results, self.source)
end = time.time()
self.elapse = round(end - start, 1)
logger.log('INFOR', f'结束执行{self.source}模块爆破域名{self.domain}')
length = len(self.subdomains)
logger.log('INFOR', f'{self.source}模块耗时{self.elapse}'
f'发现{self.domain}的域名{length}')
logger.log('DEBUG', f'{self.source}模块发现{self.domain}的域名:\n'
f'{self.subdomains}')
if not self.path:
name = f'{self.domain}_brute_result.{self.format}'
self.path = config.result_save_dir.joinpath(name)
# 数据库导出
if self.export:
dbexport.export(self.domain,
valid=self.valid,
path=self.path,
format=self.format,
show=self.show)
if __name__ == '__main__':
fire.Fire(AIOBrute)
# domain = 'example.com'
# result = queue.Queue()
# brute = AIOBrute(domain)
# brute.run(result)
+613
View File
@@ -0,0 +1,613 @@
#!/usr/bin/python3
# coding=utf-8
"""
OneForAll子域爆破模块
:copyright: Copyright (c) 2019, Jing Ling. All rights reserved.
:license: GNU General Public License v3.0, see LICENSE for more details.
"""
import json
import time
import queue
import asyncio
import random
import secrets
import platform
import subprocess
import exrex
import fire
import config
import dbexport
from common import resolve, utils
from common.module import Module
from common.database import Database
from config import logger
def detect_wildcard(domain):
"""
探测域名是否使用泛解析
:param str domain: 域名
:return: 如果没有使用泛解析返回False 反之返回泛解析的IP集合和ttl整型值
"""
logger.log('INFOR', f'正在探测{domain}是否使用泛解析')
token = secrets.token_hex(4)
random_subdomain = f'{token}.{domain}'
resolver = resolve.dns_resolver()
try:
answer = resolver.query(random_subdomain, 'A')
# 如果查询随机域名A记录出错 说明不存在随机子域的A记录 即没有开启泛解析
except Exception as e:
logger.log('DEBUG', e.args)
logger.log('INFOR', f'{domain}没有使用泛解析')
return False
ttl = answer.ttl
name = answer.name
ips = {item.address for item in answer}
logger.log('ALERT', f'{domain}使用了泛解析')
logger.log('ALERT', f'{random_subdomain} 解析到域名: {name} '
f'IP: {ips} TTL: {ttl}')
return True
def gen_fuzz_subdomains(expression, rule):
"""
生成基于fuzz模式的爆破子域
:param str expression: 子域域名生成表达式
:param str rule: 生成子域所需的正则规则
:return: 用于爆破的子域
"""
subdomains = list()
fuzz_count = exrex.count(rule)
if fuzz_count > 10000000:
logger.log('ALERT', f'请注意该规则生成的字典太大:{fuzz_count} > 10000000')
logger.log('DEBUG', f'fuzz模式下生成的字典大小:{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'请注意检查基于fuzz模式生成的{random_domain}是否正确')
return subdomains
def gen_domains(iterable, place):
subdomains = set()
for _ in range(place.count('*')):
for item in iterable:
subdomain = place.replace('*', item)
subdomains.add(subdomain)
return subdomains
def gen_word_subdomains(expression, path):
"""
生成基于word模式的爆破子域
:param str expression: 子域域名生成表达式
:param str path: 字典路径
:return: 用于爆破的子域
"""
subdomains = list()
with open(path, encoding='utf-8', errors='ignore') as fd:
for line in fd:
word = line.strip().lower()
if not word.isalnum():
continue
if word.endswith('.'):
word = word[:-1]
subdomain = expression.replace('*', word)
subdomains.append(subdomain)
random_domain = random.choice(subdomains)
logger.log('DEBUG', f'fuzz模式下生成的字典大小:{len(subdomains)}')
logger.log('ALERT', f'请注意检查基于word模式生成的{random_domain}是否正确')
return subdomains
def query_domain_ns_a(ns_list):
if not isinstance(ns_list, list):
return list()
ns_ip_list = []
resolver = resolve.dns_resolver()
for ns in ns_list:
try:
answer = resolver.query(ns, 'A')
except Exception as e:
logger.log('ERROR', e.args)
logger.log('ERROR', f'查询权威DNS名称服务器{ns}的A记录出错')
continue
if answer:
for item in answer:
ns_ip_list.append(item.address)
logger.log('INFOR', f'权威DNS名称服务器对应A记录 {ns_ip_list}')
return ns_ip_list
def query_domain_ns(domain):
resolver = resolve.dns_resolver()
try:
answer = resolver.query(domain, 'NS')
except Exception as e:
logger.log('ERROR', e.args)
logger.log('ERROR', f'查询{domain}的NS记录出错')
return list()
ns = [item.to_text() for item in answer]
logger.log('INFOR', f'{domain}的权威DNS名称服务器 {ns}')
return ns
def get_wildcard_record(domain, authoritative_ns):
if not authoritative_ns:
return list(), int()
resolver = resolve.dns_resolver()
resolver.nameservers = authoritative_ns
token = secrets.token_hex(4)
random_subdomain = f'{token}.{domain}'
logger.log('INFOR', f'查询{random_subdomain}在权威DNS名称服务器的泛解析记录')
try:
answer = resolver.query(random_subdomain, 'A')
except Exception as e:
logger.log('ERROR', e.args)
logger.log('ERROR', f'查询{random_subdomain}在权威DNS名称服务器泛解析记录出错')
return None, None
name = answer.name
ips = {item.address for item in answer}
ttl = answer.ttl
logger.log('INFOR', f'{random_subdomain} 在权威DNS上解析到域名: {name} '
f'IP: {ips} TTL: {ttl}')
return ips, ttl
def get_nameservers_path(enable_wildcard, ns_ip_list):
path = config.brute_nameservers_path
if not enable_wildcard:
return path
if not ns_ip_list:
return path
path = config.authoritative_dns_path
ns_data = '\n'.join(ns_ip_list)
utils.save_data(path, ns_data)
return path
def get_massdns_path(massdns_dir):
path = config.brute_massdns_path
if path:
return path
system = platform.system().lower()
machine = platform.machine().lower()
name = f'massdns_{system}_{machine}'
if system == 'windows':
name = name + '.exe'
if machine == 'amd64':
massdns_dir = massdns_dir.joinpath('windows', 'x64')
else:
massdns_dir = massdns_dir.joinpath('windows', 'x84')
path = massdns_dir.joinpath(name)
if not path.exists():
logger.log('FATAL', '暂无该系统平台及架构的massdns')
logger.log('INFOR', '请尝试自行编译massdns并在配置里指定路径')
exit(0)
return path
def check_dict():
if not config.enable_check_dict:
return
sec = config.check_time
logger.log('ALERT', f'你有{sec}秒时间检查爆破配置是否正确')
logger.log('ALERT', f'退出爆破请使用`Ctrl+C`')
try:
time.sleep(sec)
except KeyboardInterrupt:
logger.log('INFOR', '爆破配置有误退出爆破')
exit(0)
def do_brute(massdns_path, dict_path, ns_path, output_path, log_path,
query_type='A', process_num=1, concurrent_num=10000,
quiet_mode=False):
quiet = ''
if quiet_mode:
quiet = '--quiet'
status_format = config.brute_status_format
socket_num = config.brute_socket_num
resolve_num = config.brute_resolve_num
#
cmd = f'{massdns_path} {quiet} --status-format {status_format} ' \
f'--processes {process_num} --socket-count {socket_num} ' \
f'--hashmap-size {concurrent_num} --resolvers {ns_path} ' \
f'--resolve-count {resolve_num} --type {query_type} ' \
f'--flush --output J --outfile {output_path} ' \
f'--error-log {log_path} {dict_path}'
logger.log('INFOR', f'执行命令 {cmd}')
subprocess.run(args=cmd, shell=False)
def read_result(result_path):
result = list()
with open(result_path) as fd:
for line in fd:
line = line.strip()
try:
record = json.loads(line)
except Exception as e:
logger.log('ERROR', e.args)
logger.log('ERROR', f'解析行{line}出错跳过解析该行')
continue
result.append(record)
return result
def deal_result(result_list):
logger.log('INFOR', f'正在处理解析结果')
records = dict() # 用来记录域名解析数据
times = dict() # 用来统计IP出现次数
for items in result_list:
record = dict()
qname = items.get('name')[:-1] # 去出最右边的`.`点号
record['resolver'] = items.get('resolver')
status = items.get('status')
record['reason'] = status
records[qname] = record
if status != 'NOERROR':
record['reason'] = status
record['resolve'] = 0
record['alive'] = 0
records[qname] = record
continue
data = items.get('data')
if 'answers' not in data:
record['reason'] = 'NOANSWER'
record['resolve'] = 0
record['alive'] = 0
records[qname] = record
continue
answers = data.get('answers')
flag = False
cname = list()
ips = list()
public = list()
ttl = list()
for answer in answers:
if answer.get('type') == 'A':
flag = True
ttl.append(answer.get('ttl'))
cname.append(answer.get('name')[:-1]) # 去出最右边的`.`点号
ip = answer.get('data')
ips.append(ip)
public.append(utils.ip_is_public(ip))
record['ttl'] = ttl
record['cname'] = cname
record['content'] = ips
record['public'] = public
records[qname] = record
# 取值 如果是首次出现的IP集合 出现次数先赋值0
value = times.setdefault(ip, 0)
times[ip] = value + 1
if not flag:
record['reason'] = 'NOA'
record['resolve'] = 0
record['alive'] = 0
records[qname] = record
return records, times
def add_times(records, ip_times):
for name, record in records.items():
times = list()
ips = record.get('content')
if not ips:
continue
for ip in ips:
times.append(ip_times.get(ip))
record['times'] = times
records[name] = record
return records
def check_validity(records, ip_times, wildcard_ips, wildcard_ttl):
valid_subdomains = list()
for name, record in records.items():
if record.get('resolve') is None:
ips = record['content']
ttl = record['ttl']
status, reason = is_valid_subdomain(ips, ttl, ip_times,
wildcard_ips, wildcard_ttl)
record['resolve'], record['reason'] = status, reason
records[name] = record
# 在打了有效性标签后 暂且把除无效子域的子域都认为是有效子域
if record.get('resolve') != 0:
valid_subdomains.append(name)
return records, valid_subdomains
def check_by_compare(ips, ttl, wildcard_ips, wildcard_ttl):
"""
通过与泛解析返回的IP集合和返回的TTL值进行对比判断发现的子域是否是泛解析子域
:param set ips: 子域A记录查询出的IP集合
:param int ttl: 子域A记录查询出的TTL
:param set wildcard_ips: 泛解析的IP集合
:param int wildcard_ttl: 泛解析的TTL
:return: 判断结果
"""
# 参考:http://sh3ll.me/archives/201704041222.txt
if not ips.intersection(wildcard_ips):
return False # 子域IP集合与泛解析IP集合无任何交集则不是泛解析
if ttl != wildcard_ttl and ttl % 60 == 0 and wildcard_ttl % 60 == 0:
return False
return True
def check_ip_times(ips, times):
"""
根据ip出现次数判断是否为泛解析
:param set ips: 子域IP集合
:param times: 子域IP出现次数统计字典
:return: 判断结果
"""
for ip in ips:
num = times.get(ip)
if num > config.ip_appear_maximum:
# 解析得到IPS集合有任意IP出现次数大于指定值都标记为非法(泛解析)子域
return True
return False
def is_valid_subdomain(ips, ttl, times, wildcard_ips, wildcard_ttl):
ip_blacklist = config.brute_ip_blacklist
ips = set(ips)
if ips.intersection(ip_blacklist): # 解析ip与黑名单ip有交集则标记为非法子域
return 0, 'IP blacklist'
if len(set(ttl)) == 1: # 只有一个相同TTL才进行对比
ttl = ttl[0]
if all([wildcard_ttl, wildcard_ttl]): # 有泛解析记录才进行对比
if check_by_compare(ips, ttl, wildcard_ips, wildcard_ttl):
return 0, 'IP wildcard '
if check_ip_times(ips, times):
return 0, 'IP exceeded'
return 1, None
def save_brute_dict(path, data):
if not utils.save_data(path, data):
logger.log('FATAL', '保存生成的字典出错')
exit(1)
def delete_file(dict_path, output_path):
if config.delete_generated_dict:
dict_path.unlink()
if config.delete_massdns_result:
output_path.unlink()
class Brute(Module):
"""
OneForAll子域爆破模块
Example
brute.py --target domain.com --word True run
brute.py --target ./domains.txt --word True run
brute.py --target domain.com --word True --process 1 run
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
Note:
参数valid可选值1,0,None,分别表示导出有效,无效,全部子域
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
参数path默认None使用OneForAll结果目录自动生成路径
:param str target: 单个域名或者每行一个域名的文件路径
:param int process: 爆破进程数(默认1)
:param int concurrent: 并发爆破数量(默认10000)
:param bool word: 是否使用word模式进行爆破(默认False)
:param str wordlist: word模式爆破使用的字典路径(默认使用config.py配置)
:param bool recursive: 是否使用递归进行爆破(默认False)
:param int depth: 递归爆破的深度(默认2)
:param str nextlist: 递归爆破所使用的字典路径(默认使用config.py配置)
:param bool fuzz: 是否使用fuzz模式进行爆破(默认False)
:param str place: 指定爆破位置(开启fuzz模式时必需指定此参数)
:param str rule: 指定fuzz模式爆破使用的正则规则(开启fuzz模式时必需指定此参数)
:param bool export: 是否导出爆破结果(默认True)
:param bool valid: 只导出有效的子域结果(默认False)
:param str format: 结果导出格式(默认csv)
:param str path: 结果导出路径(默认None)
"""
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, valid=True,
format='csv', path=None):
Module.__init__(self)
self.module = 'Brute'
self.source = 'Brute'
self.target = target
self.process_num = process or utils.get_process_num()
self.concurrent_num = concurrent or config.brute_concurrent_num
self.word = word
self.wordlist = wordlist or config.brute_wordlist_path
self.recursive_brute = recursive or config.enable_recursive_brute
self.recursive_depth = depth or config.brute_recursive_depth
self.recursive_nextlist = nextlist or config.recursive_nextlist_path
self.fuzz = fuzz or config.enable_fuzz
self.place = place or config.fuzz_place
self.rule = rule or config.fuzz_rule
self.export = export
self.valid = valid
self.format = format
self.path = path
self.bulk = False # 是否是批量爆破场景
self.domains = list() # 待爆破的所有域名集合
self.domain = str() # 当前正在进行爆破的域名
self.ips_times = dict() # IP集合出现次数
self.enable_wildcard = False # 当前域名是否使用泛解析
self.wildcard_check = config.enable_wildcard_check
self.wildcard_deal = config.enable_wildcard_deal
def gen_brute_dict(self, domain):
logger.log('INFOR', f'正在为{domain}生成爆破字典')
dict_set = set()
# 如果domain不是self.subdomain 而是self.domain的子域则生成递归爆破字典
if self.place is None:
self.place = '*.' + domain
wordlist = self.wordlist
main_domain = self.register(domain)
if domain != main_domain:
wordlist = self.recursive_nextlist
if self.word:
word_subdomains = gen_word_subdomains(self.place, wordlist)
# set可以合并list
dict_set = dict_set.union(word_subdomains)
if self.fuzz:
fuzz_subdomains = gen_fuzz_subdomains(self.place, self.rule)
dict_set = dict_set.union(fuzz_subdomains)
# logger.log('INFOR', f'正在去重爆破字典')
# dict_set = utils.uniq_dict_list(dict_set)
count = len(dict_set)
logger.log('INFOR', f'生成的爆破字典大小为{count}')
if count > 10000000:
logger.log('ALERT', f'注意生成的爆破字典太大:{count} > 10000000')
return dict_set
def check_brute_params(self):
if len(self.domains) > 1:
self.bulk = True
if self.fuzz:
if not (self.word or self.fuzz):
logger.log('FATAL', f'请至少指定一种爆破模式')
exit(1)
if self.place is None or self.rule is None:
logger.log('FATAL', f'没有指定fuzz位置或规则')
exit(1)
if self.bulk:
logger.log('FATAL', f'批量爆破的场景下不能使用fuzz模式')
exit(1)
if self.recursive_brute:
logger.log('FATAL', f'使用fuzz模式下不能使用递归爆破')
exit(1)
fuzz_count = self.place.count('*')
if fuzz_count < 1:
logger.log('FATAL', f'没有指定fuzz位置')
exit(1)
if fuzz_count > 1:
logger.log('FATAL', f'只能指定1个fuzz位置')
exit(1)
if self.domain not in self.place:
logger.log('FATAL', f'指定fuzz的域名有误')
exit(1)
def main(self, domain):
start = time.time()
logger.log('INFOR', f'正在爆破域名{domain}')
massdns_dir = config.third_party_dir.joinpath('massdns')
result_dir = config.result_save_dir
temp_dir = result_dir.joinpath('temp')
utils.check_dir(temp_dir)
massdns_path = get_massdns_path(massdns_dir)
timestring = utils.get_timestring()
self.enable_wildcard = detect_wildcard(domain)
wildcard_ips = list() # 泛解析IP列表
wildcard_ttl = int() # 泛解析TTL整型值
ns_ip_list = list() # DNS权威名称服务器对应A记录列表
if self.enable_wildcard:
ns_list = query_domain_ns(self.domain)
ns_ip_list = query_domain_ns_a(ns_list)
wildcard_ips, wildcard_ttl = get_wildcard_record(domain, ns_ip_list)
ns_path = get_nameservers_path(self.enable_wildcard, ns_ip_list)
dict_set = self.gen_brute_dict(domain)
self.subdomains = dict_set
dict_data = '\n'.join(dict_set)
dict_name = f'generated_subdomains_{domain}_{timestring}.txt'
dict_path = temp_dir.joinpath(dict_name)
save_brute_dict(dict_path, dict_data)
output_name = f'resolved_result_{domain}_{timestring}.json'
output_path = temp_dir.joinpath(output_name)
log_path = result_dir.joinpath('massdns.log')
check_dict()
logger.log('INFOR', f'开始执行massdns')
do_brute(massdns_path, dict_path, ns_path, output_path, log_path,
process_num=self.process_num,
concurrent_num=self.concurrent_num)
logger.log('INFOR', f'结束执行massdns')
result_data = read_result(output_path)
delete_file(dict_path, output_path)
resolved_records, ip_times = deal_result(result_data)
added_records = add_times(resolved_records, ip_times)
checked_records, valid_subdomains = check_validity(added_records,
ip_times,
wildcard_ips,
wildcard_ttl)
self.records = checked_records
end = time.time()
self.elapse = round(end - start, 1)
logger.log('INFOR', f'{self.source}模块耗时{self.elapse}'
f'发现{domain}的子域{len(valid_subdomains)}')
logger.log('DEBUG', f'{self.source}模块发现{domain}的子域:\n'
f'{valid_subdomains}')
self.gen_result(brute=len(self.subdomains), valid=len(valid_subdomains))
self.save_db()
return valid_subdomains
def run(self):
logger.log('INFOR', f'开始执行{self.source}模块')
self.domains = utils.get_domains(self.target)
all_subdomains = list()
for self.domain in self.domains:
self.check_brute_params()
if self.recursive_brute:
logger.log('INFOR', f'开始递归爆破{self.domain}的第1层子域')
valid_subdomains = self.main(self.domain)
all_subdomains.extend(valid_subdomains)
# 递归爆破下一层的子域
# fuzz模式不使用递归爆破
if self.recursive_brute:
for layer_num in range(1, self.recursive_depth):
# 之前已经做过1层子域爆破 当前实际递归层数是layer+1
logger.log('INFOR', f'开始递归爆破{self.domain}'
f'{layer_num + 1}层子域')
for subdomain in all_subdomains:
self.place = '*.' + subdomain
# 进行下一层子域爆破的限制条件
num = subdomain.count('.') - self.domain.count('.')
if num == layer_num:
valid_subdomains = self.main(subdomain)
all_subdomains.extend(valid_subdomains)
logger.log('INFOR', f'结束执行{self.source}模块爆破域名{self.domain}')
if not self.path:
name = f'{self.domain}_brute_result.{self.format}'
self.path = config.result_save_dir.joinpath(name)
# 数据库导出
if self.export:
dbexport.export(self.domain,
valid=self.valid,
limit='resolve',
path=self.path,
format=self.format)
if __name__ == '__main__':
fire.Fire(Brute)
# domain = 'example.com'
# result = queue.Queue()
# brute = AIOBrute(domain)
# brute.run(result)
+34 -18
View File
@@ -58,24 +58,32 @@ class Database(object):
self.query(f'create table "{table_name}" ('
f'id integer primary key,'
f'type text,'
f'valid int,'
f'alive int,'
f'request int,'
f'resolve int,'
f'new int,'
f'url text,'
f'subdomain text,'
f'port int,'
f'level int,'
f'cname text,'
f'content text,'
f'public int,'
f'port int,'
f'status int,'
f'reason text,'
f'title text,'
f'banner text,'
f'header text,'
f'response text,'
f'times text,'
f'ttl text,'
f'resolver text,'
f'module text,'
f'source text,'
f'elapse float,'
f'count int)')
f'find int,'
f'brute int,'
f'valid int)')
def save_db(self, table_name, results, module_name=None):
"""
@@ -92,15 +100,17 @@ class Database(object):
try:
self.conn.bulk_query(
f'insert into "{table_name}" ('
f'id, type, valid, new, url, subdomain, port, level, content,'
f'public, status, reason, title, banner, header, response,'
f'module, source, elapse, count)'
f'values (:id, :type, :valid, :new, :url, :subdomain,'
f':port, :level, :content, :public, :status, :reason,'
f':title, :banner, :header, :response, :module, :source,'
f':elapse, :count)', results)
f'id, type, alive, resolve, request, new, url, subdomain,'
f'port, level, cname, content, public, status, reason,'
f'title, banner, header, response, times, ttl, resolver,'
f'module, source, elapse, find, brute, valid) '
f'values (:id, :type, :alive, :resolve, :request, :new,'
f':url, :subdomain, :port, :level, :cname, :content,'
f':public, :status, :reason, :title, :banner, :header,'
f':response, :times, :ttl, :resolver, :module, :source,'
f':elapse, :find, :brute, :valid)', results)
except Exception as e:
logger.log('ERROR', e.args)
logger.log('ERROR', e)
def exist_table(self, table_name):
"""
@@ -187,7 +197,7 @@ class Database(object):
table_name = table_name.replace('.', '_')
logger.log('TRACE', f'正在去除{table_name}表中的无效子域')
self.query(f'delete from "{table_name}" where '
f'subdomain is null or valid == 0')
f'subdomain is null or resolve == 0')
def deal_table(self, deal_table_name, backup_table_name):
"""
@@ -210,19 +220,25 @@ class Database(object):
logger.log('TRACE', f'获取{table_name}表中的所有数据')
return self.query(f'select * from "{table_name}"')
def export_data(self, table_name, valid):
def export_data(self, table_name, valid, limit):
"""
获取表中的部分数据
:param str table_name: 表名
:param any valid: 有效性
:param str limit: 限制字段
"""
table_name = table_name.replace('.', '_')
query = f'select id, type, valid, new, url, subdomain, level, ' \
f'content, public, port, status, reason, title, banner ' \
f'from "{table_name}"'
if valid:
where = f' where valid = 1'
query = f'select id, type, new, alive, request, resolve, url, ' \
f'subdomain, level, cname, content, public, port, status, ' \
f'reason, title, banner, times, ttl, resolver, module, ' \
f'source, elapse, find, brute, valid from "{table_name}"'
if valid and limit:
if limit in ['resolve', 'request']:
where = f' where {limit} = 1'
query += where
else:
where = f' where alive = 1'
query += where
logger.log('TRACE', f'获取{table_name}表中的所有数据')
return self.query(query)
+1 -1
View File
@@ -18,7 +18,7 @@ class Lookup(Module):
if answer is None:
return None
for item in answer:
record = str(item)
record = item.to_text()
subdomains = utils.match_subdomain(self.domain, record)
self.subdomains = self.subdomains.union(subdomains)
self.gen_record(subdomains, record)
+55 -15
View File
@@ -236,7 +236,7 @@ class Module(object):
'name': self.module,
'source': self.source,
'elapse': self.elapse,
'count': len(self.subdomains),
'find': len(self.subdomains),
'subdomains': list(self.subdomains),
'records': self.records}
json.dump(result, file, ensure_ascii=False, indent=4)
@@ -246,21 +246,27 @@ class Module(object):
"""
生成记录字典
"""
item = dict()
item['content'] = record
for subdomain in subdomains:
self.records[subdomain] = record
self.records[subdomain] = item
def gen_result(self):
def gen_result(self, find=0, brute=None, valid=0):
"""
生成结果
"""
logger.log('DEBUG', f'正在生成最终结果')
if not len(self.subdomains): # 该模块一个子域都没有发现的情况
result = {'id': None,
'type': self.type,
'valid': None,
'alive': None,
'request': None,
'resolve': None,
'new': None,
'url': None,
'subdomain': None,
'level': None,
'cname': None,
'content': None,
'public': None,
'port': None,
@@ -270,39 +276,72 @@ class Module(object):
'banner': None,
'header': None,
'response': None,
'times': None,
'ttl': None,
'resolver': None,
'module': self.module,
'source': self.source,
'elapse': self.elapse,
'count': 0}
'find': find,
'brute': brute,
'valid': valid}
self.results.append(result)
else:
for subdomain in self.subdomains:
valid = None
if self.type != 'A': # 不是利用的DNS记录的A记录查询子域默认都有效
valid = 1
url = 'http://' + subdomain
level = subdomain.count('.') - self.domain.count('.')
content = self.records.get(subdomain)
record = self.records.get(subdomain)
if record is None:
record = dict()
resolve = record.get('resolve')
request = record.get('request')
alive = record.get('alive')
if self.type != 'A': # 不是利用的DNS记录的A记录查询子域默认都有效
resolve = 1
request = 1
alive = 1
reason = record.get('reason')
resolver = record.get('resolver')
cname = record.get('cname')
content = record.get('content')
times = record.get('times')
ttl = record.get('ttl')
public = record.get('public')
if isinstance(cname, list):
cname = ','.join(cname)
content = ','.join(content)
times = ','.join([str(num) for num in times])
ttl = ','.join([str(num) for num in ttl])
public = ','.join([str(num) for num in public])
result = {'id': None,
'type': self.type,
'valid': valid,
'alive': alive,
'request': request,
'resolve': resolve,
'new': None,
'url': url,
'subdomain': subdomain,
'level': level,
'cname': cname,
'content': content,
'public': None,
'port': None,
'public': public,
'port': 80,
'status': None,
'reason': None,
'reason': reason,
'title': None,
'banner': None,
'module': self.module,
'header': None,
'response': None,
'times': times,
'ttl': ttl,
'resolver': resolver,
'module': self.module,
'source': self.source,
'elapse': self.elapse,
'count': len(self.subdomains)}
'find': find,
'brute': brute,
'valid': valid,
}
self.results.append(result)
def save_db(self):
@@ -310,6 +349,7 @@ class Module(object):
将模块结果存入数据库中
"""
logger.log('DEBUG', f'正在将结果存入到数据库')
lock.acquire()
db = Database()
db.create_table(self.domain)
+11 -9
View File
@@ -43,10 +43,9 @@ def gen_req_data(data, ports):
logger.log('INFOR', f'正在生成请求地址')
new_data = []
for data in data:
valid = data.get('valid')
# 无效(0)和有效子域(1)不进行http请求探测
# 有效性待确认(None)的子域才进行http请求探测
if valid == 0 or valid == 1:
resolve = data.get('resolve')
# 解析失败(0)的子域不进行http请求探测
if resolve == 0:
continue
subdomain = data.get('subdomain')
for port in ports:
@@ -162,15 +161,18 @@ def request_callback(future, index, datas):
logger.log('TRACE', result.args)
name = utils.get_classname(result)
datas[index]['reason'] = name + ' ' + str(result)
datas[index]['valid'] = 0
datas[index]['request'] = 0
datas[index]['alive'] = 0
elif isinstance(result, tuple):
resp, text = result
datas[index]['reason'] = resp.reason
datas[index]['status'] = resp.status
if resp.status == 400 or resp.status >= 500:
datas[index]['valid'] = 0
datas[index]['request'] = 0
datas[index]['alive'] = 0
else:
datas[index]['valid'] = 1
datas[index]['request'] = 1
datas[index]['alive'] = 1
headers = resp.headers
datas[index]['banner'] = utils.get_sample_banner(headers)
datas[index]['header'] = str(dict(headers))[1:-1]
@@ -244,8 +246,8 @@ def run_request(domain, data, port):
data = loop.run_until_complete(request_coroutine)
# 在关闭事件循环前加入一小段延迟让底层连接得到关闭的缓冲时间
loop.run_until_complete(asyncio.sleep(0.25))
count = utils.count_valid(data)
logger.log('INFOR', f'经验证{domain}有效子域{count}')
count = utils.count_alive(data)
logger.log('INFOR', f'经验证{domain}存活子域{count}')
return data
+2
View File
@@ -214,6 +214,8 @@ def run_resolve(data):
loop = asyncio.get_event_loop()
asyncio.set_event_loop(loop)
need_resolve_subdomains = filter_subdomain(data)
if not need_resolve_subdomains:
return data
resolve_coroutine = run_aio_resolve(need_resolve_subdomains)
results_list = loop.run_until_complete(resolve_coroutine)
results_dict = convert_results(results_list)
+45 -16
View File
@@ -135,7 +135,11 @@ def get_domains(target):
domains.append(domain)
elif Domain(target).match():
domains = [target]
logger.log('INFOR', f'获取到{len(domains)}个域名')
count = len(domains)
if count == 0:
logger.log('FATAL', f'获取到{count}个域名')
exit(1)
logger.log('INFOR', f'获取到{count}个域名')
return domains
@@ -154,6 +158,12 @@ def get_semaphore():
return 800
def check_dir(dir_path):
if not dir_path.exists():
logger.log('INFOR', f'不存在{dir_path}目录将会新建')
dir_path.mkdir(parents=True, exist_ok=True)
def check_path(path, name, format):
"""
检查结果输出目录路径
@@ -206,7 +216,7 @@ def check_format(format, count):
def save_data(path, data):
"""
保存结果数据到文件
保存数据到文件
:param path: 保存路径
:param data: 待存数据
@@ -283,12 +293,14 @@ def remove_invalid_string(string):
def check_value(values):
for i, value in enumerate(values):
if not isinstance(values, dict):
return values
for key, value in values.items():
if value is None:
continue
if isinstance(value, str) and len(value) > 32767:
# Excel文件中单元格值长度不能超过32767
values[i] = value[:32767]
values[key] = value[:32767]
return values
@@ -301,18 +313,12 @@ def export_all(format, path, datas):
:param list datas: 待导出的结果数据
"""
format = check_format(format, len(datas))
timestamp = get_timestamp()
timestamp = get_timestring()
name = f'all_subdomain_result_{timestamp}'
path = check_path(path, name, format)
logger.log('INFOR', f'所有主域的子域结果 {path}')
row_list = list()
for row in datas:
row.pop('header')
row.pop('response')
row.pop('module')
row.pop('source')
row.pop('elapse')
row.pop('count')
keys = row.keys()
values = row.values()
if format in {'xls', 'xlsx'}:
@@ -348,6 +354,10 @@ def get_timestamp():
return int(time.time())
def get_timestring():
return time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time()))
def get_classname(classobj):
return classobj.__class__.__name__
@@ -356,8 +366,8 @@ def python_version():
return sys.version
def count_valid(data):
return len(list(filter(lambda item: item.get('valid') == 1, data)))
def count_alive(data):
return len(list(filter(lambda item: item.get('alive') == 1, data)))
def get_subdomains(data):
@@ -404,16 +414,23 @@ def check_ip_public(ip_list):
return 1
def ip_is_public(ip_str):
ip = ip_address(ip_str)
if not ip.is_global:
return 0
return 1
def get_process_num():
process_num = config.brute_process_num
if isinstance(process_num, int):
return max(1, process_num)
return min(os.cpu_count(), process_num)
else:
return os.cpu_count()
return 1
def get_coroutine_num():
coroutine_num = config.brute_coroutine_num
coroutine_num = config.resolve_coroutine_num
if isinstance(coroutine_num, int):
return max(64, coroutine_num)
elif coroutine_num is None:
@@ -434,3 +451,15 @@ def get_coroutine_num():
return 2048
else:
return 64
def uniq_dict_list(dict_list):
return list(filter(lambda name: dict_list.count(name) == 1, dict_list))
def delete_file(*paths):
for path in paths:
try:
path.unlink()
except Exception as e:
logger.log('ERROR', e.args)
+26 -8
View File
@@ -12,6 +12,7 @@ from loguru import logger
# 路径设置
relative_directory = pathlib.Path(__file__).parent # OneForAll代码相对路径
module_dir = relative_directory.joinpath('modules') # OneForAll模块目录
third_party_dir = relative_directory.joinpath('thirdparty') # 三方工具目录
data_storage_dir = relative_directory.joinpath('data') # 数据存放目录
result_save_dir = relative_directory.joinpath('results') # 结果保存目录
@@ -44,19 +45,35 @@ module_thread_timeout = 360.0 # 每个收集模块线程超时时间(默认6分
enable_brute_module = False # 使用爆破模块(默认False)
enable_wildcard_check = True # 开启泛解析检测(默认True)
enable_wildcard_deal = True # 开启泛解析处理(默认True)
# 爆破时使用的进程数(根据计算机中CPU数量情况设置 不宜大于CPU数量)
brute_process_num = None # 默认None为系统中的CPU数量
# 爆破时每个进程下的协程数(根据计算机中内存大小情况设置 默认为系统中的CPU数)
brute_coroutine_num = None # 默认None根据内存大小设置
brute_massdns_path = None # 默认None自动选择 如需填写请填写绝对路径
brute_status_format = 'ansi' # 爆破时状态输出格式(默认asni,可选json)
# 爆破时使用的进程数(根据计算机中CPU数量情况设置 不宜大于逻辑CPU数)
brute_process_num = 1 # 默认1
brute_concurrent_num = 10000 # 并发查询数量(默认10000)
brute_socket_num = 1 # 爆破时每个进程下的socket数量
brute_resolve_num = 50 # 解析失败时尝试换名称服务器重查次数
# 爆破所使用的字典路径 默认data/subdomains.txt
brute_wordlist_path = data_storage_dir.joinpath('subnames.txt')
enable_recursive_brute = False # 是否使用递归爆破(默认禁用)
brute_nameservers_path = data_storage_dir.joinpath('cn_nameservers.txt')
# 域名的权威DNS名称服务器的保存路径 当域名开启了泛解析时会使用该名称服务器来进行A记录查询
authoritative_dns_path = data_storage_dir.joinpath('authoritative_dns.txt')
enable_recursive_brute = False # 是否使用递归爆破(默认False)
brute_recursive_depth = 2 # 递归爆破深度(默认2层)
# 爆破下一层子域所使用的字典路径 默认data/next_subdomains.txt
recursive_namelist_path = data_storage_dir.joinpath('next_subnames.txt')
recursive_nextlist_path = data_storage_dir.joinpath('next_subnames.txt')
enable_check_dict = False # 是否开启字典配置检查提示(默认False)
delete_generated_dict = True # 是否删除爆破时临时生成的字典(默认True)
# 是否删除爆破时massdns输出的解析结果 (默认True)
# massdns输出的结果中包含更详细解析结果
# 注意: 当爆破的字典较大或使用递归爆破或目标域名存在泛解析时生成的文件可能会很大
delete_massdns_result = True
only_save_valid = True # 是否在处理爆破结果时只存入解析成功的子域
check_time = 10 # 检查字典配置停留时间(默认10秒)
enable_fuzz = False # 是否使用fuzz模式枚举域名
fuzz_rule = '' # fuzz域名的正则 示例:[a-z][0-9] 第一位是字母 第二位是数字
ips_appear_maximum = 10 # 同一IP集合出现次数超过10认为是泛解析
fuzz_place = None # 指定爆破的位置 指定的位置用`@`表示 示例:www.@.example.com
fuzz_rule = None # fuzz域名的正则 示例:'[a-z][0-9]' 表示第一位是字母 第二位是数字
brute_ip_blacklist = {'0.0.0.0', '0.0.0.1'} # IP黑名单 子域解析到IP黑名单则标记为非法子域
ip_appear_maximum = 100 # 多个子域解析到同一IP次数超过100次则标记为非法(泛解析)子域
# 代理设置
enable_proxy = False # 是否使用代理(全局开关)
@@ -84,6 +101,7 @@ enable_recursive_search = False # 递归搜索子域
search_recursive_times = 2 # 递归搜索层数
# DNS解析设置
resolve_coroutine_num = 64
resolver_nameservers = [
'119.29.29.29', '182.254.116.116', # DNSPod
'180.76.76.76', # Baidu DNS
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+941713 -35725
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -15,7 +15,7 @@ from common.database import Database
from config import logger
def export(table, db=None, valid=False, path=None, format='csv', show=False):
def export(table, db=None, valid=False, limit=None, path=None, format='csv', show=False):
"""
OneForAll数据库导出模块
@@ -31,13 +31,14 @@ def export(table, db=None, valid=False, path=None, format='csv', show=False):
:param str table: 要导出的表
:param str db: 要导出的数据库路径(默认为results/result.sqlite3)
:param bool valid: 只导出有效的子域结果(默认False)
:param str limit: 导出限制条件(默认None)
:param str format: 导出文件格式(默认csv)
:param str path: 导出文件路径(默认None)
:param bool show: 终端显示导出数据(默认False)
"""
database = Database(db)
rows = database.export_data(table, valid)
rows = database.export_data(table, valid, limit)
format = utils.check_format(format, len(rows))
path = utils.check_path(path, table, format)
if show:
+2 -2
View File
@@ -15,7 +15,7 @@ import dbexport
from datetime import datetime
from config import logger
from collect import Collect
from aiobrute import AIOBrute
from brute import Brute
from common import utils, resolve, request
from common.database import Database
from takeover import Takeover
@@ -176,7 +176,7 @@ class OneForAll(object):
collect.run()
if self.brute:
# 由于爆破会有大量dns解析请求 并发爆破可能会导致其他任务中的网络请求异常
brute = AIOBrute(self.domain, export=False)
brute = Brute(self.domain, word=True, export=False)
brute.run()
# 有关数据库处理