mirror of
https://github.com/shmilylty/OneForAll.git
synced 2026-10-25 00:56:14 +08:00
@@ -2,7 +2,7 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
"""
|
"""
|
||||||
OneForAll子域爆破模块
|
OneForAll subdomain brute module
|
||||||
|
|
||||||
:copyright: Copyright (c) 2019, Jing Ling. All rights reserved.
|
:copyright: Copyright (c) 2019, Jing Ling. All rights reserved.
|
||||||
:license: GNU General Public License v3.0, see LICENSE for more details.
|
:license: GNU General Public License v3.0, see LICENSE for more details.
|
||||||
@@ -30,43 +30,43 @@ from config.log import logger
|
|||||||
def do_query_a(domain, resolver):
|
def do_query_a(domain, resolver):
|
||||||
try:
|
try:
|
||||||
answer = resolver.query(domain, 'A')
|
answer = resolver.query(domain, 'A')
|
||||||
# 如果查询随机域名A记录时抛出Timeout异常则重新探测
|
# If resolve random subdomain raise timeout error, try again
|
||||||
except Timeout as e:
|
except Timeout as e:
|
||||||
logger.log('ALERT', f'探测超时重新探测中')
|
logger.log('ALERT', f'DNS resolve timeout, retrying...')
|
||||||
logger.log('DEBUG', e.args)
|
logger.log('DEBUG', e.args)
|
||||||
raise tenacity.TryAgain
|
raise tenacity.TryAgain
|
||||||
# 如果查询随机域名A记录时抛出NXDOMAIN异常
|
# If resolve random subdomain raise NXDOMAIN error
|
||||||
# 则说明不存在随机子域的A记录 即没有开启泛解析
|
# It means that there is no A record of random subdomain and not use wildcard dns record
|
||||||
except (NXDOMAIN, YXDOMAIN, NoAnswer, NoNameservers) as e:
|
except (NXDOMAIN, YXDOMAIN, NoAnswer, NoNameservers) as e:
|
||||||
logger.log('DEBUG', e.args)
|
logger.log('DEBUG', e.args)
|
||||||
logger.log('INFOR', f'{domain}没有使用泛解析')
|
logger.log('INFOR', f'{domain} seems not use wildcard dns record')
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('ALERT', f'探测{domain}是否使用泛解析出错')
|
logger.log('ALERT', f'Detect {domain} wildcard dns record error')
|
||||||
logger.log('FATAL', e.args)
|
logger.log('FATAL', e.args)
|
||||||
exit(1)
|
exit(1)
|
||||||
else:
|
else:
|
||||||
if answer.rrset is None:
|
if answer.rrset is None:
|
||||||
logger.log('ALERT', f'结果无记录重新探测中')
|
logger.log('ALERT', f'DNS resolve dont have result, retrying...')
|
||||||
raise tenacity.TryAgain
|
raise tenacity.TryAgain
|
||||||
ttl = answer.ttl
|
ttl = answer.ttl
|
||||||
name = answer.name
|
name = answer.name
|
||||||
ips = {item.address for item in answer}
|
ips = {item.address for item in answer}
|
||||||
logger.log('ALERT', f'{domain}使用了泛解析')
|
logger.log('ALERT', f'{domain} use wildcard dns record')
|
||||||
logger.log('ALERT', f'{domain} 解析到域名: {name} '
|
logger.log('ALERT', f'{domain} resolve to: {name} '
|
||||||
f'IP: {ips} TTL: {ttl}')
|
f'IP: {ips} TTL: {ttl}')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def detect_wildcard(domain, authoritative_ns):
|
def detect_wildcard(domain, authoritative_ns):
|
||||||
"""
|
"""
|
||||||
探测域名是否使用泛解析
|
Detect use wildcard dns record or not
|
||||||
|
|
||||||
:param str domain: 域名
|
:param str domain: domain
|
||||||
:param list authoritative_ns: 权威DNS
|
:param list authoritative_ns: authoritative name server
|
||||||
:return: 是否使用泛解析
|
:return bool use wildcard dns record or not
|
||||||
"""
|
"""
|
||||||
logger.log('INFOR', f'正在探测{domain}是否使用泛解析')
|
logger.log('INFOR', f'Detecting {domain} use wildcard dns record or not')
|
||||||
token = secrets.token_hex(4)
|
token = secrets.token_hex(4)
|
||||||
random_subdomain = f'{token}.{domain}'
|
random_subdomain = f'{token}.{domain}'
|
||||||
resolver = utils.dns_resolver()
|
resolver = utils.dns_resolver()
|
||||||
@@ -77,7 +77,7 @@ def detect_wildcard(domain, authoritative_ns):
|
|||||||
wildcard = do_query_a(random_subdomain, resolver)
|
wildcard = do_query_a(random_subdomain, resolver)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('DEBUG', e.args)
|
logger.log('DEBUG', e.args)
|
||||||
logger.log('ALERT', f'多次探测出错暂且认为{domain}没有使用泛解析')
|
logger.log('ALERT', f'Multiple detection errors, so temporarily {domain} does not use wildcard dns record')
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return wildcard
|
return wildcard
|
||||||
@@ -85,17 +85,17 @@ def detect_wildcard(domain, authoritative_ns):
|
|||||||
|
|
||||||
def gen_fuzz_subdomains(expression, rule):
|
def gen_fuzz_subdomains(expression, rule):
|
||||||
"""
|
"""
|
||||||
生成基于fuzz模式的爆破子域
|
Generate subdomains based on fuzz mode
|
||||||
|
|
||||||
:param str expression: 子域域名生成表达式
|
:param str expression: generate subdomains's expression
|
||||||
:param str rule: 生成子域所需的正则规则
|
:param str rule: regexp rule
|
||||||
:return: 用于爆破的子域
|
:return list subdomains: list of subdomains
|
||||||
"""
|
"""
|
||||||
subdomains = list()
|
subdomains = list()
|
||||||
fuzz_count = exrex.count(rule)
|
fuzz_count = exrex.count(rule)
|
||||||
if fuzz_count > 10000000:
|
if fuzz_count > 10000000:
|
||||||
logger.log('ALERT', f'请注意该规则生成的字典太大:{fuzz_count} > 10000000')
|
logger.log('ALERT', f'The dictionary generated by this rule is too large:{fuzz_count} > 10000000')
|
||||||
logger.log('DEBUG', f'fuzz模式下生成的字典大小:{fuzz_count}')
|
logger.log('DEBUG', f'Dictionary size based on fuzz mode: {fuzz_count}')
|
||||||
for fuzz_string in exrex.generate(rule):
|
for fuzz_string in exrex.generate(rule):
|
||||||
fuzz_string = fuzz_string.lower()
|
fuzz_string = fuzz_string.lower()
|
||||||
if not fuzz_string.isalnum():
|
if not fuzz_string.isalnum():
|
||||||
@@ -103,17 +103,17 @@ def gen_fuzz_subdomains(expression, rule):
|
|||||||
fuzz_domain = expression.replace('*', fuzz_string)
|
fuzz_domain = expression.replace('*', fuzz_string)
|
||||||
subdomains.append(fuzz_domain)
|
subdomains.append(fuzz_domain)
|
||||||
random_domain = random.choice(subdomains)
|
random_domain = random.choice(subdomains)
|
||||||
logger.log('ALERT', f'请注意检查基于fuzz模式生成的{random_domain}是否正确')
|
logger.log('ALERT', f'Please check whether {random_domain} is correct or not')
|
||||||
return subdomains
|
return subdomains
|
||||||
|
|
||||||
|
|
||||||
def gen_word_subdomains(expression, path):
|
def gen_word_subdomains(expression, path):
|
||||||
"""
|
"""
|
||||||
生成基于word模式的爆破子域
|
Generate subdomains based on word mode
|
||||||
|
|
||||||
:param str expression: 子域域名生成表达式
|
:param str expression: generate subdomains's expression
|
||||||
:param str path: 字典路径
|
:param str path: path of wordlist
|
||||||
:return: 用于爆破的子域
|
:return list subdomains: list of subdomains
|
||||||
"""
|
"""
|
||||||
subdomains = list()
|
subdomains = list()
|
||||||
with open(path, encoding='utf-8', errors='ignore') as fd:
|
with open(path, encoding='utf-8', errors='ignore') as fd:
|
||||||
@@ -126,13 +126,13 @@ def gen_word_subdomains(expression, path):
|
|||||||
subdomain = expression.replace('*', word)
|
subdomain = expression.replace('*', word)
|
||||||
subdomains.append(subdomain)
|
subdomains.append(subdomain)
|
||||||
random_domain = random.choice(subdomains)
|
random_domain = random.choice(subdomains)
|
||||||
logger.log('DEBUG', f'fuzz模式下生成的字典大小:{len(subdomains)}')
|
logger.log('DEBUG', f'Dictionary based on word mode size: {len(subdomains)}')
|
||||||
logger.log('ALERT', f'请注意检查基于word模式生成的{random_domain}是否正确')
|
logger.log('ALERT', f'Please check whether {random_domain} is correct or not')
|
||||||
return subdomains
|
return subdomains
|
||||||
|
|
||||||
|
|
||||||
def query_domain_ns_a(ns_list):
|
def query_domain_ns_a(ns_list):
|
||||||
logger.log('INFOR', f'正在查询权威DNS名称服务器{ns_list}的A记录')
|
logger.log('INFOR', f'Querying authoritative name server {ns_list} A record')
|
||||||
if not isinstance(ns_list, list):
|
if not isinstance(ns_list, list):
|
||||||
return list()
|
return list()
|
||||||
ns_ip_list = []
|
ns_ip_list = []
|
||||||
@@ -142,62 +142,62 @@ def query_domain_ns_a(ns_list):
|
|||||||
answer = resolver.query(ns, 'A')
|
answer = resolver.query(ns, 'A')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('ERROR', e.args)
|
logger.log('ERROR', e.args)
|
||||||
logger.log('ERROR', f'查询权威DNS名称服务器{ns}的A记录出错')
|
logger.log('ERROR', f'Query authoritative name server {ns} A record error')
|
||||||
continue
|
continue
|
||||||
if answer:
|
if answer:
|
||||||
for item in answer:
|
for item in answer:
|
||||||
ns_ip_list.append(item.address)
|
ns_ip_list.append(item.address)
|
||||||
logger.log('INFOR', f'权威DNS名称服务器对应A记录 {ns_ip_list}')
|
logger.log('INFOR', f'Authoritative name server A record: {ns_ip_list}')
|
||||||
return ns_ip_list
|
return ns_ip_list
|
||||||
|
|
||||||
|
|
||||||
def query_domain_ns(domain):
|
def query_domain_ns(domain):
|
||||||
logger.log('INFOR', f'正在查询{domain}的NS记录')
|
logger.log('INFOR', f'Querying NS records of {domain}')
|
||||||
domain = utils.get_maindomain(domain)
|
domain = utils.get_maindomain(domain)
|
||||||
resolver = utils.dns_resolver()
|
resolver = utils.dns_resolver()
|
||||||
try:
|
try:
|
||||||
answer = resolver.query(domain, 'NS')
|
answer = resolver.query(domain, 'NS')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('ERROR', e.args)
|
logger.log('ERROR', e.args)
|
||||||
logger.log('ERROR', f'查询{domain}的NS记录出错')
|
logger.log('ERROR', f'Querying NS records of {domain} error')
|
||||||
return list()
|
return list()
|
||||||
ns = [item.to_text() for item in answer]
|
ns = [item.to_text() for item in answer]
|
||||||
logger.log('INFOR', f'{domain}的权威DNS名称服务器 {ns}')
|
logger.log('INFOR', f'{domain}\'s authoritative name server: {ns}')
|
||||||
return ns
|
return ns
|
||||||
|
|
||||||
|
|
||||||
@tenacity.retry(stop=tenacity.stop_after_attempt(2))
|
@tenacity.retry(stop=tenacity.stop_after_attempt(2))
|
||||||
def get_wildcard_record(domain, resolver):
|
def get_wildcard_record(domain, resolver):
|
||||||
logger.log('INFOR', f'查询{domain}在权威DNS名称服务器的泛解析记录')
|
logger.log('INFOR', f'Query {domain} \'s wildcard dns record in authoritative name server')
|
||||||
try:
|
try:
|
||||||
answer = resolver.query(domain, 'A')
|
answer = resolver.query(domain, 'A')
|
||||||
# 如果查询随机域名A记录时抛出Timeout异常则重新查询
|
# 如果查询随机域名A记录时抛出Timeout异常则重新查询
|
||||||
except Timeout as e:
|
except Timeout as e:
|
||||||
logger.log('ALERT', f'查询超时重新查询中')
|
logger.log('ALERT', f'Query timeout, retrying')
|
||||||
logger.log('DEBUG', e.args)
|
logger.log('DEBUG', e.args)
|
||||||
raise tenacity.TryAgain
|
raise tenacity.TryAgain
|
||||||
except (NXDOMAIN, YXDOMAIN, NoAnswer, NoNameservers) as e:
|
except (NXDOMAIN, YXDOMAIN, NoAnswer, NoNameservers) as e:
|
||||||
logger.log('DEBUG', e.args)
|
logger.log('DEBUG', e.args)
|
||||||
logger.log('INFOR', f'{domain}在权威DNS名称服务器上没有A记录')
|
logger.log('INFOR', f'{domain} dont have A record on authoritative name server')
|
||||||
return None, None
|
return None, None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('ERROR', e.args)
|
logger.log('ERROR', e.args)
|
||||||
logger.log('ERROR', f'查询{domain}在权威DNS名称服务器泛解析记录出错')
|
logger.log('ERROR', f'Query {domain} wildcard dns record in authoritative name server error')
|
||||||
exit(1)
|
exit(1)
|
||||||
else:
|
else:
|
||||||
if answer.rrset is None:
|
if answer.rrset is None:
|
||||||
logger.log('DEBUG', f'查询结果无记录')
|
logger.log('DEBUG', f'No record of query results')
|
||||||
return None, None
|
return None, None
|
||||||
name = answer.name
|
name = answer.name
|
||||||
ip = {item.address for item in answer}
|
ip = {item.address for item in answer}
|
||||||
ttl = answer.ttl
|
ttl = answer.ttl
|
||||||
logger.log('INFOR', f'{domain} 在权威DNS上解析到域名: {name} '
|
logger.log('INFOR', f'{domain} results on authoritative name server: {name} '
|
||||||
f'IP: {ip} TTL: {ttl}')
|
f'IP: {ip} TTL: {ttl}')
|
||||||
return ip, ttl
|
return ip, ttl
|
||||||
|
|
||||||
|
|
||||||
def collect_wildcard_record(domain, authoritative_ns):
|
def collect_wildcard_record(domain, authoritative_ns):
|
||||||
logger.log('INFOR', f'正在收集{domain}的泛解析记录')
|
logger.log('INFOR', f'Collecting wildcard dns record for {domain}')
|
||||||
if not authoritative_ns:
|
if not authoritative_ns:
|
||||||
return list(), int()
|
return list(), int()
|
||||||
resolver = utils.dns_resolver()
|
resolver = utils.dns_resolver()
|
||||||
@@ -214,7 +214,7 @@ def collect_wildcard_record(domain, authoritative_ns):
|
|||||||
ip, ttl = get_wildcard_record(random_subdomain, resolver)
|
ip, ttl = get_wildcard_record(random_subdomain, resolver)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('DEBUG', e.args)
|
logger.log('DEBUG', e.args)
|
||||||
logger.log('ALERT', f'多次查询出错将尝试查询新的随机子域')
|
logger.log('ALERT', f'Multiple query errors, try to query a new random subdomain')
|
||||||
continue
|
continue
|
||||||
if ip is None:
|
if ip is None:
|
||||||
continue
|
continue
|
||||||
@@ -231,7 +231,7 @@ def collect_wildcard_record(domain, authoritative_ns):
|
|||||||
# 大部分的IP地址出现次数大于2次停止收集泛解析IP记录
|
# 大部分的IP地址出现次数大于2次停止收集泛解析IP记录
|
||||||
if len(addrs) / len(ips) >= 0.8:
|
if len(addrs) / len(ips) >= 0.8:
|
||||||
break
|
break
|
||||||
logger.log('DEBUG', f'收集到{domain}的泛解析记录\n{ips}\n{ttl}')
|
logger.log('DEBUG', f'Collected the wildcard dns record of {domain}\n{ips}\n{ttl}')
|
||||||
return ips, ttl
|
return ips, ttl
|
||||||
|
|
||||||
|
|
||||||
@@ -251,17 +251,17 @@ def check_dict():
|
|||||||
if not setting.enable_check_dict:
|
if not setting.enable_check_dict:
|
||||||
return
|
return
|
||||||
sec = setting.check_time
|
sec = setting.check_time
|
||||||
logger.log('ALERT', f'你有{sec}秒时间检查爆破配置是否正确')
|
logger.log('ALERT', f'You have {sec} seconds to check whether the configuration is correct or not')
|
||||||
logger.log('ALERT', f'退出爆破请使用`Ctrl+C`')
|
logger.log('ALERT', f'If you want to exit, please use `Ctrl + C`')
|
||||||
try:
|
try:
|
||||||
time.sleep(sec)
|
time.sleep(sec)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.log('INFOR', '爆破配置有误退出爆破')
|
logger.log('INFOR', 'Due to configuration incorrect, exited')
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
def gen_records(items, records, subdomains, ip_times, wc_ips, wc_ttl):
|
def gen_records(items, records, subdomains, ip_times, wc_ips, wc_ttl):
|
||||||
qname = items.get('name')[:-1] # 去出最右边的`.`点号
|
qname = items.get('name')[:-1] # 去除最右边的`.`点号
|
||||||
reason = items.get('status')
|
reason = items.get('status')
|
||||||
resolver = items.get('resolver')
|
resolver = items.get('resolver')
|
||||||
data = items.get('data')
|
data = items.get('data')
|
||||||
@@ -276,9 +276,9 @@ def gen_records(items, records, subdomains, ip_times, wc_ips, wc_ttl):
|
|||||||
have_a_record = False
|
have_a_record = False
|
||||||
for answer in answers:
|
for answer in answers:
|
||||||
if answer.get('type') != 'A':
|
if answer.get('type') != 'A':
|
||||||
logger.log('TRACE', f'查询{qname}返回的应答没有A记录\n{answer}')
|
logger.log('TRACE', f'The query result of {qname} has no A record\n{answer}')
|
||||||
continue
|
continue
|
||||||
logger.log('TRACE', f'查询{qname}返回的应答具有A记录\n{answer}')
|
logger.log('TRACE', f'The query result of {qname} no A record\n{answer}')
|
||||||
have_a_record = True
|
have_a_record = True
|
||||||
ttl = answer.get('ttl')
|
ttl = answer.get('ttl')
|
||||||
ttls.append(ttl)
|
ttls.append(ttl)
|
||||||
@@ -289,10 +289,10 @@ def gen_records(items, records, subdomains, ip_times, wc_ips, wc_ttl):
|
|||||||
num = ip_times.get(ip)
|
num = ip_times.get(ip)
|
||||||
times.append(num)
|
times.append(num)
|
||||||
isvalid, reason = is_valid_subdomain(ip, ttl, num, wc_ips, wc_ttl)
|
isvalid, reason = is_valid_subdomain(ip, ttl, num, wc_ips, wc_ttl)
|
||||||
logger.log('TRACE', f'{ip}是否有效:{isvalid} 原因:{reason}')
|
logger.log('TRACE', f'{ip} effective: {isvalid} reason: {reason}')
|
||||||
is_valid_flags.append(isvalid)
|
is_valid_flags.append(isvalid)
|
||||||
if not have_a_record:
|
if not have_a_record:
|
||||||
logger.log('TRACE', f'查询{qname}返回的所有应答都中没有A记录{answers}')
|
logger.log('TRACE', f'All query result of {qname} no A record{answers}')
|
||||||
# 为了优化内存 只添加有A记录且通过判断的子域到记录中
|
# 为了优化内存 只添加有A记录且通过判断的子域到记录中
|
||||||
if have_a_record and all(is_valid_flags):
|
if have_a_record and all(is_valid_flags):
|
||||||
record['resolve'] = 1
|
record['resolve'] = 1
|
||||||
@@ -309,10 +309,10 @@ def gen_records(items, records, subdomains, ip_times, wc_ips, wc_ttl):
|
|||||||
|
|
||||||
|
|
||||||
def stat_ip_times(result_paths):
|
def stat_ip_times(result_paths):
|
||||||
logger.log('INFOR', f'正在统计IP次数')
|
logger.log('INFOR', f'Counting IP...')
|
||||||
times = dict()
|
times = dict()
|
||||||
for result_path in result_paths:
|
for result_path in result_paths:
|
||||||
logger.log('DEBUG', f'正在读取{result_path}')
|
logger.log('DEBUG', f'Reading {result_path}')
|
||||||
with open(result_path) as fd:
|
with open(result_path) as fd:
|
||||||
for line in fd:
|
for line in fd:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
@@ -320,7 +320,7 @@ def stat_ip_times(result_paths):
|
|||||||
items = json.loads(line)
|
items = json.loads(line)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('ERROR', e.args)
|
logger.log('ERROR', e.args)
|
||||||
logger.log('ERROR', f'解析{result_path}行{line}出错跳过解析该行')
|
logger.log('ERROR', f'Error parsing {result_path} line {line} Skip this line')
|
||||||
continue
|
continue
|
||||||
status = items.get('status')
|
status = items.get('status')
|
||||||
if status != 'NOERROR':
|
if status != 'NOERROR':
|
||||||
@@ -339,11 +339,11 @@ def stat_ip_times(result_paths):
|
|||||||
|
|
||||||
|
|
||||||
def deal_output(output_paths, ip_times, wildcard_ips, wildcard_ttl):
|
def deal_output(output_paths, ip_times, wildcard_ips, wildcard_ttl):
|
||||||
logger.log('INFOR', f'正在处理解析结果')
|
logger.log('INFOR', f'Processing results...')
|
||||||
records = dict() # 用来记录所有域名解析数据
|
records = dict() # 用来记录所有域名解析数据
|
||||||
subdomains = list() # 用来保存所有通过有效性检查的子域
|
subdomains = list() # 用来保存所有通过有效性检查的子域
|
||||||
for output_path in output_paths:
|
for output_path in output_paths:
|
||||||
logger.log('DEBUG', f'正在处理{output_path}')
|
logger.log('DEBUG', f'Processing {output_path}')
|
||||||
with open(output_path) as fd:
|
with open(output_path) as fd:
|
||||||
for line in fd:
|
for line in fd:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
@@ -351,16 +351,16 @@ def deal_output(output_paths, ip_times, wildcard_ips, wildcard_ttl):
|
|||||||
items = json.loads(line)
|
items = json.loads(line)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.log('ERROR', e.args)
|
logger.log('ERROR', e.args)
|
||||||
logger.log('ERROR', f'解析行{line}出错跳过解析该行')
|
logger.log('ERROR', f'Error parsing {line} Skip this line')
|
||||||
continue
|
continue
|
||||||
qname = items.get('name')[:-1] # 去出最右边的`.`点号
|
qname = items.get('name')[:-1] # 去除最右边的`.`点号
|
||||||
status = items.get('status')
|
status = items.get('status')
|
||||||
if status != 'NOERROR':
|
if status != 'NOERROR':
|
||||||
logger.log('TRACE', f'处理{line}时发现{qname}查询结果状态{status}')
|
logger.log('TRACE', f'Found {qname}\'s result {status} while processing {line}')
|
||||||
continue
|
continue
|
||||||
data = items.get('data')
|
data = items.get('data')
|
||||||
if 'answers' not in data:
|
if 'answers' not in data:
|
||||||
logger.log('TRACE', f'处理{line}时发现{qname}返回的结果无应答')
|
logger.log('TRACE', f'Processing {line}, {qname} no response')
|
||||||
continue
|
continue
|
||||||
records, subdomains = gen_records(items, records, subdomains,
|
records, subdomains = gen_records(items, records, subdomains,
|
||||||
ip_times, wildcard_ips,
|
ip_times, wildcard_ips,
|
||||||
@@ -370,15 +370,15 @@ def deal_output(output_paths, ip_times, wildcard_ips, wildcard_ttl):
|
|||||||
|
|
||||||
def check_by_compare(ip, ttl, wc_ips, wc_ttl):
|
def check_by_compare(ip, ttl, wc_ips, wc_ttl):
|
||||||
"""
|
"""
|
||||||
通过与泛解析返回的IP集合和返回的TTL值进行对比判断发现的子域是否是泛解析子域
|
Use TTL comparison to detect wildcard dns record
|
||||||
|
|
||||||
:param set ip: 子域A记录查询出的IP
|
:param set ip: A record IP address set
|
||||||
:param int ttl: 子域A记录查询出的TTL
|
:param int ttl: A record TTL value
|
||||||
:param set wc_ips: 泛解析的IP集合
|
:param set wc_ips: wildcard dns record IP address set
|
||||||
:param int wc_ttl: 泛解析的TTL
|
:param int wc_ttl: wildcard dns record TTL value
|
||||||
:return: 判断结果
|
:return bool: result
|
||||||
"""
|
"""
|
||||||
# 参考:http://sh3ll.me/archives/201704041222.txt
|
# Reference:http://sh3ll.me/archives/201704041222.txt
|
||||||
if ip not in wc_ips:
|
if ip not in wc_ips:
|
||||||
return False # 子域IP不在泛解析IP集合则不是泛解析
|
return False # 子域IP不在泛解析IP集合则不是泛解析
|
||||||
if ttl != wc_ttl and ttl % 60 == 0 and wc_ttl % 60 == 0:
|
if ttl != wc_ttl and ttl % 60 == 0 and wc_ttl % 60 == 0:
|
||||||
@@ -388,10 +388,10 @@ def check_by_compare(ip, ttl, wc_ips, wc_ttl):
|
|||||||
|
|
||||||
def check_ip_times(times):
|
def check_ip_times(times):
|
||||||
"""
|
"""
|
||||||
根据ip出现次数判断是否为泛解析
|
Use IP address times to determine wildcard or not
|
||||||
|
|
||||||
:param times: 子域IP出现次数
|
:param times: IP address times
|
||||||
:return: 判断结果
|
:return bool: result
|
||||||
"""
|
"""
|
||||||
if times > setting.ip_appear_maximum:
|
if times > setting.ip_appear_maximum:
|
||||||
return True
|
return True
|
||||||
@@ -413,7 +413,7 @@ def is_valid_subdomain(ip, ttl, times, wc_ips, wc_ttl):
|
|||||||
def save_brute_dict(dict_path, dict_set):
|
def save_brute_dict(dict_path, dict_set):
|
||||||
dict_data = '\n'.join(dict_set)
|
dict_data = '\n'.join(dict_set)
|
||||||
if not utils.save_data(dict_path, dict_data):
|
if not utils.save_data(dict_path, dict_data):
|
||||||
logger.log('FATAL', '保存生成的字典出错')
|
logger.log('FATAL', 'Saving dictionary error')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -438,26 +438,27 @@ class Brute(Module):
|
|||||||
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 --rule '[a-z]' run
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
参数alive可选值True,False分别表示导出存活,全部子域结果
|
--alive True/False Only export alive subdomains or not (default False)
|
||||||
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
|
||||||
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
--path Result directory (default directory is ./results)
|
||||||
参数path默认None使用OneForAll结果目录自动生成路径
|
|
||||||
|
|
||||||
|
:param str target: One domain or File path of one domain per line (required)
|
||||||
|
: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 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 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 bool export: Export the results (default True)
|
||||||
|
:param str format: Result format (default csv)
|
||||||
|
:param str path: Result directory (default None)
|
||||||
|
|
||||||
:param str target: 单个域名或者每行一个域名的文件路径
|
|
||||||
:param int process: 爆破进程数(默认1)
|
|
||||||
:param int concurrent: 并发爆破数量(默认10000)
|
|
||||||
:param bool word: 是否使用word模式进行爆破(默认False)
|
|
||||||
:param str wordlist: word模式爆破使用的字典路径(默认使用setting.py配置)
|
|
||||||
:param bool recursive: 是否使用递归进行爆破(默认False)
|
|
||||||
:param int depth: 递归爆破的深度(默认2)
|
|
||||||
:param str nextlist: 递归爆破所使用的字典路径(默认使用setting.py配置)
|
|
||||||
:param bool fuzz: 是否使用fuzz模式进行爆破(默认False)
|
|
||||||
:param str place: 指定爆破位置(开启fuzz模式时必需指定此参数)
|
|
||||||
:param str rule: 指定fuzz模式爆破使用的正则规则(开启fuzz模式时必需指定此参数)
|
|
||||||
:param bool export: 是否导出爆破结果(默认True)
|
|
||||||
:param bool alive: 只导出存活的子域结果(默认True)
|
|
||||||
:param str format: 结果导出格式(默认csv)
|
|
||||||
:param str path: 结果导出路径(默认None)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, target, process=None, concurrent=None, word=False,
|
def __init__(self, target, process=None, concurrent=None, word=False,
|
||||||
@@ -493,7 +494,7 @@ class Brute(Module):
|
|||||||
self.quite = False
|
self.quite = False
|
||||||
|
|
||||||
def gen_brute_dict(self, domain):
|
def gen_brute_dict(self, domain):
|
||||||
logger.log('INFOR', f'正在为{domain}生成爆破字典')
|
logger.log('INFOR', f'Generating dictionary for {domain}')
|
||||||
dict_set = set()
|
dict_set = set()
|
||||||
# 如果domain不是self.subdomain 而是self.domain的子域则生成递归爆破字典
|
# 如果domain不是self.subdomain 而是self.domain的子域则生成递归爆破字典
|
||||||
if self.place is None:
|
if self.place is None:
|
||||||
@@ -512,41 +513,41 @@ class Brute(Module):
|
|||||||
# logger.log('INFOR', f'正在去重爆破字典')
|
# logger.log('INFOR', f'正在去重爆破字典')
|
||||||
# dict_set = utils.uniq_dict_list(dict_set)
|
# dict_set = utils.uniq_dict_list(dict_set)
|
||||||
count = len(dict_set)
|
count = len(dict_set)
|
||||||
logger.log('INFOR', f'生成的爆破字典大小为{count}')
|
logger.log('INFOR', f'Dictionary size: {count}')
|
||||||
if count > 10000000:
|
if count > 10000000:
|
||||||
logger.log('ALERT', f'注意生成的爆破字典太大:{count} > 10000000')
|
logger.log('ALERT', f'The dictionary generated is too large:{count} > 10000000')
|
||||||
return dict_set
|
return dict_set
|
||||||
|
|
||||||
def check_brute_params(self):
|
def check_brute_params(self):
|
||||||
if not (self.word or self.fuzz):
|
if not (self.word or self.fuzz):
|
||||||
logger.log('FATAL', f'请至少指定一种爆破模式')
|
logger.log('FATAL', f'Please specify at least one brute mode')
|
||||||
exit(1)
|
exit(1)
|
||||||
if len(self.domains) > 1:
|
if len(self.domains) > 1:
|
||||||
self.bulk = True
|
self.bulk = True
|
||||||
if self.fuzz:
|
if self.fuzz:
|
||||||
if self.place is None or self.rule is None:
|
if self.place is None or self.rule is None:
|
||||||
logger.log('FATAL', f'没有指定fuzz位置或规则')
|
logger.log('FATAL', f'No fuzz position or rules specified')
|
||||||
exit(1)
|
exit(1)
|
||||||
if self.bulk:
|
if self.bulk:
|
||||||
logger.log('FATAL', f'批量爆破的场景下不能使用fuzz模式')
|
logger.log('FATAL', f'Cannot use fuzz mode in the bulk brute')
|
||||||
exit(1)
|
exit(1)
|
||||||
if self.recursive_brute:
|
if self.recursive_brute:
|
||||||
logger.log('FATAL', f'使用fuzz模式下不能使用递归爆破')
|
logger.log('FATAL', f'Cannot use recursive brute in fuzz mode')
|
||||||
exit(1)
|
exit(1)
|
||||||
fuzz_count = self.place.count('*')
|
fuzz_count = self.place.count('*')
|
||||||
if fuzz_count < 1:
|
if fuzz_count < 1:
|
||||||
logger.log('FATAL', f'没有指定fuzz位置')
|
logger.log('FATAL', f'No fuzz position specified')
|
||||||
exit(1)
|
exit(1)
|
||||||
if fuzz_count > 1:
|
if fuzz_count > 1:
|
||||||
logger.log('FATAL', f'只能指定1个fuzz位置')
|
logger.log('FATAL', f'Only one fuzz position can be specified')
|
||||||
exit(1)
|
exit(1)
|
||||||
if self.domain not in self.place:
|
if self.domain not in self.place:
|
||||||
logger.log('FATAL', f'指定fuzz的域名有误')
|
logger.log('FATAL', f'Incorrect domain for fuzz')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
def main(self, domain):
|
def main(self, domain):
|
||||||
start = time.time()
|
start = time.time()
|
||||||
logger.log('INFOR', f'正在爆破域名{domain}')
|
logger.log('INFOR', f'Blasting {domain} ...')
|
||||||
massdns_dir = setting.third_party_dir.joinpath('massdns')
|
massdns_dir = setting.third_party_dir.joinpath('massdns')
|
||||||
result_dir = setting.result_save_dir
|
result_dir = setting.result_save_dir
|
||||||
temp_dir = result_dir.joinpath('temp')
|
temp_dir = result_dir.joinpath('temp')
|
||||||
@@ -597,16 +598,16 @@ class Brute(Module):
|
|||||||
delete_file(dict_path, output_paths)
|
delete_file(dict_path, output_paths)
|
||||||
end = time.time()
|
end = time.time()
|
||||||
self.elapse = round(end - start, 1)
|
self.elapse = round(end - start, 1)
|
||||||
logger.log('INFOR', f'{self.source}模块耗时{self.elapse}秒'
|
logger.log('INFOR', f'{self.source} module spends {self.elapse} seconds'
|
||||||
f'发现{domain}的子域{len(self.subdomains)}个')
|
f'Found {len(self.subdomains)} subdomains of {domain}')
|
||||||
logger.log('DEBUG', f'{self.source}模块发现{domain}的子域:\n'
|
logger.log('DEBUG', f'{self.source} module found subdomains of {domain}:\n'
|
||||||
f'{self.subdomains}')
|
f'{self.subdomains}')
|
||||||
self.gen_result(brute=dict_len, valid=len(self.subdomains))
|
self.gen_result(brute=dict_len, valid=len(self.subdomains))
|
||||||
self.save_db()
|
self.save_db()
|
||||||
return self.subdomains
|
return self.subdomains
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
logger.log('INFOR', f'开始执行{self.source}模块')
|
logger.log('INFOR', f'Start runing {self.source} module')
|
||||||
if self.check_env:
|
if self.check_env:
|
||||||
utils.check_env()
|
utils.check_env()
|
||||||
self.domains = utils.get_domains(self.target)
|
self.domains = utils.get_domains(self.target)
|
||||||
@@ -614,7 +615,7 @@ class Brute(Module):
|
|||||||
for self.domain in self.domains:
|
for self.domain in self.domains:
|
||||||
self.check_brute_params()
|
self.check_brute_params()
|
||||||
if self.recursive_brute:
|
if self.recursive_brute:
|
||||||
logger.log('INFOR', f'开始递归爆破{self.domain}的第1层子域')
|
logger.log('INFOR', f'Start recursively brute the first layer subdomain of {self.domain}')
|
||||||
valid_subdomains = self.main(self.domain)
|
valid_subdomains = self.main(self.domain)
|
||||||
all_subdomains.extend(valid_subdomains)
|
all_subdomains.extend(valid_subdomains)
|
||||||
|
|
||||||
@@ -623,8 +624,8 @@ class Brute(Module):
|
|||||||
if self.recursive_brute:
|
if self.recursive_brute:
|
||||||
for layer_num in range(1, self.recursive_depth):
|
for layer_num in range(1, self.recursive_depth):
|
||||||
# 之前已经做过1层子域爆破 当前实际递归层数是layer+1
|
# 之前已经做过1层子域爆破 当前实际递归层数是layer+1
|
||||||
logger.log('INFOR', f'开始递归爆破{self.domain}的'
|
logger.log('INFOR', f'Start recursively brute'
|
||||||
f'第{layer_num + 1}层子域')
|
f'the {layer_num + 1} layer subdomain of {self.domain}')
|
||||||
for subdomain in all_subdomains:
|
for subdomain in all_subdomains:
|
||||||
self.place = '*.' + subdomain
|
self.place = '*.' + subdomain
|
||||||
# 进行下一层子域爆破的限制条件
|
# 进行下一层子域爆破的限制条件
|
||||||
@@ -633,7 +634,7 @@ class Brute(Module):
|
|||||||
valid_subdomains = self.main(subdomain)
|
valid_subdomains = self.main(subdomain)
|
||||||
all_subdomains.extend(valid_subdomains)
|
all_subdomains.extend(valid_subdomains)
|
||||||
|
|
||||||
logger.log('INFOR', f'结束执行{self.source}模块爆破域名{self.domain}')
|
logger.log('INFOR', f'Finished {self.source} module\'s brute {self.domain}')
|
||||||
if not self.path:
|
if not self.path:
|
||||||
name = f'{self.domain}_brute_result.{self.format}'
|
name = f'{self.domain}_brute_result.{self.format}'
|
||||||
self.path = setting.result_save_dir.joinpath(name)
|
self.path = setting.result_save_dir.joinpath(name)
|
||||||
|
|||||||
+18
-18
@@ -2,7 +2,7 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
"""
|
"""
|
||||||
OneForAll子域接管模块
|
OneForAll subdomain takeover module
|
||||||
|
|
||||||
:copyright: Copyright (c) 2019, Jing Ling. All rights reserved.
|
:copyright: Copyright (c) 2019, Jing Ling. All rights reserved.
|
||||||
:license: GNU General Public License v3.0, see LICENSE for more details.
|
:license: GNU General Public License v3.0, see LICENSE for more details.
|
||||||
@@ -47,22 +47,22 @@ def get_maindomain(subdomain):
|
|||||||
|
|
||||||
class Takeover(Module):
|
class Takeover(Module):
|
||||||
"""
|
"""
|
||||||
OneForAll多线程子域接管风险检查模块
|
OneForAll subdomain takeover module
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
python3 takeover.py --target www.example.com --format csv run
|
python3 takeover.py --target www.example.com --format csv run
|
||||||
python3 takeover.py --target ./subdomains.txt --thread 10 run
|
python3 takeover.py --target ./subdomains.txt --thread 10 run
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
参数format可选格式有'txt', 'rst', 'csv', 'tsv', 'json', 'yaml', 'html',
|
--format rst/csv/tsv/json/yaml/html/jira/xls/xlsx/dbf/latex/ods (result format)
|
||||||
'jira', 'xls', 'xlsx', 'dbf', 'latex', 'ods'
|
--path Result directory (default directory is ./results)
|
||||||
参数path默认None使用OneForAll结果目录生成路径
|
|
||||||
|
|
||||||
:param any target: 单个子域或者每行一个子域的文件路径(必需参数)
|
:param any target: One domain or File path of one domain per line (required)
|
||||||
:param int thread: 线程数(默认100)
|
:param int thread: threads number (default 100)
|
||||||
:param str format: 导出格式(默认csv)
|
:param str format: Result format (default csv)
|
||||||
:param str path: 导出路径(默认None)
|
:param str path: Result directory (default None)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, target, thread=100, path=None, format='csv'):
|
def __init__(self, target, thread=100, path=None, format='csv'):
|
||||||
Module.__init__(self)
|
Module.__init__(self)
|
||||||
self.subdomains = set()
|
self.subdomains = set()
|
||||||
@@ -78,7 +78,7 @@ class Takeover(Module):
|
|||||||
self.results = Dataset()
|
self.results = Dataset()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
logger.log('DEBUG', '正在保存检查结果')
|
logger.log('DEBUG', 'Saving Results...')
|
||||||
if self.format == 'txt':
|
if self.format == 'txt':
|
||||||
data = str(self.results)
|
data = str(self.results)
|
||||||
else:
|
else:
|
||||||
@@ -93,7 +93,7 @@ class Takeover(Module):
|
|||||||
|
|
||||||
for resp in responses:
|
for resp in responses:
|
||||||
if resp in domain_resp.text and resp in cname_resp.text:
|
if resp in domain_resp.text and resp in cname_resp.text:
|
||||||
logger.log('ALERT', f'{subdomain}存在子域接管风险')
|
logger.log('ALERT', f'{subdomain}Subdomain takeover threat found')
|
||||||
self.results.append([subdomain, cname])
|
self.results.append([subdomain, cname])
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -131,14 +131,14 @@ class Takeover(Module):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
start = time.time()
|
start = time.time()
|
||||||
logger.log('INFOR', f'开始执行{self.source}模块')
|
logger.log('INFOR', f'Start runing {self.source} module')
|
||||||
self.subdomains = utils.get_domains(self.target)
|
self.subdomains = utils.get_domains(self.target)
|
||||||
self.format = utils.check_format(self.format, len(self.subdomains))
|
self.format = utils.check_format(self.format, len(self.subdomains))
|
||||||
timestamp = utils.get_timestamp()
|
timestamp = utils.get_timestamp()
|
||||||
name = f'takeover_check_result_{timestamp}'
|
name = f'takeover_check_result_{timestamp}'
|
||||||
self.path = utils.check_path(self.path, name, self.format)
|
self.path = utils.check_path(self.path, name, self.format)
|
||||||
if self.subdomains:
|
if self.subdomains:
|
||||||
logger.log('INFOR', f'正在检查子域接管风险')
|
logger.log('INFOR', f'Checking subdomain takeover...')
|
||||||
self.fingerprints = get_fingerprint()
|
self.fingerprints = get_fingerprint()
|
||||||
self.results.headers = ['subdomain', 'cname']
|
self.results.headers = ['subdomain', 'cname']
|
||||||
# 创建待检查的子域队列
|
# 创建待检查的子域队列
|
||||||
@@ -155,13 +155,13 @@ class Takeover(Module):
|
|||||||
self.subdomainq.join()
|
self.subdomainq.join()
|
||||||
self.save()
|
self.save()
|
||||||
else:
|
else:
|
||||||
logger.log('FATAL', f'获取域名失败')
|
logger.log('FATAL', f'Failed to obtain domain')
|
||||||
end = time.time()
|
end = time.time()
|
||||||
elapse = round(end - start, 1)
|
elapse = round(end - start, 1)
|
||||||
logger.log('INFOR', f'{self.source}模块耗时{elapse}秒'
|
logger.log('INFOR', f'{self.source} module spends {elapse} seconds'
|
||||||
f'发现{len(self.results)}个子域存在接管风险')
|
f'There are {len(self.results)} subdomains exit takeover')
|
||||||
logger.log('INFOR', f'子域接管风险检查结果 {self.path}')
|
logger.log('INFOR', f'Subdomain takeover results: {self.path}')
|
||||||
logger.log('INFOR', f'结束执行{self.source}模块')
|
logger.log('INFOR', f'Finished {self.source} module')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Reference in New Issue
Block a user