opensquat 2.2__0-py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
opensquat/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Module: __init__.py
3
+ """openSquat Version."""
4
+ __VERSION__ = "2.2.0"
opensquat/app.py ADDED
@@ -0,0 +1,160 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Module: app.py
3
+ """
4
+ openSquat
5
+
6
+ (c) Andre Tenreiro
7
+
8
+ * https://github.com/atenreiro/opensquat
9
+
10
+ software licensed under GNU version 3
11
+ """
12
+ import concurrent.futures
13
+ import functools
14
+ import io
15
+ from colorama import Fore, Style
16
+
17
+ from opensquat import file_input
18
+ from opensquat.feed_manager import FeedManager
19
+ from opensquat.dns_validator import DNSValidator
20
+ from opensquat.squatting_detector import SquattingDetector
21
+
22
+
23
+ class Domain:
24
+ """
25
+ Main orchestration class for OpenSquat.
26
+ """
27
+
28
+ def __init__(self):
29
+ """Initiator."""
30
+ self.domain_filename = None
31
+ self.keywords_filename = None
32
+ self.domain_total = 0
33
+ self.keywords_total = 0
34
+ self.list_domains = []
35
+ self.confidence_level = 2
36
+ self.doppelganger_only = False
37
+
38
+ self.feed_manager = None
39
+ self.dns_validator = None
40
+ self.squatting_detector = None
41
+
42
+ self.list_file_domains = []
43
+ self.list_file_keywords = []
44
+
45
+ def count_files(self):
46
+ (self.keywords_total, self.domain_total) = file_input.InputFile().main(
47
+ self.keywords_filename,
48
+ self.domain_filename
49
+ )
50
+
51
+ def read_files(self):
52
+ """
53
+ Method to read domain files
54
+ """
55
+ with open(self.domain_filename, mode='r') as file_domains:
56
+ for mydomains in file_domains:
57
+ domain = mydomains.replace("\n", "")
58
+ domain = domain.lower().strip()
59
+ # Skip comments and empty lines
60
+ if domain and not domain.startswith("#"):
61
+ self.list_file_domains.append(domain)
62
+
63
+ with open(self.keywords_filename, mode='r') as file_keywords:
64
+ for line in file_keywords:
65
+ line = line.strip()
66
+ if line and not line.startswith("#"):
67
+ self.list_file_keywords.append(line)
68
+
69
+ def print_info(self):
70
+ """
71
+ Method to print some configuration information.
72
+ """
73
+ print("[*] keywords:", self.keywords_filename)
74
+ print("[*] keywords total:", self.keywords_total)
75
+ print("[*] Total domains:", f"{self.domain_total:,}")
76
+
77
+ print("[*] Threshold:", self.squatting_detector.confidence.get(self.confidence_level, "unknown"))
78
+
79
+ @staticmethod
80
+ def verify_keyword_task(detector, domains_list, keyword_info):
81
+ """
82
+ Static worker method for parallel execution.
83
+ """
84
+ keyword, keyword_line_number, keywords_total = keyword_info
85
+
86
+ result_buffer = io.StringIO()
87
+ print(
88
+ f"[+] Starting Domain Squatting verification for '{keyword}' [{keyword_line_number}/{keywords_total}]",
89
+ file=result_buffer
90
+ )
91
+
92
+ print(
93
+ Fore.WHITE + "\n[*] Verifying keyword:",
94
+ keyword,
95
+ "[",
96
+ keyword_line_number,
97
+ "/",
98
+ keywords_total,
99
+ "]" + Style.RESET_ALL,
100
+ file=result_buffer
101
+ )
102
+
103
+ result_domains = detector.check(keyword, domains_list, result_buffer)
104
+ return result_buffer, result_domains
105
+
106
+ def worker(self):
107
+ """
108
+ Method that will compute all the similarity calculations between
109
+ the keywords and domain names.
110
+ """
111
+ with concurrent.futures.ProcessPoolExecutor() as executor:
112
+ keyword_infos = [
113
+ (keyword, i + 1, self.keywords_total)
114
+ for i, keyword in enumerate(self.list_file_keywords) if keyword
115
+ ]
116
+
117
+ worker_func = functools.partial(self.verify_keyword_task, self.squatting_detector, self.list_file_domains)
118
+
119
+ futs = [executor.submit(worker_func, k_info) for k_info in keyword_infos]
120
+
121
+ for fut in futs:
122
+ result_buffer, result_domains = fut.result()
123
+ print(result_buffer.getvalue())
124
+ self.list_domains.extend(result_domains)
125
+
126
+ return self.list_domains
127
+
128
+ def main(
129
+ self,
130
+ keywords_file,
131
+ confidence_level,
132
+ domains_file,
133
+ dns,
134
+ doppelganger_only=False,
135
+ feed_url="https://feeds.opensquat.com/opensquat-nrd-latest.txt",
136
+ ):
137
+ print("+---------- Checking Domain Squatting ----------+")
138
+ self.keywords_filename = keywords_file
139
+ self.domain_filename = domains_file
140
+ self.confidence_level = confidence_level
141
+ self.doppelganger_only = doppelganger_only
142
+
143
+ self.dns_validator = DNSValidator(use_dns=dns)
144
+ self.squatting_detector = SquattingDetector(
145
+ confidence_level=confidence_level,
146
+ doppelganger_only=doppelganger_only,
147
+ dns_validator=self.dns_validator
148
+ )
149
+
150
+ # Feed Management
151
+ self.feed_manager = FeedManager(feed_url=feed_url)
152
+ if self.domain_filename == "":
153
+ self.domain_filename = self.feed_manager.local_filename
154
+ self.feed_manager.ensure_feeds()
155
+
156
+ self.count_files()
157
+ self.read_files()
158
+ self.print_info()
159
+
160
+ return self.worker()
@@ -0,0 +1,167 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Module: arg_parser.py
3
+ """
4
+ openSquat.
5
+
6
+ (c) Andre Tenreiro
7
+
8
+ * https://github.com/atenreiro/opensquat
9
+
10
+ software licensed under GNU version 3
11
+
12
+ """
13
+ import argparse
14
+
15
+
16
+ def validate_type(file_type):
17
+ """
18
+ Validate file_type.
19
+
20
+ Args:
21
+ file_type: string containing file type, can only be txt, json or csv.
22
+
23
+ Return:
24
+ file_type
25
+
26
+ Raise:
27
+ If value is not valid, raise an exception to argparse
28
+ """
29
+ file_type = str(file_type)
30
+
31
+ if (file_type != "txt") and (file_type != "json") and (file_type != "csv"):
32
+ raise argparse.ArgumentTypeError("File format unkown!")
33
+ return file_type
34
+
35
+
36
+ def validate_confidence(confidence_level):
37
+ """
38
+ Validate confidence_level.
39
+
40
+ Args:
41
+ confidence_level: int containing confidence_level, can only be an int
42
+ between 0 and 4.
43
+
44
+ Return:
45
+ confidence_level
46
+
47
+ Raise:
48
+ If value is not valid, raise an exception to argparse
49
+
50
+ """
51
+ confidence_level = int(confidence_level)
52
+
53
+ if confidence_level not in range(0, 5):
54
+ raise argparse.ArgumentTypeError("confidence must be between 0 and 4")
55
+ return confidence_level
56
+
57
+
58
+ def get_args():
59
+ """
60
+ Parser main function.
61
+
62
+ Args:
63
+ none
64
+
65
+ Return:
66
+ args: returns arguments
67
+ """
68
+ parser = argparse.ArgumentParser(description="openSquat")
69
+ parser.add_argument(
70
+ "-k",
71
+ "--keywords",
72
+ type=str,
73
+ default="keywords.txt",
74
+ help="keywords file (default: keywords.txt)",
75
+ )
76
+ parser.add_argument(
77
+ "-o",
78
+ "--output",
79
+ type=str,
80
+ default="results.txt",
81
+ help="output filename (default: results.txt)",
82
+ )
83
+ parser.add_argument(
84
+ "-c",
85
+ "--confidence",
86
+ type=validate_confidence,
87
+ default=1,
88
+ help="0 (very high), 1 (high), 2 (medium), 3 (low),"
89
+ "4 (very low) (default: 1)",
90
+ )
91
+ parser.add_argument(
92
+ "-t",
93
+ "--type",
94
+ type=validate_type,
95
+ default="txt",
96
+ help="output file type [txt|json|csv] (default: txt)",
97
+ )
98
+ parser.add_argument(
99
+ "-d",
100
+ "--domains",
101
+ type=str,
102
+ default="",
103
+ help="update from FILE instead of downloading new domains",
104
+ )
105
+ parser.add_argument(
106
+ "-u",
107
+ "--url",
108
+ type=str,
109
+ default="https://feeds.opensquat.com/opensquat-nrd-latest.txt",
110
+ help="URL to download domain feed (default: https://feeds.opensquat.com/opensquat-nrd-latest.txt)",
111
+ )
112
+ parser.add_argument(
113
+ "-p",
114
+ "--period",
115
+ type=str,
116
+ default=None,
117
+ help=argparse.SUPPRESS, # Hide from help since deprecated
118
+ )
119
+
120
+ parser.add_argument(
121
+ "--ct",
122
+ action="store_true",
123
+ help="search in certificate transparency",
124
+ )
125
+
126
+ parser.add_argument(
127
+ "--doppelganger",
128
+ action="store_true",
129
+ help="doppelganger-only mode: check if domains contain the keyword and are reachable",
130
+ )
131
+
132
+ parser.add_argument(
133
+ "--dns",
134
+ action="store_true",
135
+ help="Check if domain is flagged by Quad9 DNS"
136
+ )
137
+
138
+ parser.add_argument(
139
+ "--phishing",
140
+ type=str,
141
+ default="",
142
+ help="search known and active Phishing sites (arg: output.txt)",
143
+ )
144
+ parser.add_argument(
145
+ "--subdomains",
146
+ action="store_true",
147
+ help="search for subdomains from flagged domains",
148
+ )
149
+ parser.add_argument(
150
+ "--portcheck",
151
+ action="store_true",
152
+ help="Verify is port 80/443 is open",
153
+ )
154
+ parser.add_argument(
155
+ "--vt",
156
+ action="store_true",
157
+ help="validate against VirusTotal",
158
+ )
159
+
160
+ args = parser.parse_args()
161
+
162
+ # Check for deprecated -p/--period argument
163
+ if args.period is not None:
164
+ print("\n[ERROR] The weekly/monthly feeds have been deprecated. Please use the daily feeds.\n")
165
+ exit(1)
166
+
167
+ return args
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Module: check_update.py
3
+ """
4
+ openSquat
5
+
6
+ * https://github.com/atenreiro/opensquat
7
+
8
+ software licensed under GNU version 3
9
+ """
10
+ import requests
11
+ from colorama import Fore, Style
12
+ from opensquat import __VERSION__
13
+
14
+ class CheckUpdate:
15
+ """
16
+ This domain class verifies if there is a newer upgrade avaiable
17
+
18
+ To use:
19
+ CheckUpdate.main()
20
+
21
+ Attribute:
22
+ URL: The URL with the latest upgrade version
23
+ current: The current running version
24
+ """
25
+
26
+ def __init__(self):
27
+ """Initiator."""
28
+ self.URL = ("https://feeds.opensquat.com/latest_version.txt")
29
+ self.current = __VERSION__
30
+
31
+ def check(self):
32
+
33
+ # User-Agent
34
+ ver = "openSquat-" + self.current
35
+ headers = {'User-Agent': ver}
36
+
37
+ try:
38
+ response = requests.get(self.URL, headers=headers)
39
+ except requests.exceptions.RequestException:
40
+ return False
41
+
42
+ if (response.status_code != 200):
43
+ return False
44
+
45
+ latest_ver = response.content
46
+ latest_ver = latest_ver.decode()
47
+ response.close()
48
+
49
+ current_ver = self.current
50
+
51
+ def parse_version(v):
52
+ return tuple(int(x) for x in v.strip().split("."))
53
+
54
+ if parse_version(latest_ver) > parse_version(current_ver):
55
+ print(
56
+ Style.BRIGHT+Fore.MAGENTA +
57
+ "[INFO] New version avaiable!" +
58
+ Style.RESET_ALL)
59
+ print(
60
+ Style.BRIGHT+Fore.WHITE +
61
+ "-> Current ver:", current_ver, Style.RESET_ALL)
62
+ print(
63
+ Style.BRIGHT+Fore.WHITE +
64
+ "-> Latest ver:", latest_ver, Style.RESET_ALL)
65
+ print(
66
+ Style.BRIGHT+Fore.WHITE +
67
+ "-> Changelog: https://github.com/atenreiro/opensquat/blob/" +
68
+ "master/CHANGELOG" +
69
+ Style.RESET_ALL)
70
+ print(
71
+ Style.BRIGHT+Fore.WHITE + "-> update now: $ git pull\n" +
72
+ Style.RESET_ALL)
73
+ return True
74
+
75
+ return False
76
+
77
+ def main(self):
78
+ self.check()
opensquat/cli.py ADDED
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # Module: cli.py
4
+ """
5
+ openSquat CLI entry point.
6
+
7
+ * https://github.com/atenreiro/opensquat
8
+
9
+ software licensed under GNU version 3
10
+ """
11
+ import time
12
+ import signal
13
+ import functools
14
+ import concurrent.futures
15
+
16
+ from colorama import init, Fore, Style
17
+ from opensquat import __VERSION__, vt
18
+ from opensquat import arg_parser, output, app, phishing, check_update
19
+ from opensquat import port_check
20
+
21
+
22
+ def signal_handler(sig, frame):
23
+ """Function to catch CTR+C and terminate."""
24
+ print("\n[*] openSquat is terminating...\n")
25
+ exit(0)
26
+
27
+
28
+ def main():
29
+ signal.signal(signal.SIGINT, signal_handler)
30
+
31
+ init()
32
+
33
+ logo = (
34
+ Style.BRIGHT + Fore.GREEN +
35
+ """
36
+ █████████ █████
37
+ ███░░░░░███ ░░███
38
+ ██████ ████████ ██████ ████████ ░███ ░░░ ████████ █████ ████ ██████ ███████
39
+ ███░░███░░███░░███ ███░░███░░███░░███ ░░█████████ ███░░███ ░░███ ░███ ░░░░░███ ░░░███░
40
+ ░███ ░███ ░███ ░███░███████ ░███ ░███ ░░░░░░░░███░███ ░███ ░███ ░███ ███████ ░███
41
+ ░███ ░███ ░███ ░███░███░░░ ░███ ░███ ███ ░███░███ ░███ ░███ ░███ ███░░███ ░███ ███
42
+ ░░██████ ░███████ ░░██████ ████ █████░░█████████ ░░███████ ░░████████░░████████ ░░█████
43
+ ░░░░░░ ░███░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░░░░░ ░░░░░███ ░░░░░░░░ ░░░░░░░░ ░░░░░
44
+ ░███ ░███
45
+ █████ █████
46
+ ░░░░░ ░░░░░
47
+ (c) openSquat - https://opensquat.com
48
+ """ + Style.RESET_ALL
49
+ )
50
+
51
+ print(logo)
52
+ print("\t\t\tversion " + __VERSION__ + "\n")
53
+
54
+ args = arg_parser.get_args()
55
+
56
+ start_time_squatting = time.time()
57
+
58
+ file_content = app.Domain().main(
59
+ args.keywords,
60
+ args.confidence,
61
+ args.domains,
62
+ args.dns,
63
+ doppelganger_only=args.doppelganger,
64
+ feed_url=args.url,
65
+ )
66
+
67
+ if args.subdomains or args.vt or args.phishing or args.portcheck:
68
+ print("\n[*] Total found:", len(file_content))
69
+
70
+ # Check for subdomains
71
+ if (args.subdomains):
72
+ list_aux = []
73
+ print("\n+---------- Checking for Subdomains ----------+")
74
+ time.sleep(1)
75
+ for domain in file_content:
76
+ print("[*]", domain)
77
+ subdomains = vt.VirusTotal().main(domain, "subdomains")
78
+
79
+ if subdomains:
80
+ for subdomain in subdomains:
81
+ print(
82
+ Style.BRIGHT + Fore.YELLOW +
83
+ " \\_", subdomain +
84
+ Style.RESET_ALL,
85
+ )
86
+ list_aux.append(subdomain)
87
+ file_content = list_aux
88
+ print("[*] Total found:", len(file_content))
89
+
90
+ # Check for VirusTotal (if domain is flagged as malicious)
91
+ if (args.vt):
92
+ list_aux = []
93
+ print("\n+---------- VirusTotal ----------+")
94
+ time.sleep(1)
95
+ for domain in file_content:
96
+ total_votes = vt.VirusTotal().main(domain)
97
+
98
+ # total votes
99
+ harmless = total_votes[0]
100
+ malicious = total_votes[1]
101
+
102
+ if malicious > 0:
103
+ print(
104
+ Style.BRIGHT + Fore.RED +
105
+ "[*] found:", domain, "({})".format(str(malicious)) +
106
+ Style.RESET_ALL,
107
+ )
108
+ list_aux.append(domain)
109
+ elif malicious < 0:
110
+ print(
111
+ Style.BRIGHT + Fore.YELLOW +
112
+ "[*] VT is throttling the response:", domain +
113
+ Style.RESET_ALL,
114
+ )
115
+ list_aux.append(domain)
116
+ file_content = list_aux
117
+ print("[*] Total found:", len(file_content))
118
+
119
+ # Check for phishing
120
+ if (args.phishing != ""):
121
+ file_phishing = phishing.Phishing().main(args.keywords)
122
+ output.SaveFile().main(args.phishing, "txt", file_phishing)
123
+
124
+ # Check if domain has webserver port opened
125
+ if (args.portcheck):
126
+ list_aux = []
127
+ print("\n+---------- Domains with open webserver ports ----------+")
128
+ time.sleep(1)
129
+
130
+ with concurrent.futures.ThreadPoolExecutor() as executor:
131
+ futs = [(domain, executor.submit(functools.partial(port_check.PortCheck().main, domain)))
132
+ for domain in file_content]
133
+
134
+ for tested_domain, result_domain_port_check in futs:
135
+ ports = result_domain_port_check.result()
136
+ if ports:
137
+ list_aux.append(tested_domain)
138
+ print(
139
+ Fore.YELLOW +
140
+ "[*]", tested_domain, ports, "" +
141
+ Style.RESET_ALL
142
+ )
143
+
144
+ file_content = list_aux
145
+ print("[*] Total found:", len(file_content))
146
+
147
+ output.SaveFile().main(args.output, args.type, file_content)
148
+ end_time_squatting = round(time.time() - start_time_squatting, 2)
149
+
150
+ # Print summary
151
+ print("\n")
152
+ print(
153
+ Style.BRIGHT+Fore.GREEN +
154
+ "+---------- Summary Squatting ----------+" +
155
+ Style.RESET_ALL)
156
+
157
+ print("[*] Domains flagged:", len(file_content))
158
+ print("[*] Domains result:", args.output)
159
+
160
+ if (args.phishing != ""):
161
+ print("[*] Phishing results:", args.phishing)
162
+ print("[*] Active Phishing sites:", len(file_phishing))
163
+
164
+ print("[*] Running time: %s seconds" % end_time_squatting)
165
+ print("")
166
+
167
+ check_update.CheckUpdate().main()
168
+
169
+
170
+ if __name__ == "__main__":
171
+ main()
opensquat/ct.py ADDED
@@ -0,0 +1,78 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Module: ct.py
3
+ """
4
+ openSquat.
5
+
6
+ (c) Andre Tenreiro
7
+
8
+ * https://github.com/atenreiro/opensquat
9
+
10
+ software licensed under GNU version 3
11
+ """
12
+ import requests
13
+ from bs4 import BeautifulSoup, NavigableString
14
+
15
+ NOT_TRUSTED_CA = ["Let's Encrypt Authority X3"]
16
+
17
+
18
+ class CTLog:
19
+ def __init__(
20
+ self, _id, logget_at, not_before, not_after, matching_ident, issuer_name
21
+ ):
22
+ self._id = _id
23
+ self.logget_at = logget_at
24
+ self.not_before = not_before
25
+ self.not_after = not_after
26
+ self.matching_ident = matching_ident
27
+ self.issuer_name = issuer_name
28
+
29
+
30
+ class CRTSH:
31
+ """Class responsible for checking given domain for CT logs."""
32
+
33
+ URL = "https://crt.sh/"
34
+
35
+ @classmethod
36
+ def check_certificate(cls, domain: str) -> bool:
37
+ url = f"{cls.URL}?q={domain}"
38
+
39
+ try:
40
+ html_text = requests.get(url).text
41
+ except Exception:
42
+ print(f"Cannot fetch data from {url}")
43
+ return True
44
+
45
+ soup = BeautifulSoup(html_text, "html.parser")
46
+
47
+ collected_logs = []
48
+ for table in soup.find_all("table")[1]:
49
+ if isinstance(table, NavigableString):
50
+ continue
51
+
52
+ for table_row in table.find_all("tr")[1:]:
53
+ tds = [td for td in table_row.find_all("td")]
54
+ try:
55
+ collected_logs.append(
56
+ CTLog(
57
+ tds[0].text,
58
+ tds[1].text,
59
+ tds[2].text,
60
+ tds[3].text,
61
+ [x for x in tds[4] if isinstance(x, NavigableString)],
62
+ tds[5].text,
63
+ )
64
+ )
65
+ except Exception as e:
66
+ print(f"Could not retreive CT logs from {domain}, {e}")
67
+ return False
68
+
69
+ for ca in NOT_TRUSTED_CA:
70
+ for ctlog in collected_logs:
71
+ if domain in ctlog.matching_ident: # checking domain matching
72
+ if ca in ctlog.issuer_name: # checking CA
73
+ return False
74
+
75
+ if not collected_logs:
76
+ return False
77
+
78
+ return True