本文为“保与与SQL注入的一千次相遇”,第五个案例。其它案例请看文末合集【One night in SQL injection】。
等了半年没等来奇安信的活动,我有点像深宫里的怨妇^ ^。不管了。
. . . * . * ☄️. * . * . 🔆.* . * . 🧶 * . * . . .
有点标题党我先说了,别喷先看完。
本来也没想到能整出这么复杂的活,在这还是要稍微感谢一下甲方:没有你们离谱的要求整不出今天的大活。
简单的开始
还是一样为保密不上截图,入口用户名注入(记住这个,后面要考)
:
都爆出语句了,由于客户要求证明危害,文艺地注了个当前用户首字母:
一个星期之后客户那边反馈说不认可危害性,需要能看数据。由于1.已经过了一个星期漏洞详情都快忘了2.这个客户b事很多=>SQLmap启动。
本来是打算简简单单跑一波吧,但是没想到怎么样都跑不出(跑不出的过程就略了)。迫不得已只能再次手注。
确定数据库类型
先简单确认下数据库类型,使用user、substr两板斧,user()不行substring不行,怀疑是mssql。
然后尝试waitfordelay,报错:
unexpected token,那看来不是mssql,抱着总不可能是达梦的想法试下oracle:
nls_initcap
是一个oracle特有的i18n函数——确定数据库类型为oracle。
巧遇Hibernate
既然数据库类型都能确定,中间也没遇到什么WAF,怎么会跑不出来呢?百思不得其解之下我决定先试试访问下系统表,比如all_users
。
结果是报错,怎么说呢,意料之外,情理之中?毕竟要是咋拼接都行的话就不会跑不出来了。
仔细看看报错,显示all_users is not mapped
。讲道理在之前的注入里我真没见过这个报错,比起oracle这更像是java报错。
再往下看,这是一个hibernate下的报错:
简单讲一下Hibernate是啥
首先我不是一个开发,并且我也没有用过Hibernate,所以我得提前说明一下——以下所有言论都是我个人理解,很可能有问题,请选择性查看。
Hibernate,理论上是一个ORM框架,一般也会被当作ORM来使用。
它有自己的一套语法,可以叫做HQL。它拥有类似MVC里类的Model-View功能,也就是将数据库里的具体数据记录绑定到一个Java变量 OR Java类 OR 我们说的JavaBean上。也就是说,hibernate会建立一种从数据库到对象的映射。
这里宽着讲内容就很多了,不做展开,简单了解下。
在我短暂的从业生涯中,基于HQL的注入出现次数比较少但并非没有。总而言之它是一个罕见但并非无解的漏洞,只要开发够大意,还是能够找到解法的。
映射限制突破
现在平心静气,来梳理一下已知情况。
1.从上文我们可以得知all_users不在hibernate的映射范围内
2.这个注入一定程度上属于盲注(报错不报数据)
基于1,我们下一步的行动应该是找到一个可以利用且在hibernate映射范围内的类,但事实上基于开发的“最小原则”,系统表理论上不会被映射进来,因为这属于画蛇添足之举。基于2,爆破表名应该可以说完全不可能,因为①我们不能访问到存储表名的那个系统表;②也没法通过报错注入获得提示。
那么下一步怎么做呢?回头重新查看报错信息,我们能知道的是当前语句:
虽然被写成了类的形式,但我们很快能发现:当前语句里不就有个表名?
虽然不知道用户表是怎么沾上Community(这也揭示了两点,一是表命名使用驼峰,二是臭开发黔驴技穷取名水平很糟糕),但好歹也是提供了一个可用的表和一些可用的爆破提示。
在需要急速下班(其实每篇文章里我都在急速下班,但结果都是下不了- -)时自然优先想办法利用已经存在的表。
爆破用户名
虽然,我们知道当前注入所在点的语句,也就是下文这个:
from com.abc.pojos.PersonCommunity where personNumber='*' and personType=0 and meetingID='11111111111111111111111'
但使用万能密码式闭合之后我发现登录操作中还进行了其它校验,会显示非XX系统用户登录失败:
这就说明我们需要弄出一个真正的用户名来。回顾注入语句,我们知道用户名的字段为personNumber,那么直接盲注:
...' or 1/(case when (substr((select personNmber from personCommunity where rownum=1),1,1)='A' then 1 else 0 end))=1 or 1='...
构造一个OR从句使语句一定会走这里,然后构造一个除零报错直接从状态码看结果。
但是我发现:即使确认过这个语句没问题,单个从句拎出来也没问题,这个payload仍然会报错。
也许hibernate里的某个特性不允许这样的语句?
转半天,想起还有另一个类似的语句可以用:
...' or 1/(case when ((select personNmber from personCommunity where rownum=1) like 'A%' then 1 else 0 end))=1 or 1='...
是的,用like不就行了?
单个字符爆破,很快得到了一个用户名'E132654'。这又给了一个模糊的信息:这个用户名是否是可枚举的(后面试了一下这个系统用户名貌似是一段一段的,一些可以枚举一些不可以)。
然后就是根据用户名爆破密码了。
密码密文到明文
这一步算是这次渗透中我运气最好的部分了。根据用户名爆破密码的语句很简单,从句里再加一段就行:
...' or 1/(case when ((select personNmber from personCommunity where rownum=1 and personNumber='E132654' ) like 'A%' then 1 else 0 end))=1 or 1='...
但在爆破密码之前,我们还需要捋一下目标顺序:
首先,我们不知道密码所属列名,这需要爆破;
其次,我们需要知道密码是不是密文,是不是哈希:是密文的话可以直接下班了,是哈希的话要确定是否有盐(这很难知道,但也可以爆破);
最后,我们可以尽可能找到一个初始密码(如果有的话),这是我运气最好的部分——我爆破的第一个密码就是初始密码。
猜解列名
我们先拿出已知的列名:personNumber
personType
meetingId
,可以看出基本都是驼峰两个词,那么理论上密码字段应该会类似personPassword、personPwd,或者是通用列名比如password、pwd、Password。
构造一下跑出来是password。
爆破密码
先不要浪费爆出来的用户名,根据用户名爆破此用户名对应的密码:
一位一位爆破了五六位,根据开头感觉是哈希。这里又犯了个傻,先入为主认为哈希算法是MD5,于是把常用口令MD5一下跑一遍:
emmm。。。没有。就怕其实是加密,或者强制性设置为强口令。
不过这都不是定论,还可以平心静气
,再推断一下:
1.实际上,爆破不出只能说明密码不是常见弱口令,或密码不是MD5格式。
2.我们可以回到之前爆破的密码,把它继续爆破出来。我相信这个密码大概率会是个特殊的密码,因为我爆破的是第一行账户的密码,理论上会是第一个被创建的账号的密码。第一个账号通常会是超管或测试账号,如果有所谓的初始密码,这个账号上出现的概率会更高。
3.我们不需要爆破出完整的字符串,在有前几位的情况下已经可以将其与常见哈希字典进行比对。
那么直接将爆出的这几位字符串与哈希字典比较,没有哈希字典不要慌,百度一下你就知道:
只能说运气来了真是挡也挡不住,这个888888是初始密码的可能性非常大!
要确定它是否真是初始密码也很简单,改一下语句:
...' or password='1f82...' and 1/(case when ( personNumber like '§§%') ...
看一下一次爆破能命中几个字母就行了,间接性判断了数量。
看爆破结果也是很乐观,基本板上钉钉是大量使用的密码。
取得账号密码
用户名找到了,密码也找到了,理论上已经可以登录了,但输入正确的用户名密码登录居然还是显示非XX系统用户登录失败。登录语句是能看到的,这之中肯定出现了什么问题。
再次再次折回来看登陆语句:
看来还需要后面的条件满足,这个也简单,从句里多加上限制条件和限制密码,再爆破一个账号即可。这次爆出来的账号是一个纯数字字符串134*****
,输入密码!登陆!成功!跳转?另一个登陆口???
东方不亮西方亮
是的没错,登录之后跳到了另一个登陆口,这真是我万万没想到的:
这下更没法说服客户漏洞有危害了,万万没想到是这个结果- -。不过这也是实战的魅力所在——你是真不知道开发还能给你写出个啥来。
总之虽然血压飙升气得快上天了还是得平心静气
,毕竟现实是不以人的意志而转移的,简单来说气也没有用。
那么坐下来,思考一下我们还能如何破局?
首先我们从登陆中能拿到的信息主要有哪些?初步来看应该是一个页面(入口)和一个登陆跳转过程,过程里有一些路由。
页面分析v1
最重要的应该是新的页面,还是个入口。这个入口也会有SQL注入嘛?很遗憾,经过测试是没有的,并且当前用户名也不可用。
那么之前登陆的Cookie有用吗?用原始登陆后的Cookie尝试刷新页面,还是无效。删除Cookie访问新入口,可以访问。
看上去是跳到了一个完全不需要鉴权的地方。**这应该吗?**这么异常的情况提示我还是需要返回路由看看。
路由分析
看看路由。
可以看到一个POST之后跳转到上一页面,然后跳转到index,再跳转回另一个入口(login)。
也就是到open_index这个页面的权限不足,它的权限应该由open_login来授予——那最开始的login页面要给谁授权呢?从路由命名上来看,这几个系统并不像是分支和sso(其实从前端长相看也不像,这里不能放出),而更像是各自分立的关系,毕竟它们的命名方式就各不相同。
那么是否还存在另一个index,是由原始的dLogin来授权呢?**
就像猜测列名一样,按照现有的路由信息对d**的index进行猜测:d**Index
d**_index
d**_Index
都走一遍,很可惜这次没有这么走运,都是404。
不过还是没事,虽然爆破是失败的,但我相信思路是对的。
页面分析v2
既然生猜猜不出,也许可以从页面中找到新的线索。
返回第二个系统前端页面,看看js。前端一堆js,先挑个看上去最有可能露出马脚的看起,比如这个08年的老兵:
向下翻翻翻:
这是什么好难猜啊,这个全是index的玩意是不是各个系统的跳转路径好难猜啊^ ^。果然上文中的猜测是对的,存在很多个系统。
那么拿起之前的Cookie爆破一下路径:
Yes,it is!下班成功~
后续
向客户讨封了,客户决定封我为真神
希望下次拷打我的时候他能记住这两个字,好了下课。
. . . * . * 🌟 * . * . . .
由于很多人问我微信群的事情,所以我建了一个小微信群。现在可以在公众号菜单里选择合作交流->交流群获取交流群二维码,希望大家和谐交流,为更好更友善的行业环境贡献自己的力量。
如果喜欢我的文章,请点赞在看。网安技术文章、安卓逆向、渗透测试、吃瓜报道,尽在我的公众号:
原文始发于微信公众号(重生之成为赛博女保安):【打破ORM不败神话】记手注击穿ORM到后台的一次渗透
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论