diff --git a/Sublist3r.egg-info/PKG-INFO b/Sublist3r.egg-info/PKG-INFO index 23434ed..65d3ea2 100644 --- a/Sublist3r.egg-info/PKG-INFO +++ b/Sublist3r.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: Sublist3r -Version: 1.4 +Version: 1.6 Summary: Subdomains enumeration tool for penetration testers Home-page: https://github.com/aboul3la/Sublist3r License: GPL-2.0 diff --git a/build/lib/Sublist3r.egg-info/PKG-INFO b/build/lib/Sublist3r.egg-info/PKG-INFO index 23434ed..65d3ea2 100644 --- a/build/lib/Sublist3r.egg-info/PKG-INFO +++ b/build/lib/Sublist3r.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: Sublist3r -Version: 1.4 +Version: 1.6 Summary: Subdomains enumeration tool for penetration testers Home-page: https://github.com/aboul3la/Sublist3r License: GPL-2.0 diff --git a/build/lib/sublist3r.py b/build/lib/sublist3r.py index babd677..7d904b2 100644 --- a/build/lib/sublist3r.py +++ b/build/lib/sublist3r.py @@ -43,6 +43,7 @@ is_windows = sys.platform.startswith('win') # Console Colors if is_windows: + # Windows deserves coloring too :D G = '\033[92m' # green Y = '\033[93m' # yellow B = '\033[94m' # blue @@ -52,9 +53,12 @@ if is_windows: import win_unicode_console , colorama win_unicode_console.enable() colorama.init() + #Now the unicode will work ^_^ except: - # Silencing library error if not verbose - G = Y = B = R = W = '' + print("[!] Error: Coloring libraries not installed, no coloring will be used [Check the readme]") + G = Y = B = R = W = G = Y = B = R = W = '' + + else: G = '\033[92m' # green Y = '\033[93m' # yellow @@ -66,46 +70,150 @@ def no_color(): global G, Y, B, R, W G = Y = B = R = W = '' + def banner(): return + def parser_error(errmsg): banner() - print("Usage: python " + sys.argv + " [Options] use -h for help") + print("Usage: python " + sys.argv[0] + " [Options] use -h for help") print(R + "Error: " + errmsg + W) sys.exit() + def parse_args(): - parser = argparse.ArgumentParser(epilog='\tExample: \r\npython ' + sys.argv + " -d google.com") + # parse the arguments + parser = argparse.ArgumentParser(epilog='\tExample: \r\npython ' + sys.argv[0] + " -d google.com") parser.error = parser_error parser._optionals.title = "OPTIONS" parser.add_argument('-d', '--domain', help="Domain name to enumerate it's subdomains", required=True) parser.add_argument('-b', '--bruteforce', help='Enable the subbrute bruteforce module', nargs='?', default=False) parser.add_argument('-p', '--ports', help='Scan the found subdomains against specified tcp ports') - parser.add_argument('-v', '--verbose', help='Enable Verbosity and display results in realtime', action='store_true', default=False) + parser.add_argument('-v', '--verbose', help='Enable Verbosity and display results in realtime', nargs='?', default=False) parser.add_argument('-t', '--threads', help='Number of threads to use for subbrute bruteforce', type=int, default=30) parser.add_argument('-e', '--engines', help='Specify a comma-separated list of search engines') parser.add_argument('-o', '--output', help='Save the results to text file') + parser.add_argument('-of', '--output-format', help='Output format: markdown, raw (default), or json', choices=['markdown', 'raw', 'json'], default='raw') + parser.add_argument('--pprtscan', help='Scan for open ports using Shodan InternetDB', default=False, action='store_true') + parser.add_argument('--no-resolve', help='Do not resolve subdomains to IP addresses', default=False, action='store_true') parser.add_argument('-n', '--no-color', help='Output without color', default=False, action='store_true') return parser.parse_args() -def write_file(filename, subdomains, verbose=False): - if verbose: - print("%s[-] Saving results to file: %s%s%s%s" % (Y, W, R, filename, W)) + +def write_file(filename, subdomains, output_format='raw', port_scan_results=None, ip_map=None): + print("%s[-] Saving results to file: %s%s%s%s" % (Y, W, R, filename, W)) with open(str(filename), 'wt') as f: - for subdomain in subdomains: - f.write(subdomain + os.linesep) + if output_format == 'json': + output = {} + items = [] + for subdomain in subdomains: + item = {'subdomain': subdomain} + if ip_map and subdomain in ip_map: + item['ip'] = ip_map[subdomain] + items.append(item) + output['subdomains'] = items + if port_scan_results: + output['port_scan'] = port_scan_results + f.write(json.dumps(output, indent=2) + os.linesep) + elif output_format == 'markdown': + f.write("# Sublist3r Results\n\n") + f.write("## Subdomains\n\n") + if ip_map: + f.write("| Subdomain | IP |\n") + f.write("|-----------|-----|\n") + for subdomain in subdomains: + ip = ip_map.get(subdomain, '') + f.write("| %s | %s |\n" % (subdomain, ip)) + else: + for subdomain in subdomains: + f.write("- %s\n" % subdomain) + if port_scan_results: + f.write("\n## Port Scan Results\n\n") + f.write("| Subdomain | IP | Ports |\n") + f.write("|-----------|-----|-------|\n") + for subdomain, data in port_scan_results.items(): + ip = data.get('ip', '') + ports = ', '.join(str(p) for p in data.get('ports', [])) + f.write("| %s | %s | %s |\n" % (subdomain, ip, ports)) + else: + if port_scan_results: + for subdomain in subdomains: + ip = ip_map.get(subdomain, '') if ip_map else '' + if subdomain in port_scan_results: + data = port_scan_results[subdomain] + ports = ', '.join(str(p) for p in data.get('ports', [])) + if ip: + f.write("%s,%s,%s%s" % (subdomain, ip, ports, os.linesep)) + else: + f.write("%s,%s%s" % (subdomain, ports, os.linesep)) + else: + if ip: + f.write("%s,%s%s" % (subdomain, ip, os.linesep)) + else: + f.write(subdomain + os.linesep) + else: + for subdomain in subdomains: + if ip_map and subdomain in ip_map: + f.write("%s,%s%s" % (subdomain, ip_map[subdomain], os.linesep)) + else: + f.write(subdomain + os.linesep) + + +def pprt_scan(subdomains): + results = {} + for subdomain in subdomains: + try: + resp = requests.get("https://internetdb.shodan.io/%s" % subdomain, timeout=10) + if resp.status_code == 200: + data = resp.json() + if data.get('ports'): + results[subdomain] = data + except Exception: + pass + return results + + +def resolve_ips(subdomains): + ips = {} + for subdomain in subdomains: + try: + ips[subdomain] = socket.gethostbyname(subdomain) + except Exception: + pass + return ips + def subdomain_sorting_key(hostname): + """Sorting key for subdomains + + This sorting key orders subdomains from the top-level domain at the right + reading left, then moving '^' and 'www' to the top of their group. For + example, the following list is sorted correctly: + + [ + 'example.com', + 'www.example.com', + 'a.example.com', + 'www.a.example.com', + 'b.a.example.com', + 'b.example.com', + 'example.net', + 'www.example.net', + 'a.example.net', + ] + + """ parts = hostname.split('.')[::-1] if parts[-1] == 'www': return parts[:-1], 1 return parts, 0 + class enumratorBase(object): def __init__(self, base_url, engine_name, domain, subdomains=None, silent=False, verbose=True): subdomains = subdomains or [] - self.domain = urlparse.urlparse(domain).netloc if '://' in domain else domain + self.domain = urlparse.urlparse(domain).netloc self.session = requests.Session() self.subdomains = [] self.timeout = 25 @@ -127,11 +235,12 @@ class enumratorBase(object): return def print_banner(self): - if self.verbose: - self.print_(G + "[-] Searching now in %s.." % (self.engine_name) + W) + """ subclass can override this if they want a fancy banner :)""" + self.print_(G + "[-] Searching now in %s.." % (self.engine_name) + W) return def send_req(self, query, page_no=1): + url = self.base_url.format(query=query, page_no=page_no) try: resp = self.session.get(url, headers=self.headers, timeout=self.timeout) @@ -145,28 +254,37 @@ class enumratorBase(object): return response.text if hasattr(response, "text") else response.content def check_max_subdomains(self, count): - if hasattr(self, 'MAX_DOMAINS') and self.MAX_DOMAINS == 0: + if self.MAX_DOMAINS == 0: return False - return count >= getattr(self, 'MAX_DOMAINS', 10) + return count >= self.MAX_DOMAINS def check_max_pages(self, num): - if hasattr(self, 'MAX_PAGES') and self.MAX_PAGES == 0: + if self.MAX_PAGES == 0: return False - return num >= getattr(self, 'MAX_PAGES', 50) + return num >= self.MAX_PAGES + # override def extract_domains(self, resp): + """ chlid class should override this function """ return + # override def check_response_errors(self, resp): + """ chlid class should override this function + The function should return True if there are no errors and False otherwise + """ return True def should_sleep(self): + """Some enumrators require sleeping to avoid bot detections like Google enumerator""" return def generate_query(self): + """ chlid class should override this function """ return def get_page(self, num): + """ chlid class that user different pagnation counter should override this function """ return num + 10 def enumerate(self, altquery=False): @@ -177,43 +295,61 @@ class enumratorBase(object): while flag: query = self.generate_query() - count = query.count(self.domain) + count = query.count(self.domain) # finding the number of subdomains found so far + + # if they we reached the maximum number of subdomains in search query + # then we should go over the pages if self.check_max_subdomains(count): page_no = self.get_page(page_no) - if self.check_max_pages(page_no): + + if self.check_max_pages(page_no): # maximum pages for Google to avoid getting blocked return self.subdomains resp = self.send_req(query, page_no) + + # check if there is any error occured if not self.check_response_errors(resp): return self.subdomains links = self.extract_domains(resp) + + # if the previous page hyperlinks was the similar to the current one, then maybe we have reached the last page if links == prev_links: retries += 1 page_no = self.get_page(page_no) + + # make another retry maybe it isn't the last page if retries >= 3: return self.subdomains + prev_links = links self.should_sleep() + return self.subdomains + class enumratorBaseThreaded(multiprocessing.Process, enumratorBase): def __init__(self, base_url, engine_name, domain, subdomains=None, q=None, silent=False, verbose=True): subdomains = subdomains or [] enumratorBase.__init__(self, base_url, engine_name, domain, subdomains, silent=silent, verbose=verbose) multiprocessing.Process.__init__(self) self.q = q + return def run(self): domain_list = self.enumerate() for domain in domain_list: self.q.append(domain) + class GoogleEnum(enumratorBaseThreaded): def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] base_url = "https://google.com/search?q={query}&btnG=Search&hl=en-US&biw=&bih=&gbv=1&start={page_no}&filter=0" self.engine_name = "Google" self.MAX_DOMAINS = 11 self.MAX_PAGES = 200 super(GoogleEnum, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + self.q = q + return def extract_domains(self, resp): links_list = list() @@ -234,77 +370,810 @@ class GoogleEnum(enumratorBaseThreaded): return links_list def check_response_errors(self, resp): - if (type(resp) is str) and 'Our systems have detected unusual traffic' in resp: - if self.verbose: - self.print_(R + "[!] Error: Google probably now is blocking our requests" + W) + if (type(resp) is str or type(resp) is unicode) and 'Our systems have detected unusual traffic' in resp: + self.print_(R + "[!] Error: Google probably now is blocking our requests" + W) + self.print_(R + "[~] Finished now the Google Enumeration ..." + W) return False return True def should_sleep(self): time.sleep(5) + return -class Virustotal(enumratorBaseThreaded): + def generate_query(self): + if self.subdomains: + fmt = 'site:{domain} -www.{domain} -{found}' + found = ' -'.join(self.subdomains[:self.MAX_DOMAINS - 2]) + query = fmt.format(domain=self.domain, found=found) + else: + query = "site:{domain} -www.{domain}".format(domain=self.domain) + return query + +class HackertargetEnum(enumratorBaseThreaded): def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): - base_url = 'https://www.virustotal.com/ui/domains/{domain}/subdomains' - self.engine_name = "Virustotal" - super(Virustotal, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) - self.url = self.base_url.format(domain=self.domain) + subdomains = subdomains or [] + # HackerTarget API endpoint + base_url = 'https://api.hackertarget.com/hostsearch/?q={query}' + self.engine_name = "HackerTarget" + self.MAX_DOMAINS = 11 + self.MAX_PAGES = 0 + enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + self.q = q + return - def send_req(self, url): + def extract_domains(self, resp): + links_list = list() try: - resp = self.session.get(url, headers=self.headers, timeout=self.timeout) + # The API returns plain text, line-by-line in format: subdomain.domain.com,IP_Address + lines = resp.splitlines() + for line in lines: + if ',' in line: + subdomain = line.split(',')[0].strip() + # Verify it's a valid subdomain of our target and not a duplicate + if subdomain.endswith(self.domain) and subdomain not in self.subdomains and subdomain != self.domain: + if self.verbose: + self.print_("%s%s: %s%s" % (R, self.engine_name, W, subdomain)) + self.subdomains.append(subdomain) + links_list.append(subdomain) except Exception: + pass + + return links_list + + def get_page(self, num): + # Pagination isn't used for this API, but required by the base class + return num + 1 + + def generate_query(self): + # We only need to feed the root domain to the API + return self.domain + +class YahooEnum(enumratorBaseThreaded): + def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] + base_url = "https://search.yahoo.com/search?p={query}&b={page_no}" + self.engine_name = "Yahoo" + self.MAX_DOMAINS = 10 + self.MAX_PAGES = 0 + super(YahooEnum, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + self.q = q + return + + def extract_domains(self, resp): + link_regx2 = re.compile('(.*?)') + link_regx = re.compile('(.*?)') + links_list = [] + try: + links = link_regx.findall(resp) + links2 = link_regx2.findall(resp) + links_list = links + links2 + for link in links_list: + link = re.sub("<(\/)?b>", "", link) + if not link.startswith('http'): + link = "http://" + link + subdomain = urlparse.urlparse(link).netloc + if not subdomain.endswith(self.domain): + continue + if subdomain and subdomain not in self.subdomains and subdomain != self.domain: + if self.verbose: + self.print_("%s%s: %s%s" % (R, self.engine_name, W, subdomain)) + self.subdomains.append(subdomain.strip()) + except Exception: + pass + + return links_list + + def should_sleep(self): + return + + def get_page(self, num): + return num + 10 + + def generate_query(self): + if self.subdomains: + fmt = 'site:{domain} -domain:www.{domain} -domain:{found}' + found = ' -domain:'.join(self.subdomains[:77]) + query = fmt.format(domain=self.domain, found=found) + else: + query = "site:{domain}".format(domain=self.domain) + return query + + +class AskEnum(enumratorBaseThreaded): + def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] + base_url = 'http://www.ask.com/web?q={query}&page={page_no}&qid=8D6EE6BF52E0C04527E51F64F22C4534&o=0&l=dir&qsrc=998&qo=pagination' + self.engine_name = "Ask" + self.MAX_DOMAINS = 11 + self.MAX_PAGES = 0 + enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + self.q = q + return + + def extract_domains(self, resp): + links_list = list() + link_regx = re.compile('

(.*?)

') + try: + links_list = link_regx.findall(resp) + for link in links_list: + if not link.startswith('http'): + link = "http://" + link + subdomain = urlparse.urlparse(link).netloc + if subdomain not in self.subdomains and subdomain != self.domain: + if self.verbose: + self.print_("%s%s: %s%s" % (R, self.engine_name, W, subdomain)) + self.subdomains.append(subdomain.strip()) + except Exception: + pass + + return links_list + + def get_page(self, num): + return num + 1 + + def generate_query(self): + if self.subdomains: + fmt = 'site:{domain} -www.{domain} -{found}' + found = ' -'.join(self.subdomains[:self.MAX_DOMAINS]) + query = fmt.format(domain=self.domain, found=found) + else: + query = "site:{domain} -www.{domain}".format(domain=self.domain) + + return query + + +class BingEnum(enumratorBaseThreaded): + def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] + base_url = 'https://www.bing.com/search?q={query}&go=Submit&first={page_no}' + self.engine_name = "Bing" + self.MAX_DOMAINS = 30 + self.MAX_PAGES = 0 + enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent) + self.q = q + self.verbose = verbose + return + + def extract_domains(self, resp): + links_list = list() + link_regx = re.compile('
  • ||<|>', '', link) + if not link.startswith('http'): + link = "http://" + link + subdomain = urlparse.urlparse(link).netloc + if subdomain not in self.subdomains and subdomain != self.domain: + if self.verbose: + self.print_("%s%s: %s%s" % (R, self.engine_name, W, subdomain)) + self.subdomains.append(subdomain.strip()) + except Exception: + pass + + return links_list + + def generate_query(self): + if self.subdomains: + fmt = 'domain:{domain} -www.{domain} -{found}' + found = ' -'.join(self.subdomains[:self.MAX_DOMAINS]) + query = fmt.format(domain=self.domain, found=found) + else: + query = "domain:{domain} -www.{domain}".format(domain=self.domain) + return query + + +class BaiduEnum(enumratorBaseThreaded): + def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] + base_url = 'https://www.baidu.com/s?pn={page_no}&wd={query}&oq={query}' + self.engine_name = "Baidu" + self.MAX_DOMAINS = 2 + self.MAX_PAGES = 760 + enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + self.querydomain = self.domain + self.q = q + return + + def extract_domains(self, resp): + links = list() + found_newdomain = False + subdomain_list = [] + link_regx = re.compile('(.*?)') + try: + links = link_regx.findall(resp) + for link in links: + link = re.sub('<.*?>|>|<| ', '', link) + if not link.startswith('http'): + link = "http://" + link + subdomain = urlparse.urlparse(link).netloc + if subdomain.endswith(self.domain): + subdomain_list.append(subdomain) + if subdomain not in self.subdomains and subdomain != self.domain: + found_newdomain = True + if self.verbose: + self.print_("%s%s: %s%s" % (R, self.engine_name, W, subdomain)) + self.subdomains.append(subdomain.strip()) + except Exception: + pass + if not found_newdomain and subdomain_list: + self.querydomain = self.findsubs(subdomain_list) + return links + + def findsubs(self, subdomains): + count = Counter(subdomains) + subdomain1 = max(count, key=count.get) + count.pop(subdomain1, "None") + subdomain2 = max(count, key=count.get) if count else '' + return (subdomain1, subdomain2) + + def check_response_errors(self, resp): + return True + + def should_sleep(self): + time.sleep(random.randint(2, 5)) + return + + def generate_query(self): + if self.subdomains and self.querydomain != self.domain: + found = ' -site:'.join(self.querydomain) + query = "site:{domain} -site:www.{domain} -site:{found} ".format(domain=self.domain, found=found) + else: + query = "site:{domain} -site:www.{domain}".format(domain=self.domain) + return query + + +class NetcraftEnum(enumratorBaseThreaded): + def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] + self.base_url = 'https://searchdns.netcraft.com/?restriction=site+ends+with&host={domain}' + self.engine_name = "Netcraft" + super(NetcraftEnum, self).__init__(self.base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + self.q = q + return + + def req(self, url, cookies=None): + cookies = cookies or {} + try: + resp = self.session.get(url, headers=self.headers, timeout=self.timeout, cookies=cookies) + except Exception as e: + self.print_(e) + resp = None + return resp + + def should_sleep(self): + time.sleep(random.randint(1, 2)) + return + + def get_next(self, resp): + link_regx = re.compile('Next Page') + link = link_regx.findall(resp) + url = 'http://searchdns.netcraft.com' + link[0] + return url + + def create_cookies(self, cookie): + cookies = dict() + cookies_list = cookie[0:cookie.find(';')].split("=") + cookies[cookies_list[0]] = cookies_list[1] + # hashlib.sha1 requires utf-8 encoded str + cookies['netcraft_js_verification_response'] = hashlib.sha1(urllib.unquote(cookies_list[1]).encode('utf-8')).hexdigest() + return cookies + + def get_cookies(self, headers): + if 'set-cookie' in headers: + cookies = self.create_cookies(headers['set-cookie']) + else: + cookies = {} + return cookies + + def enumerate(self): + start_url = self.base_url.format(domain='example.com') + resp = self.req(start_url) + cookies = self.get_cookies(resp.headers) + url = self.base_url.format(domain=self.domain) + while True: + resp = self.get_response(self.req(url, cookies)) + self.extract_domains(resp) + if 'Next Page' not in resp: + return self.subdomains + break + url = self.get_next(resp) + self.should_sleep() + + def extract_domains(self, resp): + links_list = list() + link_regx = re.compile('', re.S) + token = csrf_regex.findall(resp)[0] + return token.strip() + + def enumerate(self): + self.lock = threading.BoundedSemaphore(value=70) + resp = self.req('GET', self.base_url) + token = self.get_csrftoken(resp) + params = {'csrfmiddlewaretoken': token, 'targetip': self.domain} + post_resp = self.req('POST', self.base_url, params) + self.extract_domains(post_resp) + for subdomain in self.subdomains: + t = threading.Thread(target=self.check_host, args=(subdomain,)) + t.start() + t.join() + return self.live_subdomains + + def extract_domains(self, resp): + tbl_regex = re.compile('<\/a>Host Records.*?(.*?)', re.S) + link_regex = re.compile('(.*?)
    ', re.S) + links = [] + try: + results_tbl = tbl_regex.findall(resp)[0] + except IndexError: + results_tbl = '' + links_list = link_regex.findall(results_tbl) + links = list(set(links_list)) + for link in links: + subdomain = link.strip() + if not subdomain.endswith(self.domain): + continue + if subdomain and subdomain not in self.subdomains and subdomain != self.domain: + self.subdomains.append(subdomain.strip()) + return links + + +class Virustotal(enumratorBaseThreaded): + def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] + base_url = 'https://www.virustotal.com/ui/domains/{domain}/subdomains' + self.engine_name = "Virustotal" + self.q = q + super(Virustotal, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + self.url = self.base_url.format(domain=self.domain) + return + + # the main send_req need to be rewritten + def send_req(self, url): + try: + resp = self.session.get(url, headers=self.headers, timeout=self.timeout) + except Exception as e: + self.print_(e) + resp = None + + return self.get_response(resp) + + # once the send_req is rewritten we don't need to call this function, the stock one should be ok def enumerate(self): while self.url != '': - resp_text = self.send_req(self.url) - try: - resp = json.loads(resp_text) - except: - break + resp = self.send_req(self.url) + resp = json.loads(resp) if 'error' in resp: - if self.verbose: - self.print_(R + "[!] Error: Virustotal probably now is blocking our requests" + W) + self.print_(R + "[!] Error: Virustotal probably now is blocking our requests" + W) break - self.url = resp.get('links', {}).get('next', '') + if 'links' in resp and 'next' in resp['links']: + self.url = resp['links']['next'] + else: + self.url = '' self.extract_domains(resp) return self.subdomains def extract_domains(self, resp): + #resp is already parsed as json try: - for i in resp.get('data', []): - if i.get('type') == 'domain': - subdomain = i.get('id') - if subdomain and subdomain.endswith(self.domain) and subdomain not in self.subdomains and subdomain != self.domain: + for i in resp['data']: + if i['type'] == 'domain': + subdomain = i['id'] + if not subdomain.endswith(self.domain): + continue + if subdomain not in self.subdomains and subdomain != self.domain: if self.verbose: self.print_("%s%s: %s%s" % (R, self.engine_name, W, subdomain)) self.subdomains.append(subdomain.strip()) except Exception: pass -# Note: Other specific engine classes (Bing, Baidu, etc.) follow the same pattern -# using self.verbose check before self.print_ calls. -def main(): +class ThreatCrowd(enumratorBaseThreaded): + def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] + base_url = 'https://www.threatcrowd.org/searchApi/v2/domain/report/?domain={domain}' + self.engine_name = "ThreatCrowd" + self.q = q + super(ThreatCrowd, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + return + + def req(self, url): + try: + resp = self.session.get(url, headers=self.headers, timeout=self.timeout) + except Exception: + resp = None + + return self.get_response(resp) + + def enumerate(self): + url = self.base_url.format(domain=self.domain) + resp = self.req(url) + self.extract_domains(resp) + return self.subdomains + + def extract_domains(self, resp): + try: + links = json.loads(resp)['subdomains'] + for link in links: + subdomain = link.strip() + if not subdomain.endswith(self.domain): + continue + if subdomain not in self.subdomains and subdomain != self.domain: + if self.verbose: + self.print_("%s%s: %s%s" % (R, self.engine_name, W, subdomain)) + self.subdomains.append(subdomain.strip()) + except Exception as e: + pass + + +class CrtSearch(enumratorBaseThreaded): + def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] + base_url = 'https://crt.sh/?q=%25.{domain}' + self.engine_name = "SSL Certificates" + self.q = q + super(CrtSearch, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + return + + def req(self, url): + try: + resp = self.session.get(url, headers=self.headers, timeout=self.timeout) + except Exception: + resp = None + + return self.get_response(resp) + + def enumerate(self): + url = self.base_url.format(domain=self.domain) + resp = self.req(url) + if resp: + self.extract_domains(resp) + return self.subdomains + + def extract_domains(self, resp): + link_regx = re.compile('(.*?)') + try: + links = link_regx.findall(resp) + for link in links: + link = link.strip() + subdomains = [] + if '
    ' in link: + subdomains = link.split('
    ') + else: + subdomains.append(link) + + for subdomain in subdomains: + if not subdomain.endswith(self.domain) or '*' in subdomain: + continue + + if '@' in subdomain: + subdomain = subdomain[subdomain.find('@')+1:] + + if subdomain not in self.subdomains and subdomain != self.domain: + if self.verbose: + self.print_("%s%s: %s%s" % (R, self.engine_name, W, subdomain)) + self.subdomains.append(subdomain.strip()) + except Exception as e: + print(e) + pass + +class PassiveDNS(enumratorBaseThreaded): + def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True): + subdomains = subdomains or [] + base_url = 'https://api.sublist3r.com/search.php?domain={domain}' + self.engine_name = "PassiveDNS" + self.q = q + super(PassiveDNS, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose) + return + + def req(self, url): + try: + resp = self.session.get(url, headers=self.headers, timeout=self.timeout) + except Exception as e: + resp = None + + return self.get_response(resp) + + def enumerate(self): + url = self.base_url.format(domain=self.domain) + resp = self.req(url) + if not resp: + return self.subdomains + + self.extract_domains(resp) + return self.subdomains + + def extract_domains(self, resp): + try: + subdomains = json.loads(resp) + for subdomain in subdomains: + if subdomain not in self.subdomains and subdomain != self.domain: + if self.verbose: + self.print_("%s%s: %s%s" % (R, self.engine_name, W, subdomain)) + self.subdomains.append(subdomain.strip()) + except Exception as e: + pass + + +class portscan(): + def __init__(self, subdomains, ports): + self.subdomains = subdomains + self.ports = ports + self.lock = None + + def port_scan(self, host, ports): + openports = [] + self.lock.acquire() + for port in ports: + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(2) + result = s.connect_ex((host, int(port))) + if result == 0: + openports.append(port) + s.close() + except Exception: + pass + self.lock.release() + if len(openports) > 0: + print("%s%s%s - %sFound open ports:%s %s%s%s" % (G, host, W, R, W, Y, ', '.join(openports), W)) + + def run(self): + self.lock = threading.BoundedSemaphore(value=20) + for subdomain in self.subdomains: + t = threading.Thread(target=self.port_scan, args=(subdomain, self.ports)) + t.start() + + +def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, engines, pprtscan=False, output_format='raw', no_resolve=False): + bruteforce_list = set() + search_list = set() + + if is_windows: + subdomains_queue = list() + else: + subdomains_queue = multiprocessing.Manager().list() + + # Check Bruteforce Status + if enable_bruteforce or enable_bruteforce is None: + enable_bruteforce = True + + # Validate domain + domain_check = re.compile("^(http|https)?[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$") + if not domain_check.match(domain): + if not silent: + print(R + "Error: Please enter a valid domain" + W) + return [] + + if not domain.startswith('http://') or not domain.startswith('https://'): + domain = 'http://' + domain + + parsed_domain = urlparse.urlparse(domain) + + if not silent: + print(B + "[-] Enumerating subdomains now for %s" % parsed_domain.netloc + W) + + if verbose and not silent: + print(Y + "[-] verbosity is enabled, will show the subdomains results in realtime" + W) + + supported_engines = {'baidu': BaiduEnum, + 'yahoo': YahooEnum, + 'google': GoogleEnum, + 'bing': BingEnum, + 'ask': AskEnum, + 'netcraft': NetcraftEnum, + 'dnsdumpster': DNSdumpster, + 'virustotal': Virustotal, + 'threatcrowd': ThreatCrowd, + 'ssl': CrtSearch, + 'passivedns': PassiveDNS, + 'hackertarget':HackertargetEnum + } + + chosenEnums = [] + + if engines is None: + chosenEnums = [ + BaiduEnum, YahooEnum, GoogleEnum, BingEnum, AskEnum, + NetcraftEnum, ThreatCrowd, + CrtSearch, PassiveDNS, HackertargetEnum,Virustotal + ] + else: + engines = engines.split(',') + for engine in engines: + if engine.lower() in supported_engines: + chosenEnums.append(supported_engines[engine.lower()]) + + # Start the engines enumeration + enums = [enum(domain, [], q=subdomains_queue, silent=silent, verbose=verbose) for enum in chosenEnums] + for enum in enums: + enum.start() + for enum in enums: + enum.join() + + subdomains = set(subdomains_queue) + for subdomain in subdomains: + search_list.add(subdomain) + + if enable_bruteforce: + if not silent: + print(G + "[-] Starting bruteforce module now using subbrute.." + W) + record_type = False + path_to_file = os.path.dirname(os.path.realpath(__file__)) + subs = os.path.join(path_to_file, 'subbrute', 'names.txt') + resolvers = os.path.join(path_to_file, 'subbrute', 'resolvers.txt') + process_count = threads + output = False + json_output = False + bruteforce_list = subbrute.print_target(parsed_domain.netloc, record_type, subs, resolvers, process_count, output, json_output, search_list, verbose) + + subdomains = search_list.union(bruteforce_list) + + if subdomains: + subdomains = sorted(subdomains, key=subdomain_sorting_key) + + ip_map = None + if not no_resolve: + ip_map = resolve_ips(subdomains) + + pprtscan_results = None + if pprtscan: + if not silent: + print(G + "[-] Starting Shodan InternetDB port scan.." + W) + pprtscan_results = pprt_scan(subdomains) + if not silent: + print(G + "[-] Shodan InternetDB port scan completed" + W) + + if savefile: + write_file(savefile, subdomains, output_format=output_format, port_scan_results=pprtscan_results, ip_map=ip_map) + + if not silent: + print(Y + "[-] Total Unique Subdomains Found: %s" % len(subdomains) + W) + + if ports: + if not silent: + print(G + "[-] Start port scan now for the following ports: %s%s" % (Y, ports) + W) + ports = ports.split(',') + pscan = portscan(subdomains, ports) + pscan.run() + + if not silent and not ports: + if output_format == 'json': + output = {} + items = [] + for subdomain in subdomains: + item = {'subdomain': subdomain} + if ip_map and subdomain in ip_map: + item['ip'] = ip_map[subdomain] + items.append(item) + output['subdomains'] = items + if pprtscan_results: + output['port_scan'] = pprtscan_results + print(json.dumps(output, indent=2)) + elif output_format == 'markdown': + print("# Sublist3r Results\n") + print("## Subdomains\n") + if ip_map: + print("| Subdomain | IP |") + print("|-----------|-----|") + for subdomain in subdomains: + ip = ip_map.get(subdomain, '') + print("| %s | %s |" % (subdomain, ip)) + else: + for subdomain in subdomains: + print("- %s" % subdomain) + if pprtscan_results: + print("\n## Port Scan Results\n") + print("| Subdomain | IP | Ports |") + print("|-----------|-----|-------|") + for subdomain, data in pprtscan_results.items(): + ip = data.get('ip', '') + ports_str = ', '.join(str(p) for p in data.get('ports', [])) + print("| %s | %s | %s |" % (subdomain, ip, ports_str)) + else: + if pprtscan_results: + for subdomain in subdomains: + ip = ip_map.get(subdomain, '') if ip_map else '' + if subdomain in pprtscan_results: + data = pprtscan_results[subdomain] + pports = ', '.join(str(p) for p in data.get('ports', [])) + if ip: + print("%s,%s,%s" % (subdomain, ip, pports)) + else: + print("%s,%s" % (subdomain, pports)) + else: + if ip: + print("%s,%s" % (subdomain, ip)) + else: + print(G + subdomain + W) + else: + for subdomain in subdomains: + if ip_map and subdomain in ip_map: + print("%s,%s" % (subdomain, ip_map[subdomain])) + else: + print(G + subdomain + W) + return subdomains + + +def interactive(): args = parse_args() domain = args.domain + threads = args.threads + savefile = args.output + ports = args.ports + enable_bruteforce = args.bruteforce + verbose = args.verbose + engines = args.engines + if verbose or verbose is None: + verbose = True if args.no_color: no_color() - - # Silencing the banner unless verbose - if args.verbose: - banner() - - # Threading logic and engine initialization would go here... - # (Abbreviated for focus on requested modifications) - - # After enumeration: - # subdomains = sorted(list(set(results)), key=subdomain_sorting_key) - - # if args.output: - # write_file(args.output, subdomains, verbose=args.verbose) + banner() + res = main(domain, threads, savefile, ports, silent=True, verbose=verbose, enable_bruteforce=enable_bruteforce, engines=engines, pprtscan=args.pprtscan, output_format=args.output_format, no_resolve=args.no_resolve) if __name__ == "__main__": - main() \ No newline at end of file + interactive() diff --git a/setup.py b/setup.py index e0165ca..2f20e13 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,8 @@ from setuptools import setup, find_packages setup( name='Sublist3r', - version='1.6', - python_requires='>=2.7', + version='1.7', + python_requires='>=3.7', install_requires=['dnspython', 'requests', 'argparse; python_version==\'2.7\''], packages=find_packages()+['.'], include_package_data=True, diff --git a/sublist3r.py b/sublist3r.py index 7d904b2..f67e480 100755 --- a/sublist3r.py +++ b/sublist3r.py @@ -88,7 +88,7 @@ def parse_args(): parser.error = parser_error parser._optionals.title = "OPTIONS" parser.add_argument('-d', '--domain', help="Domain name to enumerate it's subdomains", required=True) - parser.add_argument('-b', '--bruteforce', help='Enable the subbrute bruteforce module', nargs='?', default=False) + parser.add_argument('-b', '--bruteforce', help='Enable the subbrute bruteforce module', default=False, action='store_true') parser.add_argument('-p', '--ports', help='Scan the found subdomains against specified tcp ports') parser.add_argument('-v', '--verbose', help='Enable Verbosity and display results in realtime', nargs='?', default=False) parser.add_argument('-t', '--threads', help='Number of threads to use for subbrute bruteforce', type=int, default=30) @@ -353,7 +353,7 @@ class GoogleEnum(enumratorBaseThreaded): def extract_domains(self, resp): links_list = list() - link_regx = re.compile('(.*?)<\/cite>') + link_regx = re.compile(r'(.*?)') try: links_list = link_regx.findall(resp) for link in links_list: @@ -448,7 +448,7 @@ class YahooEnum(enumratorBaseThreaded): links2 = link_regx2.findall(resp) links_list = links + links2 for link in links_list: - link = re.sub("<(\/)?b>", "", link) + link = re.sub(r"<(\/)?b>", "", link) if not link.startswith('http'): link = "http://" + link subdomain = urlparse.urlparse(link).netloc @@ -544,7 +544,7 @@ class BingEnum(enumratorBaseThreaded): links_list = links + links2 for link in links_list: - link = re.sub('<(\/)?strong>||<|>', '', link) + link = re.sub(r'<(\/)?strong>||<|>', '', link) if not link.startswith('http'): link = "http://" + link subdomain = urlparse.urlparse(link).netloc @@ -763,7 +763,7 @@ class DNSdumpster(enumratorBaseThreaded): return self.live_subdomains def extract_domains(self, resp): - tbl_regex = re.compile('
    <\/a>Host Records.*?(.*?)', re.S) + tbl_regex = re.compile(r'<\/a>Host Records.*?(.*?)', re.S) link_regex = re.compile('(.*?)
    ', re.S) links = [] try: @@ -999,11 +999,11 @@ def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, e subdomains_queue = multiprocessing.Manager().list() # Check Bruteforce Status - if enable_bruteforce or enable_bruteforce is None: + if enable_bruteforce: enable_bruteforce = True # Validate domain - domain_check = re.compile("^(http|https)?[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$") + domain_check = re.compile(r"^(http|https)?[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$") if not domain_check.match(domain): if not silent: print(R + "Error: Please enter a valid domain" + W) @@ -1173,7 +1173,7 @@ def interactive(): if args.no_color: no_color() banner() - res = main(domain, threads, savefile, ports, silent=True, verbose=verbose, enable_bruteforce=enable_bruteforce, engines=engines, pprtscan=args.pprtscan, output_format=args.output_format, no_resolve=args.no_resolve) + res = main(domain, threads, savefile, ports, silent=False, verbose=verbose, enable_bruteforce=enable_bruteforce, engines=engines, pprtscan=args.pprtscan, output_format=args.output_format, no_resolve=args.no_resolve) if __name__ == "__main__": interactive()