一、LDAP简介
轻型目录访问协议(英文:Lightweight Directory Access Protocol,缩写:LDAP)。LDAP是一种通讯协议,LDAP支持TCP/IP,默认的端口是 389,加密的端口是 636。这个定义有点抽象,简单点来说就是,LDAP就是一个数据库,它是用来存储数据的。
但是,LDAP和其他的数据库(如MySQL、SQLserver)是不一样的,MySQL这类数据库的数据是按记录一条条记录存在表中,而LDAP数据库的结构是树状的,似于“目录服务”的特殊数据库,数据是存储在叶子节点上的。
为什么要用LDAP目录树来存储数据?用MySQL不行吗,为什么非要搞出一个树形的数据库呢?这是因为用树形结构存储数据,查询效率更高(具体为什么,可以看一下关系型数据库索引的实现原理——B树/B+树)。在某些特定的场景下,使用树形数据库更理想。比如:需要储存大量的数据,而且数据不是经常更改,需要很快速的查找。
LDAP结构如下图所示:
像其它数据库一样,LDAP也是有client端和server端。server端是用来存放资源,client端用来操作增删改查等操作。而我们通常说的LDAP是指运行这个数据库的服务器。
二、LDAP查询语法
我们来看这么一个例子:
假设你要树上的一个苹果(一条记录),你怎么告诉园丁它的位置呢?当然首先要说明是哪一棵树(dc,相当于MYSQL的DB),然后是从树根到那个苹果所经过的所有“分叉”(ou),最后就是这个苹果的名字(uid,相当于MySQL表主键id)。好了!这时我们可以清晰的指明这个苹果的位置了,就是那棵“歪脖树”的东边那个分叉上的靠西边那个分叉的再靠北边的分叉上的半红半绿的……,晕了!你直接爬上去吧!
这样子就能描述清楚“树结构”上的每一条记录了。
LDAP目录树形结构,存储在叶子节点上:
dn标志一条记录,描述一条数据的详细路径(根)
dc表示一条记录所属区域,相当于数据库(表示一棵树)
ou表示一条记录所属组织(表示树的分支)
cn表示一条记录的名字,相当于数据库中的主键即需要查找的目标
位置表示:dn:cn=xx,ou=xx,ou=xx,dc=xx
从树根到达目标的位置,可能要经过好几个树杈,所有ou可能有多个值
1、=(等于)查询
此 LDAP 参数表明某个属性等于某个值的条件得到满足。例如,如果希望查找“名“属性为“John”的所有对象,可以使用:(givenName=John)。
这会返回“名”属性为“John”的所有对象。圆括号是必需的,以便强调 LDAP 语句的开始和结束。
2、&(逻辑与)查询
如果具有多个条件并且希望全部条件都得到满足,则可使用此语法。例如,如果希望查找居住在 Dallas 并且“名”为“John”的所有人员,可以使用:(&(givenName=John)(l=Dallas))
请注意,每个参数都被属于其自己的圆括号括起来。整个 LDAP 语句必须包括在一对主圆括号中。操作符 & 表明,只有每个参数都为真,才会将此筛选条件应用到要查询的对象。
3、!(逻辑非)查询
此操作符用来排除具有特定属性的对象。假定您需要查找“名”为“John”的对象以外的所有对象。则应使用如下语句:(!givenName=John)
此语句将查找“名”不为“John”的所有对象。请注意:! 操作符紧邻参数的前面,并且位于参数的圆括号内。由于本语句只有一个参数,因此使用圆括号将其括起以示说明。
4、*(通配符)查询
可使用通配符表示值可以等于任何值。使用它的情况可能是:您希望查找具有职务头衔的所有对象。为此,可以使用:(title=*)
这会返回“title”属性包含内容的所有对象。另一个例子是:您知道某个对象的“名”属性的开头两个字母是“Jo”。那么,可以使用如下语法进行查找:(givenName=Jo*)
这会返回“名”以“Jo”开头的所有对象。
三、LDAP注入
1、漏洞原理
LDAP具有特定的查询结构,并具有特定的语法,来对特定目录进行遍历。LDAP注入攻击和SQL注入攻击类似,利用用户引入的参数生成LDAP查询,由于部分参数没有适当的过滤,因此攻击者可以注入恶意代码以造成恶意攻击。
2、过滤器结构
LDAP同样基于客户端/服务器模型,最常见的操作时使用过滤器搜索目录入口。客户端向服务器发送查询,服务器则响应匹配这些过滤器的目录入口。
LDAP过滤器定义于RFC4515中,这些过滤器的结构可概括如下:
Fileter = (filtercomp) Filtercomp = and / or / not / item And = & filterlist Or = | filterlist Not = ! filter Filterlist = 1*filter Item = simple / present / substring Simple = “=” / “~=” / ”>=” / “<=” Present = attr =* Substring = attr “=” [initial]*[final] Initial = assertion value Final = assertion value
所有过滤器必须置于括号中,只有简化的逻辑操作符(AND、OR、NOT)和关系操作符(=、>=、<=、~=)可用于构造它们。特殊符“*”可用来替换过滤器中的一个或多个字符。
除使用逻辑操作符外,RFC4256还允许使用下面的单独符号作为两个特殊常量:
(&) ->Absolute TRUE (|) ->Absolute FALSE
3、LDAP注入
目前使用得最广泛的LDAP:ADAM和OpenLDAP。
先来看一下简单注入的思路:
①(attribute=value):如果过滤器用于构造查询单缺少逻辑操作符,如value)(injected_filter的注入会导致两个过滤器(attribute=value)(injected\_filter)。在OpenLDAP实施中,第二个过滤器会被忽略(即(injected\_filter),起查询作用的是(attribute=value)),只有第一个会被执行。而在ADAM中,有两个过滤器的查询是不被允许的,因而这个注入毫无用处。
②(|(attribute=value)(second_filter)) or (&(attribute=value)(second_filter)):如果第一个(注意,这里“第一个”不是指“(|(attribute=value)(second_filter))”,而是指注入导致出现两个过滤器的第一个过滤器。如下面“(&(attribute=value)(injected_filter)) (second_filter)”的“(&(attribute=value)(injected_filter))”)用于构造查询的过滤器有逻辑操作符,形如value)(injected_filter)的注入会变成如下过滤器:(&(attribute=value)(injected_filter)) (second_filter)。虽然过滤器语法上并不正确,OpenLDAP还是会从左到右进行处理,忽略第一个过滤器闭合后的任何字符(即“(&(attribute=value)(injected_filter)) (second_filter)”的“(second_filter)”会被忽略)。一些LDAP客户端Web组成会忽略第二个过滤器,将ADAM和OpenLDAP发送给第一个完成的过滤器,因而存在注入。
③一些应用框架在将请求发送给服务器之前会检查过滤器是否正确,在这种情况下,过滤器语义上必须是正确的,其注入如:value)(injected_filter))(&(1=0。这会导致出现两个不同的过滤器,第二个会被忽略:(&(attribute=value)(injected_filter))(&(1=0)(second_filter))。
既然第二个过滤器会被LDAP服务器忽略,有些部分便不允许有两个过滤器的查询。这种情况下,只能构建一个特殊的注入以获得单个过滤器的LDAP查询。value)(injected_filter这样的注入产生的结果是:(&(attribute=value)(injected_filter)(second_filter))。
测试一个应用是否存在代码注入漏洞典型的方法是向服务器发送会生成一个无效输入的请求,如果服务器返回一个错误消息,那么攻击者就能知道服务器执行了他的查询,他就可以利用代码注入技术来达到攻击的目的。
3.1 AND注入
这种情况,应用会构造由”&”操作符和用户引入的的参数组成的正常查询在LDAP目录中搜索,例如:
(&(parameter1=value1)(parameter2=value2))
这里Value1和value2是在LDAP目录中搜索的值,攻击者可以注入代码,维持正确的过滤器结构但能使用查询实现他自己的目标。
案例1:绕过访问控制
一个登陆页有两个文本框用于输入用户名和密码(下图)。Uname和Pwd是用户对应的输入。为了验证客户端提供的user/password对,构造如下LDAP过滤器并发送给LDAP服务器:
(&(USER=Uname)(PASSWORD=Pwd))
如果攻击者输入一个有效地用户名,如r00tgrok,然后再这个名字后面注入恰当的语句,password检查就会被绕过。
使得Uname=slisberger)(&)),引入任何字符串作为Pwd值,构造如下查询并发送给服务器:
(&(USER= slisberger)(&))(PASSWORD=Pwd))
LDAP服务器只处理第一个过滤器,即仅查询(&(USER=slidberger)(&))得到了处理。这个查询永真,因而攻击者无需有效地密码就能获取对系统的访问(下图)。
案例二:权限提升
现假设下面的查询会向用户列举出所有可见的低安全等级文档:
(&(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))
则会被忽略。结果就是,所有安全等级的可用文档都会列举给攻击者,尽管他没有权限查看它们。
低级文档:
高级文档:
3.2 OR注入
这种情况,应用会构造由”|”操作符和用户引入的的参数组成的正常查询在LDAP目录中搜索,例如:
(|(parameter1=value1)(parameter2=value2))
这里Value1和value2是在LDAP目录中搜索的值,攻击者可以注入代码,维持正确的过滤器结构但能使用查询实现他自己的目标。
案例1:信息泄露
假设一个资源管理器允许用户了解系统中可用的资源(打印机、扫描器、存储系统等)。这便是一个典型的OR注入案例,因为用于展示可用资源的查询为:
(|(type=Rsc1)(type=Rsc2))
Rsc1和Rsc2表示系统中不同种类的资源,在下图中,Rsc1=printer,Rsc2=scanner用于列出系统中所以可用的打印机和扫描器:
如果攻击者输入Rsc=printer)(uid=*),则下面的查询被发送给服务器:
(|(type=printer)(uid=*))(type=scanner)
LDAP服务器会响应所有的打印机和用户对象,见下图:
4、LDAP盲注
假设攻击者可以从服务器响应中推测出什么,尽管应用没有报出错信息,LDAP过滤器中注入的代码却生成了有效的响应或错误。攻击者可以利用这一行为向服务器问正确的或错误的问题。这种攻击称之为盲攻击。LDAP的盲注攻击比较慢但容易实施,因为它们基于二进制逻辑,能让攻击者从lDAP目录提取信息。
4.1 AND盲注
假设一个Web应用想从一个LDAP目录列出所有可用的Epson打印机,错误信息不会返回,应用发送如下的过滤器:
(&(objectClass=printer)(type=Epson*))
使用这个查询,如果有可用的Epson打印机,其图标就会显示给客户端,否则没有图标出现。如果攻击者进行LDAP盲注入攻击:
*)(objectClass=*))(&(objectClass=void
Web应用会构造如下查询:
(&(objectClass=*)(objectClass=*))(&(objectClass=void)(type=Epson*))
仅第一个LDAP过滤器会被处理:
(&(objectClass=*)(objectClass=*))
结果是,打印机的图标一定会显示到客户端,因为这个查询总是会获得结果:过滤器objectClass=*总是返回一个对象。当图标被显示时响应为真,否则为假。
从这一点来看,使用盲注技术比较容易,例如构造如下的注入:
1、(&(objectClass=*)(objectClass=users))(&(objectClass=foo)(type=Epson*)) 2、(&(objectClass=*)(objectClass=resources))(&(objectClass=foo)(type=Epson*))
这种代码注入的设置允许攻击者推测可能存在于LDAP目录服务中不同对象类的值。当响应Web页面至少包含一个打印机图标时,对象类的值就是存在的,另一方面而言,如果对象类的值不存在或没有对它的访问,就不会有图标出现。
LDAP盲注技术让攻击者使用基于TRUE/FALSE的技术访问所有的信息。
4.2 OR盲注
这种情况下,用于推测想要的信息的逻辑与AND是相反的,因为使用的是OR逻辑操作符。接下来使用的是同一个例子,OR环境的注入为:
(|(objectClass=void)(objectClass=void))(&(objectClass=void)(type=Epson*))
这个LDAP查询没有从LDAP目录服务获得任何对象,打印机的图标也不会显示给客户端(FALSE)。如果在响应的Web页面中有任何图标,则响应为TRUE。故攻击者可以注入下列LDAP过滤器来收集信息:
1、(|(objectClass=void)(objectClass=users))(&(objectClass=void)(type=Epson*)) 2、(|(objectClass=void)(objectClass=resources))(&(objectClass=void)(type=Epson*))
4.3利用案例
在本节中,部署了LDAP环境以展示上面说到的注入技术的使用,另外也描述了利用这些漏洞可能造成的影响及这些攻击对当前系统安全性的重要影响。
在本例中,printerstatus.php页面接收idprinter参数构造如下的LDAP搜索过滤器:
(&(idprinter=value1)(objectclass=printer))
4.3.1发现属性
LDAP盲注技术可以通过利用Web应用中内建的搜索过滤器首部的AND操作符,获得LDAP目录服务的敏感信息。例如,给出图1中为打印机对象定义的属性和图2中找个查询的响应页面,这里value1=HPLaserJet2100:
图1
图2
一个属性发现攻击可以使用下面的LDAP注入:
1、(&(idprinter=HPLaserJet2100)(ipaddresss=*))(objectclass=printer) 2、(&(idprinter=HPLaserJet2100)(departments=*))(objectclass=printer)
[属性不存在时的响应]
[属性存在时的响应]
很显然,攻击者可以根据返回的结果判断属性是否存在。在第一种情况中,应用没有给出打印机的信息,因为属性ipaddress并不存在或不可访问(FALSE)。另一方面,第二种情况中,响应页面显示了打印机的状态,department属性存在且可以访问。进一步说,可以使用LDAP注入获得一些属性的值。例如,假设攻击者想获得department属性的值,他可以使用booleanization和字符集削减技术,这将在下一节中介绍。
4.3.2 Booleanization
攻击者可以使用字母、数字搜索提取属性的值,这个想法的关键在于将一个复杂的值转化为TRUE/FALSE列表。这个机制,通常称为booleanization,大意是二值化吧,图十二概括了该机制,可用于不同的方式。
假设攻击者想知道department属性的值,处理如下:
1、(&(idprinter=HPLaserJet2100)(department=a*))(object=printer)) 2、(&(idprinter=HPLaserJet2100)(department=f*))(object=printer)) 3、(&(idprinter=HPLaserJet2100)(department=fa*))(object=printer))
[值不是以’a’开始]
[值以’fi’开始]
本例中department的值是financial,用”a”的尝试没有获取任何打印机信息,因而第一个字母不是”a”。测试过其他字母后,唯一能正常返回的只有”f”,接下来测试第二个字母,当为”i”时才正常返回,以此类推即可获得department的值。
4.3.3 字符集削减
攻击者可以使用字符集削减技术减少获得信息所需的请求数,为完成这一点,他使用通配符测试给定的字符在值中是否为*anywhere*:
1、(&(idprinter=HPLaserJet2100)(department=*b*))(object=printer)) 2、(&(idprinter=HPLaserJet2100)(department=*n*))(object=printer))
[图3,department的之中没有字母”b”]
[图4,department的之中没有字母”n”]
图3是测试”b”的响应页面,没有结果说明没有字母”b”,图4中响应正常,意味着’n’出现在department值中。
通过这样处理,构成depaetment值的字母是哪些就可以知道了,一旦字符集削减完成,只有发现的那些字母会用于booleanization处理,因此减少了请求的数量。
四、LDAP注入的危害
攻击者可以恶意查询LDAP里面的所有数据,导致非常严重的信息泄露。
五、防御LDAP注入
对特殊字符进行过滤和转义,具体操作如下:
左边的字符在正常情况下是不会用到的,如果在用户的输入中出现了需要用反斜杠转义处理。而右边的圆括号这些如果不过滤的话就会导致过滤器闭合而生产攻击者需要的filter,这里看到不仅是用反斜杠处理,还将字符变成了相应的ASCII码值,这些符号本不该出现。