Skip to content

Commit 3d54091

Browse files
lot's of patch, great optimization, better comments
1 parent a9793f8 commit 3d54091

18 files changed

+3766
-3365
lines changed

api/global_parser.py

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,48 @@
1+
import importlib
2+
import pkgutil
13
import lib.custom_logger as custom_logger
24
from lib import generics as gen
35
from lib.configuration import configuration
4-
from api import *
5-
import os
6+
import api
67

78
logger = custom_logger.logger
89

910

10-
def main(domain: str, config: configuration):
11-
# get all the lib in the api folder and call the main function
11+
def main(domain: str, config: configuration) -> list:
12+
"""
13+
This function is the entry point of the program. It takes a domain name and a configuration object as parameters.
14+
:param domain: The domain name to search for subdomains.
15+
:type domain: str
16+
:param config: The configuration object.
17+
:type config: configuration
18+
:return: A list of subdomains.
19+
:rtype: list
20+
"""
1221
results = []
13-
for os_file in os.listdir(os.path.dirname(__file__)):
14-
if os_file.endswith(".py") and os_file not in [
15-
"__init__.py",
16-
"global_parser.py",
17-
]:
18-
try:
19-
try:
20-
data = config.config["API"]["mapper"][os_file.split(".")[0]]
21-
if not data["activate"]:
22-
logger.info(f"[*] {os_file} is deactivated")
23-
continue
24-
key = data["api_key"]
25-
if key == "":
26-
key == None
27-
except:
28-
logger.info(
29-
"[*] This API seems to not appear in the config file or no key is provided"
30-
)
31-
logger.info("[*] Continuing without API key")
32-
key = None
33-
logger.info(f"[*] Getting subdomains of {domain} from {os_file}")
34-
results += eval(
35-
os_file.split(".")[0] + ".main(domain, config.handler, key)"
36-
)
37-
if len(results) == 0:
38-
warn = ""
39-
logger.info(f"[*] No subdomains of {domain} found from {os_file}")
40-
else:
41-
# remove duplicates
42-
results = list(dict.fromkeys(results))
43-
logger.info(f"[*] {len(results)} subdomains of {domain} found")
44-
except Exception as e:
45-
logger.error(f"Impossible to get subdomains of {domain} from {os_file}")
22+
# Get a list of all the modules in the api package
23+
for _, module_name, _ in pkgutil.walk_packages(api.__path__):
24+
if module_name in ["global_parser", "__init__"]:
25+
continue
26+
# Dynamically import the module
27+
module = importlib.import_module(f"api.{module_name}")
28+
try:
29+
# Call the main() function of the module
30+
data = config.config["API"]["mapper"][module_name]
31+
if not data["activate"]:
32+
logger.info(f"[*] {module_name} is deactivated")
4633
continue
34+
key = data["api_key"] or None
35+
logger.info(f"[*] Getting subdomains of {domain} from {module_name}")
36+
results += module.main(domain, config.handler, key)
37+
if len(results) == 0:
38+
logger.info(f"[*] No subdomains of {domain} found from {module_name}")
39+
else:
40+
# Remove duplicates and invalid subdomains
41+
results = list(set(results))
42+
logger.info(f"[*] {len(results)} subdomains of {domain} found")
43+
except Exception as e:
44+
logger.error(
45+
f"Impossible to get subdomains of {domain} from {module_name}: {e}"
46+
)
47+
continue
4748
return results

api/hackertarget.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,33 @@
55
logger = custom_logger.logger
66

77

8-
def main(domain, handler: handler, key: str):
9-
# get all the subdomain of the domain from hackertarget
10-
# the url is https://api.hackertarget.com/hostsearch/?q={domain}
11-
# key is the api key
8+
def main(domain: str, handler: handler, key: str = "") -> list:
9+
"""
10+
Get all the subdomains of the domain from hackertarget.
11+
The url is https://api.hackertarget.com/hostsearch/?q={domain}.
12+
:param domain: The domain to search for subdomains.
13+
:param handler: The handler to use for the request.
14+
:param key: The API key to use for the request.
15+
:return: A list of subdomains.
16+
"""
17+
# Construct the URL for the API request.
1218
if key:
13-
url = "https://api.hackertarget.com/hostsearch/?q=" + domain + "&apikey=" + key
19+
url = f"https://api.hackertarget.com/hostsearch/?q={domain}&apikey={key}"
1420
else:
15-
url = "https://api.hackertarget.com/hostsearch/?q=" + domain
21+
url = f"https://api.hackertarget.com/hostsearch/?q={domain}"
1622
try:
23+
# Make the API request and decode the response.
1724
response = handler.get(url, until_ok=True)._body.decode("utf-8")
1825
if response == "API count exceeded - Increase Quota with Membership":
1926
raise Exception("API")
20-
# split the response in linesr
27+
# Split the response into lines.
2128
lines = response.split("\n")
22-
# get all the subdomains
23-
subdomains = []
24-
for line in lines:
25-
if line != "" and "*" not in line.split(",")[0]:
26-
subdomains.append(line.split(",")[0])
27-
# delete all the occurences in the list
28-
29+
# Get all the subdomains.
30+
subdomains = [
31+
line.split(",")[0]
32+
for line in lines
33+
if line != "" and "*" not in line.split(",")[0]
34+
]
2935
return subdomains
3036
except Exception as e:
3137
if e.args[0] == "API":

lib/configuration.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@
1515

1616

1717
class configuration:
18-
"""Configuration is ./congiuration.yaml"""
18+
"""Configuration is ./configuration.yaml"""
1919

2020
def __init__(self):
21+
"""
22+
Initializes the configuration object by loading the configuration.yaml file and checking its version.
23+
:param self: The configuration object.
24+
:return: None.
25+
"""
2126
try:
2227
self.config = yaml.load(
2328
open("configuration.yaml", "r"), Loader=yaml.FullLoader
@@ -64,6 +69,11 @@ def __init__(self):
6469
exit()
6570

6671
def load(self):
72+
"""
73+
Loads the proxy file if it exists, otherwise it activates the proxy and loads the proxies from the links specified in the configuration.yaml file.
74+
:param self: The configuration object.
75+
:return: None.
76+
"""
6777
if self.config["Proxy"]["file"]:
6878
if os.path.isfile(self.config["Proxy"]["file"]):
6979
self.proxy_file = self.config["proxy"]["file"]
@@ -106,7 +116,16 @@ def load(self):
106116
# self.check_proxy()
107117

108118
def is_in_scope(self, to_test: str, mode: str):
109-
"""Check if a fqdn or ip is in scope"""
119+
"""
120+
Check if a fqdn or ip is in scope.
121+
122+
:param to_test: The fqdn or ip to test.
123+
:type to_test: str
124+
:param mode: The mode to use for testing.
125+
:type mode: str
126+
:return: True if the fqdn or ip is in scope, False otherwise.
127+
:rtype: bool
128+
"""
110129
scope = []
111130
if self.config["SCOPE"][mode]["file"]:
112131
if os.path.isfile(self.config["SCOPE"][mode]["file"]):
@@ -121,21 +140,24 @@ def is_in_scope(self, to_test: str, mode: str):
121140
for i in scope:
122141
if i == to_test:
123142
return True
124-
else:
125-
return False
126143
if self.config["SCOPE"][mode]["regex"]:
127144
# example : regex: ["r'tesla'"]
128145
for regex in self.config["SCOPE"][mode]["regex"]:
129146
# convert it to r
130147
regex = r"{}".format(regex)
131148
if re.search(regex, to_test):
132149
return True
133-
else:
134-
return False
135150
if len(scope) == 0:
136151
return True
152+
return False
137153

138154
def is_there_scope(self):
155+
"""
156+
Check if there is a scope.
157+
158+
:return: True if there is a scope, False otherwise.
159+
:rtype: bool
160+
"""
139161
mode = ["IPs", "FQDNs"]
140162
there_is = False
141163
for i in mode:
@@ -148,11 +170,11 @@ def is_there_scope(self):
148170
return there_is
149171

150172
def get_github_proxy(self, links: dict):
151-
"""Get a proxy from github.com
152-
Format must be ip:port for each line
153-
links{
154-
link:type (http, https, socks)
155-
}
173+
"""
174+
Get a proxy from github.com.
175+
176+
:param links: The links to get the proxy from.
177+
:type links: dict
156178
"""
157179
for link, type in links.items():
158180
try:
@@ -167,13 +189,13 @@ def get_github_proxy(self, links: dict):
167189
self.socks_proxy.append(line)
168190
else:
169191
logger.warning(
170-
"[!] Warning : {} returned status code {}".format(
192+
"[!] Warning: {} returned status code {}".format(
171193
link, r.status_code
172194
)
173195
)
174196
except:
175-
logger.warning("[!] Warning : {} returned an error".format(link))
176-
# check if proxy are working
197+
logger.warning("[!] Warning: {} returned an error".format(link))
198+
# Check if proxies are working
177199

178200
def check_proxy(self):
179201
# open proxy_test.txt and add all line to a list

lib/domain.py

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,70 +3,62 @@
33
import lib.custom_logger as custom_logger
44
import api.global_parser as global_parser
55
from lib.configuration import configuration
6+
import re
67

78
logger = custom_logger.logger
89

910

11+
def valid_fqdn(fqdn: str) -> bool:
12+
fqdn_regex = re.compile(
13+
r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.(?!-)[A-Za-z0-9-]{1,63}(?<!-)(?:\.(?!-)[A-Za-z0-9-]{1,63}(?<!-))*$"
14+
)
15+
if fqdn_regex.match(fqdn) is None:
16+
return False
17+
else:
18+
if fqdn != "\\r" and "." in fqdn:
19+
return True
20+
21+
1022
class domain:
1123
def __init__(self, name: str, config: configuration):
24+
"""
25+
Initializes a domain object with a given name and configuration.
26+
27+
:param name: The name of the domain.
28+
:param config: The configuration object to use.
29+
"""
1230
self.name = name
1331
self.config = config
1432
self.subdomains = []
15-
self.ip = self.get_ip()
33+
if valid_fqdn(self.name):
34+
self.ip = self.get_ip()
35+
else:
36+
self.ip = "Invalid"
1637
self.handler = self.config.handler
1738
self.trough_proxy = config.api_trough_proxy
1839

1940
def get_subs(self, ip_trough_proxy: bool = False):
41+
"""
42+
Gets all the subdomains of the domain.
43+
44+
:param ip_trough_proxy: Whether to get the IP through a proxy.
45+
:return: A list of subdomains.
46+
"""
2047
# get all the subdomains of the domain
2148
changed = False
2249
if not self.trough_proxy and self.handler.there_is_proxy():
2350
logger.info("[*] Disabling proxy for requesting API")
2451
changed = True
2552
olds = self.handler.remove_proxys()
2653
logger.info(f"[*] Getting subdomains for {self.name}")
27-
28-
self.subdomains += global_parser.main(self.name, self.config)
54+
self.subdomains += [
55+
subdomain
56+
for subdomain in global_parser.main(self.name, self.config)
57+
if subdomain and valid_fqdn(subdomain)
58+
]
2959
if changed:
3060
logger.info("[*] Re-enabling proxy")
3161
self.handler.add_proxys(olds)
32-
# get the subdomains from bufferover
33-
# self.subdomains += bufferover_parser(self.name)
34-
# # get the subdomains from threatcrowd
35-
# self.subdomains += threatcrowd_parser(self.name)
36-
# # get the subdomains from threatminer
37-
# self.subdomains += threatminer_parser(self.name)
38-
# # get the subdomains from virustotal
39-
# self.subdomains += virustotal_parser(self.name)
40-
# # get the subdomains from anubis
41-
# self.subdomains += anubis_parser(self.name)
42-
# # get the subdomains from securitytrails
43-
# self.subdomains += securitytrails_parser(self.name)
44-
# # get the subdomains from certspotter
45-
# self.subdomains += certspotter_parser(self.name)
46-
# # get the subdomains from riddler
47-
# self.subdomains += riddler_parser(self.name)
48-
# # get the subdomains from sublist3r
49-
# self.subdomains += sublist3r_parser(self.name)
50-
# # get the subdomains from subfinder
51-
# self.subdomains += subfinder_parser(self.name)
52-
# # get the subdomains from amass
53-
# self.subdomains += amass_parser(self.name)
54-
# # get the subdomains from assetfinder
55-
# self.subdomains += assetfinder_parser(self.name)
56-
# # get the subdomains from findomain
57-
# self.subdomains += findomain_parser(self.name)
58-
# # get the subdomains from dnsdumpster
59-
# self.subdomains += dnsdumpster_parser(self.name)
60-
# # get the subdomains from spyse
61-
# self.subdomains += spyse_parser(self.name)
62-
# # get the subdomains from rapiddns
63-
# self.subdomains += rapiddns_parser(self.name)
64-
# # get the subdomains from threatbook
65-
# self.subdomains += threatbook_parser(self.name)
66-
# # get the subdomains from dnsbuffer
67-
# self.subdomains += dnsbuffer_parser(self.name)
68-
# # get the subdomains from threatminer
69-
# self.subdomains += threatminer_parser(self.name)
7062
changed = False
7163
if not ip_trough_proxy and self.handler.there_is_proxy():
7264
logger.info("[*] Deactivating proxy")
@@ -75,18 +67,22 @@ def get_subs(self, ip_trough_proxy: bool = False):
7567
if self.config.get_fqdn_cert:
7668
logger.info("[*] Getting fqdns trough certificate for {}".format(self.name))
7769
with_cert = self.handler.get_certificate_san(self.name)
78-
if with_cert:
70+
if with_cert and valid_fqdn(with_cert) and with_cert not in self.subdomains:
7971
self.subdomains += with_cert
8072
with_cert = self.handler.get_cert_fqdn(self.name)
81-
if with_cert:
73+
if with_cert and valid_fqdn(with_cert) and with_cert not in self.subdomains:
8274
self.subdomains += [with_cert]
8375
if changed:
8476
logger.info("[*] Re-enabling proxy")
8577
self.handler.add_proxys(olds)
8678
return self.subdomains
8779

8880
def get_ip(self):
89-
# get the ip address from the domain by using dns resolver
81+
"""
82+
Gets the IP address from the domain by using DNS resolver.
83+
84+
:return: The IP address of the domain.
85+
"""
9086
try:
9187
ip = resolver.resolve(self.name, "A")
9288
return ip[0].to_text()

0 commit comments

Comments
 (0)