Skip to content

Added Support for LDAPS When Retrieving LAPS Password from Windows Server 2025 #1942

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

Merged
merged 2 commits into from
Apr 30, 2025

Conversation

Ibrahim8879
Copy link
Contributor

@Ibrahim8879 Ibrahim8879 commented Apr 7, 2025

Description

Added support for LDAPS in GetLAPSPassword.py to ensure compatibility with Windows Server 2025, which enforces LDAPS over LDAP. Introduced a boolean flag -ldaps to enable LDAPS support when retrieving LAPS passwords.
This PR addresses and resolves: #1880

Configuration

impacket version: v0.130.dev
Python version: 3.13.2
Target OS: Windows Server 2025 Datacenter Evaluation 24H2
Attacking OS: Kali

Debug Output With Command String

└─$ python3 GetLAPSPassword.py -dc-ip 192.168.164.129 "TESTLAB.local/Administrator:<Password>" -debug -ldaps
Impacket v0.13.0.dev0+20250401.172759.352695f1 - Copyright Fortra, LLC and its affiliated companies

[+] Impacket Library Installation Path: /home/kali/Desktop/impacket (2)/examples/venv/lib/python3.13/site-packages/impacket-0.13.0.dev0+20250401.172759.352695f1-py3.13.egg/impacket
[*] Using LDAPS
[+] Connecting to 192.168.164.129, port 636, SSL True
[+] Total of records returned 4
[+] Connecting to ncacn_ip_tcp:192.168.164.129[49682]
[+] Connected
[+] Successfully bound
[+] Calling MS-GKDI GetKey
Host     	LAPS Username  LAPS Password   LAPS Password Expiration  LAPSv2
-----------  -------------  --------------  ------------------------  ------
CLIENT-PC1$  Administrator  6Yg[RfHz%@50x+  2025-05-06 08:59:59   	True   

Screenshot

impacket

@gabrielg5
Copy link
Collaborator

hey @Ibrahim8879 , Thanks for this PR!

In the ldap_login function, exceptions of type ldap.LDAPSessionError are being handled and if "strongerAuthRequired" is in the error, connection through ldaps is tried.
I'll check it, but did you notice which is the exception you were receiving in this scenario... I guess perhaps can be "automatically" handled (first try with LDAP and if fails, go with LDAPS)

@Ibrahim8879
Copy link
Contributor Author

hi @gabrielg5 , thanks for the quick response. here's the details of my testing:

As mentioned in the issue, the Server 2025 setup enforces LDAPS. In my environment, Server 2025 is configured to handle only LDAPS for LAPS password requests—LDAP requests are not allowed at all. When I tested your implementation in this environment, it initially tries to connect via LDAP and does not automatically switch to LDAPS, resulting in a timeout.

That’s why I added the -ldaps flag—to ensure the connection uses LDAPS directly. I hope this clarifies the reasoning. I’ve also attached a screenshot for further clarification, showing LAPS password requests with and without the -ldaps flag.

github

@gabrielg5
Copy link
Collaborator

Hey,
mm being a "Connection timeout" the error you are receiving, no LDAPS connection is triggered. LDAPS It's only tried if strongerAuthRequired error is returned
Wondering if testing with LDAPS may makes sense also for timeouts. Just to handle it automatically from the lib and saving the need to add a new flag

Creating a 2025 environment here to test.

@Ibrahim8879
Copy link
Contributor Author

Sounds good — I’ll try implementing this since I already have the environment set up in the library. I’ll get back to you once I’ve tested it.

@Ibrahim8879
Copy link
Contributor Author

Ibrahim8879 commented Apr 16, 2025

Hey @gabrielg5 ,
I’ve added timeout handling for LDAPS and refactored the workflow in the ldap.py file located at impacket/impacket/ldap/ldap.py. I’ve also tested the changes and attached a screenshot for reference.
Let me know if you spot any issues.
(Tested on this pushed code)
test with ldaps only

@Ibrahim8879
Copy link
Contributor Author

hey @gabrielg5 ,
I pushed an updated version last week after your feedback. Wanted to check if you’ve had a chance to test it.

@gabrielg5 gabrielg5 self-assigned this Apr 21, 2025
@gabrielg5
Copy link
Collaborator

gabrielg5 commented Apr 22, 2025

Hey @Ibrahim8879, hello!

I was referring more to changing the ldap_login function

def ldap_login(target, base_dn, kdc_ip, kdc_host, do_kerberos, username, password, domain, lmhash, nthash, aeskey, target_domain=None, fqdn=False):

Think it's better in terms of classes responsibilities

This is the one used by that example (and some others as well) to handle it. So, by changing this behavior there, we are also fixing the issue in a more global standard way.

That function is handling errors with the non-SSL connection and retrying with LDAPS

except ldap.LDAPSessionError as e:
if str(e).find('strongerAuthRequired') >= 0:
# We need to try SSL
ldapConnection = ldap.LDAPConnection('ldaps://%s' % target, base_dn, kdc_ip)
if do_kerberos is not True:
ldapConnection.login(username, password, domain, lmhash, nthash)
else:
ldapConnection.kerberosLogin(username, password, domain, lmhash, nthash, aeskey, kdcHost=kdc_ip)

Currently only retrying when strongerAuthRequired is in the exception message; we should also consider this new scenario there

@gabrielg5 gabrielg5 added the enhancement Implemented features can be improved or revised label Apr 22, 2025
@Ibrahim8879
Copy link
Contributor Author

hi @gabrielg5 ,
I've noticed that the timeout error is actually occurring within ldap.py. Although I recently implemented your suggestion to handle the timeout in utils.py, it seems that the error is caught earlier in ldap.py, so the check in utils.py is never reached.
Given this, I’m considering reverting to commit 8e438e1 and continuing improvements from there. What do you think?

436198166-df88a990-59cb-48f7-b382-233daf3e6004

@gabrielg5
Copy link
Collaborator

Hey @Ibrahim8879, don't hate me that much for this...

we've been talking internally with other maintainers and agreed that it is not desired that the tool takes decisions in behalf of the operator (like going through ldaps if ldap timeouts)
in this case we think it makes sense rollback to your first commit and go with the flag that will allow the operator to decide the protocol to use.

I'm setting up an environment here to test and validate the PR asap

Thanks and sorry again for this back and forth in this

@gabrielg5
Copy link
Collaborator

Also, to replicate your same scenario here, which configuration you have there making LDAP not reply??
I was only able to get a "timeout" when configured a firewall blocking connections on the ldap port (389)

@Ibrahim8879
Copy link
Contributor Author

Hi @gabrielg5

Sorry for the late response, I was busy with office work. It's all fine, this is a learning experience, so no problem at all.

Regarding the configuration: I used Microsoft's guide to configure LDAP over SSL (LDAPS) by setting registry keys and applying GPOs. However, the issue with this approach is that sometimes it works as intended (forcing communication over LDAPS and blocking LDAP), but other times, it still allows LDAP connections.

To ensure more consistent behavior for testing purposes, I instead added a firewall inbound rule to block LDAP traffic (port 389) directly. This is where the timeout issue starts, as I mentioned earlier, when LDAP is blocked at the firewall, there's no fallback mechanism to automatically check for LDAPS.
That's why I added the flag to allow the operator to explicitly choose the protocol.

@gabrielg5
Copy link
Collaborator

ok, yeah!
let's revert all changesets and only work from the first one you submitted; the one adding the -ldaps flag to the example.
Thanks!

@Ibrahim8879
Copy link
Contributor Author

hi @gabrielg5
I have reset this PR to the first commit where we implemented the -ldaps flag.
Here’s a brief explanation of how it works:
I’ve added a -ldaps option, which can be seen using the help command.
When this flag is passed, the if-else check in utils.py modifies the URL from ldap to ldaps.

# Added ldaps flag & placed check for ldaps if flag is enabled.
url = 'ldaps://%s' if ldaps_flag else 'ldap://%s'

If the flag is not provided, the default ldap workflow is followed.

@gabrielg5 gabrielg5 added the waiting for response Further information is needed from people who opened the issue or pull request label Apr 30, 2025
@gabrielg5
Copy link
Collaborator

Hi @Ibrahim8879, last change requested before merging...

Let's not make ldaps_flag a mandatory parameter for ldap_login as there are other scripts calling it without setting them... they will fail if we don't set it

Thanks!

@gabrielg5
Copy link
Collaborator

Realized I could directly push that change...
Merging! 🚀
Thank you!

@gabrielg5 gabrielg5 removed the waiting for response Further information is needed from people who opened the issue or pull request label Apr 30, 2025
@gabrielg5 gabrielg5 merged commit 756ca96 into fortra:master Apr 30, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Implemented features can be improved or revised
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LAPS Password Retrieval Only Possible Over LDAPS
2 participants