diff --git a/CHANGELOG b/CHANGELOG index 06c7599..84adf58 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v1.5] - 2021-10-20 +### Added +- Optimized the HOSTS collision function +- Add HOSTS collision cross collision function +- Increase the output file path customization function + ## [v1.4] - 2021-9-4 ### Added - Modify the POC storage directory of the pocsuite module diff --git a/README.md b/README.md index a8dde87..d334e86 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,18 @@ kunyu init --apikey --seebug ``` ![](./images/setinfo.png) +You need to log in with ZoomEye credentials before using this tool for information collection. + +Visit address: https://www.zoomeye.org/ + +The output file path can be customized by the following command + +``` +kunyu init --output C:\Users\风起\kunyu\output +``` + +![](./images/setoutput.png) + # 0x03 Tool instructions ## Detailed command @@ -178,7 +190,9 @@ In versions after v1.3.1, you can use kunyu to link the console mode of pocsuite Through the HOSTS collision, the hidden assets in the intranet can be effectively collided, and the intranet service can be accessed according to the ServerName domain name and IP configured in the middleware httpf.conf. This can be achieved by setting the local hosts file later, because the local hosts file takes precedence. The level is higher than DNS server resolution. Support reverse check through ZoomEye domain name library or read TXT file to get the list of domain names. -![](./images/searchcrash.png) +HOSTS cross collision + +![](./images/searchcrashs.png) **Data result** diff --git a/doc/README_CN.md b/doc/README_CN.md index 0538ab3..cbfbc84 100644 --- a/doc/README_CN.md +++ b/doc/README_CN.md @@ -54,6 +54,10 @@ kunyu init --apikey --seebug ``` ![](../images/setinfo.png) +需要通过ZoomEye登录凭证,才使用该工具进行信息收集。 + +访问地址:https://www.zoomeye.org/ + # 0x03 工具使用 ## 命令详解 @@ -177,7 +181,9 @@ ZoomEye: 通过HOSTS碰撞,可以有效的碰撞出内网中隐藏的资产,根据中间件httpf.conf中配置的ServerName域名和IP捆绑即可访问内网服务,后续通过设置本地hosts文件实现,因为本地hosts文件优先级高于DNS服务器解析。支持通过ZoomEye域名库反查或者读取TXT文件获取域名列表。 -![](../images/searchcrash.png) +HOSTS交叉碰撞 + +![](../images/searchcrashs.png) **数据结果** diff --git a/images/infos.png b/images/infos.png index 8dab992..85eadf5 100644 Binary files a/images/infos.png and b/images/infos.png differ diff --git a/images/searchcrash.png b/images/searchcrash.png deleted file mode 100644 index 3f67f06..0000000 Binary files a/images/searchcrash.png and /dev/null differ diff --git a/images/searchcrashs.png b/images/searchcrashs.png new file mode 100644 index 0000000..b586b89 Binary files /dev/null and b/images/searchcrashs.png differ diff --git a/images/setinfo.png b/images/setinfo.png index 04caa1b..1c8b66a 100644 Binary files a/images/setinfo.png and b/images/setinfo.png differ diff --git a/images/setoutput.png b/images/setoutput.png new file mode 100644 index 0000000..1d23337 Binary files /dev/null and b/images/setoutput.png differ diff --git a/kunyu/config/__version__.py b/kunyu/config/__version__.py index 4638323..e445736 100644 --- a/kunyu/config/__version__.py +++ b/kunyu/config/__version__.py @@ -15,7 +15,7 @@ __python_version__ = sys.version.split()[0] __platform__ = platform.platform() __url__ = "https://github.com/knownsec/Kunyu" -__version__ = '1.4.0' +__version__ = '1.5.0' __author__ = '风起' __Team__ = 'KnownSec 404 Team' __author_email__ = 'onlyzaliks@gmail.com' @@ -64,7 +64,8 @@ Usage: kunyu -h kunyu init -h - kunyu init --apikey "01234567-acbd-00000-1111-22222222222" + kunyu init --output /root/kunyu/output + kunyu init --apikey "xxxxx911-6D2A-12345-4e23-64exxxxx6fb" kunyu init --username "404@knownsec.com" --password "P@ssword" kunyu init --seebug "012345200157abcdef981bcc89a1452c34d62b8c" kunyu init --apikey "01234567-acbd-0000" --seebug "a73503200157"(推荐) diff --git a/kunyu/core/__init__.py b/kunyu/core/__init__.py index ad542eb..e1b9b31 100644 --- a/kunyu/core/__init__.py +++ b/kunyu/core/__init__.py @@ -18,6 +18,7 @@ import configparser from kunyu.utils import * +from kunyu.config import setting from kunyu.config.__version__ import usage, init, __title__, __help__ parser = argparse.ArgumentParser(prog=__title__) @@ -39,6 +40,7 @@ parser_init_console.add_argument("--username", help='ZoomEye Username') parser_init_console.add_argument("--password", help='ZoomEye Password') parser_init_console.add_argument("--seebug", help='ZoomEye Password') +parser_init_console.add_argument("--output", help='Set Output File Path') args = parser.parse_args() @@ -52,6 +54,10 @@ conf.read(__path) def initial_config(): + """ + Determine whether the parameters in the configuration file exist + If it does not exist, create a parameter and set the initial value to None + """ if not conf.has_section("zoomeye") and not conf.has_section("login"): conf.add_section('zoomeye') conf.set("zoomeye", "apikey", "None") @@ -62,6 +68,11 @@ def initial_config(): conf.add_section('seebug') conf.set("seebug", "apikey", "None") + # The path of the output file + if not conf.has_section("path"): + conf.add_section("path") + conf.set("path", "output", setting.OUTPUT_PATH) + def _get_login(): param = '{{"username": "{}", "password": "{}"}}'.format(args.username, args.password) resp = requests.post( @@ -89,6 +100,10 @@ def _get_login(): if args.seebug: conf.set("seebug", "apikey", args.seebug) + # set output file path + if args.output: + conf.set("path", "output", args.output) + except requests.HTTPError as err: print("\033[31;1m{}\033[0m".format(err)) print(__help__.format(datil=init)) diff --git a/kunyu/core/crash.py b/kunyu/core/crash.py index bced0b1..c70f8ee 100644 --- a/kunyu/core/crash.py +++ b/kunyu/core/crash.py @@ -9,6 +9,7 @@ import re import json import random +import sys import grequests import requests @@ -17,8 +18,8 @@ from rich.console import Console from kunyu.utils.log import logger from kunyu.utils.convert import convert -from kunyu.lib.batchfile import get_domain_file -from kunyu.config.setting import UA, DOMAIN_SEARCH_API, DOMAIN_CHECK_REGEX +from kunyu.lib.batchfile import get_domain_file, get_file +from kunyu.config.setting import UA, DOMAIN_SEARCH_API, DOMAIN_CHECK_REGEX, IP_ADDRESS_REGEX console = Console(color_system="auto", record=True) ZOOMEYE_KEY = conf.get("zoomeye", "apikey") @@ -31,6 +32,7 @@ def __init__(self): "User-Agent": random.choice(UA) } self.params = {"type": 1} + self.IP_STATUS = 0 self.__get_login() # Check whether the HTTP request returns an error @@ -67,7 +69,6 @@ def request_get(): # Dynamically calculate the number of pages that need to be queried to obtain all result sets count = int(result["total"] / 30) page = count if (result["total"] % 30) == 0 else count + 1 - console.log("Host Header Scan Domain Total: ", result["total"], style="green") # Get ZoomEye Domain result for i in range(page): self.params["page"] = str(i + 1) @@ -76,6 +77,9 @@ def request_get(): data = convert(result["list"][num]) domain_list.append(data.name) + # Remove duplicate domain names + domain_list = list(set(domain_list)) + console.log("Host Header Scan Domain Total: ", len(domain_list), style="green") return domain_list except requests.HTTPError as err: @@ -90,6 +94,15 @@ def __is_valid_domain(self, search): pattern = re.compile(DOMAIN_CHECK_REGEX) return True if pattern.match(search) else False + def __is_valid_ip(self, ip): + """ + Return whether or not given value is a valid ip address. + If the value is valid ip address this function returns ``True``, otherwise False + :param ip: ip string to validate + """ + pattern = re.compile(IP_ADDRESS_REGEX) + return True if pattern.match(ip) else False + def _get_file(self, search): """ Get the array of domain names required for HOST collision @@ -108,23 +121,43 @@ def _get_file(self, search): console.log("Host Header Scan Domain Total: ", len(domain_list), style="green") return domain_list + def _get_ip_file(self, ip): + """ + Get the array of ip address required for HOST collision + If it is IP, directly HOST collision + Otherwise, read the file to obtain the IP address + :param ip: Enter the main ip address or ip address file path + """ + ip_list = [] + if self.__is_valid_ip(ip): + ip_list.append(ip) + else: + for ip_address in get_file(ip): + ip_list.append(ip_address) + return ip_list + def host_scan(self, search, ip): """ Obtain hidden assets through HOST collision :param search: Ways to obtain domain names :param ip: IP address to be collided """ + resp = [] crash_list = [] + protocol = ['http://{}/', 'https://{}/'] self.params["q"] = search domain_list = self._get_file(search) - url = "http://{}/".format(ip) - resp = [] - for domain in domain_list: - headers = {'Host': domain.strip(), - 'User-Agent': random.choice(UA) - } - # Concurrent requests through the encapsulated coroutine module - resp.append(grequests.get(url, headers=headers, timeout=1)) + url = self._get_ip_file(ip) + for server in protocol: + for ip_address in url: + urls = server.format(ip_address) + for domain in domain_list: + headers = {'Host': domain.strip(), + 'User-Agent': random.choice(UA), + 'ip': urls + } + # Concurrent requests through the encapsulated coroutine module + resp.append(grequests.get(urls, headers=headers, timeout=2)) res_list = grequests.map(resp) for res in res_list: try: @@ -133,7 +166,7 @@ def host_scan(self, search, ip): res.encoding = 'gbk2312' # Get the title of the returned result title = re.findall('(.+)', res.text) - crash_list.append([ip, res.request.headers['Host'], title[0]]) + crash_list.append([res.request.headers['ip'], res.request.headers['Host'], title[0]]) except Exception: continue diff --git a/kunyu/core/zoomeye.py b/kunyu/core/zoomeye.py index f7646ec..032277f 100644 --- a/kunyu/core/zoomeye.py +++ b/kunyu/core/zoomeye.py @@ -50,7 +50,8 @@ def __init__(self, method): self.page = 1 self.method = method self.headers = { - "User-Agent": random.choice(UA) + "User-Agent": random.choice(UA), + "author": "ZoomEye Kunyu" } def __call__(self, func): @@ -82,14 +83,14 @@ def __request(self, login_url, data=None, headers=None): login_url, data=data, headers=headers, - timeout=20 + timeout=30 ) else: resp = requests.post( login_url, data=data, headers=headers, - timeout=20 + timeout=30 ) self.check_status(resp) self.check_error(resp.json()) @@ -326,6 +327,7 @@ def command_searchbatch(cls, filename): # Use ZooEye batch query mode,Search: "ip:1.1.1.1 ip:2.2.2.2 ip:3.3.3.3" for ip in get_file(filename): search += "ip:{} ".format(ip) + # Determine the type of interface used if cls.btype == "host": return cls.command_searchhost(search) diff --git a/kunyu/lib/batchfile.py b/kunyu/lib/batchfile.py index d5a7ed9..e7e0dd1 100644 --- a/kunyu/lib/batchfile.py +++ b/kunyu/lib/batchfile.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- # encoding: utf-8 ''' @author: 风起 @@ -17,7 +18,7 @@ def getresult(func): - __ip_list = [] + @wraps(func) def getfile(file): @@ -26,6 +27,7 @@ def getfile(file): Determine whether it is a txt file :param file: The path of the file to be read """ + __ip_list = [] try: # Check File Type if file.endswith(".txt"): @@ -34,7 +36,6 @@ def getfile(file): logger.warning("Only input TXT type files are allowed") raise Exception - nonlocal __ip_list with open(file, "r", encoding='utf-8') as ip_text: for line in ip_text: __ip_list.append(line.strip()) diff --git a/kunyu/lib/export.py b/kunyu/lib/export.py index 56fd5a5..c0d9cb4 100644 --- a/kunyu/lib/export.py +++ b/kunyu/lib/export.py @@ -13,6 +13,7 @@ import csv import xlwt +from kunyu.core import conf from kunyu.utils.log import logger from kunyu.config import setting @@ -24,11 +25,12 @@ Security researchers can also modify code files as needed. """ +OUTPUT_PATH = conf.get("path", "output") def createdir(): # Create the results output directory. __dirnamae = datetime.datetime.now().strftime("%Y%m%d%H%M") - path = os.path.expanduser(setting.OUTPUT_PATH) + path = os.path.expanduser(OUTPUT_PATH) __path = os.path.join(path, __dirnamae) setting.OUTPUT_PATH = __path if os.path.exists(__path):