【安全编码】Python安全编码之预防LDAP注入

详细解说LDAP注入原理、注入过程以及如何利用python安全编码来防止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://:/,:[?[??]]`

例如:

`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中加一行:

```loglevel 4095 ```

2.在/etc/rsyslog.conf 中加入ldap日志文档:

```local4.* /var/log/ldap.log```

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

```# service rsyslog restart```

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*"```

```-D binddn bind DN

-W         prompt for bind password

-x         Simple authentication

-b basedn base dn for search```

3 python 安全编码

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

```

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',.......

```
</pre>

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

<pre class="lang:default decode:true ">```python

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

```
</pre>

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

<pre class="lang:default decode:true ">```python

name=ldap.filter.escape_filter_chars(name)

```

经过过滤之后再丢到查询参数中。

或者使用filter_format,注意占位符%s和参数的对应关系。

<pre class=”lang:default decode:true “>“`python

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

如果您需要了解更多内容,可以
加入QQ群:570982169、486207500
直接询问:010-68438880-8669

Spread the word. Share this post!

Meet The Author

Leave Comment