Table of Contents
What is LDAP?
Lightweight directory access protocol (LDAP) is an open and well supported standards-based mechanism for interacting with directory servers over an Internet Protocol (IP) network. It enables anyone to locate data about organizations, individuals and other resources.
LDAP Authentication methods
LDAP supports many operations which define how to interact with the LDAP server instance, such as creating, retrieving, updating, searching for, and deleting objects in the directory. Some operations are below.
- LDAP add
- LDAP search
- LDAP modify
- LDAP delete
- LDAP bind
Authentication is supplied in the "LDAP bind" operation. There are three different authentication methods that can be configured for LDAPv3.
This method sends username and password in clear text, which is a great security concern. Let’s see it with naked eyes.
Step-1: I will create a simple LDAP client in Python and make a search request for an object.
Step-2: "python-ldap" module provides an object-oriented API to access LDAP directory servers from Python programs. We will use the module to create a search request. Below code starts simple authentication and then searches for an object whose 'sAMAccountName’ attribute is equal to ‘john’.
import ldap #First, we need to import ldap module ldap_connection= ldap.initialize('ldap://192.168.1.150') # Initializes a new connection to the ldap server ldap_connection.simple_bind_s('jane', 'PassMe123.') # starts authentication, using simple bind with the users credentials result = ldap_connection.search_s('dc=mydomain,dc=com', ldap.SCOPE_SUBTREE, 'sAMAccountName=john', ['memberOf']) #makes a search request
Step-3: I will start Wireshark and then run the code to capture the ldap packets. Following screenshot shows the packet I captured.
Packet number 1-3: The first 3 packets belong to TCP 3-way handshaking.
Packet number 4: Once the TCP connection establishes, the client sends a ldap bind request to the server.
The binding operation includes user name (jane) and password (PassMe123) in clear text as it is seen above. The packet also contains which version of LDAP it wants to use (in this case version 3).
Packet number 5: The server authenticates the client successfully and returns "success" as "resultCode" using bindResponse operation. From this moment, the client can query the ldap server.
Packet number 6: The client starts a search request specifying distinguished name (dn), scope and filter criteria (in this case it is asking about whose 'sAMAccountName’ attribute is equal to ‘john’). The client also uses "memberOf" attribute to tell the server to send group(s) of which "john" is member.
Packet number 7: The server responses with a search result entry which includes the group information. As it can be seen below, john is a member of both "Network Operator" and "Users" groups.
Packet number 8: The client acknowledges the server it got the response. It is a simple TCP ACK packet.
Packet number 9: The client uses unbind operation to end the session.
This method is similar to simple binding. Anonymous access is requested by providing no name and no password to the "simple" bind operation. I will demonstrate how to create an anonymous bind operation in the following steps.
Step-1: We need to modify our python code. Change "ldap_connection.simple_bind_s('jane', 'PassMe123.')" to "ldap_connection.simple_bind_s('', ''). It simply sends null credentials to the server.
Step-2: Start Wireshark and run your code. After that you should get packets like below.
Packet number 1-3: The first 3 packets belong to TCP 3-way handshaking.
Packet number 4: The client creates a binding request with null credentials. As you can see from the below, "name" and "simple" fields are empty, which means we have used Anonymous binding.
Packet number 5: It appears that the ldap server accepts Anonymous authentication, since it returned with success result code.
Packet number 6: The client creates a ldap search request with relevant parameters.
Packet number 7: The server returns an operation error code with "In order to perform this operation a successful bind must be completed" message. It simply says you do not have a permission to search the object with anonymous bind.
Packet number 8-10: The client unbinds and tears down the session.
Simple Authentication and Security Layer (SASL)
SASL is an extensible framework that makes it possible to plug almost any kind of authentication into LDAP. Beside authentication, it can also provide a data security layer offering data integrity and data confidentiality services. All authentication mechanisms may not support confidentiality (encryption). You can see it from the table here. (https://docs.oracle.com/cd/E19253-01/816-4556/ldapsecure-75/index.html#ldapsecure-tbl-5)
Several SASL mechanisms are currently defined:
- Anonymous (RFC 2245)
- CRAM-MD5 (RFC 2195)
- Digest-MD5 (RFC 2831)
- External (RFC 2222)
- Kerberos V4 (RFC 2222)
- Kerberos V5 (RFC 2222)
- SecurID (RFC 2808)
- Secure Remote Password (Using the Secure Remote Password (SRP) Protocol for TLS Authentication)
- S/Key (RFC 2222)
- 509 (draft-ietf-ldapext-x509-sasl-03)
When you need to figure out what SASL mechanism your Active Directory supports, change the Pyton code "
result = ldap_connection.search_s('dc=mydomain,dc=com',ldap.SCOPE_SUBTREE,'sAMAccountName=john',['memberOf'])" to "
result = ldap_connection.search_s('',ldap.SCOPE_BASE)" and run it.
It simply creates a search request. As you can see in the response below, my server supports only GSSAPI, GSS-SPNEGO, EXTERNAL and DIGEST-MD5.
Since SASL supports multiple authentication mechanism, I will cover only one of them for packet analysis.
Step-1: We will use two different trace files one of which uses Kerberos authentication without data encryption, while the other uses Kerberos and data encryption. Download the first file from here (https://wiki.wireshark.org/uploads/__moin_import__/attachments/SampleCaptures/ldap-controls-dirsync-01.cap)
Step-2: Expand packet number 4 and it will show that SASL is used for the authentication. The client proposes Kerberos to use as the authentication mechanism.
Step-3: Expand the packet number 5 and you will see the server has authenticated the client successfully.
Step-4: Expand the packet number 6 and you will see the search request in clear text.
Step-5: Download the second trace file from here (https://wiki.wireshark.org/uploads/__moin_import__/attachments/SampleCaptures/ldap-krb5-sign-seal-01.cap)
Step-6: Expand the packet number 10 and you will see the client desires to use SASL. The server accepts the offer in the packet number 11.
Step-7: Expand packet number 12 and you will see the search request is encrypted.
Securing LDAP traffic
There are two methods to secure LDAP traffic. The first method is to using Secure Sockets Layer (SSL) /Transport Layer Security (TLS) technology. When this method is used, simple or SASL authentication will not pose a threat to the system. The second method is to use SASL. Since all of authentication methods in SASL do not support data encryption, picking the right authentication will be important.
Decrypting LDAP traffic
I will use SSL/TLS to protect LDAP traffic.
Step-1: We need to modify our Python code so that it supports SSL/LTS.
import ldap # First, we need to import our module ldap_connection= ldap.initialize('ldap://192.168.1.150') # Initializes a new connection to the ldap server ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) # accepts self signed certificates ldap_connection.start_tls_s() # ldap_connection.set_option(ldap.OPT_REFERRALS, 0) ldap_connection.simple_bind_s('jane', 'PassMe123.') # starts authentication, using simple bind with the users credentials result = ldap_connection.search_s('dc=mydomain,dc=com', ldap.SCOPE_SUBTREE, 'userPrincipalName=john', ['memberOf'])
Step-2: Launch Wireshark and run the code. You should see output like below.
After establishing the TCP connection, the client asks for using TLS in the packet number 7. The server accepts that offer in the packet number 8. From this moment, the client and the server start to establish a TLS tunnel through which LDAP requests will be carried.
Step-3: I will use "frida" tool to obtain the session keys for TLS. Since I covered all details about collecting TLS session keys with "frida" here (https://www.golinuxcloud.com/decrypt-rdp-traffic-wireshark-frida/), I will not explain it here. I assume you have the keys.
Step-4: After feeding Wireshark with the keys, it will show decrypted LDAP packets. Following screenshot shows user credentials in clear text.
LDAP databases contain sensitive data and are critical components for organizations and as a result, they need to be protected very well. When an application needs to communicate with a LDAP server, it should select a secure method to reach it. The safest method is to choose TLS with SASL mechanism.