Skip to content

Commit ecd3fcd

Browse files
authored
Merge pull request Pennyw0rth#508 from Pennyw0rth/ldap_wt_smb
Remove smb from ldap proto
2 parents d05aeb5 + 4767762 commit ecd3fcd

2 files changed

Lines changed: 55 additions & 143 deletions

File tree

nxc/protocols/ldap.py

Lines changed: 55 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,22 @@
1414
from OpenSSL.SSL import SysCallError
1515
from bloodhound.ad.authentication import ADAuthentication
1616
from bloodhound.ad.domain import AD
17-
from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP
18-
from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE
1917
from impacket.dcerpc.v5.samr import (
2018
UF_ACCOUNTDISABLE,
2119
UF_DONT_REQUIRE_PREAUTH,
2220
UF_TRUSTED_FOR_DELEGATION,
2321
UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
2422
UF_SERVER_TRUST_ACCOUNT,
2523
)
26-
from impacket.dcerpc.v5.transport import DCERPCTransportFactory
2724
from impacket.krb5 import constants
2825
from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError
2926
from impacket.krb5.types import Principal, KerberosException
3027
from impacket.ldap import ldap as ldap_impacket
3128
from impacket.ldap import ldaptypes
3229
from impacket.ldap import ldapasn1 as ldapasn1_impacket
3330
from impacket.ldap.ldap import LDAPFilterSyntaxError
34-
from impacket.smb import SMB_DIALECT
35-
from impacket.smbconnection import SMBConnection, SessionError
31+
from impacket.smbconnection import SessionError
32+
from impacket.ntlm import getNTLMSSPType1
3633

3734
from nxc.config import process_secret, host_info_colors
3835
from nxc.connection import connection
@@ -42,6 +39,7 @@
4239
from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB
4340
from nxc.protocols.ldap.kerberos import KerberosAttacks
4441
from nxc.parsers.ldap_results import parse_result_attributes
42+
from nxc.helpers.ntlm_parser import parse_challenge
4543

4644
ldap_error_status = {
4745
"1": "STATUS_NOT_SUPPORTED",
@@ -136,7 +134,7 @@ def __init__(self, args, db, host):
136134
self.server_os = None
137135
self.os_arch = 0
138136
self.hash = None
139-
self.ldapConnection = None
137+
self.ldap_connection = None
140138
self.lmhash = ""
141139
self.nthash = ""
142140
self.baseDN = ""
@@ -163,25 +161,25 @@ def proto_logger(self):
163161
}
164162
)
165163

166-
def get_ldap_info(self, host):
164+
def create_conn_obj(self):
167165
try:
168166
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
169-
ldap_url = f"{proto}://{host}"
167+
ldap_url = f"{proto}://{self.host}"
170168
self.logger.info(f"Connecting to {ldap_url} with no baseDN")
171169
try:
172-
ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host)
173-
if ldap_connection:
174-
self.logger.debug(f"ldap_connection: {ldap_connection}")
170+
self.ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host)
171+
if self.ldap_connection:
172+
self.logger.debug(f"ldap_connection: {self.ldap_connection}")
175173
except SysCallError as e:
176174
if proto == "ldaps":
177175
self.logger.fail(f"LDAPs connection to {ldap_url} failed - {e}")
178176
# https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/enable-ldap-over-ssl-3rd-certification-authority
179177
self.logger.fail("Even if the port is open, LDAPS may not be configured")
180178
else:
181179
self.logger.fail(f"LDAP connection to {ldap_url} failed: {e}")
182-
exit(1)
180+
return False
183181

184-
resp = ldap_connection.search(
182+
resp = self.ldap_connection.search(
185183
scope=ldapasn1_impacket.Scope("baseObject"),
186184
attributes=["defaultNamingContext", "dnsHostName"],
187185
sizeLimit=0,
@@ -208,42 +206,18 @@ def get_ldap_info(self, host):
208206
self.logger.debug("Exception:", exc_info=True)
209207
self.logger.info(f"Skipping item, cannot process due to error {e}")
210208
except OSError:
211-
return [None, None, None]
209+
return False
212210
self.logger.debug(f"Target: {target}; target_domain: {target_domain}; base_dn: {base_dn}")
213-
return [target, target_domain, base_dn]
214-
215-
def get_os_arch(self):
216-
try:
217-
string_binding = rf"ncacn_ip_tcp:{self.host}[135]"
218-
transport = DCERPCTransportFactory(string_binding)
219-
transport.setRemoteHost(self.host)
220-
transport.set_connect_timeout(5)
221-
dce = transport.get_dce_rpc()
222-
if self.args.kerberos:
223-
dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
224-
dce.connect()
225-
try:
226-
dce.bind(
227-
MSRPC_UUID_PORTMAP,
228-
transfer_syntax=("71710533-BEBA-4937-8319-B5DBEF9CCC36", "1.0"),
229-
)
230-
except DCERPCException as e:
231-
if str(e).find("syntaxes_not_supported") >= 0:
232-
dce.disconnect()
233-
return 32
234-
else:
235-
dce.disconnect()
236-
return 64
237-
except Exception as e:
238-
self.logger.fail(f"Error retrieving os arch of {self.host}: {e!s}")
239-
240-
return 0
211+
self.target = target
212+
self.targetDomain = target_domain
213+
self.baseDN = base_dn
214+
return True
241215

242216
def get_ldap_username(self):
243217
extended_request = ldapasn1_impacket.ExtendedRequest()
244218
extended_request["requestName"] = "1.3.6.1.4.1.4203.1.11.3" # whoami
245219

246-
response = self.ldapConnection.sendReceive(extended_request)
220+
response = self.ldap_connection.sendReceive(extended_request)
247221
for message in response:
248222
search_result = message["protocolOp"].getComponent()
249223
if search_result["resultCode"] == ldapasn1_impacket.ResultCode("success"):
@@ -254,46 +228,26 @@ def get_ldap_username(self):
254228
return ""
255229

256230
def enum_host_info(self):
257-
self.target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host)
258231
self.baseDN = self.args.base_dn if self.args.base_dn else self.baseDN # Allow overwriting baseDN from args
259-
self.hostname = self.target
232+
self.hostname = self.target.split(".")[0].upper()
260233
self.remoteName = self.target
261234
self.domain = self.targetDomain
262-
# smb no open, specify the domain
263-
if not self.args.no_smb:
264-
self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]
265235

266-
try:
267-
self.conn.login("", "")
268-
except BrokenPipeError as e:
269-
self.logger.fail(f"Broken Pipe Error while attempting to login: {e}")
270-
except Exception as e:
271-
if "STATUS_NOT_SUPPORTED" in str(e):
272-
self.no_ntlm = True
273-
if not self.no_ntlm:
274-
self.hostname = self.conn.getServerName()
275-
self.targetDomain = self.domain = self.conn.getServerDNSDomainName()
276-
self.server_os = self.conn.getServerOS()
277-
self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection["RequireSigning"]
278-
self.os_arch = self.get_os_arch()
279-
self.logger.extra["hostname"] = self.hostname
280-
281-
if not self.domain:
282-
self.domain = self.hostname
283-
if self.args.domain:
284-
self.domain = self.args.domain
285-
if self.args.local_auth:
286-
self.domain = self.hostname
287-
self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}"
288-
289-
try: # noqa: SIM105
290-
# DC's seem to want us to logoff first, windows workstations sometimes reset the connection
291-
self.conn.logoff()
292-
except Exception:
293-
pass
294-
295-
# Re-connect since we logged off
296-
self.create_conn_obj()
236+
ntlm_challenge = None
237+
bindRequest = ldapasn1_impacket.BindRequest()
238+
bindRequest["version"] = 3
239+
bindRequest["name"] = ""
240+
negotiate = getNTLMSSPType1()
241+
bindRequest["authentication"]["sicilyNegotiate"] = negotiate.getData()
242+
try:
243+
response = self.ldap_connection.sendReceive(bindRequest)[0]["protocolOp"]
244+
ntlm_challenge = bytes(response["bindResponse"]["matchedDN"])
245+
except Exception as e:
246+
self.logger.debug(f"Failed to get target {self.host} ntlm challenge, error: {e!s}")
247+
248+
if ntlm_challenge:
249+
ntlm_info = parse_challenge(ntlm_challenge)
250+
self.server_os = ntlm_info["os_version"]
297251

298252
if not self.kdcHost and self.domain:
299253
result = self.resolver(self.domain)
@@ -304,17 +258,10 @@ def enum_host_info(self):
304258

305259
def print_host_info(self):
306260
self.logger.debug("Printing host info for LDAP")
307-
if self.args.no_smb:
308-
self.logger.extra["protocol"] = "LDAP" if self.port == 389 else "LDAPS"
309-
self.logger.extra["port"] = self.port
310-
self.logger.display(f'{self.baseDN} (Hostname: {self.hostname.split(".")[0]}) (domain: {self.domain})')
311-
else:
312-
self.logger.extra["protocol"] = "SMB" if not self.no_ntlm else "LDAP"
313-
self.logger.extra["port"] = "445" if not self.no_ntlm else "389"
314-
signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"])
315-
smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"])
316-
self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})")
317-
self.logger.extra["protocol"] = "LDAP"
261+
self.logger.extra["protocol"] = "LDAP" if str(self.port) == "389" else "LDAPS"
262+
self.logger.extra["port"] = self.port
263+
self.logger.extra["hostname"] = self.hostname
264+
self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})")
318265

319266
def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False):
320267
self.username = username
@@ -355,8 +302,8 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="",
355302
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
356303
ldap_url = f"{proto}://{self.target}"
357304
self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [1]")
358-
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host)
359-
self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache)
305+
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host)
306+
self.ldap_connection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache)
360307
if self.username == "":
361308
self.username = self.get_ldap_username()
362309

@@ -400,8 +347,8 @@ def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="",
400347
self.logger.extra["port"] = "636"
401348
ldaps_url = f"ldaps://{self.target}"
402349
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [2]")
403-
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
404-
self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache)
350+
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
351+
self.ldap_connection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache)
405352
if self.username == "":
406353
self.username = self.get_ldap_username()
407354

@@ -457,8 +404,8 @@ def plaintext_login(self, domain, username, password):
457404
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
458405
ldap_url = f"{proto}://{self.target}"
459406
self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [3]")
460-
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host)
461-
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
407+
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host)
408+
self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
462409
self.check_if_admin()
463410

464411
# Prepare success credential text
@@ -478,8 +425,8 @@ def plaintext_login(self, domain, username, password):
478425
self.logger.extra["port"] = "636"
479426
ldaps_url = f"ldaps://{self.target}"
480427
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [4]")
481-
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
482-
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
428+
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
429+
self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
483430
self.check_if_admin()
484431

485432
# Prepare success credential text
@@ -543,8 +490,8 @@ def hash_login(self, domain, username, ntlm_hash):
543490
proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap"
544491
ldaps_url = f"{proto}://{self.target}"
545492
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}")
546-
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
547-
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
493+
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
494+
self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
548495
self.check_if_admin()
549496

550497
# Prepare success credential text
@@ -564,8 +511,8 @@ def hash_login(self, domain, username, ntlm_hash):
564511
self.logger.extra["port"] = "636"
565512
ldaps_url = f"{proto}://{self.target}"
566513
self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}")
567-
self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
568-
self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
514+
self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host)
515+
self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash)
569516
self.check_if_admin()
570517

571518
# Prepare success credential text
@@ -594,40 +541,6 @@ def hash_login(self, domain, username, ntlm_hash):
594541
self.logger.fail(f"{self.domain}\\{self.username}:{process_secret(self.password)} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}")
595542
return False
596543

597-
def create_smbv1_conn(self):
598-
self.logger.debug("Creating smbv1 connection object")
599-
try:
600-
self.conn = SMBConnection(self.host, self.host, None, 445, preferredDialect=SMB_DIALECT)
601-
self.smbv1 = True
602-
if self.conn:
603-
self.logger.debug("SMBv1 Connection successful")
604-
except OSError as e:
605-
if str(e).find("Connection reset by peer") != -1:
606-
self.logger.debug(f"SMBv1 might be disabled on {self.host}")
607-
return False
608-
except Exception as e:
609-
self.logger.debug(f"Error creating SMBv1 connection to {self.host}: {e}")
610-
return False
611-
return True
612-
613-
def create_smbv3_conn(self):
614-
self.logger.debug("Creating smbv3 connection object")
615-
try:
616-
self.conn = SMBConnection(self.host, self.host, None, 445)
617-
self.smbv1 = False
618-
if self.conn:
619-
self.logger.debug("SMBv3 Connection successful")
620-
except OSError:
621-
return False
622-
except Exception as e:
623-
self.logger.debug(f"Error creating SMBv3 connection to {self.host}: {e}")
624-
return False
625-
626-
return True
627-
628-
def create_conn_obj(self):
629-
return bool(self.args.no_smb or self.create_smbv1_conn() or self.create_smbv3_conn())
630-
631544
def get_sid(self):
632545
self.logger.highlight(f"Domain SID {self.sid_domain}")
633546

@@ -692,12 +605,12 @@ def getUnixTime(self, t):
692605

693606
def search(self, searchFilter, attributes, sizeLimit=0) -> list:
694607
try:
695-
if self.ldapConnection:
608+
if self.ldap_connection:
696609
self.logger.debug(f"Search Filter={searchFilter}")
697610

698611
# Microsoft Active Directory set an hard limit of 1000 entries returned by any search
699612
paged_search_control = ldapasn1_impacket.SimplePagedResultsControl(criticality=True, size=1000)
700-
return self.ldapConnection.search(
613+
return self.ldap_connection.search(
701614
searchBase=self.baseDN,
702615
searchFilter=searchFilter,
703616
attributes=attributes,
@@ -1245,7 +1158,7 @@ def password_not_required(self):
12451158
searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=32)"
12461159
try:
12471160
self.logger.debug(f"Search Filter={searchFilter}")
1248-
resp = self.ldapConnection.search(
1161+
resp = self.ldap_connection.search(
12491162
searchBase=self.baseDN,
12501163
searchFilter=searchFilter,
12511164
attributes=[
@@ -1373,7 +1286,7 @@ def admin_count(self):
13731286
def gmsa(self):
13741287
self.logger.display("Getting GMSA Passwords")
13751288
search_filter = "(objectClass=msDS-GroupManagedServiceAccount)"
1376-
gmsa_accounts = self.ldapConnection.search(
1289+
gmsa_accounts = self.ldap_connection.search(
13771290
searchBase=self.baseDN,
13781291
searchFilter=search_filter,
13791292
attributes=[
@@ -1426,7 +1339,7 @@ def gmsa_convert_id(self):
14261339
else:
14271340
# getting the gmsa account
14281341
search_filter = "(objectClass=msDS-GroupManagedServiceAccount)"
1429-
gmsa_accounts = self.ldapConnection.search(
1342+
gmsa_accounts = self.ldap_connection.search(
14301343
searchBase=self.baseDN,
14311344
searchFilter=search_filter,
14321345
attributes=["sAMAccountName"],
@@ -1456,7 +1369,7 @@ def gmsa_decrypt_lsa(self):
14561369
gmsa_pass = gmsa[1]
14571370
# getting the gmsa account
14581371
search_filter = "(objectClass=msDS-GroupManagedServiceAccount)"
1459-
gmsa_accounts = self.ldapConnection.search(
1372+
gmsa_accounts = self.ldap_connection.search(
14601373
searchBase=self.baseDN,
14611374
searchFilter=search_filter,
14621375
attributes=["sAMAccountName"],

nxc/protocols/ldap/proto_args.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ def proto_args(parser, parents):
55
ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=parents, formatter_class=DisplayDefaultsNotNone)
66
ldap_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes")
77
ldap_parser.add_argument("--port", type=int, default=389, help="LDAP port")
8-
ldap_parser.add_argument("--no-smb", action="store_true", help="No smb connection")
98

109
dgroup = ldap_parser.add_mutually_exclusive_group()
1110
dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, default=None, help="domain to authenticate to")

0 commit comments

Comments
 (0)