从一次漏洞挖掘入门Ldap注入

k1   ·   发表于 2019-08-21 09:16:57   ·   漏洞文章

在最近的一次测试中,随缘摸到了一个sso系统,留给前台的功能只有登陆。

没有验证码,但是登陆点强制要求每个用户更改强密码,而且除了管理员和测试账号其他大部分都是工号形式,直接fuzz一把梭

测试过程中发现username对于下面payload会存在两种不同回显

当时我并不理解这种payload是什么库的数据格式。但是看到存在"!"字符时,页面的回显是不同的,而"!"在绝大多数语言中都是取反的表达形式,自然会产生不同的布尔值,那么无疑就是个xxx注入点了

何为LDAP

通过payload的类型,看到是经典的ldap注入语句。一种老协议和数据存储形式了

LDAP协议

LDAP(Lightweight Directory Access Protocol):即轻量级目录访问协议。是一种运行于TCP/IP之上的在线目录访问协议,主要用于目录中资源的搜索和查询。使用最广泛的LDAP服务如微软的ADAM(Active Directory Application Mode)和OpenLDAP

LDAP存储

MySQL数据库,数据都是按记录一条条记录存在表中。而LDAP数据库,是树结构的,数据存储在叶子节点上。

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查询语句

    一个圆括号内的判断语句又称为一个过滤器filter。

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

    逻辑与&

    (&(username=Hpdoger)(password=ikun))

    查找name属性为Hpdoger并且password属性值为ikun的所有条目

    逻辑或|

    (|(username=Hpdoger)(displayname=Hpdoger))

    查找username或者displayname为Hpdoger的所有条目

    特殊说明

    除使用逻辑操作符外,还允许使用下面的单独符号作为两个特殊常量

    (&)     ->Absolute TRUE 
    (|)     ->Absolute FALSE 
    *       ->通配符

    另外,默认情况下,LDAP的DN和所有属性都不区分大小写,即在查询时:

    (username=Hpdoger) <=> (username=HPDOGER)

    LDAP注入

    由于LDAP的出现可以追溯到1980年,关于它的漏洞也是历史悠久。LDAP注入攻击和SQL注入攻击相似,利用用户引入的参数生成LDAP查询。攻击者构造恶意的查询语句读取其它数据/跨objectclass读取属性,早在wooyun时代就有师傅详细的剖析了这类漏洞。

    上文说到LDAP过滤器的结构和使用得最广泛的LDAP:ADAM和OpenLDAP。然而对于下面两种情况

    无逻辑操作符的注入

    情景:(attribute=$input)

    我们构造输入:$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,达到万能密码的效果。

    后注入分析

    注入大致分为and、or类型这里就不赘述,感兴趣的可以看之前wooyun的文章:
    LDAP注入与防御剖析

    还有一个joomla的一个userPassword注入实例:
    Joomla! LDAP注入导致登录认证绕过漏洞

    回到实例

    大致了解注入类型,就开始了第一轮尝试

    当通配符匹配到用户名时返回

    用户名不存在时返回

    构造用户名恒真username=admin)(%26&password=123

    说明它判断用户的形式并不是(&(USER=$username)(PASSWORD=$pwd)),因为我们查到的用户名是true,但是验证密码false

    由于自己也没搞过LDAP的开发..就盲猜后端应该就是这种情况:
    执行了(&(USER=$username)(objectclass=xxx))后,取password与$password进行对比

    ACTION

    那么首先要知道它继承了哪些objectclass?因为树结构都有根,使我们能顺藤摸瓜。首先是top肯定存在,回显如下:

    但是top的子类太多了,先fuzz一下objectclass的值缩小范围,payload:

    username=admin)(objectclass%3d$str

    发现存在personuser两个objectclass

    再fuzz一下attribute得到的值如下:

    username=admin)($str%3d*

    凭借这些信息去LDAP文档里溯继承链,先去找user类,继承自organizationalPerson

    同理organizationalperson又是继承自person的,person继承自top,最终的继承链为:

    top->person->organizationalperson->user

    也就是说这些类存在的属性都可能被调用。很遗憾的是我并没有fuzz到password类型参数,一般来说password会以userPassword的形式存储在person对象中,很多基于ldap的开发demo中也是这样写的。

    但是userPassword毕竟也只是person类可选的属性,开发大概率是改名或者重写属性了,这也是这个漏洞没有上升到严重危害的瓶颈点

    不过依然可以注出一些有用的数据。例如所有用户的用户名、邮箱、手机号、姓名、性别等等,说不定以后可以越权修改某账号性别呢-3-

    盲注mobile

    尝试注入管理员的手机号mobile

    username=admin)(mobile=%s*&password=123

    利用通配符不断添加数字,同理邮箱也可以注出来,与sql盲注的思路相同。

    盲注username

    毕竟对于sso,收集username是很有用的信息。那么问题来了,我们是可以通过生成字典来遍历存在的用户名,但是这个工作量是指数倍的增长,一天能跑完一个字母开头的就不错了,而且浪费了通配符的作用。

    可是又想做到无限迭代把所有用户一个不漏的跑完,passer6y师傅提醒我用笛卡尔积

    最后画出来的流程图大致如下:

    最后跑了两三天才大概有1w多个用户,然而这些大部分是测试帐号,但也算是验证了思路的可执行性。至于为什么跑了两三天..因为打游戏忘了调bug(逃

    总结

    网上关于这类漏洞的fuzz思路也比较久远了,第一次接触这种漏洞,若文章思路如果有什么不对的地方还请师傅们斧正。自己对这类漏洞的姿势理解很浅,现在漏洞已经修复,但是如果有师傅对于password的注入有想法,可以私下交流一下

    相关链接

    https://wooyun.js.org/drops/LDAP%E6%B3%A8%E5%85%A5%E4%B8%8E%E9%98%B2%E5%BE%A1%E5%89%96%E6%9E%90.html
    https://www.cnblogs.com/pycode/p/9495808.html
    https://zhuanlan.zhihu.com/p/32732045

    转自先知社区

    打赏我,让我更有动力~

    0 条回复   |  直到 2019-8-21 | 1198 次浏览
    登录后才可发表内容
    返回顶部 投诉反馈

    © 2016 - 2024 掌控者 All Rights Reserved.