Python Tools Using ldap3 and Issues with Channel Binding and Signing

Fixing issues in common tools with Python ldap3 when connecting to Domain Controllers with LDAP signing and binding enabled.

On a number of recent internal penetration tests I have come across domain controllers with both LDAP signing and channel binding enabled. This is great for the client's security as it means relaying attacks are a non starter but it also means that a number of tools that we use to query LDAP, dump information and perform attacks are not able to connect.

I first noticed this issue when Crackmapexec would connect to the DC fine over SMB and confirm credentials are valid but using the same credentials to connect over LDAP would return that the credentials were invalid. Additionally ldapdomaindump and certipy would return also return invalid credentials despite them being correct. Additionally, msldap would work fine and connect with no issue.

If you encounter the same issue it is a simple fix to get ldapdomaindump and certipy working (and any other tools that use the python ldap3 library in the same manner)

Both ldapdomaindump and certipy use ldap3, as you can see on lines 118-124 in the source of certipy's ldap.py file, it builds the following ldap connection object.

ldap_conn = ldap3.Connection(
    ldap_server,
    user=user,
    password=ldap_pass,
    authentication=ldap3.NTLM,
    auto_referrals=False,
    receive_timeout=self.target.timeout * 10,
)

for some reason when LDAP signing and channel binding are enabled this will fail, in order for the connection to work the ldap connection needs to instead be in the following format

ldap_conn = ldap3.Connection(
    ldap_server,
    users_distinguished_name,
    password,
    )

here is a hard coded example.

ldap_conn = ldap3.Connection(
    ldap_server,
    'CN=pwneduser ,OU=Standard Users,OU=Users,OU=Accounts,OU=Data Management,DC=evilcorp,DC=local',
    'supersecurepassword'
    )

Rebuilding/reinstalling certipy with a hard coded connection string like above will authenticate without issue.

in the case of ldapdomain dump you just need to change the line in __init__.py from:

c = Connection(s, user=args.user, password=args.password, authentication=authentication)

to (hard coded example):

c = Connection(s, 'CN=pwneduser ,OU=Standard Users,OU=Users,OU=Accounts,OU=Data Management,DC=evilcorp,DC=local', 'supersecurepassword', auto_bind=True)

This means you will need to know the distinguished name (DN) of any user credentials you get your hands on, this can be gathered using msldap or pywerview which handle LDAP signing and binding fine.