在最近的一次测试中,随缘摸到了一个sso系统,留给前台的功能只有登陆。
没有验证码,但是登陆点强制要求每个用户更改强密码,而且除了管理员和测试账号其他大部分都是工号形式,直接fuzz一把梭
测试过程中发现username对于下面payload会存在两种不同回显
当时我并不理解这种payload是什么库的数据格式。但是看到存在"!"字符时,页面的回显是不同的,而"!"在绝大多数语言中都是取反的表达形式,自然会产生不同的布尔值,那么无疑就是个xxx注入点了
通过payload的类型,看到是经典的ldap注入语句。一种老协议和数据存储形式了
LDAP(Lightweight Directory Access Protocol):即轻量级目录访问协议。是一种运行于TCP/IP之上的在线目录访问协议,主要用于目录中资源的搜索和查询。使用最广泛的LDAP服务如微软的ADAM(Active Directory Application Mode)和OpenLDAP
MySQL数据库,数据都是按记录一条条记录存在表中。而LDAP数据库,是树结构的,数据存储在叶子节点上。
LDAP目录中的信息是按照树形结构组织的:
dn:一条记录的位置 dc:一条记录所属的区域 ou:一条记录所属的组织 cn/uid:一条记录的名字/ID
这种树结构非常有利于数据的查询。首先要说明是哪一棵树(dc),然后是从树根到目标所经过的所有分叉(ou),最后就是目标的名字(cn/uid),借用一张图来表明结构如下:
对象类(obejectclass):对象类封装了可选/必选属性,同时对象类也是支持继承的。一个entry必须包含一个objectClass,且需要赋予至少一个值。而且objectClass有着严格的等级之分,最顶层是top和alias。例如,organizationalPerson这个objectClass就隶属于person,而person又隶属于top
属性(atrribute):顾名思义,用来存储字段值。被封装在objectclass里的,每个属性(attribute)也会分配唯一的OID号码
一个圆括号内的判断语句又称为一个过滤器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的出现可以追溯到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进行对比
那么首先要知道它继承了哪些objectclass?因为树结构都有根,使我们能顺藤摸瓜。首先是top肯定存在,回显如下:
但是top的子类太多了,先fuzz一下objectclass的值缩小范围,payload:
username=admin)(objectclass%3d$str
发现存在person和user两个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
username=admin)(mobile=%s*&password=123
利用通配符不断添加数字,同理邮箱也可以注出来,与sql盲注的思路相同。
毕竟对于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
转自先知社区
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.