Python安全编码之预防LDAP注入

1 LDAP简介

LDAP(Lightweight Directory Access Protocol)轻量级目录协议,是一种在线目录访问协议,主要用于资源查询,是X.500的一种简便的实现。它是一种树结构,查询效率很高,插入效率稍低。目录和数据库有很多相似之处,都用于存放数据,可以查询插入,目录可以存放各种数据,而数据库的数据则有比较严格的约束条件。
LDAP目录以入口(entry,目录中存储的基本信息单元)的形式存储和组织数据结构,每个入口有一个唯一标示自己的专属名称(distnguished name),DN由系列RDNs(ralative distinguished names)组成。另外还有两个常见的结构,对象类和属性。对象类(object class)会定义独一的OID,每个属性(attribute)也会分配唯一的OID号码。

2 LDAP注入原理

谈起LDAP注入首先得从LDAP的查询语法开始,基本的查询语法如下:
search语法:attribute operator value

   search filter options:( "&" or "|" (filter1) (filter2) (filter3) ...) ("!" (filter))

 主要根据属性和值进行搜索,就如浏览网页时我们通常并不会浏览某个目录,而是其下存在的某个文件。
  LDAP的URL形式为:ldap://<host>:<port>/<path>,<path>:<dn>[?<artribute>[?<scope>?<filter>]]
   例如: ldap://austin.ibm.com/ou=Austin,o=IBM

2.1 注入过程

从注入原理来看,ldap注入分为and注入和or注入,先看and注入情形,假设查询结构如下(&(user=username)(passwd=password)),这可能是采用采用LDAP进行登录验证的查询语句,其中username和password都是用户可控制的参数,那么可以在user处注入admin*)(objectClass=*)形成如下
(&(user=admin*)(objectClass=*))(passwd=password)) 有点小语法错误,如果在user处注入admin*)(objectClass=*))(&(objectClass=*
(&(user=admin*)(objectClass=*))(|(objectClass=void)(passwd=passwd)) 无语法错误,对于openldap来说,只会执行第一个&括号内的内容,由于objectClass=*恒为真,我们就能无需密码以admin用户的身份登录系统
如果不允许两个过滤服务器的执行,则是
(&(user=username)(injected_filter)(passwd=password))

对于or注入,查询表达式如下:
(|(user=username)(email=email_addr))
假如username可控,即可注入
username*)(objectClass=void))(|objectClass=void
形成
(|(user=username)(objectClass=void))(|objectClass=void)(email=email_addr)),由于objectClass=void恒为假,所以只有user=username的时候整个值为真。

2.2 渗透技巧

对于渗透测试来说,还是要看报错,比如输入\,如果关闭了报错接口,可以通过正确参数后加”“字符,如果返回一致,必有蹊跷,很有可能就是一个注入点,接着就可以尝试盲注的方式,简单的盲注就是”a*”这种方式。

2.3 调试与验证

在代码审计过程中,有些时候代码结构庞大,为了验证是否存在注入点,不好直接改写在python命令行中执行,那么就可以尝试打开ldap的日志,这样直接从url参数中加入注入元素,就能很好的观察注入的效果。下面是打开ldap日志的方法。一般的文章中都是打开syslogd,如果已经替换成了rsyslogd,也不要惊慌。rsyslogd是syslogd的升级包,原来的配置文件都还可用,增加了很多新功能,如能监听端口或者IP。下面就是打开LDAP日志的步骤:

1.在slapd.conf中加一行:

4095 ```
1
2
3
4
2.在/etc/rsyslog.conf 中加入ldap日志文档:
```local4.* /var/log/ldap.log

3.在终端用命令重启syslog服务

service rsyslog restart```
1
2
3
4
5
6
4.在/var/log/下可以找到一个ldap.log文件
随着时间增长,这个日志会增长较快,注意删除。
有时候为了查看实际的搜索结果,可以下载ldap的相关工具,在windows下推荐使用LDAP Administrator,linux下可以使用ldapsearch工具,ldapsearch具体用法如下:
```ldapsearch -h 10.5.5.5 -p 389 -D 'o=customer' -W -x -b "o=customer" "cn=645*"
binddn bind DN
1
2
3
-W prompt for bind password
-x Simple authentication
-b basedn base dn for search

3 python 安全编码

如何在python中防止LDAP注入呢?首先我们来看下简单的ldap连接,绑定再到查询的示例,这个查询是存在注入风险的,请不要模仿,请不要模仿,请不要模仿。

1
2
3
4
5
6
7
In [70]: import ldap
In [71]: l = ldap.initialize('ldap://10.5.0.220:389')
In [76]: l.bind('LDAP_ROOTDN','LDAP_ROOTPW')
Out[76]: 4
In [77]: l.search_s('LDAP_ROOTDN',ldap.SCOPE_SUBTREE,'(cn=645*)')
Out[77]:
[('cn=64502d93-a8ab-3ba1-991a-74cfde8cb333,cn=admin,o=3333049f-92d2-3c3a-91c2-6e1ef4c6a6bf,o=customer',.......

而且ldap的查询接口不像sql结果,有参数化查询,ldap的接口只能从参数过滤上做功夫来防止注入,但是好歹ldap提供了一个安全过滤接口ldap.filter.在这个接口中有escape_filter_chars函数,源码如下:

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
def escape_filter_chars(assertion_value,escape_mode=0):
"""
Replace all special characters found in assertion_value
by quoted notation.
escape_mode
If 0 only special chars mentioned in RFC 4515 are escaped.
If 1 all NON-ASCII chars are escaped.
If 2 all chars are escaped.
"""
if escape_mode:
r = []
if escape_mode==1:
for c in assertion_value:
if c < '0' or c > 'z' or c in "\\*()":
c = "\\%02x" % ord(c)
r.append(c)
elif escape_mode==2:
for c in assertion_value:
r.append("\\%02x" % ord(c))
else:
raise ValueError('escape_mode must be 0, 1 or 2.')
s = ''.join(r)
else:
s = assertion_value.replace('\\', r'\5c')
s = s.replace(r'*', r'\2a')
s = s.replace(r'(', r'\28')
s = s.replace(r')', r'\29')
s = s.replace('\x00', r'\00')
return s

源码解读如下:如果未设置转义模式,就将\,*,(,),\x00这5个字符转成其ascii码值。那么如何过滤呢?代码如下:

1
name=ldap.filter.escape_filter_chars(name)

经过过滤之后再丢到查询参数中。
或者使用filter_format,注意占位符%s和参数的对应关系。

1
2
3
current_app.setdefault('LDAP_GROUP_OBJECT_FILTER', '(&(objectclass=Group)(userPrincipalName=%s))')
query = ldap.filter.filter_format(
current_app['LDAP_USER_OBJECT_FILTER'], (user,))

总之记住一条,ldap的搜索参数是需要手工过滤的。

参考链接:

http://www.cnblogs.com/r00tgrok/p/LDAP_INJECTION_AND_PREVENTION.html