Skip to content

Commit

Permalink
added partial support for LDAP channel binding
Browse files Browse the repository at this point in the history
  • Loading branch information
ly4k committed Sep 19, 2023
1 parent d7291d1 commit 8d6ff21
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 4 deletions.
7 changes: 7 additions & 0 deletions certipy/commands/parsers/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
)
22 changes: 19 additions & 3 deletions certipy/lib/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -107,21 +109,27 @@ 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:
if self.target.hashes is not 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,
password=ldap_pass,
authentication=ldap3.NTLM,
auto_referrals=False,
receive_timeout=self.target.timeout * 10,
channel_binding=channel_binding,
)

if not ldap_conn.bound:
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions certipy/lib/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -304,6 +306,7 @@ def create(
ns: str = None,
dns_tcp: bool = False,
timeout: int = 5,
ldap_channel_binding: bool = False,
) -> "Target":

self = Target()
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 8d6ff21

Please sign in to comment.