版权声明:
本文首发于微信号:inn0team
此文章版权归属于 inn0team 所有,未经授权不得转载。
写在前面
让我们继续学习PoC编写指南这一系列的文章,这次的作者是我们团队的城管打人(本来应该是大人,故意写错的),团队里战斗力最高的一位,除了长得娘了点,其他都是优点。
漏洞分析
这次我们选择的漏洞为 MetInfo 5.3 /include/global/listmod.php SQL 注入漏洞。(http://www.wooyun.org/bugs/wooyun-2015-0119166)
想看原文分析的可以点上面的链接去研究,你别看我的标题和原文作者的不一样,内容其实是一样的。
原文中的分析不是太详细,但是呢,我暂时不想详解这个漏洞,后面再看吧,如果有需要的话。
根据原文中分析我们知道了,存在 SQL 注入的文件是/include/global/listmod.php , 存在注入的变量是$serch_sql。
在 listmod.php 文件 200 行的位置拼接了 SQL 语句,在拼接 SQL 语句之前,对 $serch_sql 变量进行了初始化操作,但是呢,控制它是否初始化的另一个变量为 $imgproduct。
当这个 $imgproduct 变量非 search 的任意字符的时候,导致 $serch_sql 不能进行初始化,从而可以自定义$serch_sql 进行注入。
阅读原文后我们得到了目标 URL:http://xxx.com/news/news.php?lang=cn&class2=5&serch_sql=123qweasd&imgproduct=xxxx
存在注入的参数是 search_sql 注入的类型是 Boolean-Based Blind。
漏洞复现
手工复现漏洞对学习可是很有帮助的。不管你信不信,我反正是信了。
本地搭建 MetInfo 环境, 下载地址Metinfo5.3下载地址:http://pan.baidu.com/s/1o6TvPwA
读者下载完安装包后,进行安装,具体安装步骤在此处不再赘述。
笔者安装完毕后网站的地址为: http://127.0.0.1/MetInfo/
访问安装后的地址,如果正常访问,那就代表安装成功,我们可以继续后面的步骤了。
这里要强调一点,如果你是 Linux 系统,那么 Web 容器对目录与文件名的大小写是敏感的,笔者建议直接目录在创建的时候用小写
根据原漏洞详情的描述,我们直接访问:http://127.0.0.1/MetInfo/news/news.php?lang=cn&class2=5&serch_sql=123qwe&imgproduct=xxxx
注意看下图中业界资讯下方的条目
好啦,然后我们给 search_sql 的参数后加一个单引号:http://127.0.0.1/MetInfo/news/news.php?lang=cn&class2=5&serch_sql=123qwe'&imgproduct=xxxx
下图中业界资讯下方一条记录也没有了。
于是我们得出一个结论存在 SQL 注入。那么我们再想一下,我们怎么判断存在 SQL 注入的呢?
先请求正常的页面(也就是上面的第一个链接),然后再请求带单引号的页面(也就是上面的第二个链接),如果两次结果不一样,就判断存在注入了。
看起来是这样的,对吧?实际上呢,上面说的话不是很严谨。
我们验证 SQL 注入的时候,一定一定一定是为了证明我们输入的字符被当作 SQL 指令执行了。
如果我们只用上面这两个链接来判断存在注入的话,这误报率简直高到没边了。仔细思考一下为什么。
在实际中,是非常之复杂的,我们试想一下,假设有一个网站,它装了一个 WAF, 当你请求第一个没单引号的链接的时候,它返回的是正常的页面,然后,当你请求中带了一个单引号的时候,WAF 给你拦了,然后返回了一个请不要注入的提示的页面出来。
妥妥的误报。是吧?这样我们就得修改一下这个两个请求了。
我们看一下这个请求的 SQL 语句是什么样子的:SELECT * FROM met_news 123qwe where lang = 'cn' and (recycle = '0' or recycle = '-1') and (( class 1 = '2' and class 2 = '5' ) ) and displaytype= '1' and addtime <= '2015-12-29 17:59:20' order by top_ok desc,no_order desc,updatetime desc,id desc LIMIT 0, 8
上面这个你可以通过 tcpdump 抓包来得到,也可以通过修改网站源代码的方式,在查询前打印 SQL 语句,两种方法都可以,看个人喜好了
看到上面的 SQL 语句之后,我直接把对应的 Payload 也贴出来。这里我为了省流量我只贴重点部分
解释一下后面的-- x
,这个是 Mysql 的注释,后面的 x 是为了让读者看清楚两个横线后面是有个空格的。
对比两个 Payload, 发现唯一的差别就是 4343=4343 和 4343=4342 了,当然这里的这个数字嘛,随便写的,以前大家都喜欢用 1=1, 1=2 这种来测试,那么有些 WAF 自然也是把这个加入到其特征里面喽,所以建议不要用这种。两者其实功能上都是一样的。
然后要详细说一下为什么要这样请求了。第一个 4343=4343 表达式返回的肯定是为真的,那么是会有数据的,而 4343=4342 这显然是不相等的,所以第二个 SQL 语句肯定是没有数据的。那么我们就可以对比两次请求的结果来判断是不是存在注入了。
思考一下,这与前面说的两种请求方式有什么不同
WAF。没错,我们两次请求的 Payload 也只有数字这里不同,如果说目标有 WAF 的话(比方说这个 WAF 拦的是 where 这个关键字),那么我们两次请求的结果都会是被拦截的页面。
于是我们要请求的两个链接就是:
那意思是说,我们现在就可以编写 PoC 了?打住。如果这个时候急着写 PoC,还是考虑的不够深入。
把视线再移到 GET 参数上面,我们看到除了 serch_sql 和 imgproduct 两个参数之外,还有 lang 和 class2 这两个参数。试着删除掉这两个参数看看结果发现这两个参数其实是必不可少的,同时,也会影响页面访问结果。
-
lang
网站语言环境,我们测试安装的时候语言环境是 cn,但是你不能说所有的网站的语言都支持 cn。 -
class2
这个参数是二级栏目, 取值也是相应的二级栏目的 id。我们测试的时候,栏目的 id 用的是 5,对应的测试数据是 “业界资讯”,那么问题来了,所有的 MetInfo 二次开发的站都会有 id 等于 5 的这个栏目吗?显然不是。 -
如果我们选择的这个栏目下,本来就没有数据呢,即使存在注入也会被判断成没注入吧?好在这个漏洞中返回的新闻列表是所有分类的新闻
当然,如果这个站真的一条新闻都没有的话,就不能通过 Boolean-Based Blind 这种类型来注入了。那自然就不在本节的讨论范围之内了。
OK, 终于可以整理验证的思路了。
-
访问 /news/ 获取到真实的栏目 id 和 lang
-
带上返回值为 True 的 Payload 即:
serch_sql=123qwe where 4343=4343 -- x&imgproduct=xxxx
-
带上返回值一定为 False 的 Payload 即:
serch_sql=123qwe where 4343=4342 -- x&imgproduct=xxxx
-
比较 2, 3 中的新闻列表处的数据是否有变化,如果 2 有数据而 3 无数据,就证明存在注入。
上面说的这些,都是一些小细节,除了 SQL 注入 Payload 构造的技巧之外,还应该要结合具体的 CMS 的一些特点。这样 PoC 用来批量扫描的时候才不会出太多问题。
既然说到应该结合整个 CMS 具体的一些特点的话,我们观察在访问 /news/index.php 的时候,在正文部分其实是有一部分数据的,那么这两者之前肯定是存在调用的,我们访问下面地址看看:
访问上面两个链接之后发现,第一个请求的响应页面中有数据,而第二个请求的响应页面中没有数据
怎么样?这不就达到我们一开始想说的了效果了吗?这时完全可以不考虑 class2 的值是什么了呀。突然觉得之前折腾了那么久全是白费力气,这感觉真酸爽。
所以说洞主给的方案不一定是唯一的,PoC 编写的时候做一下必要的分析还是能减少很多无用功的。
无框架 PoC 编写
我直接上 python 脚本吧,我也没统一处理输入输出什么的,因为这些都不是重点。
代码2_3_1.py
:
上面的脚本在注释中已经把判断的逻辑写的很清楚了,也没什么要说的地方。
值得一提的是:我们在判断的时候,选取的判断字符串一定要能区分这两个页面,并且要有一定的通用性。除了以上这些,还应该尽可能的复杂一些,这样在全网扫的时候误报率相对就低了下来。
当然还可以通过返回包的大小来判断,但是如果仅仅靠这个来判断的话,误报率是比较高的。
我们运行一下2_3_1.py
看看效果吧:
之前的几篇已经写了如何利用框架写PoC,这篇就不再赘述了(点击下面的阅读原文还是能看到的),下面基本上验证就已经讲完了,有兴趣的可以继续看下面的爆数据部分。
盲注猜数据
暂时不想写怎么爆数据了,后面会更, 我给个思路,读者可以先自己实现一下
思路
我们不要着急,一层一层思考啊
-
管理员的用户名和密码肯定是在 [a-zA-Z0-9] 这个集合里面的(如果不区分大小写就是 [a-Z0-9])。比如
e10adc3949ba59abbe56e057f20f883e
-
如果让你人工去猜这个密码字符串,你会怎么猜?这里我们就可以用 if 语句来判断了,如果我们猜对了,就返回 True,猜错了,就返回 False,那么一旦出现了有数据的页面,说明你猜对了。
-
一次猜整个字符串的那概率相当之小,所以我们可以拆分字符串呀,从第一个字符开始猜,猜到最后一个。那假设你在猜第 1 个字符,它可取的值有 36 个,怎么猜?从 a 一个一个猜到 z ,从 0 猜到 9,肯定有一个满足条件的。
-
天呐,这样一个一个猜好累啊。有什么办法能提高比较效率呢?这里我们可以使用二分法(也叫折半查找)
举个例子子来说啊,比如我们要猜 1 中给的这个例子的第 1 个字符(e),我先看它是不是在 n 后面(a-z 的中间字母)?不在,好,然后再看是不是在 g 后面(a-n 的中间字母)?不在,那我将 a-g 再从中间分一下,就是字母 d 了,在不在 d 后面呢?在。OK,现在这个字符所在的区间是 d-g(defg),中间字母是 e ,那么这个字符在不在 e 后面呢?不在。于是现在区间又成了 de, 那么再比较一次, 这样就把猜出来是 e 了。
这样对一个字母,平均比较次数是 5 次,这样一来就会节约了大量的时间。你也许会说,呵呵呵,如果我猜的字母是 a ,我用传统的比较法一次就蒙对了呢。少年,那你有考虑过这个字符有可能是 z 吗?
总结
总结一下这节学到的东西
-
布尔类型的 SQL 盲注,一般通过比较返回页面的数据变化来判断
-
SQL 注入检测是证明指令被执行了
-
编写 PoC 如果能结合 CMS 的具体特点,会通用,提高性能
-
适当的应用一些算法,可以在幅度提高检测效率,节约时间
-
不要用默认的表前缀在一定程度上可以提高被黑的门槛
inn0team只是一个正在成长的小的安全团队
微信号:inn0team
长按便可关注我们
点击阅读原文可以上文中提到的基于框架的PoC编写
本文始发于微信公众号(inn0team):PoC 编写指南(基于布尔盲注的 SQL 注入PoC编写)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论