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 +4 -0
- opensquat/app.py +160 -0
- opensquat/arg_parser.py +167 -0
- opensquat/check_update.py +78 -0
- opensquat/cli.py +171 -0
- opensquat/ct.py +78 -0
- opensquat/dns_resolvers.py +76 -0
- opensquat/dns_validator.py +46 -0
- opensquat/feed_manager.py +140 -0
- opensquat/file_input.py +99 -0
- opensquat/homograph.py +59 -0
- opensquat/output.py +114 -0
- opensquat/phishing.py +159 -0
- opensquat/port_check.py +68 -0
- opensquat/squatting_detector.py +144 -0
- opensquat/validations.py +59 -0
- opensquat/vt.py +106 -0
- opensquat-2.2.0.dist-info/METADATA +249 -0
- opensquat-2.2.0.dist-info/RECORD +23 -0
- opensquat-2.2.0.dist-info/WHEEL +5 -0
- opensquat-2.2.0.dist-info/entry_points.txt +2 -0
- opensquat-2.2.0.dist-info/licenses/LICENSE +674 -0
- opensquat-2.2.0.dist-info/top_level.txt +1 -0
opensquat/__init__.py
ADDED
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()
|
opensquat/arg_parser.py
ADDED
|
@@ -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
|