概述
LDAP(Lightweight Directory Access Protocol):即轻量级目录访问协议。是一种运行于TCP/IP之上的在线目录访问协议,主要用于目录中资源的搜索和查询。使用最广泛的LDAP服务如微软的ADAM(Active Directory Application Mode)和OpenLDAP。
而LDAP 注入是利用用户引入的参数生成恶意 LDAP 查询,通过构造 LDAP 过滤器来绕过访问控制、用户权限提升。在维持正常过滤器的情况下构造出 AND、OR 操作注入来获得敏感信息。
目录数据库结构
LDAP数据库,是树结构的,数据存储在叶子节点上。
- dn:一条记录的位置
- dc:一条记录所属的区域
- ou:一条记录所属的组织
- cn/uid:一条记录的名字/ID
首先要说明是哪一棵树(dc),然后是从树根到目标所经过的所有分叉(ou),最后就是目标的名字(cn/uid),借用一张图来表明结构如下:
条目&对象类&属性
- 条目(entry):是目录中存储的基本信息单元,上图每一个方框代表一个entry。一个entry有若干个属性和若干个值,有些entry还能包含子entry
- 对象类(obejectclass):对象类封装了可选/必选属性,同时对象类也是支持继承的。一个entry必须包含一个objectClass,且需要赋予至少一个值。而且objectClass有着严格的等级之分,最顶层是top和alias。例如,organizationalPerson这个objectClass就隶属于person,而person又隶属于top
- 属性(atrribute):顾名思义,用来存储字段值。被封装在objectclass里的,每个属性(attribute)也会分配唯一的OID号码
基本的LDAP语法
- = 等于
- & 逻辑和
- | 逻辑或
- ! 逻辑不
- * 通配符
逻辑操作符(AND、OR、NOT)和关系操作符(=、>=、<=、~=)
除使用逻辑操作符外,RFC4256还允许使用下面的单独符号作为两个特殊常量:
(&) ->Absolute TRUE
(|) ->Absolute FALSE
对象定义:
objectclass: top
objectclass: person
对象类定义:
objectclass: person
objectclasses=( 2.5.6.6 NAME 'person' DESC 'Defines entries that generically represent people.' SUP 'top' STRUCTURAL MUST ( cn $ sn ) MAY ( userPassword $ telephoneNumber $ seeAlso $ description )
属性定义:
attributetypes=( 2.5.4.4 NAME ( 'sn' 'surName' ) DESC 'This is the X.500 surname attribute, which contains the family name of a person.' SUP 2.5.4.41 EQUALITY 2.5.13.2 ORDERING 2.5.13.3 SUBSTR 2.5.13.4 USAGE userApplications )
搜索语法:
主要根据属性和值进行搜索
LDAP查询语句
一个圆括号内的判断语句又称为一个过滤器filter。
默认情况下,LDAP的DN和所有属性都不区分大小写。
( "&" or "|" (filter1) (filter2) (filter3) ...) ("!" (filter))
LDAP注入
无逻辑操作符的注入
后端代码如果是这样写的:
我们构造输入语句:
$input=value)(injected_filter
完整的语句就成下面这样了:
(attribute=value)(injected_filter)
由于一个括号内代表一个过滤器,在OpenLDAP实施中,第二个过滤器会被忽略,只有第一个会被执行。而在ADAM中,有两个过滤器的查询是不被允许的。因而这类情况仅对于OpenLDAP有一定的影响。
例如我们要想查询一个字段是否存在某值时,可以用$input=x*进行推移,利用页面响应不同判断x*是否查询成功。
带有逻辑操作符的注入
(|(attribute=$input)(second_filter))
(&(attribute=$input)(second_filter))
此时带有逻辑操作符的括号相当于一个过滤器。此时形如value)(injected_filter)的注入会变成如下过滤器结构
(&(attribute=value)(injected_filter))(second_filter)
虽然过滤器语法上并不正确,OpenLDAP还是会从左到右进行处理,忽略第一个过滤器闭合后的任何字符。一些LDAP客户端Web组成会忽略第二个过滤器,将ADAM和OpenLDAP发送给第一个完成的过滤器,因而存在注入。
案例分享
万能用户名案例
验证登陆的查询语句是这样:
(&(USER=$username)(PASSWORD=$pwd))
输入$username = admin)(&)(使查询语句变为:
(&(USER=admin)(&))((PASSWORD=$pwd))
即可让后面的password过滤器失效,执行第一个过滤器而返回true,达到万能密码的效果。
权限提升案例
现假设下面的查询会向用户列举出所有可见的低安全等级文档:
(&(directory=document)(security_level=low))
这里第一个参数”document”是用户入口,low是第二个参数的值。如果攻击者想列举出所有可见的高安全等级的文档,他可以利用如下的注入:
document)(security_level=*))(&(directory=documents
生成的过滤器为:
(&(directory=documents)(security_level=*))(&(direcroty=documents)(security_level=low))
###
LDAP服务器仅会处理第一个过滤器而忽略第二个,因而只有下面的查询会被处理:(&(directory=documents)(security_level=*)),而(&(direcroty=documents)(security_level=low))则会被忽略。结果就是,所有安全等级的可用文档都会列举给攻击者,尽管他没有权限看它们。
总结
LDAP注入本质就是在OpenLDAP实施中,由于一个括号内代表一个过滤器,第二个过滤器会被忽略,只有第一个会被执行。当查询语句带有逻辑操作符时,可以通过注入恶意的LDAP语句去达到不同的目的。