LDAP 原理与报文 — DIT 目录树、DN 命名与 LDAPMessage 结构

LDAP 工作原理:目录信息树、DN/RDN、objectClass 与 Schema、LDAPMessage/十种操作、SearchRequest scope 与 RFC 4515 过滤器、Bind 与 StartTLS / TCP 389 / RFC 4511

先建立直觉

LDAP 做的事情,说白了就是"查通讯录"和"核对身份"两件事。

想象公司有一本按部门分层整理好的花名册:翻到某一层能看到这个部门下有哪些人,每个人名下记着邮箱、电话、属于哪几个小组。你可以说"从整个公司范围里,找出所有研发部且有邮箱的人",它就把符合条件的一批人给你列出来。

同时它还兼职门卫:你把名字和口令报给它,它自己去核对,只回你"对"或"不对",从不把口令本身给你看。所有需要登录的系统都来问同一个门卫,公司的账号就统一了。

它解决什么问题

如果没有 LDAP,每个系统都得自己养一份用户名单:邮箱一份、VPN 一份、代码仓库一份、OA 一份。新人入职要建七八个账号,离职了总有一两个忘了删——那就是安全漏洞。

而且这些名单天生是有层级的(公司下面是部门,部门下面是小组),还特别"参差不齐":有人有三个邮箱,有人一个都没有。用普通的表格来装,要么列多到离谱,要么全是空格。

LDAP 就是专门为这种"层级 + 稀疏 + 读得多改得少 + 大家共用"的数据设计的,顺便把"验密码"这件事也统一收了过去。

工作流程(简化版)

  1. 客户端跟服务器建一条 TCP 连接,通常先把这条连接加密(StartTLS 或直接连 636)。
  2. 客户端报上身份做一次认证(Bind),之后这条连接上的所有操作都以这个身份进行。
  3. 客户端发起搜索:说清楚"从哪儿开始找"“找多深"“按什么条件筛"“要哪些字段”。
  4. 服务器把命中的条目一条一条发回来,最后再发一条"搜完了"作为结束标志。
  5. 需要验证某个用户的密码时,就拿这个用户的完整路径(DN)加上他输入的密码再 Bind 一次——服务器说成功,就说明密码对。

报文 / 头部长什么样

LDAP 报文用 BER(Basic Encoding Rules)编码 ASN.1,与 SNMP 同源。

先看数据长什么样:DIT 目录信息树

看这个结构前先记住:LDAP 的数据不是表,是一棵树;树上每个节点叫"条目”,条目里的每个属性都可以有多个值

LDAP 服务器维护一棵 DIT(Directory Information Tree,目录信息树)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
                    dc=example,dc=com ← Base DN / 后缀(Suffix)
                    ┌──────┴───────┐
              ou=people ou=groups
              ┌───┴───┐ ┌───┴────┐
      uid=zhangsan uid=lisi cn=dev cn=ops
      ─────────────────────────────────────────
      条目内容示例(uid=zhangsan,ou=people,dc=example,dc=com):
         objectClass: inetOrgPerson
         objectClass: posixAccount
         uid: zhangsan
         cn: 张三
         sn: 张
         mail: zhangsan@example.com ← 属性可多值
         mail: z.san@example.com
         uidNumber: 10001
         memberOf: cn=dev,ou=groups,dc=example,dc=com

命名规则

看这张表前先记住:RDN 是"小名”,DN 是"全名";全名 = 一路把小名从自己拼到树根,而且是从叶到根倒着写的。

概念例子说明
RDN(Relative Distinguished Name,相对可辨识名)uid=zhangsan条目在同一父节点下必须唯一;可以是多值 RDN,用 + 连接:cn=张三+uid=zs
DN(Distinguished Name,可辨识名)uid=zhangsan,ou=people,dc=example,dc=comRDN 从叶到根用 , 连接,全局唯一,相当于主键
Base DN / Suffix(基准 DN / 后缀)dc=example,dc=com该服务器管辖的子树根
常用命名属性dc(domainComponent,域名分量)
ou(organizationalUnit,组织单元)
o(organization,组织)
cn(commonName,通用名)
uid(userid,用户标识)
c(country,国家)
AD 中常用 CN=OU=,OpenLDAP 常用 uid=ou=

顺序陷阱:DN 是从叶到根书写的,与文件路径 /dc/example/ou/people 方向相反。cn=admin,dc=example,dc=com 中,dc=com 是最顶层。

Schema:objectClass 决定条目长什么样

看这张表前先记住:objectClass 就是条目的"表结构声明"——它规定这类条目必须有哪些属性、可以有哪些属性。每个条目必须至少声明一个。

每个条目必须至少有一个 objectClass 属性,它决定了这个条目允许/必须拥有哪些属性。

概念说明
AttributeType(属性类型)定义一个属性的 OID、名称、语法(syntax)、匹配规则(EQUALITY / SUBSTR / ORDERING)、是否单值
objectClass(对象类)定义一组属性的集合,分 MUST(必需)与 MAY(可选)
STRUCTURAL(结构类)结构类,决定条目"是什么",每个条目有且仅有一条结构类继承链(如 inetOrgPerson
AUXILIARY(辅助类)辅助类,给条目"加料",可叠加多个(如 posixAccount 加 Unix 属性、shadowAccount 加密码策略)
ABSTRACT(抽象类)抽象类,只能被继承,如所有类的根 top

常见继承链:topperson(MUST: cn, sn)→ organizationalPersoninetOrgPerson(MAY: mail, uid, employeeNumber, jpegPhoto…)

匹配规则很关键(决定搜索行为):

看这张表前先记住:同样是"等于",不同属性的比法不一样——文本类多半忽略大小写,数字类按数值大小比,这直接决定了你的过滤器能不能命中。

属性EQUALITY 匹配规则效果
cn, mail, uidcaseIgnoreMatch大小写不敏感ZhangSan 能匹配 zhangsan
userPasswordoctetStringMatch二进制精确匹配
uidNumberintegerMatch数值比较,10002 > 999 成立(若按字符串则不成立)

一条 TCP 连接上的多路复用

LDAP 是有状态协议,但一条 TCP 连接上可以并发发起多个操作

  • 每个请求带一个 messageID(客户端递增分配),响应用同一个 messageID 关联;
  • 服务器可以乱序返回,客户端靠 messageID 对号入座;
  • 一次 Search 的结果由多条消息组成:N 个 SearchResultEntry + 0~N 个 SearchResultReference + 1 个 SearchResultDone(收到它才算搜完);
  • 客户端可用 AbandonRequest 中途放弃一个操作(无响应)。

连接的认证状态是连接级的:Bind 一次后,该连接上后续所有操作都以该身份执行。再次 Bind 会替换当前身份。

LDAPMessage 外层(RFC 4511 §4.1.1)

看这张表前先记住:每条 LDAP 消息都是"编号 + 干什么 + 附加选项"三段式,编号用来对号入座,附加选项(controls)是协议留的万能扩展口。

1
2
3
4
5
6
7
┌─────────────────────────────────────────────────────────┐
│ LDAPMessage ::= SEQUENCE { │
│ messageID MessageID (INTEGER 0..2147483647) │
│ protocolOp CHOICE { bindRequest, searchRequest, ...}│
│ controls [0] Controls OPTIONAL │
│ } │
└─────────────────────────────────────────────────────────┘
字段含义
messageID(消息标识)请求/响应配对标识。同一连接内对未完成的操作必须唯一;0 保留给 Unsolicited Notification(服务器主动通知,如"即将断开")
protocolOp(协议操作)具体操作,由 BER 应用类标签区分(见下表)
controls(控制项)控制项,协议的扩展点。携带 OID + 值,如分页(1.2.840.113556.1.4.319)、排序、AD 的 LDAP_SERVER_SHOW_DELETED_OID

protocolOp 应用标签一览

看这张表前先记住:LDAP 全部能力就这十来种操作,其中真正天天用的只有 Bind(认证)和 Search(查询)两个;注意 Unbind 和 Abandon 是不回响应的。

Tag操作方向说明
[APPLICATION 0]BindRequestC→S认证
[APPLICATION 1]BindResponseS→C
[APPLICATION 2]UnbindRequestC→S无响应,通知服务器关闭连接
[APPLICATION 3]SearchRequestC→S查询
[APPLICATION 4]SearchResultEntryS→C每条结果一个
[APPLICATION 5]SearchResultDoneS→C搜索结束标志 + 结果码
[APPLICATION 6]ModifyRequestC→S改属性
[APPLICATION 7]ModifyResponseS→C
[APPLICATION 8]AddRequestC→S加条目
[APPLICATION 9]AddResponseS→C
[APPLICATION 10]DelRequestC→S删条目(只能删叶子节点
[APPLICATION 11]DelResponseS→C
[APPLICATION 12]ModifyDNRequestC→S改名 / 移动子树
[APPLICATION 13]ModifyDNResponseS→C
[APPLICATION 14]CompareRequestC→S比较属性值(不返回值,只返回真假
[APPLICATION 15]CompareResponseS→C
[APPLICATION 16]AbandonRequestC→S放弃操作,无响应
[APPLICATION 19]SearchResultReferenceS→C引用(referral),指向别的服务器
[APPLICATION 23]ExtendedRequestC→S扩展操作(StartTLS、改密码、Whoami)
[APPLICATION 24]ExtendedResponseS→C
[APPLICATION 25]IntermediateResponseS→C中间响应(如同步复制推送)

BindRequest 字段

看这张表前先记住:Bind 就是"我是谁 + 我怎么证明"——name 填完整 DN,authentication 决定是把明文密码直接甩过去(simple)还是走安全机制(sasl)。

字段类型含义
version(协议版本)INTEGER (1..127)协议版本,LDAPv3 固定为 3
name(认证身份 DN)LDAPDN要认证的 DN(如 cn=admin,dc=example,dc=com)。匿名 Bind 时为空串
authentication(认证信息)CHOICEsimple [0] OCTET STRING明文密码!)或 sasl [3] SaslCredentials{ mechanism, credentials }

三种 Bind 形态

形态name密码安全性
匿名 Bind只能访问允许匿名的公开条目
未认证 Bind有 DN空串RFC 4513 明确警告:许多服务器会误判为"匿名成功",是经典漏洞。客户端必须自己拒绝空密码
Simple Bind有 DN明文裸奔,必须叠加 TLS
SASL Bind空(身份在 SASL 里)mechanism=GSSAPI/EXTERNAL/DIGEST-MD5推荐。GSSAPI 即 Kerberos,EXTERNAL 即客户端证书

SearchRequest 字段(最重要)

看这张表前先记住:一次搜索 = 从哪儿开始(base)+ 找多深(scope)+ 筛什么(filter)+ 要哪些字段(attributes),其余都是限流参数。

字段类型含义
baseObject(搜索基准)LDAPDN搜索起点,从哪个 DN 开始
scope(搜索范围)ENUMERATED搜索范围0 baseObject(只查该条目本身)/ 1 singleLevel(只查直接子节点,不含自己)/ 2 wholeSubtree(整棵子树,含自己
derefAliases(别名解引用)ENUMERATED别名解引用策略:0 neverDerefAliases / 1 derefInSearching / 2 derefFindingBaseObj / 3 derefAlways
sizeLimit(条数上限)INTEGER客户端要求的最大返回条数(0=无限制)。服务器端还有自己的硬上限,取两者较小值
timeLimit(时间上限)INTEGER秒数上限
typesOnly(仅返回类型)BOOLEANTRUE 时只返回属性名不返回值(探测 Schema 用)
filter(过滤器)Filter过滤器(见下节)
attributes(返回属性列表)SEQUENCE OF要返回的属性列表。空列表 = 返回所有用户属性* = 所有用户属性;+ = 所有操作属性(如 createTimestampentryUUID);1.1 = 不返回任何属性(只要 DN)

过滤器语法(RFC 4515)

看这张表前先记住:LDAP 过滤器是"运算符写在前面"的——不是 A and B,而是 (&(A)(B));而且每一层都要用括号包起来

前缀(波兰)表示法,整体必须用括号包裹

类型语法例子
等于(attr=value)(uid=zhangsan)
存在(attr=*)(mail=*) — 有邮箱的条目
子串(attr=前*中*后)(cn=张*)(mail=*@example.com)
大于等于(attr>=value)(uidNumber>=10000)没有 >,只有 >=
小于等于(attr<=value)(uidNumber<=19999)
近似(attr~=value)(cn~=zhangsan)(实现依赖,很少用)
(&(A)(B)(C))(&(objectClass=person)(department=研发))
`((A)(B))`
(!(A))(!(objectClass=computer))
扩展匹配(attr:rule:=value)AD 递归查组:(memberOf:1.2.840.113556.1.4.1941:=CN=dev,...)

必须转义的字符(RFC 4515 §3):

看这张表前先记住:这五个字符是过滤器的"语法字符",用户输入里带了它们又不转义,就是 LDAP 注入的入口。

字符转义
*\2a
(\28
)\29
\\5c
NUL\00

LDAP 注入:把用户输入直接拼进过滤器是高危漏洞。用户输入 *)(uid=*))(|(uid=* 可以让 (uid=<输入>) 变成永真式,绕过认证。必须对上述字符转义

常见结果码(BindResponse / SearchResultDone 等共用)

看这张表前先记住:排错时先看结果码,它基本就是答案——最常撞见的是 49(凭据错)、32(Base DN 错)、50(权限不足)这三个。

名称含义 / 排错方向
0success成功
1operationsError操作顺序错(如 StartTLS 后又 StartTLS)
3timeLimitExceeded超时,缩小搜索范围或加索引
4sizeLimitExceeded结果超过上限,需要分页
7authMethodNotSupported认证机制不支持
8strongerAuthRequired服务器要求 TLS/SASL(ldap_bind: Confidentiality required
10referral条目在别的服务器上,跟随引用
11adminLimitExceededAD 默认单次最多返回 1000
16noSuchAttribute要改/删的属性不存在
17undefinedAttributeTypeSchema 里没这个属性
19constraintViolation违反约束(如密码复杂度不足、单值属性给了多值)
20attributeOrValueExists添加了已存在的属性值
21invalidAttributeSyntax值格式不符合语法
32noSuchObjectBase DN 写错(最高频错误之一)
34invalidDNSyntaxDN 格式错误(少逗号、多空格)
49invalidCredentials用户名或密码错(最高频)
50insufficientAccessRights权限不足,检查 ACL
53unwillingToPerform服务器拒绝(如账号禁用、试图删非空子树、AD 无 SSL 改密码)
64namingViolationRDN 与条目属性不匹配
65objectClassViolation缺少 MUST 属性或 objectClass 组合非法
66notAllowedOnNonLeaf试图删除有子节点的条目
68entryAlreadyExistsDN 已存在

交互时序

一句话看懂这张图:先加密、再认证、然后"搜到人 → 用这个人的 DN 再 Bind 一次验密码"——阶段三和阶段四合起来就是所有应用对接 LDAP 的标准套路(两次 Bind)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
sequenceDiagram
    autonumber
    participant C as LDAP Client<br/>(应用/ldapsearch)
    participant S as LDAP Server<br/>(OpenLDAP / AD DC)

    Note over C,S: 阶段一 · 建立连接与加密
    C->>S: TCP 三次握手 → :389
    C->>S: ExtendedRequest (StartTLS, OID 1.3.6.1.4.1.1466.20037)
    S-->>C: ExtendedResponse resultCode=0 (success)
    C->>S: TLS Handshake(此后全部加密)
    Note right of S: 或直接连 :636 走 LDAPS<br/>先 TLS 握手再发 LDAP

    Note over C,S: 阶段二 · 认证(Bind)
    C->>S: BindRequest version=3<br/>name="cn=svc-app,ou=svc,dc=example,dc=com"<br/>simple="******"
    S-->>C: BindResponse resultCode=0 (success)
    Note left of C: 失败则 resultCode=49<br/>invalidCredentials

    Note over C,S: 阶段三 · 搜索用户(应用登录场景第一步)
    C->>S: SearchRequest msgID=2<br/>base="dc=example,dc=com"<br/>scope=wholeSubtree(2)<br/>filter=(&(objectClass=inetOrgPerson)(uid=zhangsan))<br/>attributes=[dn, cn, mail, memberOf]
    S-->>C: SearchResultEntry msgID=2<br/>dn=uid=zhangsan,ou=people,dc=example,dc=com
    S-->>C: SearchResultDone msgID=2 resultCode=0
    Note right of S: 若结果在其它服务器<br/>返回 SearchResultReference

    Note over C,S: 阶段四 · 验证用户密码(第二次 Bind)
    C->>S: BindRequest name=<上一步拿到的用户 DN><br/>simple=<用户输入的密码>
    alt 密码正确
        S-->>C: BindResponse resultCode=0 → 登录成功
    else 密码错误
        S-->>C: BindResponse resultCode=49 (invalidCredentials)
    end
    Note over C,S: 这就是"两次 Bind"模式:<br/>服务账号先搜到 DN,再用用户凭据 Bind 验证

    Note over C,S: 阶段五 · 修改属性
    C->>S: ModifyRequest dn=uid=zhangsan,...<br/>replace: mail = new@example.com
    S-->>C: ModifyResponse resultCode=0

    Note over C,S: 阶段六 · 结束
    C->>S: UnbindRequest(无响应)
    C->>S: TCP FIN

关键机制 / 变体

1. “两次 Bind” —— 应用集成 LDAP 的标准姿势

这是干什么的:解决"用户只会输一个短用户名,但 Bind 必须用完整 DN"这个矛盾。

几乎所有应用(GitLab、Jenkins、Grafana)对接 LDAP 都是这个流程:

1
2
3
4
① 用【服务账号】Bind → 获得搜索权限
② Search 过滤用户名 → 拿到用户的完整 DN
③ 用【用户 DN + 用户输入的密码】再 Bind → Bind 成功即密码正确
④ (可选)Search memberOf → 拿到用户所属组,做授权

为什么不直接用用户名 Bind? 因为 Bind 需要完整 DN,而用户只会输 zhangsan。不同组织的 DN 结构千差万别(可能在 ou=people 也可能在 ou=北京,ou=分公司),只能先搜。

为什么不直接读 userPassword 属性比对? ① 密码通常是加盐哈希({SSHA}...),格式不定;② 服务器一般禁止读取 userPassword;③ AD 根本不通过 LDAP 暴露密码哈希。用 Bind 让服务器自己验证是唯一正确做法

2. Scope 的三种范围(最易错的概念)

这是干什么的:控制"这次搜索往下挖多深",挖浅了搜不到,挖深了拖垮服务器。

base = ou=people,dc=example,dc=com 为例:

scope搜索范围是否含 base 自身
base(0)ou=people 这一个条目✅ 只有它
one(1)ou=people直接子节点uid=zhangsanuid=lisi),不含孙节点,不含自己
sub(2)ou=people 及其所有后代,无限深度

实践:应用集成一律用 sub;只想验证某个 DN 是否存在用 base + filter=(objectClass=*);列举某 OU 下的直接成员用 one

3. 分页控制(Simple Paged Results,RFC 2696)

这是干什么的:绕开服务器"单次最多返回 N 条"的硬限制,分批把全量数据拉完。

服务器有硬性返回上限(AD 默认 1000 条,OpenLDAP sizelimit 默认 500)。超过就返回 sizeLimitExceeded(4)adminLimitExceeded(11)且已返回的部分结果不完整

解法是分页控制(Control OID 1.2.840.113556.1.4.319):

1
2
3
4
① 客户端在 SearchRequest 的 controls 里带 { size=500, cookie="" }
② 服务器返回 500 条 + SearchResultDone(带 cookie)
③ 客户端用该 cookie 再发下一次 SearchRequest
④ 直到服务器返回空 cookie

命令行:ldapsearch -E pr=500/noprompt ...

4. StartTLS vs LDAPS

这是干什么的:给 LDAP 加密的两条不同路子——一条是"先明文连上再升级",一条是"专用端口一上来就加密"。

维度StartTLSLDAPS
端口389(与明文同端口)636(独立端口)
机制先建明文 TCP,再用 ExtendedRequest(OID 1.3.6.1.4.1.1466.20037协商升级连上就直接 TLS 握手(隐式 TLS)
标准化RFC 4513 正式定义,官方推荐无 RFC,事实标准(源自 LDAPv2 时代)
风险存在降级攻击风险(中间人剥离 StartTLS 能力),客户端必须强制要求成功无降级风险,但需额外端口
URIldap://host:389 + -ZZldaps://host:636

必须加密的理由:Simple Bind 密码是明文的。抓包直接就能看到。RFC 4513 明确要求:不在受保护的传输上不得使用 Simple Bind。

5. Referral(引用)与 Alias(别名)

这是干什么的:处理"数据不在我这台服务器上"以及"给条目做个快捷方式"这两种跳转情况。

  • Referral:服务器说"我这儿没有,去 ldap://other.example.com/dc=sub,dc=example,dc=com 找"。返回 resultCode=10SearchResultReference。客户端需自己发起新连接(ldapsearch -C 开启自动跟随)。AD 的多域森林大量使用 referral,跨域搜索时容易报错。
  • Alias:条目级的"符号链接"(objectClass=alias + aliasedObjectName)。由 derefAliases 参数控制是否解引用。易造成环路,生产环境慎用

6. AD 与标准 LDAP 的关键差异

这是干什么的:告诉你同一套协议在微软那边换了哪些"名词"——不知道这些,照抄 OpenLDAP 的过滤器到 AD 上一条都搜不到。

OpenLDAP(标准)Active Directory
用户 objectClassinetOrgPerson / posixAccountuser(结构类是 userpersonorganizationalPersonuser
组 objectClassgroupOfNames / posixGroupgroup
登录名属性uidsAMAccountName(NetBIOS 名,≤20 字符)
userPrincipalName(UPN,形如 zhangsan@example.com
组成员member(组上)/ memberOf(overlay 提供)member + memberOf(自动维护,只读)
递归查组需客户端自己递归扩展匹配规则 1.2.840.113556.1.4.1941(LDAP_MATCHING_RULE_IN_CHAIN)
账号禁用各实现不同userAccountControl 位掩码,& 2 为禁用;过滤禁用账号:(!(userAccountControl:1.2.840.113556.1.4.803:=2))
改密码ExtendedOp(RFC 3062)或直接改 userPassword必须走 LDAPS/StartTLS,写 unicodePwd(UTF-16LE + 双引号包裹 + Base64)
单次返回上限sizelimit(默认 500)MaxPageSize 默认 1000
匿名访问可配默认禁止,必须 Bind
全局编录端口 3268/3269,跨域搜索但只含部分属性

常见误区

  1. “LDAP 是一种数据库” —— 不准确。它是目录:为读优化、无跨条目事务、写和复制代价高。把业务数据往里塞,迟早会被写性能和一致性问题反噬。

  2. “DN 就像文件路径” —— 方向是反的。文件路径 /dc/example/ou/people 从根写到叶,而 DN uid=zhangsan,ou=people,dc=example,dc=com从叶写到根,最后一段 dc=com 才是最顶层。

  3. “读用户密码来比对” —— 做不到也不该做。密码是加盐哈希({SSHA}...)、服务器通常禁止读取 userPassword、AD 干脆不通过 LDAP 暴露密码哈希。唯一正确做法是用户 DN + 密码去 Bind,让服务器自己判定。

  4. “密码留空 Bind 失败了就安全” —— 恰恰相反。带 DN 但密码为空串的"未认证 Bind",在许多服务器上会被当作匿名 Bind 成功返回 resultCode=0,应用误以为密码正确就放行了。RFC 4513 专门警告过这一点,客户端必须自己拒绝空密码

  5. “搜不到就是没这条数据” —— 也可能是 scope 用了 base/one 挖得不够深,或者 ACL 让你 Bind 得进来却读不到,或者 objectClass 名字在 AD 那边根本不叫这个。

速记口诀

  • 命名口诀:RDN 是小名,DN 是全名;从叶写到根,逗号串一串。
  • 范围口诀:base 只看自己,one 只看儿子,sub 儿孙全要——应用集成认准 sub
  • 过滤口诀:括号包一切,符号写在前;&|! 非,>=> 无。
  • 登录口诀一 Bind 服务号,二搜拿 DN,三 Bind 验密码
  • 加密口诀:389 先连后升级(StartTLS),636 一连就加密(LDAPS);Simple Bind 不加密 = 明文送人头

知识框架

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
mindmap
  root((LDAP))
    数据模型
      DIT 目录信息树
        Base DN 后缀
        条目 Entry
        属性 Attribute 可多值
      命名
        RDN 相对可辨识名
        DN 全局唯一 叶到根
        dc ou o cn uid c
      Schema
        AttributeType 语法与匹配规则
        objectClass
          STRUCTURAL 唯一
          AUXILIARY 可叠加
          ABSTRACT top
        MUST 必需 MAY 可选
        inetOrgPerson 继承链
    协议操作
      Bind 认证
        匿名 Bind
        未认证 Bind 空密码漏洞
        Simple Bind 明文
        SASL Bind GSSAPI EXTERNAL
      Search 查询
        baseObject 起点
        scope base one sub
        filter RFC4515
        attributes 星号 加号 1.1
        sizeLimit timeLimit
      Add Delete Modify
      ModifyDN 改名与移动
      Compare 只回真假
      Abandon 放弃
      Unbind 无响应
      Extended StartTLS 改密码 Whoami
    报文结构
      LDAPMessage
        messageID 多路复用
        protocolOp 应用标签
        controls 扩展点
      BER TLV 编码
      SearchResultEntry 多条
      SearchResultDone 结束标志
      SearchResultReference 引用
    过滤器语法
      等于 存在 子串
      大于等于 小于等于
      与 或 非 前缀表示法
      扩展匹配 1941 递归查组
      转义 2a 28 29 5c
      LDAP 注入风险
    安全
      StartTLS 389 官方推荐
      LDAPS 636 事实标准
      SASL over Kerberos
      ACL 与最小授权
      禁止匿名与空密码 Bind
    工程实践
      两次 Bind 模式
      分页控制 1.2.840.113556.1.4.319
      Referral 跨服务器引用
      索引与读多写少
      与 AD 的属性差异