├── subseeker_core ├── __init__.py ├── subseeker_config.json ├── useragents.py ├── create.py ├── configurations.py ├── options.py ├── subseeker.py └── searchmodes.py ├── images └── logo.jpg ├── wordlists └── keywords.txt ├── LICENSE ├── setup.py ├── CHANGELOG.md └── README.md /subseeker_core/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DFC302/subseeker/HEAD/images/logo.jpg -------------------------------------------------------------------------------- /wordlists/keywords.txt: -------------------------------------------------------------------------------- 1 | mail 2 | mx 3 | ns 4 | remote 5 | smtp 6 | blog 7 | server 8 | redbus 9 | vpn 10 | dev 11 | secure 12 | shop 13 | cloud 14 | ftp 15 | api 16 | portal 17 | test 18 | dns 19 | email 20 | host 21 | app 22 | support 23 | pop 24 | bbs 25 | web 26 | forum 27 | owa 28 | biz 29 | ops 30 | jenkins 31 | -------------------------------------------------------------------------------- /subseeker_core/subseeker_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "API_INFO":[ 3 | { 4 | "certspotter": "01", 5 | "key": "" 6 | }, 7 | 8 | { 9 | "certdb": "02", 10 | "key": "" 11 | }, 12 | 13 | { 14 | "censys": "03", 15 | "id": "", 16 | "secret": "" 17 | }, 18 | 19 | { 20 | "virustotal": "04", 21 | "key": "" 22 | }, 23 | 24 | { 25 | "securitytrails": "05", 26 | "key": "" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /subseeker_core/useragents.py: -------------------------------------------------------------------------------- 1 | from subseeker_core.options import options 2 | 3 | def useragent(): 4 | # Chrome header 5 | if options().useragent == "chrome".lower(): 6 | ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \ 7 | (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36" 8 | return ua 9 | # Firefox header, default 10 | elif options().useragent == "firefox".lower(): 11 | ua = "Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 \ 12 | Firefox/7.0.1" 13 | return ua 14 | # Opera header 15 | elif options().useragent == "opera".lower(): 16 | ua = "Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.18" 17 | return ua 18 | # If no header specifed, use set firefox 19 | elif not options().useragent: 20 | ua = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 \ 21 | Firefox/40.1" 22 | return ua 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2019 Google, Inc. http://angularjs.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as f: 4 | long_description = f.read() 5 | 6 | setuptools.setup( 7 | name='subseeker', 8 | version='2.1.2', 9 | author="Matthew Greer", 10 | author_email="pydev302@gmail.com", 11 | license='MIT', 12 | description="A sub enumeration tool.", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | url="https://github.com/DFC302/subseeker", 16 | keywords=['enumeration', 'domain', 'sub', 'tool'], 17 | packages=setuptools.find_packages(), 18 | install_requires=[ 19 | "requests", 20 | "argparse", 21 | "termcolor", 22 | ], 23 | package_data={'': ['LICENSE'], '': ['README.md'], '': ['wordlists.keywords.txt'], '': ['core.subseeker_config.json'],}, 24 | include_package_data=True, 25 | classifiers=[ 26 | "Programming Language :: Python :: 3", 27 | "License :: OSI Approved :: MIT License", 28 | "Operating System :: OS Independent", 29 | ], 30 | entry_points={ 31 | 'console_scripts': [ 32 | "subseeker = subseeker_core.subseeker:main", 33 | ], 34 | }, 35 | ) 36 | -------------------------------------------------------------------------------- /subseeker_core/create.py: -------------------------------------------------------------------------------- 1 | from subseeker_core.options import options 2 | import re 3 | 4 | class GenerateKeywords(): 5 | # Create sub domain keywords to search from 6 | def create(self): 7 | # if options().domain: 8 | # domain_regex = r"[a-zA-Z0-9].*" 9 | # domain = re.findall(domain_regex, options().domain)[0] 10 | 11 | #print(domain) 12 | # Specify regex to be used 13 | # Regex should grab every sub domain after domain 14 | regex = f"\w*\.(?={options().domain})" 15 | 16 | # Create a set to remove duplicates 17 | subset=set() 18 | 19 | with open(options().file, "r") as f: 20 | try: 21 | for domains in f: 22 | # Remove new line character 23 | domains = domains.strip("\n") 24 | domains = re.findall(regex, domains)[0] 25 | # Remove "." after sub domain output 26 | subset.add(domains.strip(".")) 27 | 28 | # If there is an index error, pass and keep parsing 29 | except IndexError: 30 | pass 31 | 32 | print("\n".join(subset)) 33 | 34 | # If write to outfile is specified 35 | if options().out: 36 | with open(options().out, "a") as wf: 37 | wf.write("\n".join(subset)) 38 | wf.write("\n") 39 | print(f"\nYour results have been written too {options().out}") 40 | -------------------------------------------------------------------------------- /subseeker_core/configurations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from colorama import Fore, Style 4 | from subseeker_core.options import options 5 | import os 6 | import re 7 | 8 | class Process(): 9 | GREEN = Fore.GREEN 10 | CYAN = Fore.CYAN 11 | MAG = Fore.MAGENTA 12 | RED = Fore.RED 13 | YELLOW = Fore.YELLOW 14 | BLUE = Fore.BLUE 15 | RESET = Style.RESET_ALL 16 | 17 | 18 | def process(self): 19 | print(f"{self.MAG}[~]{self.CYAN} Checking Crtsh...{self.RESET}") 20 | print(f"{self.MAG}[~]{self.CYAN} Checking Certspotter...{self.RESET}") 21 | print(f"{self.MAG}[~]{self.CYAN} Checking CertDB...{self.RESET}") 22 | print(f"{self.MAG}[~]{self.CYAN} Checking Censys...{self.RESET}") 23 | print(f"{self.MAG}[~]{self.CYAN} Checking VirusTotal...{self.RESET}") 24 | print(f"{self.MAG}[~]{self.CYAN} Checking ThreatCrowd...{self.RESET}") 25 | print(f"{self.MAG}[~]{self.CYAN} Checking SecurityTrails...{self.RESET}") 26 | print("\n") 27 | 28 | def title(self): 29 | if options().domain: 30 | domain_regex = r"[a-zA-Z0-9].*" 31 | domain = re.findall(domain_regex, options().domain)[0] 32 | 33 | print(f"{self.RED}\t\tSUBSEEKER{self.RESET}") 34 | print(f"{self.RED}\t========================={self.RESET}") 35 | print(f"{self.BLUE}\t DOMAIN:{self.RESET} {domain}") 36 | 37 | # Display choosen header, if not chosen, display default 38 | if options().useragent: 39 | print(f"{self.BLUE}\t User-Agent:{self.RESET} {options().useragent}") 40 | elif not options().useragent: 41 | print(f"{self.BLUE}\t User-Agent:{self.RESET} Firefox") 42 | 43 | # If user uses a file or keywords option, count number of subdomain keywords being used 44 | if options().file: 45 | print(f"{self.BLUE}\t File:{self.RESET} {options().file}") 46 | count_lines = sum(1 for line in open(options().file)) 47 | print(f"{self.BLUE}\t Keywords:{self.RESET} {count_lines}") 48 | 49 | elif options().keywords: 50 | print(f"{self.BLUE}\t Keywords:{self.RESET} {len(options().keywords)}") 51 | 52 | if options().threads: 53 | print(f"{self.BLUE}\t Threads:{self.RESET} {options().threads}") 54 | elif not options().threads: 55 | print(f"{self.BLUE}\t Threads:{self.RESET} 20") 56 | 57 | print("\n") 58 | 59 | def version(self): 60 | if options().version: 61 | print(f"{self.YELLOW}\t Title:{self.RESET} Subseeker") 62 | print(f"{self.YELLOW}\t Version:{self.RESET} 2.1.2") 63 | print(f"{self.YELLOW}\t Author:{self.RESET} Matthew Greer") 64 | print(f"{self.YELLOW}\t Twitter:{self.RESET} https://twitter.com/Vail__") 65 | print(f"{self.YELLOW}\t Github:{self.RESET} https://github.com/DFC302") 66 | 67 | def path_to_config(): 68 | name = "subseeker_config.json" 69 | path = "/" 70 | for root, dirs, files in os.walk(path): 71 | if name in files: 72 | filename = os.path.join(root, name) 73 | return filename 74 | -------------------------------------------------------------------------------- /subseeker_core/options.py: -------------------------------------------------------------------------------- 1 | # This file contains information regarding command line arguments, title 2 | # information and version information. 3 | 4 | import argparse 5 | import sys 6 | from termcolor import colored 7 | 8 | # User options 9 | def options(): 10 | parser = argparse.ArgumentParser() 11 | 12 | # specify domain 13 | parser.add_argument( 14 | "--domain", 15 | help="Specify domain to search.", 16 | action="store", 17 | ) 18 | 19 | # single search mode 20 | parser.add_argument( 21 | "--singlesearch", 22 | help="Search using a specific certificate site. Use --singlesearch options to list available search options.", 23 | action="store", 24 | type=str, 25 | ) 26 | 27 | # User can specify keywords instead of a file full of sub keywords 28 | parser.add_argument( 29 | "--keywords", 30 | nargs="+", 31 | help="Add a list of keywords.", 32 | type=str, 33 | ) 34 | 35 | # Parse subdomain keywords from other tools output files 36 | parser.add_argument( 37 | "--generate", 38 | help="Create a list of sub domain keywords from a file containing \ 39 | subdomains.", 40 | action="store_true", 41 | ) 42 | 43 | # search domain using subdomain keywords from file 44 | parser.add_argument( 45 | "--file", 46 | help="Specify a file containing keywords to parse crt.sh OR to create \ 47 | sub keywords from.", 48 | action="store", 49 | ) 50 | 51 | # Write to output file 52 | parser.add_argument( 53 | "--out", 54 | help="Specify a file to write results too.", 55 | action="store", 56 | ) 57 | 58 | # User specify number of threads 59 | parser.add_argument( 60 | "--threads", 61 | help="Specify number of threads to be used when performing keyword \ 62 | search.", 63 | action="store", 64 | type=int, 65 | ) 66 | 67 | # Try with different headers, firefox, chrome, opera 68 | parser.add_argument( 69 | "--useragent", 70 | help="Specify a user-agent to use. Default is a firefox UA.", 71 | action="store", 72 | type=str 73 | ) 74 | 75 | # If API information has been configured, allow use of API credentials 76 | parser.add_argument( 77 | "--api", 78 | help="Turn on api.", 79 | action="store_true", 80 | ) 81 | 82 | # Specify page number for certdb and/or censys 83 | parser.add_argument( 84 | "--page", 85 | help="Used with certdb and/or censys searchmodes. Specify page number to display.", 86 | action="store", 87 | type=int, 88 | ) 89 | 90 | parser.add_argument( 91 | "--version", 92 | help="Display version information", 93 | action="store_true", 94 | ) 95 | 96 | parser.add_argument( 97 | "--verbose", 98 | help="Display extra verbose information, such as errors.", 99 | action="store_true", 100 | ) 101 | 102 | # if not arguments are given, print usage message 103 | if len(sys.argv[1:]) == 0: 104 | parser.print_help() 105 | parser.exit() 106 | 107 | args = parser.parse_args() 108 | 109 | return args -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | All notable changes to this project will be kept and updated here. 4 | 5 | **Version 2.1.2** 6 | 7 | **(Nov 6, 2019)** 8 | # Bug Fixes 9 | * Fixed issues with certdb not displaying the full amount of subdomains. 10 | * Fixed a small print line issue to display when searching security trails. 11 | * When searching single sites, fixed issue where subseeker displayed searching all sites. Now it will only display, "searching for", the site you are using to parse subdomains. 12 | * Added ability to list available sites to search in singlesearch. 13 | * Added system exits after searches 14 | 15 | # Changes 16 | * Added verbose mode back in to help with errors. 17 | * Added new search site, securitytrails. (API credentials needed) 18 | * Fixed config file not found. 19 | * Able to display errors now if API credentials are not found. 20 | * Added ability to search keywords with just crtsh. 21 | 22 | **(Nov 2, 2019)** 23 | # Bug Fixes 24 | * Reduced code 25 | * Fixed issue with setup.py not correctly installing alias 26 | * Fixed issue with not finding config file for API when in another directory 27 | 28 | # Changes 29 | * Fixed CLI options. Reduced options, got rid of short options, and made options more simple. 30 | * Added more colors 31 | * Added icons for different status meanings; 32 | successful data will be printed in blue 33 | x No data found 34 | ! Error/Warnings 35 | * Created a basic regex to find domain easier 36 | * Removed verbose mode 37 | 38 | **Version 2.1.1** 39 | 40 | **(Oct 15, 2019)** 41 | # Bug Fixes 42 | * Fixed issue with censys page mode. Censys may or may not of been displaying data due to wrong type mode for page. Issue is corrected now and is working. 43 | 44 | **(Oct 10, 2019)** 45 | # Bug fixes 46 | * Fixed an issue with certspotter function in searchmodes.py, not properly writing results to output file and displaying a "no data found" error. 47 | 48 | # Changes 49 | * Updated config.json to allow Virustotal API 50 | 51 | # Features 52 | * Added Virustotal 53 | * Added ThreatCrowd 54 | 55 | # Future features 56 | * Working on implementing google 57 | 58 | **Version 2.0** 59 | 60 | **(Oct 7, 2019)** 61 | 62 | # Bug fixes 63 | * Corrected issue with JSON return error not printing on screen correctly. 64 | 65 | # Changes 66 | * Changed JSON error response for crtsh 67 | * Added error response for censys, if API credentials are not configured. 68 | 69 | # Features 70 | * Added the ability to use just certspotter, censys, or certdb from searchmodes.py 71 | * Added page option to specify page number for certdb and censys. 72 | * Updated README.md 73 | 74 | **(Oct 5, 2019)** 75 | 76 | # Bug Fixes 77 | * Corrected an issue with useragents not properly returning the correct value. 78 | * Fixed json file and corrected values in searchmodes.py 79 | 80 | # Features 81 | * Certspotter support now added 82 | * Certdb support now added 83 | * Censys support now added. 84 | * API option added to flag options. 85 | 86 | # Future features 87 | **In progress now** 88 | * Ability to use just certspotter, censys, or certdb by itself. 89 | * Adding page option to select different page results on certdb and censys. 90 | 91 | -------------------------------------------------------------------------------- /subseeker_core/subseeker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from subseeker_core.options import options 4 | from subseeker_core.searchmodes import SubSeeker 5 | from subseeker_core.configurations import Process 6 | from subseeker_core.create import GenerateKeywords 7 | from colorama import Fore, Style 8 | import sys 9 | import re 10 | import json 11 | 12 | def main(): 13 | GREEN = Fore.GREEN 14 | CYAN = Fore.CYAN 15 | MAG = Fore.MAGENTA 16 | RED = Fore.RED 17 | YELLOW = Fore.YELLOW 18 | BLUE = Fore.BLUE 19 | RESET = Style.RESET_ALL 20 | 21 | if options().version: 22 | Process().version() 23 | sys.exit(0) 24 | 25 | elif options().generate: 26 | if not options().domain: 27 | print(f"\n{RED}Error: Need domain to parse against!{RESET}\n") 28 | sys.exit(1) 29 | 30 | elif not options().file: 31 | print(f"\n{RED}Error: Need file to generate keywords from!{RESET}\n") 32 | sys.exit(1) 33 | 34 | elif options().file and options().domain: 35 | GenerateKeywords().create() 36 | sys.exit(0) 37 | 38 | elif options().file or options().keywords and not options().singlesearch: 39 | if not options().domain: 40 | print(f"\n{RED}Error: Missing domain.{RESET}\n") 41 | sys.exit(1) 42 | 43 | try: 44 | Process().title() 45 | Process().process() 46 | SubSeeker().thread_execution() 47 | SubSeeker().certspotter() 48 | SubSeeker().certdb() 49 | SubSeeker().threatcrowd() 50 | SubSeeker().censys() 51 | SubSeeker().virustotal() 52 | SubSeeker().securitytrails() 53 | SubSeeker().print_domains() 54 | sys.exit(0) 55 | 56 | except KeyboardInterrupt: 57 | sys.exit(0) 58 | 59 | elif options().singlesearch: 60 | if options().singlesearch.lower() == "options": 61 | print("\tcrtsh\t\tSearch crtsh database.") 62 | print("\tmcrtsh\t\tSearch crtsh database using keywords.") 63 | print("\tcertspotter\tSearch certspotter database.") 64 | print("\tcertdb\t\tSearch certdb database.") 65 | print("\tcensys\t\tSearch censys database.") 66 | print("\tvirustotal\tSearch virustotal database.") 67 | print("\tthreatcrowd\tSearch threatcrowd database.") 68 | print("\tsecuritytrails\tSearch securitytrails database.") 69 | 70 | if not options().domain and not options().singlesearch == "options": 71 | print(f"\n{RED}Error: Missing domain.{RESET}\n") 72 | sys.exit(1) 73 | 74 | if options().singlesearch.lower() == "crtsh": 75 | Process().title() 76 | print(f"{MAG}[~]{CYAN} Checking Crtsh...{RESET}\n") 77 | SubSeeker().crtsh() 78 | SubSeeker().print_domains() 79 | sys.exit(0) 80 | 81 | elif options().singlesearch.lower() == "certspotter": 82 | Process().title() 83 | print(f"{MAG}[~]{CYAN} Checking Certspotter...{RESET}\n") 84 | SubSeeker().certspotter() 85 | SubSeeker().print_domains() 86 | sys.exit(0) 87 | 88 | elif options().singlesearch.lower() == "certdb": 89 | Process().title() 90 | print(f"{MAG}[~]{CYAN} Checking CertDB...{RESET}\n") 91 | SubSeeker().certdb() 92 | SubSeeker().print_domains() 93 | sys.exit(0) 94 | 95 | elif options().singlesearch.lower() == "threatcrowd": 96 | Process().title() 97 | print(f"{MAG}[~]{CYAN} Checking threatcrowd...{RESET}\n") 98 | SubSeeker().threatcrowd() 99 | SubSeeker().print_domains() 100 | sys.exit(0) 101 | 102 | elif options().singlesearch.lower() == "censys": 103 | Process().title() 104 | print(f"{MAG}[~]{CYAN} Checking Censys...{RESET}\n") 105 | SubSeeker().censys() 106 | SubSeeker().print_domains() 107 | sys.exit(0) 108 | 109 | elif options().singlesearch.lower() == "virustotal": 110 | Process().title() 111 | print(f"{MAG}[~]{CYAN} Checking VirusTotal...{RESET}\n") 112 | SubSeeker().virustotal() 113 | SubSeeker().print_domains() 114 | sys.exit(0) 115 | 116 | elif options().singlesearch.lower() == "securitytrails": 117 | Process().title() 118 | print(f"{MAG}[~]{CYAN} Checking SecurityTrails...{RESET}\n") 119 | SubSeeker().securitytrails() 120 | SubSeeker().print_domains() 121 | sys.exit(0) 122 | 123 | elif options().singlesearch.lower() == "mcrtsh": 124 | if options().file or options().keywords and options().domain: 125 | Process().title() 126 | print(f"{MAG}[~]{CYAN} Checking Crtsh...{RESET}\n") 127 | SubSeeker().thread_execution() 128 | SubSeeker().print_domains() 129 | sys.exit(0) 130 | 131 | elif not options().file or not options().keywords: 132 | print(f"\n{RED}Error! Need --file with list of keywords or at least one --keyword.\n") 133 | sys.exit(1) 134 | 135 | else: 136 | if not options().domain: 137 | print(f"\n{RED}Error: Missing domain.{RESET}\n") 138 | sys.exit(1) 139 | 140 | try: 141 | Process().title() 142 | Process().process() 143 | SubSeeker().crtsh() 144 | SubSeeker().certspotter() 145 | SubSeeker().certdb() 146 | SubSeeker().threatcrowd() 147 | SubSeeker().censys() 148 | SubSeeker().virustotal() 149 | SubSeeker().securitytrails() 150 | SubSeeker().print_domains() 151 | sys.exit(0) 152 | 153 | except KeyboardInterrupt: 154 | sys.exit(0) 155 | 156 | 157 | if __name__ == "__main__": 158 | main() 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | [![Build Status](https://travis-ci.org/DFC302/subseeker.svg?branch=master)](https://travis-ci.org/DFC302/subseeker) \ 6 | ![version](https://img.shields.io/badge/version-2.1.2-dark_green) \ 7 | [![Follow on Twitter](https://img.shields.io/twitter/follow/Vail__.svg?logo=twitter)](https://twitter.com/Vail__) 8 | 9 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-dark_green.svg)](https://GitHub.com/Naereen/StrapDown.js/graphs/commit-activity) \ 10 | **Check changelog.md for latest updates** 11 | 12 | # Subseeker 13 | [![forthebadge made-with-python](http://ForTheBadge.com/images/badges/made-with-python.svg)](https://www.python.org/) 14 | 15 | 16 | # Description: 17 | Subseeker is a sub-domain enumeration tool, which simply iterates the recon process for finding subdomains from a target domain. 18 | 19 | # What makes it different? 20 | (**see example below**) 21 | Subseeker flourishes with the use of keywords. These keywords can be found in the wordlists folder. However, keywords can also be made up on the fly using the [--keywords] option. Subseeker can also use the output from other tools like sublister, subfinder, knock, etc. Using the [--generate] option on the output files from these tools, subseeker will create a list of keywords that can then be used to find deep level subdomains. This in turn, creates a huge list of subdomains returned for the user. 22 | 23 | Subseeker also parses all domains and keywords into a set, so dupliate domains are removed. No need to uniquely sort the output files. 24 | 25 | Subseeker can also parse data from certificate sites individually, if the user does not want to search all certificate sites at once using the [--singlesearch (option)] option. 26 | 27 | Subseeker parses data from crtsh, certdb, censys, certspotter, threatcrowd, virustotal, securitytrails. 28 | 29 | 30 | # Requirements 31 | Python 3.x 32 | 33 | **Python Modules** \ 34 | sys \ 35 | re \ 36 | platform \ 37 | requests \ 38 | argparse \ 39 | concurrent.futures \ 40 | subprocess \ 41 | colorama \ 42 | json \ 43 | os 44 | 45 | # Installation 46 | python3 setup.py install 47 | 48 | # Manual installation 49 | git clone https://github.com/DFC302/subseeker.git \ 50 | chmod 755 core/subseeker.py 51 | 52 | # Usage 53 | ``` 54 | usage: subseeker [-h] [--domain DOMAIN] [--singlesearch SINGLESEARCH] 55 | [--keywords KEYWORDS [KEYWORDS ...]] [--generate] 56 | [--file FILE] [--out OUT] [--threads THREADS] 57 | [--useragent USERAGENT] [--api] [--page PAGE] [--version] 58 | [--verbose] 59 | 60 | optional arguments: 61 | -h, --help show this help message and exit 62 | --domain DOMAIN Specify domain to search. 63 | --singlesearch SINGLESEARCH 64 | Search using a specific certificate site. Use 65 | --singlesearch options to list available search 66 | options. 67 | --keywords KEYWORDS [KEYWORDS ...] 68 | Add a list of keywords. 69 | --generate Create a list of sub domain keywords from a file 70 | containing subdomains. 71 | --file FILE Specify a file containing keywords to parse crt.sh OR 72 | to create sub keywords from. 73 | --out OUT Specify a file to write results too. 74 | --threads THREADS Specify number of threads to be used when performing 75 | keyword search. 76 | --useragent USERAGENT 77 | Specify a user-agent to use. Default is a firefox UA. 78 | --api Turn on api. 79 | --page PAGE Used with certdb and/or censys searchmodes. Specify 80 | page number to display. 81 | --version Display version information 82 | --verbose Display extra verbose information, such as errors. 83 | ``` 84 | 85 | # Usage 86 | **Note: API credentials needed for censys, virustotal, securitytrails.** 87 | 88 | **subseeker default search** \ 89 | Description: Search for subdomains from a top level domain. Wildcard notation excepted. 90 | usage: subseeker --domain [domain] \ 91 | EX: subseeker --domain *.example.com 92 | 93 | OPTIONAL ARGUMENTS: \ 94 | --useragent Choose a different useragent, default is Firefox. \ 95 | --out Choose to send results to an output file. \ 96 | --api Use an API to search certspotter, certdb, and censys.io (needed for censys, virustotal, securitytrails) \ 97 | --page Specify page number for certdb and censys \ 98 | 99 | **subseeker keyword search** \ 100 | Description: Search for subdomains from a top level domain using keywords to find deep level subdomains.(will also search domain through certspotter, censys, Virustotal, and ThreatCrowd) 101 | 102 | **WARNING: For searchmodes other than crtsh, some sites do not accept wildcard notation. So subseeker will fix the domain for you. For example, if you do something like: \*.example.com, subseeker will fix it to: example.com. Since crtsh accepts wildcard notation it will not be fixed! Whatever domain you type in is the domain that gets parsed, for example: \*.yahoo.com, when using keywords from [--keywords] option or from a file, will come out like so \*[keyword]\*\*.example.com with an extra star. This will most likely cause crtsh to not return results.** 103 | 104 | Note: keywords are processed like so: \*[keyword]\*.[domain] \ 105 | Note: If keywords are written to a file, each keyword should be on a new line, like so: 106 | 107 | dev \ 108 | test \ 109 | ops \ 110 | mail 111 | 112 | usage: subseeker --domain [domain] --file [file containing subdomain keywords] \ 113 | usage: subseeker --domain [domain] --keywords [keywords (separated by spaces, NOT COMMAS!!!)] \ 114 | EX: subseeker --domain example.com --file domain_keywords.txt 115 | EX: subseeker --domain example.com --keywords test dev product 116 | 117 | OPTIONAL ARGUMENTS: \ 118 | --useragent Choose a different useragent, default is Firefox. \ 119 | --threads Choose number of threads. \ 120 | --out Choose to send results to an output file. \ 121 | --keywords Choose keywords to parse domains with. \ 122 | --api Enable search with API credentials. \ 123 | --page Specify page mode for censys and certdb. 124 | 125 | The keywords.txt file is a file that is provided for you, that can be used with keyword searches. 126 | 127 | **subseeker parse generate keywords** \ 128 | Description: Parse through sublister, certspotter, etc. text outputs and create sub domain keywords. \ 129 | 130 | **Special Note: If using sublist3r, use sublist3r's option [-o] to send results to outfile. (Subseeker is designed to parse from a text file. Using standard redirection ">",">>", copies ANSI color codes, which will conflict with parsing.)** 131 | 132 | usage: subseeker --generate --domain [domain] --file [file contaning output from certspotter, sublister, etc. results] \ 133 | **Domain should be without wildcard notation, www, https, etc. Just the name of the domain and level (com, org, etc)** \ 134 | EX: subseeker --generate --domain example.com --file results.txt 135 | 136 | OPTIONAL ARGUMENTS: \ 137 | --out Choose to send results to an output file. 138 | 139 | **subseeker singlesearch** \ 140 | Description: Search crtsh, certspotter, certdb, censys, virustotal, threatcrowd, individually. \ 141 | usage: subseeker --singlesearch [site option] 142 | 143 | **Use --singlesearch options (display available options.)** 144 | 145 | Options: \ 146 | crtsh | Search crtsh database\ 147 | mcrtsh | Search crtsh database using keywords 148 | certspotter | Search certspotter database \ 149 | certdb | Search certdb database \ 150 | censys | Search censys database (need API) \ 151 | virustotal | Search virustotal database (need API) \ 152 | threatcrowd | Search threatcrowd database \ 153 | securitytrails | Search security trails database. (need API) 154 | 155 | EX: subseeker --singlesearch certspotter 156 | 157 | OPTIONAL ARGUMENTS: \ 158 | --useragent Choose a different useragent, default is Firefox. \ 159 | --out Choose to send results to an output file. \ 160 | --api Use an API to search certspotter, certdb, and censys.io (needed for censys, virustotal, securitytrails) \ 161 | --page Specify page number for certdb and censys \ 162 | 163 | **Configure API credentials in core/subseeker_config.json file.** 164 | 165 | # Example usage: 166 | **Here is an example usage on how you can fully take advantage of subseeker.** 167 | 168 | Yes, subseeker can locate subdomains on its own. However, it was built around the idea of using sub domain keywords to parse crtsh for even greater results. You can aquire these sub domain keywords by using subeeker to parse crtsh, censys, certspotter, and certdb into an output file or files and/or by using other subdomain tools. From there, using the [--generate] option, subseeker will parse the second layer of each domain to create keywords. 169 | 170 | For example, say I generate subdomains like so: \ 171 | test.example.com \ 172 | test.dev.example.com \ 173 | products.example.com 174 | 175 | Using the [--generate] option will then create a list of keywords like so: \ 176 | test \ 177 | dev \ 178 | products 179 | 180 | From there this list can be used to generate even more subdomains from crt.sh. Subseeker will automatically search with the syntax like so: \ 181 | \*test\*.example.com \ 182 | \*dev\*.example.com \ 183 | \*products\*.example.com 184 | 185 | This is why using other subdomain tools and parsing the results into output files can then be used to generate a huge list of keywords to parse crt.sh with. 186 | 187 | | TOOL | TIME | NUMBER OF KEYWORDS USED | SUBDOMAINS FOUND | THREAD COUNT | 188 | | --- | --- | --- | --- | --- | 189 | | subseeker | 8m9.243s | 1348 | 56793 | 10 (Default) 190 | 191 | **Using Concurrency** 192 | 193 | | TOOL | TIME | NUMBER OF KEYWORDS USED | SUBDOMAINS FOUND | THREAD COUNT | 194 | | --- | --- | --- | --- | --- | 195 | | subseeker | 4m49.491s | 1348 | 57335 | 200 (Max tested) | 196 | 197 | # Author: 198 | Coded by Matthew Greer \ 199 | Twitter: \ 200 | Email: DFC302@protonmail.com \ 201 | **Tested mainly on Linux** 202 | **Partially tested on Windows** 203 | 204 | ### Useful? Like the project and wanna show support? 205 | Buy Me A Coffee 206 | -------------------------------------------------------------------------------- /subseeker_core/searchmodes.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | import json 4 | import os 5 | import sys 6 | import concurrent.futures 7 | import subseeker_core.useragents 8 | import platform 9 | import subprocess 10 | from subseeker_core.options import options 11 | from colorama import Fore, Style 12 | 13 | class SubSeeker(): 14 | GREEN = Fore.GREEN 15 | RED = Fore.RED 16 | YELLOW = Fore.YELLOW 17 | BLUE = Fore.BLUE 18 | WHITE = Fore.WHITE 19 | RESET = Style.RESET_ALL 20 | 21 | if options().domain: 22 | # domain_regex = r"([^\.]*.(?=com).+)" 23 | domain_regex = r"[a-zA-Z0-9].*" 24 | domain = re.findall(domain_regex, options().domain)[0] 25 | 26 | # if user uses api, search for config file, that way no matter what directory user is in, 27 | # subseeker can find config file. 28 | if options().api: 29 | 30 | if options().verbose: 31 | print(f"\n{YELLOW}[!] Looking for config file containing API credentials...{RESET}") 32 | 33 | for root, dirs, files in os.walk("/"): 34 | filename = "subseeker_config.json" 35 | if filename in files: 36 | config_file = os.path.join(root, filename) 37 | 38 | if options().verbose: 39 | print(f"{GREEN}[+] Config file found: {config_file}{RESET}") 40 | print(f"{GREEN}[+] Starting search...{RESET}\n") 41 | 42 | 43 | 44 | domains = set() 45 | 46 | # Default value for API key is false, if user does not have one. 47 | def __init__(self, apikey=False, params=None, page=1): 48 | self.apikey = apikey 49 | self.params = params 50 | self.page = page 51 | self.threads = 20 52 | 53 | def crtsh(self): 54 | org_domain = options().domain.replace("*", "%25") 55 | url = f"https://crt.sh/?q={org_domain}&output=json" 56 | 57 | try: 58 | response = requests.get(url, headers={'User-Agent':subseeker_core.useragents.useragent()}) 59 | regex = r'[^%*].*' 60 | data = response.json() 61 | 62 | if data: 63 | for row in data: 64 | row = re.findall(regex, row["name_value"])[0] 65 | self.domains.add(row) 66 | 67 | elif not data: 68 | print(f"{self.RED}[x] No data found for {options().domain} using {self.WHITE}crtsh.{self.RESET}") 69 | 70 | except ValueError: 71 | pass 72 | 73 | def certspotter(self): 74 | try: 75 | if options().api: 76 | with open(self.config_file, "r") as f: 77 | 78 | jfile = json.load(f) 79 | self.apikey = jfile["API_INFO"][0]["key"] 80 | 81 | if self.apikey == "": 82 | print(f"{self.YELLOW}[!] API credentials not found for {self.WHITE}certspotter.{self.RESET}") 83 | 84 | else: 85 | params = {'Authorization': 'Bearer ' + self.apikey} 86 | 87 | url = f"https://api.certspotter.com/v1/issuances?domain={self.domain}&include_subdomains=true&expand=dns_names&expand=cert" 88 | response = requests.get(url, params=self.params, headers={'User-Agent':subseeker_core.useragents.useragent()}) 89 | data = response.json() 90 | 91 | if data: 92 | for row in data: 93 | row = row["dns_names"] 94 | 95 | for domain in row: 96 | self.domains.add(row) 97 | 98 | elif not data: 99 | print(f"{self.RED}[x] No data found for {options().domain} using {self.WHITE}certspotter.{self.RESET}") 100 | 101 | except TypeError as e: 102 | pass 103 | 104 | except IndexError: 105 | pass 106 | 107 | except KeyError: 108 | pass 109 | 110 | except ValueError: 111 | pass 112 | 113 | except AttributeError: 114 | print(f"{self.YELLOW}[!] Config file not found!{self.RESET}") 115 | print(f"{self.YELLOW}[!] System exiting now. Fix config file or run without --api command.{self.RESET}\n") 116 | sys.exit(1) 117 | 118 | def certdb(self): 119 | if options().page: 120 | page = options().page 121 | 122 | else: 123 | page = self.page 124 | 125 | try: 126 | # Default false 127 | if options().api: 128 | with open(self.config_file, "r") as f: 129 | jfile = json.load(f) 130 | self.apikey = jfile["API_INFO"][1]["key"] 131 | 132 | if self.apikey == "": 133 | print(f"{self.YELLOW}[!] API credentials not found for {self.WHITE}certdb.{self.RESET}") 134 | 135 | url = f"https://api.spyse.com/v1/subdomains?api_token={self.apikey}&domain={self.domain}&page={page}" 136 | 137 | response = requests.get(url, headers={'User-Agent':subseeker_core.useragents.useragent()}) 138 | data = response.json() 139 | 140 | if data: 141 | for row in data["records"]: 142 | subdomains = row["domain"] 143 | self.domains.add(subdomains) 144 | 145 | elif not data: 146 | print(f"{self.RED}[x] No data found for {options().domain} using {self.WHITE}certdb. {self.YELLOW}Page: {page}{self.RESET}") 147 | 148 | except KeyError: 149 | pass 150 | 151 | except IndexError: 152 | pass 153 | 154 | except ValueError: 155 | pass 156 | 157 | except AttributeError: 158 | print(f"{self.YELLOW}[!] Config file not found!{self.RESET}") 159 | print(f"{self.YELLOW}[!] System exiting now. Fix config file or run without --api command.{self.RESET}\n") 160 | sys.exit(1) 161 | 162 | def censys(self): 163 | if options().page: 164 | page = options().page 165 | 166 | else: 167 | page = self.page 168 | 169 | try: 170 | if options().api: 171 | with open(self.config_file, "r") as f: 172 | jfile = json.load(f) 173 | self.apikey = jfile["API_INFO"][2]["id"] 174 | secret = jfile["API_INFO"][2]["secret"] 175 | 176 | if self.apikey == "" or secret == "": 177 | print(f"{self.YELLOW}[!] API credentials not found for {self.WHITE}censys.{self.RESET}") 178 | 179 | api_url = "https://censys.io/api/v1/search/certificates" 180 | 181 | regex = r'(?:CN=).+' 182 | params = {"query":f"{self.domain}", "page":page} 183 | 184 | response = requests.post(api_url, json=params, auth=(self.apikey, secret), headers={'User-Agent':subseeker_core.useragents.useragent()}) 185 | data = response.json() 186 | 187 | if data: 188 | for row in data["results"]: 189 | CN = row["parsed.subject_dn"].splitlines() 190 | 191 | for line in CN: 192 | line = re.findall(regex, line)[0][3:] 193 | self.domains.add(line) 194 | 195 | elif not data: 196 | print(f"{self.RED}[x] No data found for {options().domain} using {self.WHITE}censys. {self.YELLOW}Page: {page}{self.RESET}") 197 | 198 | elif not options().api: 199 | if options().verbose: 200 | print(f"{self.YELLOW}[!] API credentials needed for {self.WHITE}censys.{self.RESET}") 201 | pass 202 | 203 | elif not options().api: 204 | pass 205 | 206 | 207 | except IndexError: 208 | pass 209 | 210 | except KeyError: 211 | pass 212 | 213 | except ValueError: 214 | pass 215 | 216 | except AttributeError: 217 | print(f"{self.YELLOW}[!] Config file not found!{self.RESET}") 218 | print(f"{self.YELLOW}[!] System exiting now. Fix config file or run without --api command.{self.RESET}\n") 219 | sys.exit(1) 220 | 221 | def virustotal(self): 222 | try: 223 | if options().api: 224 | with open(self.config_file, "r") as f: 225 | jfile = json.load(f) 226 | self.apikey = jfile["API_INFO"][3]["key"] 227 | 228 | if self.apikey == "": 229 | print(f"{self.YELLOW}[!] API credentials not found for {self.WHITE}virustotal.{self.RESET}") 230 | 231 | api_url = "https://www.virustotal.com/vtapi/v2/domain/report" 232 | params = {"apikey":f"{self.apikey}", "domain":f"{self.domain}"} 233 | 234 | response = requests.get(api_url, params=params, headers={'User-Agent':subseeker_core.useragents.useragent()}) 235 | 236 | data = response.json() 237 | 238 | if data: 239 | subdomains = "\n".join(data["subdomains"]) 240 | self.domains.add(subdomains) 241 | 242 | elif not data: 243 | print(f"{self.RED}[x] No data found for {options().domain} using {self.WHITE}virustotal.{self.RESET}") 244 | 245 | elif not options().api: 246 | if options().verbose: 247 | print(f"{self.YELLOW}[!] API credentails needed for {self.WHITE}virustotal.{self.RESET}") 248 | pass 249 | 250 | elif not options().api: 251 | pass 252 | 253 | except KeyError: 254 | pass 255 | 256 | except IndexError: 257 | pass 258 | 259 | except ValueError: 260 | pass 261 | 262 | except AttributeError: 263 | print(f"{self.YELLOW}[!] Config file not found!{self.RESET}") 264 | print(f"{self.YELLOW}[!] System exiting now. Fix config file or run without --api command.{self.RESET}\n") 265 | sys.exit(1) 266 | 267 | def threatcrowd(self): 268 | try: 269 | api_url = "http://www.threatcrowd.org/searchApi/v2/domain/report/" 270 | params = {"domain":f"{self.domain}"} 271 | 272 | response = requests.get(api_url, params=params, headers={'User-Agent':subseeker_core.useragents.useragent()}) 273 | 274 | data = json.loads(response.text) 275 | data = data["subdomains"] 276 | 277 | if data: 278 | for row in data: 279 | self.domains.add(row) 280 | 281 | elif not data: 282 | print(f"{self.RED}[x] No data found for {options().domain} using {self.WHITE}threatcrowd.{self.RESET}") 283 | 284 | except KeyError: 285 | pass 286 | 287 | except IndexError: 288 | pass 289 | 290 | except ValueError: 291 | pass 292 | 293 | def securitytrails(self): 294 | try: 295 | if options().api: 296 | with open(self.config_file, "r") as f: 297 | jfile = json.load(f) 298 | self.apikey = jfile["API_INFO"][4]["key"] 299 | 300 | if self.apikey == "": 301 | print(f"{self.YELLOW}[!] API credentials not found for {self.WHITE}securitytrails.{self.RESET}") 302 | 303 | api_url = f"https://api.securitytrails.com/v1/domain/{self.domain}/subdomains" 304 | params = {"apikey":f"{self.apikey}"} 305 | response = requests.get(api_url, params=params, headers={'User-Agent':subseeker_core.useragents.useragent()}) 306 | data = json.loads(response.text) 307 | 308 | if data: 309 | for row in data["subdomains"]: 310 | row = f"{row}.{self.domain}" 311 | self.domains.add(row) 312 | 313 | elif not data: 314 | print(f"{self.RED}[x] No data found for {options().domain} using {self.WHITE}securitytrails.{self.RESET}") 315 | 316 | elif not options().api: 317 | if options().verbose: 318 | print(f"{self.YELLOW}[!] API credentials needed for {self.WHITE}securitytrails.{self.RESET}") 319 | pass 320 | 321 | elif not options().api: 322 | pass 323 | 324 | except KeyError as e: 325 | pass 326 | 327 | except IndexError as e: 328 | pass 329 | 330 | except ValueError as e: 331 | pass 332 | 333 | except AttributeError: 334 | print(f"{self.YELLOW}[!] Config file not found!{self.RESET}") 335 | print(f"{self.YELLOW}[!] System exiting now. Fix config file or run without --api command.{self.RESET}\n") 336 | sys.exit(1) 337 | 338 | 339 | def thread_execution(self): 340 | domains = [] 341 | 342 | if options().file: 343 | with open(options().file, "r") as f: 344 | for sub in f: 345 | 346 | if sub == "": 347 | pass 348 | else: 349 | sub = str(sub.strip("\n")) 350 | domains.append(sub) 351 | 352 | elif options().keywords: 353 | for sub in options().keywords: 354 | domains.append(sub) 355 | 356 | if options().threads: 357 | threads = options().threads 358 | else: 359 | threads = self.threads 360 | 361 | with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor: 362 | executor.map(self.multi_keyword_search, domains) 363 | 364 | # Parse crt.sh for each sub domain keyword 365 | def multi_keyword_search(self, sub): 366 | try: 367 | url = f"https://crt.sh/?q=%25{sub}%25.{self.domain}&output=json" 368 | response = requests.get(url, headers={'User-Agent':subseeker_core.useragents.useragent()}) 369 | regex = r'[^%*].*' 370 | data = response.json() 371 | 372 | if data: 373 | for row in data: 374 | row = re.findall(regex, row["name_value"])[0] 375 | self.domains.add(row) 376 | 377 | elif not data: 378 | print(f"{self.RED}[x] No data found:{self.WHITE} {sub}{self.RESET}") 379 | 380 | # JSON Decode Error 381 | except ValueError: 382 | print(f"{self.YELLOW}[!]{self.RED} JSON value error:{self.WHITE} {sub}{self.RESET}") 383 | pass 384 | 385 | def print_domains(self): 386 | subdomains = "\n".join(self.domains) 387 | print(f"{self.BLUE}{subdomains}{self.RESET}") 388 | 389 | if options().out: 390 | with open(options().out, "a") as f: 391 | f.write(str(subdomains)) 392 | f.write("\n") 393 | --------------------------------------------------------------------------------