diff --git a/certipy/commands/parsers/target.py b/certipy/commands/parsers/target.py index 55e662e..a5b5e44 100755 --- a/certipy/commands/parsers/target.py +++ b/certipy/commands/parsers/target.py @@ -97,3 +97,10 @@ def add_argument_group( action="store_true", help="Don't ask for password (useful for -k and -sspi)", ) + + group = parser.add_argument_group("ldap options") + group.add_argument( + "-ldap-channel-binding", + action="store_true", + help="Use LDAP channel binding for LDAP communication (LDAPS only)", + ) \ No newline at end of file diff --git a/certipy/lib/ldap.py b/certipy/lib/ldap.py index 7ec5020..b6c9762 100755 --- a/certipy/lib/ldap.py +++ b/certipy/lib/ldap.py @@ -95,6 +95,8 @@ def connect(self, version: ssl._SSLMethod = None) -> None: connect_timeout=self.target.timeout, ) else: + if self.target.ldap_channel_binding: + raise Exception("LDAP channel binding is only available with LDAPS") ldap_server = ldap3.Server( self.target.target_ip, use_ssl=False, @@ -107,7 +109,7 @@ def connect(self, version: ssl._SSLMethod = None) -> None: if self.target.do_kerberos or self.target.use_sspi: ldap_conn = ldap3.Connection( - ldap_server, receive_timeout=self.target.timeout * 10 + ldap_server, receive_timeout=self.target.timeout * 10, ) self.LDAP3KerberosLogin(ldap_conn) else: @@ -115,6 +117,11 @@ def connect(self, version: ssl._SSLMethod = None) -> None: ldap_pass = "%s:%s" % (self.target.lmhash, self.target.nthash) else: ldap_pass = self.target.password + channel_binding = None + if self.target.ldap_channel_binding: + if not hasattr(ldap3, 'TLS_CHANNEL_BINDING'): + raise Exception("To use LDAP channel binding, install the patched ldap3 module: pip3 install git+https://github.com/ly4k/ldap3") + channel_binding = ldap3.TLS_CHANNEL_BINDING if self.target.ldap_channel_binding else None ldap_conn = ldap3.Connection( ldap_server, user=user, @@ -122,6 +129,7 @@ def connect(self, version: ssl._SSLMethod = None) -> None: authentication=ldap3.NTLM, auto_referrals=False, receive_timeout=self.target.timeout * 10, + channel_binding=channel_binding, ) if not ldap_conn.bound: @@ -140,9 +148,9 @@ def connect(self, version: ssl._SSLMethod = None) -> None: self.port = 636 return self.connect() else: - if result["description"] == "invalidCredentials": + if result["description"] == "invalidCredentials" and result["message"].split(":")[0] == "80090346": raise Exception( - "Failed to authenticate to LDAP. Invalid credentials" + "Failed to bind to LDAP. LDAP channel binding or signing is required. Use -scheme ldaps -ldap-channel-binding" ) raise Exception( "Failed to authenticate to LDAP: (%s) %s" @@ -199,6 +207,14 @@ def LDAP3KerberosLogin(self, connection: ldap3.Connection) -> bool: ) connection.sasl_in_progress = False if response[0]["result"] != 0: + if response[0]["description"] == "invalidCredentials" and response[0]["message"].split(":")[0] == "80090346": + raise Exception( + "Failed to bind to LDAP. LDAP channel binding or signing is required. Certipy only supports channel binding via NTLM authentication. Use -scheme ldaps -ldap-channel-binding and use a password or NTLM hash for authentication instead of Kerberos, if possible" + ) + if response[0]["description"] == "strongerAuthRequired" and response[0]["message"].split(":")[0] == "00002028": + raise Exception( + "Failed to bind to LDAP. LDAP signing is required but not supported by Certipy. Use -scheme ldaps -ldap-channel-binding and use a password or NTLM hash for authentication instead of Kerberos, if possible" + ) raise Exception(response) connection.bound = True diff --git a/certipy/lib/target.py b/certipy/lib/target.py index ee6ea3a..6a92068 100755 --- a/certipy/lib/target.py +++ b/certipy/lib/target.py @@ -158,6 +158,7 @@ def __init__(self): self.target_ip: str = None self.timeout: int = 5 self.resolver: Resolver = None + self.ldap_channel_binding = None @staticmethod def from_options( @@ -267,6 +268,7 @@ def from_options( self.dc_ip = dc_ip self.dc_host = dc_host self.timeout = options.timeout + self.ldap_channel_binding = options.ldap_channel_binding if dc_as_target and options.dc_ip is None and is_ip(remote_name): self.dc_ip = remote_name @@ -304,6 +306,7 @@ def create( ns: str = None, dns_tcp: bool = False, timeout: int = 5, + ldap_channel_binding: bool = False, ) -> "Target": self = Target() @@ -362,6 +365,7 @@ def create( self.use_sspi = use_sspi self.dc_ip = dc_ip self.timeout = timeout + self.ldap_channel_binding = ldap_channel_binding if ns is None: ns = dc_ip diff --git a/setup.py b/setup.py index 418422c..00285ae 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="certipy-ad", - version="4.8.0", + version="4.8.1", license="MIT", author="ly4k", url="https://github.com/ly4k/Certipy",