Skip to content
This repository was archived by the owner on Dec 6, 2023. It is now read-only.

Commit 1308bc3

Browse files
author
mpgn
committed
Adding Kerberos support for CME #22
TODO - aeskey - dc-ip - checkifadmin()
1 parent c3c9b2f commit 1308bc3

File tree

7 files changed

+154
-120
lines changed

7 files changed

+154
-120
lines changed

cme/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def gen_cli_args():
4747
std_parser.add_argument('-id', metavar="CRED_ID", nargs='+', default=[], type=str, dest='cred_id', help='database credential ID(s) to use for authentication')
4848
std_parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="username(s) or file(s) containing usernames")
4949
std_parser.add_argument("-p", metavar="PASSWORD", dest='password', nargs='+', default=[], help="password(s) or file(s) containing passwords")
50+
std_parser.add_argument("-k", "--kerberos", action='store_true', help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
5051
fail_group = std_parser.add_mutually_exclusive_group()
5152
fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='max number of global failed login attempts')
5253
fail_group.add_argument("--ufail-limit", metavar='LIMIT', type=int, help='max number of failed login attempts per username')

cme/connection.py

Lines changed: 114 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,18 @@ def __init__(self, args, db, host):
2828
self.conn = None
2929
self.admin_privs = False
3030
self.logger = None
31-
self.password = None
32-
self.username = None
31+
self.password = ''
32+
self.username = ''
33+
self.kerberos = True if self.args.kerberos else False
34+
self.aesKey = None
35+
self.dc_ip = None
3336
self.failed_logins = 0
3437
self.local_ip = None
3538

3639
try:
3740
self.host = gethostbyname(self.hostname)
41+
if self.args.kerberos:
42+
self.host = self.hostname
3843
except Exception as e:
3944
logging.debug('Error resolving hostname {}: {}'.format(self.hostname, e))
4045
return
@@ -60,6 +65,9 @@ def create_conn_obj(self):
6065
def check_if_admin(self):
6166
return
6267

68+
def kerberos_login(self):
69+
return
70+
6371
def plaintext_login(self, domain, username, password):
6472
return
6573

@@ -133,110 +141,113 @@ def over_fail_limit(self, username):
133141
return False
134142

135143
def login(self):
136-
for cred_id in self.args.cred_id:
137-
with sem:
138-
if cred_id.lower() == 'all':
139-
creds = self.db.get_credentials()
140-
else:
141-
creds = self.db.get_credentials(filterTerm=int(cred_id))
142-
143-
for cred in creds:
144-
logging.debug(cred)
145-
try:
146-
c_id, domain, username, password, credtype, pillaged_from = cred
147-
148-
if credtype and password:
149-
150-
if not domain: domain = self.domain
151-
152-
if self.args.local_auth:
153-
domain = self.domain
154-
elif self.args.domain:
155-
domain = self.args.domain
156-
157-
if credtype == 'hash' and not self.over_fail_limit(username):
158-
if self.hash_login(domain, username, password): return True
159-
160-
elif credtype == 'plaintext' and not self.over_fail_limit(username):
161-
if self.plaintext_login(domain, username, password): return True
162-
163-
except IndexError:
164-
self.logger.error("Invalid database credential ID!")
165-
166-
for user in self.args.username:
167-
if not isinstance(user, str) and isfile(user.name):
168-
for usr in user:
169-
if "\\" in usr:
170-
tmp = usr
171-
usr = tmp.split('\\')[1].strip()
172-
self.domain = tmp.split('\\')[0]
173-
if self.args.hash:
174-
with sem:
175-
for ntlm_hash in self.args.hash:
176-
if isinstance(ntlm_hash, str):
177-
if not self.over_fail_limit(usr.strip()):
178-
if self.hash_login(self.domain, usr.strip(), ntlm_hash): return True
179-
180-
elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == False:
181-
for f_hash in ntlm_hash:
182-
if not self.over_fail_limit(usr.strip()):
183-
if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True
184-
ntlm_hash.seek(0)
185-
186-
elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == True:
187-
user.seek(0)
188-
for usr, f_pass in zip(user, ntlm_hash):
144+
if self.args.kerberos:
145+
if self.kerberos_login(): return True
146+
else:
147+
for cred_id in self.args.cred_id:
148+
with sem:
149+
if cred_id.lower() == 'all':
150+
creds = self.db.get_credentials()
151+
else:
152+
creds = self.db.get_credentials(filterTerm=int(cred_id))
153+
154+
for cred in creds:
155+
logging.debug(cred)
156+
try:
157+
c_id, domain, username, password, credtype, pillaged_from = cred
158+
159+
if credtype and password:
160+
161+
if not domain: domain = self.domain
162+
163+
if self.args.local_auth:
164+
domain = self.domain
165+
elif self.args.domain:
166+
domain = self.args.domain
167+
168+
if credtype == 'hash' and not self.over_fail_limit(username):
169+
if self.hash_login(domain, username, password): return True
170+
171+
elif credtype == 'plaintext' and not self.over_fail_limit(username):
172+
if self.plaintext_login(domain, username, password): return True
173+
174+
except IndexError:
175+
self.logger.error("Invalid database credential ID!")
176+
177+
for user in self.args.username:
178+
if not isinstance(user, str) and isfile(user.name):
179+
for usr in user:
180+
if "\\" in usr:
181+
tmp = usr
182+
usr = tmp.split('\\')[1].strip()
183+
self.domain = tmp.split('\\')[0]
184+
if self.args.hash:
185+
with sem:
186+
for ntlm_hash in self.args.hash:
187+
if isinstance(ntlm_hash, str):
189188
if not self.over_fail_limit(usr.strip()):
190-
if self.plaintext_login(self.domain, usr.strip(), f_hash.strip()): return True
191-
192-
elif self.args.password:
193-
with sem:
194-
for password in self.args.password:
195-
if isinstance(password, str):
196-
if not self.over_fail_limit(usr.strip()):
197-
if self.plaintext_login(self.domain, usr.strip(), password): return True
198-
199-
elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == False:
200-
for f_pass in password:
201-
if not self.over_fail_limit(usr.strip()):
202-
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True
203-
password.seek(0)
204-
205-
elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == True:
206-
user.seek(0)
207-
for usr, f_pass in zip(user, password):
189+
if self.hash_login(self.domain, usr.strip(), ntlm_hash): return True
190+
191+
elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == False:
192+
for f_hash in ntlm_hash:
193+
if not self.over_fail_limit(usr.strip()):
194+
if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True
195+
ntlm_hash.seek(0)
196+
197+
elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == True:
198+
user.seek(0)
199+
for usr, f_pass in zip(user, ntlm_hash):
200+
if not self.over_fail_limit(usr.strip()):
201+
if self.plaintext_login(self.domain, usr.strip(), f_hash.strip()): return True
202+
203+
elif self.args.password:
204+
with sem:
205+
for password in self.args.password:
206+
if isinstance(password, str):
208207
if not self.over_fail_limit(usr.strip()):
209-
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True
210-
211-
elif isinstance(user, str):
212-
if hasattr(self.args, 'hash') and self.args.hash:
213-
with sem:
214-
for ntlm_hash in self.args.hash:
215-
if isinstance(ntlm_hash, str):
216-
if not self.over_fail_limit(user):
217-
if self.hash_login(self.domain, user, ntlm_hash): return True
218-
219-
elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name):
220-
for f_hash in ntlm_hash:
208+
if self.plaintext_login(self.domain, usr.strip(), password): return True
209+
210+
elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == False:
211+
for f_pass in password:
212+
if not self.over_fail_limit(usr.strip()):
213+
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True
214+
password.seek(0)
215+
216+
elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == True:
217+
user.seek(0)
218+
for usr, f_pass in zip(user, password):
219+
if not self.over_fail_limit(usr.strip()):
220+
if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True
221+
222+
elif isinstance(user, str):
223+
if hasattr(self.args, 'hash') and self.args.hash:
224+
with sem:
225+
for ntlm_hash in self.args.hash:
226+
if isinstance(ntlm_hash, str):
221227
if not self.over_fail_limit(user):
222-
if self.hash_login(self.domain, user, f_hash.strip()): return True
223-
ntlm_hash.seek(0)
224-
225-
elif self.args.password:
226-
with sem:
227-
for password in self.args.password:
228-
if isinstance(password, str):
229-
if not self.over_fail_limit(user):
230-
if hasattr(self.args, 'domain'):
231-
if self.plaintext_login(self.domain, user, password): return True
232-
else:
233-
if self.plaintext_login(user, password): return True
234-
235-
elif not isinstance(password, str) and isfile(password.name):
236-
for f_pass in password:
228+
if self.hash_login(self.domain, user, ntlm_hash): return True
229+
230+
elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name):
231+
for f_hash in ntlm_hash:
232+
if not self.over_fail_limit(user):
233+
if self.hash_login(self.domain, user, f_hash.strip()): return True
234+
ntlm_hash.seek(0)
235+
236+
elif self.args.password:
237+
with sem:
238+
for password in self.args.password:
239+
if isinstance(password, str):
237240
if not self.over_fail_limit(user):
238241
if hasattr(self.args, 'domain'):
239-
if self.plaintext_login(self.domain, user, f_pass.strip()): return True
242+
if self.plaintext_login(self.domain, user, password): return True
240243
else:
241-
if self.plaintext_login(user, f_pass.strip()): return True
242-
password.seek(0)
244+
if self.plaintext_login(user, password): return True
245+
246+
elif not isinstance(password, str) and isfile(password.name):
247+
for f_pass in password:
248+
if not self.over_fail_limit(user):
249+
if hasattr(self.args, 'domain'):
250+
if self.plaintext_login(self.domain, user, f_pass.strip()): return True
251+
else:
252+
if self.plaintext_login(user, f_pass.strip()): return True
253+
password.seek(0)

cme/helpers/misc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def called_from_cmd_args():
3333
for stack in inspect.stack():
3434
if stack[3] == 'print_host_info':
3535
return True
36-
if stack[3] == 'plaintext_login' or stack[3] == 'hash_login':
36+
if stack[3] == 'plaintext_login' or stack[3] == 'hash_login' or stack[3] == 'kerberos_login':
3737
return True
3838
if stack[3] == 'call_cmd_args':
3939
return True

cme/protocols/smb.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from impacket.dcerpc.v5 import transport, lsat, lsad
1212
from impacket.dcerpc.v5.rpcrt import DCERPCException
1313
from impacket.dcerpc.v5.transport import DCERPCTransportFactory
14+
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE
1415
from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP
1516
from impacket.dcerpc.v5.dcom.wmi import WBEM_FLAG_FORWARD_ONLY
1617
from impacket.dcerpc.v5.samr import SID_NAME_USE
@@ -185,6 +186,8 @@ def get_os_arch(self):
185186
transport = DCERPCTransportFactory(stringBinding)
186187
transport.set_connect_timeout(5)
187188
dce = transport.get_dce_rpc()
189+
if self._conn.kerberos:
190+
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
188191
dce.connect()
189192
try:
190193
dce.bind(MSRPC_UUID_PORTMAP, transfer_syntax=('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0'))
@@ -221,6 +224,9 @@ def enum_host_info(self):
221224
if not self.domain:
222225
self.domain = self.hostname
223226

227+
if self.args.kerberos:
228+
self.domain = self.conn.getServerDNSDomainName()
229+
224230
self.db.add_computer(self.host, self.hostname, self.domain, self.server_os)
225231

226232
try:
@@ -248,6 +254,15 @@ def print_host_info(self):
248254
self.domain,
249255
self.signing,
250256
self.smbv1))
257+
def kerberos_login(self):
258+
self.conn.kerberosLogin('', '', self.domain, self.lmhash, self.nthash, self.aesKey, self.dc_ip)
259+
# self.check_if_admin() # currently not working with kerberos so we set admin_privs to True
260+
self.admin_privs = True
261+
out = u'{}\\{} {}'.format(self.domain,
262+
self.conn.getCredentials()[0],
263+
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
264+
self.logger.success(out)
265+
return True
251266

252267
def plaintext_login(self, domain, username, password):
253268
try:
@@ -395,7 +410,7 @@ def execute(self, payload=None, get_output=False, methods=None):
395410

396411
if method == 'wmiexec':
397412
try:
398-
exec_method = WMIEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.hash, self.args.share)
413+
exec_method = WMIEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.kerberos, self.hash, self.args.share)
399414
logging.debug('Executed command via wmiexec')
400415
break
401416
except:
@@ -415,7 +430,7 @@ def execute(self, payload=None, get_output=False, methods=None):
415430

416431
elif method == 'atexec':
417432
try:
418-
exec_method = TSCH_EXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.hash) #self.args.share)
433+
exec_method = TSCH_EXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.kerberos, self.hash) #self.args.share)
419434
logging.debug('Executed command via atexec')
420435
break
421436
except:
@@ -425,7 +440,7 @@ def execute(self, payload=None, get_output=False, methods=None):
425440

426441
elif method == 'smbexec':
427442
try:
428-
exec_method = SMBEXEC(self.host, self.smb_share_name, self.args.port, self.username, self.password, self.domain, self.hash, self.args.share)
443+
exec_method = SMBEXEC(self.host, self.smb_share_name, self.args.port, self.username, self.password, self.domain, self.kerberos, self.hash, self.args.share)
429444
logging.debug('Executed command via smbexec')
430445
break
431446
except:

0 commit comments

Comments
 (0)