man-spider 1.1.1__py3-none-any.whl → 2.0.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.
- man_spider/lib/errors.py +8 -4
- man_spider/lib/file.py +16 -19
- man_spider/lib/logger.py +27 -32
- man_spider/lib/parser/__init__.py +1 -1
- man_spider/lib/parser/parser.py +102 -57
- man_spider/lib/processpool.py +24 -31
- man_spider/lib/smb.py +71 -63
- man_spider/lib/spider.py +69 -70
- man_spider/lib/spiderling.py +188 -141
- man_spider/lib/util.py +95 -29
- man_spider/manspider.py +170 -55
- {man_spider-1.1.1.dist-info → man_spider-2.0.0.dist-info}/METADATA +101 -44
- man_spider-2.0.0.dist-info/RECORD +18 -0
- {man_spider-1.1.1.dist-info → man_spider-2.0.0.dist-info}/WHEEL +1 -1
- man_spider-2.0.0.dist-info/entry_points.txt +2 -0
- man_spider-1.1.1.dist-info/RECORD +0 -18
- man_spider-1.1.1.dist-info/entry_points.txt +0 -3
- {man_spider-1.1.1.dist-info → man_spider-2.0.0.dist-info/licenses}/LICENSE +0 -0
man_spider/lib/smb.py
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import ntpath
|
|
2
2
|
import logging
|
|
3
|
-
from .errors import *
|
|
4
3
|
from contextlib import suppress
|
|
5
4
|
from impacket.nmb import NetBIOSError, NetBIOSTimeout
|
|
6
5
|
from impacket.smbconnection import SessionError, SMBConnection
|
|
7
6
|
|
|
7
|
+
from man_spider.lib.errors import *
|
|
8
|
+
|
|
8
9
|
# set up logging
|
|
9
|
-
log = logging.getLogger(
|
|
10
|
+
log = logging.getLogger("manspider.smb")
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class SMBClient:
|
|
13
|
-
|
|
14
|
+
"""
|
|
14
15
|
Wrapper around impacket's SMBConnection() object
|
|
15
|
-
|
|
16
|
+
"""
|
|
16
17
|
|
|
17
|
-
def __init__(
|
|
18
|
+
def __init__(
|
|
19
|
+
self, server, username, password, domain, nthash, use_kerberos=False, aes_key="", dc_ip=None, port=445
|
|
20
|
+
):
|
|
18
21
|
|
|
19
22
|
self.server = server
|
|
23
|
+
self.port = port
|
|
20
24
|
|
|
21
25
|
self.conn = None
|
|
22
26
|
|
|
@@ -30,22 +34,33 @@ class SMBClient:
|
|
|
30
34
|
self.hostname = None
|
|
31
35
|
self.dns_domain = None
|
|
32
36
|
if self.nthash:
|
|
33
|
-
self.lmhash =
|
|
37
|
+
self.lmhash = "aad3b435b51404eeaad3b435b51404ee"
|
|
34
38
|
else:
|
|
35
|
-
self.lmhash =
|
|
39
|
+
self.lmhash = ""
|
|
36
40
|
self._shares = None
|
|
37
41
|
|
|
38
|
-
|
|
39
42
|
def list_shares(self):
|
|
40
|
-
|
|
43
|
+
"""
|
|
41
44
|
List shares on the SMB server
|
|
42
|
-
|
|
45
|
+
"""
|
|
43
46
|
resp = self.conn.listShares()
|
|
47
|
+
log.debug(f"{self.server}: Response length: {len(resp)}")
|
|
44
48
|
for i in range(len(resp)):
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
try:
|
|
50
|
+
sharename = resp[i]["shi1_netname"].rstrip("\x00")
|
|
51
|
+
try:
|
|
52
|
+
share_type = resp[i]["shi1_type"]
|
|
53
|
+
except (KeyError, AttributeError):
|
|
54
|
+
share_type = "unknown"
|
|
55
|
+
try:
|
|
56
|
+
share_comment = resp[i]["shi1_remark"].rstrip("\x00")
|
|
57
|
+
except (KeyError, AttributeError):
|
|
58
|
+
share_comment = ""
|
|
59
|
+
log.debug(f"{self.server}: Share {i}: name='{sharename}', type={share_type}, comment='{share_comment}'")
|
|
60
|
+
yield sharename
|
|
61
|
+
except Exception as e:
|
|
62
|
+
log.debug(f"{self.server}: Error processing share {i}: {e}")
|
|
63
|
+
continue
|
|
49
64
|
|
|
50
65
|
@property
|
|
51
66
|
def shares(self):
|
|
@@ -54,27 +69,26 @@ class SMBClient:
|
|
|
54
69
|
self._shares = list(self.list_shares())
|
|
55
70
|
except Exception as e:
|
|
56
71
|
e = self.handle_impacket_error(e)
|
|
57
|
-
log.debug(f
|
|
72
|
+
log.debug(f"{self.server}: Error listing shares: {e}, retrying...")
|
|
58
73
|
self.rebuild(e)
|
|
59
74
|
try:
|
|
60
75
|
self._shares = list(self.list_shares())
|
|
61
76
|
except Exception as e:
|
|
62
77
|
e = self.handle_impacket_error(e)
|
|
63
|
-
log.warning(f
|
|
78
|
+
log.warning(f"{self.server}: Error listing shares: {e}")
|
|
64
79
|
self.rebuild(e)
|
|
65
80
|
return self._shares or []
|
|
66
81
|
|
|
67
|
-
|
|
68
82
|
def get_hostname(self):
|
|
69
|
-
|
|
83
|
+
"""
|
|
70
84
|
Get the hostname from the SMB connection
|
|
71
|
-
|
|
85
|
+
"""
|
|
72
86
|
try:
|
|
73
87
|
conn = SMBConnection(
|
|
74
88
|
self.server,
|
|
75
89
|
self.server,
|
|
76
90
|
None,
|
|
77
|
-
|
|
91
|
+
self.port,
|
|
78
92
|
timeout=10,
|
|
79
93
|
)
|
|
80
94
|
with suppress(Exception):
|
|
@@ -85,36 +99,36 @@ class SMBClient:
|
|
|
85
99
|
# Get the server name from SMB
|
|
86
100
|
self.hostname = str(conn.getServerName()).strip().replace("\x00", "").lower()
|
|
87
101
|
if self.hostname:
|
|
88
|
-
log.debug(f
|
|
102
|
+
log.debug(f"{self.server}: Got hostname: {self.hostname}")
|
|
89
103
|
else:
|
|
90
|
-
log.debug(f
|
|
104
|
+
log.debug(f"{self.server}: No hostname found")
|
|
91
105
|
except Exception as e:
|
|
92
|
-
log.debug(f
|
|
106
|
+
log.debug(f"{self.server}: Error getting hostname from SMB: {e}")
|
|
93
107
|
self.hostname = ""
|
|
94
108
|
|
|
95
109
|
if self.dns_domain is None:
|
|
96
110
|
try:
|
|
97
111
|
self.dns_domain = str(conn.getServerDNSDomainName()).strip().replace("\x00", "").lower()
|
|
98
112
|
if self.dns_domain:
|
|
99
|
-
log.debug(f
|
|
113
|
+
log.debug(f"{self.server}: Got DNS domain: {self.dns_domain}")
|
|
100
114
|
else:
|
|
101
|
-
log.debug(f
|
|
115
|
+
log.debug(f"{self.server}: No DNS domain found")
|
|
102
116
|
except Exception as e:
|
|
103
|
-
log.debug(f
|
|
104
|
-
self.dns_domain =
|
|
117
|
+
log.debug(f"{self.server}: Error getting DNS domain: {e}")
|
|
118
|
+
self.dns_domain = self.domain if self.domain else ""
|
|
105
119
|
|
|
106
120
|
except Exception as e:
|
|
107
|
-
log.debug(f
|
|
121
|
+
log.debug(f"{self.server}: Error getting hostname: {e}")
|
|
108
122
|
|
|
109
123
|
return self.hostname, self.domain
|
|
110
124
|
|
|
111
125
|
def login(self, refresh=False, first_try=True):
|
|
112
|
-
|
|
126
|
+
"""
|
|
113
127
|
Create a new SMBConnection object (if there isn't one already or if refresh is True)
|
|
114
128
|
Attempt to log in, and switch to null session if logon fails
|
|
115
129
|
Return True if logon succeeded
|
|
116
130
|
Return False if logon failed
|
|
117
|
-
|
|
131
|
+
"""
|
|
118
132
|
|
|
119
133
|
target_server = self.server
|
|
120
134
|
if self.use_kerberos:
|
|
@@ -126,20 +140,19 @@ class SMBClient:
|
|
|
126
140
|
|
|
127
141
|
if self.conn is None or refresh:
|
|
128
142
|
try:
|
|
129
|
-
self.conn = SMBConnection(target_server, target_server, sess_port=
|
|
143
|
+
self.conn = SMBConnection(target_server, target_server, sess_port=self.port, timeout=20)
|
|
130
144
|
except Exception as e:
|
|
131
145
|
log.debug(impacket_error(e))
|
|
132
146
|
return None
|
|
133
147
|
|
|
134
148
|
try:
|
|
135
|
-
|
|
136
|
-
if self.username in [None, '', 'Guest'] and first_try:
|
|
149
|
+
if self.username in [None, "", "Guest"] and first_try:
|
|
137
150
|
# skip to guest / null session
|
|
138
151
|
assert False
|
|
139
152
|
|
|
140
153
|
user_str = self.username
|
|
141
154
|
if self.domain:
|
|
142
|
-
user_str = f
|
|
155
|
+
user_str = f"{self.domain}\\{self.username}"
|
|
143
156
|
log.debug(f'{target_server} ({self.server}): Authenticating as "{user_str}"')
|
|
144
157
|
|
|
145
158
|
if self.use_kerberos:
|
|
@@ -156,7 +169,7 @@ class SMBClient:
|
|
|
156
169
|
elif self.nthash and not self.password:
|
|
157
170
|
self.conn.login(
|
|
158
171
|
self.username,
|
|
159
|
-
|
|
172
|
+
"",
|
|
160
173
|
lmhash=self.lmhash,
|
|
161
174
|
nthash=self.nthash,
|
|
162
175
|
domain=self.domain,
|
|
@@ -173,28 +186,26 @@ class SMBClient:
|
|
|
173
186
|
return True
|
|
174
187
|
|
|
175
188
|
except Exception as e:
|
|
176
|
-
|
|
177
189
|
if type(e) != AssertionError:
|
|
178
190
|
e = self.handle_impacket_error(e, display=True)
|
|
179
191
|
|
|
180
192
|
# try guest account, then null session if logon failed
|
|
181
193
|
if first_try:
|
|
182
|
-
|
|
183
|
-
bad_statuses = ['LOGON_FAIL', 'PASSWORD_EXPIRED', 'LOCKED_OUT', 'SESSION_DELETED']
|
|
194
|
+
bad_statuses = ["LOGON_FAIL", "PASSWORD_EXPIRED", "LOCKED_OUT", "SESSION_DELETED"]
|
|
184
195
|
if any([s in str(e) for s in bad_statuses]):
|
|
185
196
|
for s in bad_statuses:
|
|
186
197
|
if s in str(e):
|
|
187
|
-
log.warning(f
|
|
198
|
+
log.warning(f"{self.server}: {s}: {self.username}")
|
|
188
199
|
|
|
189
|
-
log.debug(f
|
|
190
|
-
self.username =
|
|
191
|
-
self.password =
|
|
192
|
-
self.domain =
|
|
193
|
-
self.nthash =
|
|
200
|
+
log.debug(f"{self.server}: Trying guest session")
|
|
201
|
+
self.username = "Guest"
|
|
202
|
+
self.password = ""
|
|
203
|
+
self.domain = ""
|
|
204
|
+
self.nthash = ""
|
|
194
205
|
guest_success = self.login(refresh=True, first_try=False)
|
|
195
206
|
if not guest_success:
|
|
196
|
-
log.debug(f
|
|
197
|
-
self.username =
|
|
207
|
+
log.debug(f"{self.server}: Switching to null session")
|
|
208
|
+
self.username = ""
|
|
198
209
|
self.login(refresh=True, first_try=False)
|
|
199
210
|
|
|
200
211
|
return False
|
|
@@ -202,54 +213,51 @@ class SMBClient:
|
|
|
202
213
|
else:
|
|
203
214
|
return True
|
|
204
215
|
|
|
205
|
-
|
|
206
216
|
def ls(self, share, path):
|
|
207
|
-
|
|
217
|
+
"""
|
|
208
218
|
List files in share/path
|
|
209
219
|
Raise FileListError if there's a problem
|
|
210
220
|
@byt3bl33d3r it's really not that bad
|
|
211
|
-
|
|
221
|
+
"""
|
|
212
222
|
|
|
213
|
-
nt_path = ntpath.normpath(f
|
|
223
|
+
nt_path = ntpath.normpath(f"{path}\\*")
|
|
214
224
|
|
|
215
225
|
# for every file/dir in "path"
|
|
216
226
|
try:
|
|
217
227
|
for f in self.conn.listPath(share, nt_path):
|
|
218
228
|
# exclude current and parent directory
|
|
219
|
-
if f.get_longname() not in [
|
|
229
|
+
if f.get_longname() not in ["", ".", ".."]:
|
|
220
230
|
yield f
|
|
221
231
|
except Exception as e:
|
|
222
232
|
e = self.handle_impacket_error(e)
|
|
223
233
|
raise FileListError(f'{e.args}: Error listing files at "{share}{nt_path}"')
|
|
224
234
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
'''
|
|
235
|
+
def rebuild(self, error=""):
|
|
236
|
+
"""
|
|
228
237
|
Rebuild our SMBConnection() if it gets borked
|
|
229
|
-
|
|
230
|
-
log.debug(f
|
|
238
|
+
"""
|
|
239
|
+
log.debug(f"Rebuilding connection to {self.server} after error: {error}")
|
|
231
240
|
self.login(refresh=True)
|
|
232
241
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
'''
|
|
242
|
+
def handle_impacket_error(self, e, share="", filename="", display=False):
|
|
243
|
+
"""
|
|
236
244
|
Handle arbitrary Impacket errors
|
|
237
245
|
this is needed because the library doesn't implement proper inheritance for its exceptions
|
|
238
|
-
|
|
239
|
-
resource_str =
|
|
246
|
+
"""
|
|
247
|
+
resource_str = "/".join([self.server, share, filename]).rstrip("/")
|
|
240
248
|
|
|
241
249
|
if type(e) == KeyboardInterrupt:
|
|
242
250
|
raise
|
|
243
251
|
elif type(e) in (NetBIOSError, NetBIOSTimeout, BrokenPipeError, SessionError, CSessionError):
|
|
244
252
|
# the connection may need to be rebuilt
|
|
245
253
|
if type(e) in (SessionError, CSessionError):
|
|
246
|
-
if any([x in str(e) for x in (
|
|
254
|
+
if any([x in str(e) for x in ("PASSWORD_EXPIRED",)]):
|
|
247
255
|
self.rebuild(e)
|
|
248
256
|
else:
|
|
249
257
|
self.rebuild(e)
|
|
250
258
|
if type(e) in native_impacket_errors:
|
|
251
259
|
e = impacket_error(e)
|
|
252
260
|
if display:
|
|
253
|
-
log.debug(f
|
|
261
|
+
log.debug(f"{resource_str}: {str(e)[:150]}")
|
|
254
262
|
|
|
255
263
|
return e
|
man_spider/lib/spider.py
CHANGED
|
@@ -1,53 +1,52 @@
|
|
|
1
1
|
import re
|
|
2
|
-
import sys
|
|
3
2
|
import queue
|
|
4
|
-
import threading
|
|
5
3
|
from time import sleep
|
|
6
4
|
import multiprocessing
|
|
7
5
|
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from .
|
|
6
|
+
|
|
7
|
+
from man_spider.lib.spiderling import *
|
|
8
|
+
from man_spider.lib.parser import FileParser
|
|
10
9
|
|
|
11
10
|
# set up logging
|
|
12
|
-
log = logging.getLogger(
|
|
11
|
+
log = logging.getLogger("manspider")
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class MANSPIDER:
|
|
16
|
-
|
|
17
15
|
def __init__(self, options):
|
|
18
16
|
|
|
19
|
-
self.targets
|
|
20
|
-
self.threads
|
|
21
|
-
self.maxdepth
|
|
22
|
-
self.quiet
|
|
17
|
+
self.targets = options.targets
|
|
18
|
+
self.threads = options.threads
|
|
19
|
+
self.maxdepth = options.maxdepth
|
|
20
|
+
self.quiet = options.quiet
|
|
23
21
|
|
|
24
|
-
self.username
|
|
25
|
-
self.password
|
|
26
|
-
self.domain
|
|
27
|
-
self.nthash
|
|
28
|
-
self.use_kerberos
|
|
29
|
-
self.aes_key
|
|
30
|
-
self.dc_ip
|
|
31
|
-
self.max_failed_logons
|
|
32
|
-
self.max_filesize
|
|
22
|
+
self.username = options.username
|
|
23
|
+
self.password = options.password
|
|
24
|
+
self.domain = options.domain
|
|
25
|
+
self.nthash = options.hash
|
|
26
|
+
self.use_kerberos = options.kerberos
|
|
27
|
+
self.aes_key = options.aes_key
|
|
28
|
+
self.dc_ip = options.dc_ip
|
|
29
|
+
self.max_failed_logons = options.max_failed_logons
|
|
30
|
+
self.max_filesize = options.max_filesize
|
|
33
31
|
|
|
34
|
-
self.share_whitelist
|
|
35
|
-
self.share_blacklist
|
|
32
|
+
self.share_whitelist = options.sharenames
|
|
33
|
+
self.share_blacklist = options.exclude_sharenames
|
|
36
34
|
|
|
37
|
-
self.dir_whitelist
|
|
38
|
-
self.dir_blacklist
|
|
35
|
+
self.dir_whitelist = options.dirnames
|
|
36
|
+
self.dir_blacklist = options.exclude_dirnames
|
|
39
37
|
|
|
40
|
-
self.no_download
|
|
38
|
+
self.no_download = options.no_download
|
|
41
39
|
|
|
42
40
|
# applies "or" logic instead of "and"
|
|
43
41
|
# e.g. file is downloaded if filename OR extension OR content match
|
|
44
|
-
self.or_logic
|
|
42
|
+
self.or_logic = options.or_logic
|
|
43
|
+
|
|
44
|
+
self.extension_blacklist = options.exclude_extensions
|
|
45
|
+
self.file_extensions = options.extensions
|
|
45
46
|
|
|
46
|
-
self.extension_blacklist= options.exclude_extensions
|
|
47
|
-
self.file_extensions = options.extensions
|
|
48
47
|
if self.file_extensions:
|
|
49
48
|
extensions_str = '"' + '", "'.join(list(self.file_extensions)) + '"'
|
|
50
|
-
log.info(f
|
|
49
|
+
log.info(f"Searching by file extension: {extensions_str}")
|
|
51
50
|
|
|
52
51
|
self.init_filename_filters(options.filenames)
|
|
53
52
|
self.parser = FileParser(options.content, quiet=self.quiet)
|
|
@@ -62,20 +61,27 @@ class MANSPIDER:
|
|
|
62
61
|
self.smb_client_cache = dict()
|
|
63
62
|
|
|
64
63
|
# directory to store documents when searching contents
|
|
65
|
-
self.tmp_dir = Path(
|
|
64
|
+
self.tmp_dir = Path("/tmp/.manspider")
|
|
66
65
|
self.tmp_dir.mkdir(exist_ok=True)
|
|
67
66
|
|
|
68
67
|
# directory to store matching documents
|
|
69
|
-
self.loot_dir = Path.home() /
|
|
68
|
+
self.loot_dir = Path.home() / ".manspider" / "loot"
|
|
69
|
+
|
|
70
|
+
if options.loot_dir:
|
|
71
|
+
self.loot_dir = Path(options.loot_dir)
|
|
70
72
|
|
|
71
|
-
if(options.loot_dir):
|
|
72
|
-
self.loot_dir=Path(options.loot_dir)
|
|
73
|
-
|
|
74
73
|
self.loot_dir.mkdir(parents=True, exist_ok=True)
|
|
75
74
|
|
|
76
75
|
if not options.no_download:
|
|
77
|
-
log.info(f
|
|
76
|
+
log.info(f"Matching files will be downloaded to {self.loot_dir}")
|
|
77
|
+
|
|
78
|
+
self.modified_after = options.modified_after
|
|
79
|
+
self.modified_before = options.modified_before
|
|
78
80
|
|
|
81
|
+
if self.modified_after:
|
|
82
|
+
log.info(f"Filtering files modified after: {self.modified_after.strftime('%Y-%m-%d')}")
|
|
83
|
+
if self.modified_before:
|
|
84
|
+
log.info(f"Filtering files modified before: {self.modified_before.strftime('%Y-%m-%d')}")
|
|
79
85
|
|
|
80
86
|
def start(self):
|
|
81
87
|
|
|
@@ -100,7 +106,7 @@ class MANSPIDER:
|
|
|
100
106
|
continue
|
|
101
107
|
|
|
102
108
|
# save on CPU
|
|
103
|
-
sleep(.1)
|
|
109
|
+
sleep(0.1)
|
|
104
110
|
|
|
105
111
|
while 1:
|
|
106
112
|
self.check_spiderling_queue()
|
|
@@ -111,23 +117,19 @@ class MANSPIDER:
|
|
|
111
117
|
# make sure the queue is empty
|
|
112
118
|
self.check_spiderling_queue()
|
|
113
119
|
|
|
114
|
-
|
|
115
|
-
|
|
116
120
|
def init_file_extensions(self, file_extensions):
|
|
117
|
-
|
|
121
|
+
"""
|
|
118
122
|
Get ready to search by file extension
|
|
119
|
-
|
|
123
|
+
"""
|
|
120
124
|
|
|
121
125
|
self.file_extensions = FileExtensions()
|
|
122
126
|
if file_extensions:
|
|
123
127
|
self.file_extensions.update(file_extensions)
|
|
124
|
-
|
|
125
|
-
|
|
126
128
|
|
|
127
129
|
def init_filename_filters(self, filename_filters):
|
|
128
|
-
|
|
130
|
+
"""
|
|
129
131
|
Get ready to search by filename
|
|
130
|
-
|
|
132
|
+
"""
|
|
131
133
|
|
|
132
134
|
# strings to look for in filenames
|
|
133
135
|
# if empty, all filenames are matched
|
|
@@ -135,23 +137,22 @@ class MANSPIDER:
|
|
|
135
137
|
for f in filename_filters:
|
|
136
138
|
regex_str = str(f)
|
|
137
139
|
try:
|
|
138
|
-
if not any([f.startswith(x) for x in [
|
|
139
|
-
regex_str = rf
|
|
140
|
-
if not any([f.endswith(x) for x in [
|
|
141
|
-
regex_str = rf
|
|
140
|
+
if not any([f.startswith(x) for x in ["^", ".*"]]):
|
|
141
|
+
regex_str = rf".*{regex_str}"
|
|
142
|
+
if not any([f.endswith(x) for x in ["$", ".*"]]):
|
|
143
|
+
regex_str = rf"{regex_str}.*"
|
|
142
144
|
self.filename_filters.append(re.compile(regex_str, re.I))
|
|
143
145
|
except re.error as e:
|
|
144
146
|
log.error(f'Unsupported filename regex "{f}": {e}')
|
|
145
147
|
sleep(1)
|
|
146
148
|
if self.filename_filters:
|
|
147
149
|
filename_filter_str = '"' + '", "'.join([f.pattern for f in self.filename_filters]) + '"'
|
|
148
|
-
log.info(f
|
|
149
|
-
|
|
150
|
+
log.info(f"Searching by filename: {filename_filter_str}")
|
|
150
151
|
|
|
151
152
|
def check_spiderling_queue(self):
|
|
152
|
-
|
|
153
|
+
"""
|
|
153
154
|
Empty the spiderling queue
|
|
154
|
-
|
|
155
|
+
"""
|
|
155
156
|
|
|
156
157
|
while 1:
|
|
157
158
|
try:
|
|
@@ -161,55 +162,53 @@ class MANSPIDER:
|
|
|
161
162
|
except queue.Empty:
|
|
162
163
|
break
|
|
163
164
|
|
|
164
|
-
|
|
165
165
|
def process_message(self, message):
|
|
166
|
-
|
|
166
|
+
"""
|
|
167
167
|
Process messages from spiderlings
|
|
168
168
|
Log messages, errors, files, etc.
|
|
169
|
-
|
|
170
|
-
if message.type ==
|
|
169
|
+
"""
|
|
170
|
+
if message.type == "a":
|
|
171
171
|
if message.content == False:
|
|
172
172
|
self.failed_logons += 1
|
|
173
173
|
if self.lockout_threshold():
|
|
174
|
-
log.error(f
|
|
175
|
-
log.error(
|
|
176
|
-
#for spiderling in self.spiderling_pool:
|
|
174
|
+
log.error(f"REACHED MAXIMUM FAILED LOGONS OF {self.max_failed_logons:,}")
|
|
175
|
+
log.error("KILLING EXISTING SPIDERLINGS AND CONTINUING WITH GUEST/NULL SESSIONS")
|
|
176
|
+
# for spiderling in self.spiderling_pool:
|
|
177
177
|
# spiderling.kill()
|
|
178
|
-
self.username =
|
|
179
|
-
self.password =
|
|
180
|
-
self.nthash =
|
|
181
|
-
self.domain =
|
|
182
|
-
|
|
178
|
+
self.username = ""
|
|
179
|
+
self.password = ""
|
|
180
|
+
self.nthash = ""
|
|
181
|
+
self.domain = ""
|
|
183
182
|
|
|
184
183
|
def lockout_threshold(self):
|
|
185
|
-
|
|
184
|
+
"""
|
|
186
185
|
Return True if we've reached max failed logons
|
|
187
|
-
|
|
186
|
+
"""
|
|
188
187
|
|
|
189
188
|
if self.max_failed_logons is not None:
|
|
190
189
|
if self.failed_logons >= self.max_failed_logons and self.domain:
|
|
191
190
|
return True
|
|
192
191
|
return False
|
|
193
192
|
|
|
194
|
-
|
|
195
193
|
def get_smb_client(self, target):
|
|
196
|
-
|
|
194
|
+
"""
|
|
197
195
|
Check if we already have an smb_client cached
|
|
198
196
|
If not, then create it
|
|
199
|
-
|
|
197
|
+
"""
|
|
200
198
|
|
|
201
199
|
smb_client = self.smb_client_cache.get(target, None)
|
|
202
200
|
|
|
203
201
|
if smb_client is None:
|
|
204
202
|
smb_client = SMBClient(
|
|
205
|
-
target,
|
|
203
|
+
target.host,
|
|
206
204
|
self.username,
|
|
207
205
|
self.password,
|
|
208
206
|
self.domain,
|
|
209
207
|
self.nthash,
|
|
210
208
|
self.use_kerberos,
|
|
211
209
|
self.aes_key,
|
|
212
|
-
self.dc_ip
|
|
210
|
+
self.dc_ip,
|
|
211
|
+
port=target.port,
|
|
213
212
|
)
|
|
214
213
|
logon_result = smb_client.login()
|
|
215
214
|
if logon_result == False:
|