Improve LDAP protocol

- improve authentification status error
- check if user is on a juicy group
main
mpgn 2021-07-02 04:50:41 -04:00
parent c3516fe9d5
commit d5a005898e
2 changed files with 154 additions and 59 deletions

View File

@ -48,7 +48,7 @@ def gen_cli_args():
std_parser.add_argument("-k", "--kerberos", action='store_true', help="Use Kerberos authentication from ccache file (KRB5CCNAME)")
std_parser.add_argument("--export", metavar="EXPORT", nargs='+', help="Export result into a file, probably buggy")
std_parser.add_argument("--aesKey", metavar="AESKEY", nargs='+', help="AES key to use for Kerberos Authentication (128 or 256 bits)")
std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
std_parser.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter")
fail_group = std_parser.add_mutually_exclusive_group()
fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='max number of global failed login attempts')

View File

@ -4,6 +4,7 @@
import requests
import logging
import configparser
from binascii import b2a_hex, unhexlify, hexlify
from cme.connection import *
from cme.helpers.logger import highlight
from cme.logger import CMEAdapter
@ -18,6 +19,17 @@ from impacket.krb5 import constants
from impacket.ldap import ldapasn1 as ldapasn1_impacket
from io import StringIO
ldap_error_status = {
"533":"STATUS_ACCOUNT_DISABLED",
"701":"STATUS_ACCOUNT_EXPIRED",
"531":"STATUS_ACCOUNT_RESTRICTION",
"530":"STATUS_INVALID_LOGON_HOURS",
"532":"STATUS_PASSWORD_EXPIRED",
"773":"STATUS_PASSWORD_MUST_CHANGE",
"775":"USER_ACCOUNT_LOCKED",
"50":"LDAP_INSUFFICIENT_ACCESS"
}
class ldap(connection):
def __init__(self, args, db, host):
@ -142,36 +154,12 @@ class ldap(connection):
self.smbv1))
def kerberos_login(self, aesKey, kdcHost):
# Create the baseDN
domainParts = self.domain.split('.')
self.baseDN = ''
for i in domainParts:
self.baseDN += 'dc=%s,' % i
# Remove last ','
self.baseDN = self.baseDN[:-1]
if self.kdcHost is not None:
target = self.kdcHost
else:
target = self.domain
self.kdcHost = domain
# if self.kdcHost is not None:
# target = self.kdcHost
# else:
# target = self.domain
try:
self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash,
self.aesKey, kdcHost=self.kdcHost)
except ldap_impacket.LDAPSessionError as e:
if str(e).find('strongerAuthRequired') >= 0:
# We need to try SSL
self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.kdcHost, self.baseDN, self.kdcHost)
self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash,
self.aesKey, kdcHost=self.kdcHost)
return True
def plaintext_login(self, domain, username, password):
self.username = username
self.password = password
self.domain = domain
# Create the baseDN
self.baseDN = ''
domainParts = self.kdcHost.split('.')
@ -180,10 +168,44 @@ class ldap(connection):
# Remove last ','
self.baseDN = self.baseDN[:-1]
# if self.kdcHost is not None:
# target = self.kdcHost
# else:
# target = domain
try:
self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash,
self.aesKey, kdcHost=self.kdcHost)
except ldap_impacket.LDAPSessionError as e:
if str(e).find('strongerAuthRequired') >= 0:
# We need to try SSL
self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN, self.kdcHost)
self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash,
self.aesKey, kdcHost=self.kdcHost)
else:
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.password,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
return True
def plaintext_login(self, domain, username, password):
self.username = username
self.password = password
self.domain = domain
if self.kdcHost is not None:
target = self.kdcHost
else:
target = domain
self.kdcHost = domain
# Create the baseDN
self.baseDN = ''
domainParts = self.kdcHost.split('.')
for i in domainParts:
self.baseDN += 'dc=%s,' % i
# Remove last ','
self.baseDN = self.baseDN[:-1]
if self.password == '' and self.args.asreproast:
hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username)
@ -194,9 +216,9 @@ class ldap(connection):
return False
try:
self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.kdcHost, self.baseDN, self.kdcHost)
self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN, self.kdcHost)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
#self.check_if_admin()
self.check_if_admin()
# Connect to LDAP
out = u'{}{}:{} {}'.format('{}\\'.format(domain),
@ -216,17 +238,23 @@ class ldap(connection):
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.logger.success(out)
except ldap_impacket.LDAPSessionError as e:
self.logger.error(u'{}\{}:{}'.format(self.domain,
self.username,
self.password))
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.password,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
else:
self.logger.error(u'{}\{}:{}'.format(self.domain,
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.password))
self.password,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
return False
except OSError as e:
self.logger.error(u'{}\{}:{} {}'.format(self.domain,
self.logger.error(u'{}_\{}:{} {}'.format(self.domain,
self.username,
self.password,
"Error connecting to the domain, please add option --kdcHost with the IP of the domain controller"))
@ -249,6 +277,13 @@ class ldap(connection):
self.username = username
self.domain = domain
if self.kdcHost is not None:
target = self.kdcHost
else:
target = domain
self.kdcHost = domain
# Create the baseDN
self.baseDN = ''
domainParts = self.kdcHost.split('.')
@ -257,11 +292,6 @@ class ldap(connection):
# Remove last ','
self.baseDN = self.baseDN[:-1]
# if self.kdcHost is not None:
# target = self.kdcHost
# else:
# target = domain
if self.hash == '' and self.args.asreproast:
hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username)
if hash_TGT:
@ -275,9 +305,9 @@ class ldap(connection):
username,
nthash)
try:
self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % self.kdcHost, self.baseDN, self.kdcHost)
self.ldapConnection = ldap_impacket.LDAPConnection('ldap://%s' % target, self.baseDN, self.kdcHost)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
#self.check_if_admin()
self.check_if_admin()
self.logger.success(out)
if not self.args.continue_on_success:
@ -286,17 +316,23 @@ class ldap(connection):
if str(e).find('strongerAuthRequired') >= 0:
try:
# We need to try SSL
self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % self.kdcHost, self.baseDN, self.kdcHost)
self.ldapConnection = ldap_impacket.LDAPConnection('ldaps://%s' % target, self.baseDN, self.kdcHost)
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
self.logger.success(out)
except ldap_impacket.LDAPSessionError as e:
self.logger.error(u'{}\{}:{}'.format(self.domain,
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.nthash))
self.password,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
else:
self.logger.error(u'{}\{}:{}'.format(self.domain,
self.username,
self.nthash))
errorCode = str(e).split()[-2][:-1]
self.logger.error(u'{}\\{}:{} {}'.format(self.domain,
self.username,
self.password,
ldap_error_status[errorCode] if errorCode in ldap_error_status else ''),
color='magenta' if errorCode in ldap_error_status else 'red')
return False
except OSError as e:
self.logger.error(u'{}\{}:{} {}'.format(self.domain,
@ -339,6 +375,65 @@ class ldap(connection):
return False
def sid_to_str(self, sid):
try:
# revision
revision = int(sid[0])
# count of sub authorities
sub_authorities = int(sid[1])
# big endian
identifier_authority = int.from_bytes(sid[2:8], byteorder='big')
# If true then it is represented in hex
if identifier_authority >= 2 ** 32:
identifier_authority = hex(identifier_authority)
# loop over the count of small endians
sub_authority = '-' + '-'.join([str(int.from_bytes(sid[8 + (i * 4): 12 + (i * 4)], byteorder='little')) for i in range(sub_authorities)])
objectSid = 'S-' + str(revision) + '-' + str(identifier_authority) + sub_authority
return objectSid
except Exception:
pass
return sid
def check_if_admin(self):
# 1. get SID of the domaine
sid_domaine = ""
searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=8192)"
attributes= ["objectSid"]
resp = self.search(searchFilter, attributes, sizeLimit=0)
answers = []
for attribute in resp[0][1]:
if str(attribute['type']) == 'objectSid':
sid = self.sid_to_str(attribute['vals'][0])
sid_domaine = '-'.join(sid.split('-')[:-1])
# 2. get all group cn name
searchFilter = "(|(objectSid="+sid_domaine+"-512)(objectSid="+sid_domaine+"-544)(objectSid="+sid_domaine+"-519)(objectSid=S-1-5-32-549)(objectSid=S-1-5-32-551))"
attributes= ["distinguishedName"]
resp = self.search(searchFilter, attributes, sizeLimit=0)
answers = []
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
for attribute in item['attributes']:
if str(attribute['type']) == 'distinguishedName':
answers.append(str("(memberOf:1.2.840.113556.1.4.1941:=" + attribute['vals'][0] + ")"))
# 3. get memeber of these groups
searchFilter = "(&(objectCategory=user)(sAMAccountName=" + self.username + ")(|" + ''.join(answers) + "))"
attributes= [""]
resp = self.search(searchFilter, attributes, sizeLimit=0)
answers = []
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
continue
if item:
self.admin_privs = True
def getUnixTime(self, t):
t -= 116444736000000000
t /= 10000000
@ -352,13 +447,13 @@ class ldap(connection):
sizeLimit=sizeLimit)
except ldap_impacket.LDAPSearchError as e:
if e.getErrorString().find('sizeLimitExceeded') >= 0:
logging.debug('sizeLimitExceeded exception caught, giving up and processing the data received')
self.logger.error('sizeLimitExceeded exception caught, giving up and processing the data received')
# We reached the sizeLimit, process the answers we have already and that's it. Until we implement
# paged queries
resp = e.getAnswers()
pass
else:
logging.debug(e)
self.logger.error(e)
return False
return resp
@ -446,7 +541,7 @@ class ldap(connection):
resp = self.search(searchFilter, attributes, 0)
if resp:
answers = []
logging.debug('Total of records returned %d' % len(resp))
self.logger.info('Total of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True:
@ -502,7 +597,7 @@ class ldap(connection):
resp = self.search(searchFilter, attributes, 0)
if resp:
answers = []
logging.debug('Total of records returned %d' % len(resp))
self.logger.info('Total of records returned %d' % len(resp))
for item in resp:
if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: