ly4k / Certipy

Tool for Active Directory Certificate Services enumeration and abuse
MIT License
2.43k stars 338 forks source link

ldap-shell doesn't work on port 636 (tries to ldap3.AUTO_BIND_TLS_BEFORE_BIND) #156

Closed keyos1 closed 1 year ago

keyos1 commented 1 year ago

Hello,

today I was trying to use certipy's ldap-shell on HTB box that doesn't support certificate authentication using Kerberos. When trying to use port 389 I'm getting: ldap3.core.exceptions.LDAPInvalidCredentialsResult: LDAPInvalidCredentialsResult - 49 - invalidCredentials - None - 80090317: LdapErr: DSID-0C090635, comment: The server did not receive any credentials via TLS, data 0, v4563 - bindResponse - None

When trying port 636 I get: ldap3.core.exceptions.LDAPStartTLSError: automatic start_tls befored bind not successful

Using python version of https://github.com/AlmondOffSec/PassTheCert everything works as expected on port 636 On port 389 I also get credentials None error.

From the code comparison the difference is how the ldap3.Connection arguments are set depending on port and also call to ldapConn.open() (in your case it would be ldap_conn.open()) on port 636

Here's the code from PassTheCert (including author's comments):

    if options.port == 389:
        # I don't really know why, but using this combination of parameters with ldap3 will
        # send a LDAP_SERVER_START_TLS_OID and trigger a StartTLS
        ldap_connection_kwargs = {'authentication': ldap3.SASL,
                                  'sasl_mechanism': ldap3.EXTERNAL,
                                  'auto_bind': ldap3.AUTO_BIND_TLS_BEFORE_BIND}

    ldapConn = ldap3.Connection(ldapServer, **ldap_connection_kwargs)

    if options.port == 636:
        # According to Microsoft :
        # "If the client establishes the SSL/TLS-protected connection by means of connecting
        # on a protected LDAPS port, then the connection is considered to be immediately
        # authenticated (bound) as the credentials represented by the client certificate.
        # An EXTERNAL bind is not allowed, and the bind will be rejected with an error."
        # Using bind() function will raise an error, we just have to open() the connection
        ldapConn.open()

Trying the same logic in certipy also makes it work

I'm not sure whether the above is correct for all variations of AD configs, but maybe it's worth using/copying

Thanks a lot for the great project.

ThePirateWhoSmellsOfSunflowers commented 1 year ago

Hello!

PassTheCert author here, I'm really interested by this issue, I can't figure out why implicit TLS works but not explicit in your case. Is it possible to have more information such as Wireshark, the box name, debug logs, etc? Which lines did you change within Certipy? Thanks

:sunflower:

keyos1 commented 1 year ago

Not sure it's ok to mention the box in public. Would you like talking over discord ? Let me know what your handle is and I'll move over there.

ThePirateWhoSmellsOfSunflowers commented 1 year ago

After reading some code, it seems that, as you mentioned it, if you using port 636, Certipy tries to use StartTLS on a implicit TLS port which is not supported by Active Directory. On the other hand, I suspect that HTB managed to find a way to block TLS on LDAP port, thus you cannot use Schannel on the port 389.

:sunflower:

ly4k commented 1 year ago

Thank you for reporting the issue. I have fixed this behavior in the latest release (4.6.0). The changes includes defaulting to LDAPS and also fixing the issue with connecting over port 636. Thanks for the inspiration @ThePirateWhoSmellsOfSunflowers. LDAP authentication on the specific HTB machine still doesn't work via port 389. Not sure what is going on here.