实现从跳转历史URL收集子域功能

This commit is contained in:
Jing Ling
2020-08-20 02:21:08 +08:00
parent 2afb2b1782
commit 4752a10ac6
4 changed files with 68 additions and 43 deletions
+10 -8
View File
@@ -71,6 +71,7 @@ class Database(object):
f'title text,' f'title text,'
f'banner text,' f'banner text,'
f'header text,' f'header text,'
f'history text,'
f'response text,' f'response text,'
f'times text,' f'times text,'
f'ttl text,' f'ttl text,'
@@ -99,15 +100,16 @@ class Database(object):
if results: if results:
try: try:
self.conn.bulk_query( self.conn.bulk_query(
f'insert into "{table_name}" (id, alive, resolve, request, new,' f'insert into "{table_name}" '
f'url, subdomain, port, level, cname, content, public, cdn, status,' f'(id, alive, resolve, request, new, url, subdomain, port, level,'
f'reason, title, banner, header, response, times, ttl, cidr, asn, org,' f'cname, content, public, cdn, status, reason, title, banner, header,'
f' ip2region, ip2location, resolver, module, source, elapse, find) ' f'history, response, times, ttl, cidr, asn, org, ip2region,'
f'ip2location, resolver, module, source, elapse, find) '
f'values (:id, :alive, :resolve, :request, :new, :url, ' f'values (:id, :alive, :resolve, :request, :new, :url, '
f':subdomain, :port, :level, :cname, :content, :public, :cdn, :status,' f':subdomain, :port, :level, :cname, :content, :public, :cdn,'
f':reason, :title, :banner, :header, :response, :times, :ttl, :cidr,' f':status, :reason, :title, :banner, :header, :history, :response,'
f':asn, :org, :ip2region, :ip2location, :resolver, :module, :source,' f':times, :ttl, :cidr, :asn, :org, :ip2region, :ip2location,'
f':elapse, :find)', results) f':resolver, :module, :source, :elapse, :find)', results)
except Exception as e: except Exception as e:
logger.log('ERROR', e) logger.log('ERROR', e)
+2
View File
@@ -272,6 +272,7 @@ class Module(object):
'title': None, 'title': None,
'banner': None, 'banner': None,
'header': None, 'header': None,
'history': None,
'response': None, 'response': None,
'times': None, 'times': None,
'ttl': None, 'ttl': None,
@@ -322,6 +323,7 @@ class Module(object):
'title': None, 'title': None,
'banner': None, 'banner': None,
'header': None, 'header': None,
'history': None,
'response': None, 'response': None,
'times': times, 'times': times,
'ttl': ttl, 'ttl': ttl,
+4 -2
View File
@@ -81,7 +81,7 @@ async def fetch(session, method, url):
:return: 响应对象和响应文本 :return: 响应对象和响应文本
""" """
timeout = aiohttp.ClientTimeout(total=None, timeout = aiohttp.ClientTimeout(total=None,
connect=5.0, connect=None,
sock_read=settings.sockread_timeout, sock_read=settings.sockread_timeout,
sock_connect=settings.sockconn_timeout) sock_connect=settings.sockconn_timeout)
try: try:
@@ -175,6 +175,8 @@ def request_callback(future, index, datas):
if settings.enable_banner_identify: if settings.enable_banner_identify:
datas[index]['banner'] = utils.get_sample_banner(headers) datas[index]['banner'] = utils.get_sample_banner(headers)
datas[index]['header'] = json.dumps(dict(headers)) datas[index]['header'] = json.dumps(dict(headers))
history = resp.history
datas[index]['history'] = str(history)
if isinstance(text, str): if isinstance(text, str):
title = get_title(text).strip() title = get_title(text).strip()
datas[index]['title'] = utils.remove_invalid_string(title) datas[index]['title'] = utils.remove_invalid_string(title)
@@ -229,7 +231,7 @@ async def bulk_request(data, port):
datas=to_req_data)) datas=to_req_data))
tasks.append(task) tasks.append(task)
if tasks: if tasks:
futures = asyncio.as_completed(tasks, timeout=1*60) futures = asyncio.as_completed(tasks)
for future in tqdm.tqdm(futures, for future in tqdm.tqdm(futures,
total=len(tasks), total=len(tasks),
desc='Request Progress', desc='Request Progress',
+52 -33
View File
@@ -1,5 +1,4 @@
import re import re
import json
import time import time
from urllib import parse from urllib import parse
@@ -37,9 +36,10 @@ class Finder(Module):
return data return data
file_path = settings.data_storage_dir.joinpath('common_js_library.json')
black_name = utils.load_json(file_path)
# Regular expression comes from https://github.com/GerbenJavado/LinkFinder # Regular expression comes from https://github.com/GerbenJavado/LinkFinder
def find_url(html): expression = r"""
pattern_raw = r"""
(?:"|') # Start newline delimiter (?:"|') # Start newline delimiter
( (
((?:[a-zA-Z]{1,10}://|//) # Match a scheme [a-Z]*1-10 or // ((?:[a-zA-Z]{1,10}://|//) # Match a scheme [a-Z]*1-10 or //
@@ -61,8 +61,11 @@ def find_url(html):
) )
(?:"|') # End newline delimiter (?:"|') # End newline delimiter
""" """
pattern = re.compile(pattern_raw, re.VERBOSE) url_pattern = re.compile(expression, re.VERBOSE)
result = re.finditer(pattern, html)
def find_new_urls(html):
result = re.finditer(url_pattern, html)
if result is None: if result is None:
return None return None
urls = set() urls = set()
@@ -72,7 +75,7 @@ def find_url(html):
return urls return urls
def process_url(req_url, rel_url): def convert_url(req_url, rel_url):
black_url = ["javascript:"] # Add some keyword for filter url. black_url = ["javascript:"] # Add some keyword for filter url.
raw_url = parse.urlparse(req_url) raw_url = parse.urlparse(req_url)
netloc = raw_url.netloc netloc = raw_url.netloc
@@ -97,7 +100,7 @@ def process_url(req_url, rel_url):
return result return result
def filter_name(path, black_name): def filter_name(path):
for name in black_name: for name in black_name:
if path.endswith(name): if path.endswith(name):
return True return True
@@ -115,7 +118,7 @@ def filter_name(path, black_name):
return False return False
def filter_url(domain, url, black_name): def filter_url(domain, url):
try: try:
raw_url = parse.urlparse(url) raw_url = parse.urlparse(url)
except Exception as e: # 解析失败则跳过该URL except Exception as e: # 解析失败则跳过该URL
@@ -138,42 +141,58 @@ def filter_url(domain, url, black_name):
return True return True
if path.endswith('min.js'): if path.endswith('min.js'):
return True return True
return filter_name(path, black_name) return filter_name(path)
def get_black_name():
path = settings.data_storage_dir.joinpath('common_js_library.json')
with open(path) as fp:
return json.load(fp)
def match_subdomains(domain, text): def match_subdomains(domain, text):
subdomains = utils.match_subdomains(domain, text, fuzzy=False) if isinstance(text, str):
logger.log('DEBUG', f'matched subdomains: {subdomains}') subdomains = utils.match_subdomains(domain, text, fuzzy=False)
else:
logger.log('DEBUG', f'abnormal object: {type(text)}')
subdomains = set()
logger.log('TRACE', f'matched subdomains: {subdomains}')
return subdomains return subdomains
def find_in_resp(domain, url, html):
logger.log('TRACE', f'matching subdomains from response of {url}')
return match_subdomains(domain, html)
def find_in_history(domain, url, history):
logger.log('TRACE', f'matching subdomains from history of {url}')
return match_subdomains(domain, history)
def find_js_urls(domain, req_url, rsp_html):
js_urls = set()
new_urls = find_new_urls(rsp_html)
if not new_urls:
return js_urls
for rel_url in new_urls:
url = convert_url(req_url, rel_url)
if not filter_url(domain, url):
js_urls.add(url)
return js_urls
def find_subdomains(domain, data): def find_subdomains(domain, data):
subdomains = set() subdomains = set()
js_urls = set() js_urls = set()
black_name = get_black_name() for infos in data:
for item in data: jump_history = infos.get('history')
req_url = item.get('url') req_url = infos.get('url')
rsp_html = item.get('response') subdomains = subdomains.union(find_in_history(domain, req_url, jump_history))
rsp_html = infos.get('response')
if not rsp_html: if not rsp_html:
logger.log('DEBUG', f'an abnormal response occurred in the request {req_url}')
continue continue
logger.log('DEBUG', f'matching subdomains from response of {req_url}') subdomains = subdomains.union(find_in_resp(domain, req_url, rsp_html))
subdomains = subdomains.union(match_subdomains(domain, rsp_html)) js_urls = js_urls.union(find_js_urls(domain, req_url, rsp_html))
urls = find_url(rsp_html)
if not urls:
continue
for rel_url in urls:
url = process_url(req_url, rel_url)
if not filter_url(domain, url, black_name):
js_urls.add(url)
resp_data = request.urls_request(js_urls) resp_data = request.urls_request(js_urls)
for resp, text in resp_data: for resp, text in resp_data:
if text: if not text:
logger.log('DEBUG', f'matching subdomains from response of {resp.url}') continue
subdomains = subdomains.union(match_subdomains(domain, text)) subdomains = subdomains.union(find_in_resp(domain, resp.url, text))
return subdomains return subdomains