burpsuite插件编写---sql injection

0x00 概要

在安全测试过程中,大部分人会使用burpsuite的scanner模块进行测试,可以发现一些浅显的漏洞:比如xss、sql injection、cf、xxe、Arbitrary file existence disclosure in Act、明文传输等。
说到sql injection,测试人员都会有一种想法是否存在一款自动化工具,可以将某一网站的所有链接都去尝试一边,尽可能的发现所有的sql injection。有了这种想法后大家会去想解决方案,有一种解决方案是编写burpsuite插件。
本文接上一篇文章,上一文记录到继承IHttpListener接口编写插件,影响测试效率,本文将介绍另一种方式,将sqlmapapi 和burpsuite进行对比

0x01 继承IScannerCheck接口(方法2)

先上插件代码:

> from burp import IBurpExtender
> from burp import IScannerCheck
> from java.io import PrintWriter
> import re
> import urllib
> import urllib2
> import time
> import json
> from threading import Thread
> import requests
> 
> 
> 
> 
> class BurpExtender(IBurpExtender, IScannerCheck):
> 
>     #
>     #implement IBurpExtender
>     #
>     def    registerExtenderCallbacks(self, callbacks):
>         # keep a reference to our callbacks object
>         self._callbacks = callbacks
> 
>         # set our extension name
>         callbacks.setExtensionName("fanyingjie")
> 
>         # obtain our output stream
>         self._stdout = PrintWriter(callbacks.getStdout(), True)
> 
>         self._helpers  = callbacks.getHelpers()
> 
>         # register ourselves as an
>         callbacks.registerScannerCheck(self)
> 
> 
>     def doActiveScan(self, baseRequestResponse, insertionPoint):
>         pass
>     def doPassiveScan(self, baseRequestResponse):
>         a=self._helpers.analyzeRequest(baseRequestResponse)
>         method=a.getMethod()
>         url=str(a.getUrl())
>         if(("?" in url) and (method=="GET")):
>             self._stdout.println("start")
>             t=AutoSqli(target=url,stdout=self._stdout,method=method)
>             t.run()
> 
>     def consolidateDuplicateIssues(self, existingIssue, newIssue):
>         pass
> 
>             
> 
> class AutoSqli(Thread):
>     def __init__(self,target,stdout,method):
>         self.server="http://192.168.159.134:8775"
>         self.taskid = ''
>         self.target=target
>         self.method=method
>         self._stdout=stdout
>         self.start_time = time.time()
> 
>     def task_new(self):
>         self.taskid = json.loads(urllib2.urlopen(self.server + '/task/new').read())['taskid']
>         self._stdout.println('Created new task: ' + self.taskid )
>         if len(self.taskid) > 0:
>             return True
>         return False
> 
>     def task_delete(self):
>         if json.loads(urllib2.urlopen(self.server + '/task/' + self.taskid + '/delete').read())['success']:
>             self._stdout.println('[%s] Deleted task' % (self.taskid))
>             return True
>         return False
> 
> 
>     def scan_start(self):
>         headers = {'Content-Type': 'application/json'}
>         payload = {'url':self.target}
>         url = self.server + '/scan/' + self.taskid + '/start'
>         #t = json.loads(requests.post(url, data=json.dumps(payload), headers=headers).text)
>         
>         req=urllib2.Request(url,data=json.dumps(payload),headers=headers)
>         t=json.loads(urllib2.urlopen(req).read())
>         self._stdout.println("start "+ self.taskid)
> 
>         if len(str(t['engineid'])) > 0 and t['success']:
>             return True
>         return False
> 
>     def scan_status(self):
>         status = json.loads(urllib2.urlopen(self.server + '/scan/' + self.taskid + '/status').read())['status']
>         if status == 'running':
>             return 'running'
>         if status == 'terminated':
>             return 'terminated'
>         return "error"
> 
>     def scan_data(self):
>         
>         data = json.loads(urllib2.urlopen(self.server + '/scan/' + self.taskid + '/data').read())['data']
>         if len(data) == 0:
>             self._stdout.println('not injection:\t' + self.target)
>             return False
>         else:
>             self._stdout.println('injection:\t' + self.target)
>             return True
> 
>     def scan_kill(self):
>         json.loads(rurllib2.urlopen(self.server + '/scan/' + self.taskid + '/kill').read())['success']
>         self._stdout.println("%s kill")%(self.taskid)
> 
> 
>     def scan_stop(self):
>         json.loads(urllib2.urlopen(self.server + '/scan/' + self.taskid + '/stop').read())['success']
>         self._stdout.println("%s stop")%(self.taskid)
> 
>     def run(self):
>         try:
>             if not self.task_new():
>                 return False
>             if not self.scan_start():
>                 return False
>             while True:
>                 if self.scan_status() == 'running':
>                     time.sleep(10)
>                 elif self.scan_status() == 'terminated':
>                     break
>                 else:
>                     break
>                 #print self.target + ":\t" + str(time.time() - self.start_time)
>                 if time.time() - self.start_time > 500:
>                     self.scan_stop()
>                     self.scan_kill()
>                     break
>             self.scan_data()
>             #self.task_delete()
> 
>         except Exception as e:
>             pass
> 

使用burp scanner模块和上面写的插件对http://172.16.173.136/sqli-labs/Less-8/?id=234 进行sql注入测试,burp scanner模块可以测试出存在注入,但是插件无法测试出;
因此插件进行改写,修改默认配置,在插件中添加以下代码,我只修改了level和risk

    def optionSet(self):
        headers = {'Content-Type': 'application/json'}
        payload={"level":"5","risk":"3"}
        url = self.server + '/option/' + self.taskid + '/set'
        req = urllib2.Request(url, data=json.dumps(payload), headers=headers)
        t = json.loads(urllib2.urlopen(req).read())
        self._stdout.println("set option " + self.taskid)

然后在新建扫描后,调用配置设置函数,修改后的代码:

from burp import IBurpExtender
from burp import IScannerCheck
from java.io import PrintWriter
import re
import urllib
import urllib2
import time
import json
from threading import Thread
import requests

class BurpExtender(IBurpExtender, IScannerCheck):

    #
    # implement IBurpExtender
    #
    def registerExtenderCallbacks(self, callbacks):
        # keep a reference to our callbacks object
        self._callbacks = callbacks

        # set our extension name
        callbacks.setExtensionName("fanyingjie")

        # obtain our output stream
        self._stdout = PrintWriter(callbacks.getStdout(), True)

        self._helpers = callbacks.getHelpers()

        # register ourselves as an
        callbacks.registerScannerCheck(self)

    def doActiveScan(self, baseRequestResponse, insertionPoint):
        pass

    def doPassiveScan(self, baseRequestResponse):
        a = self._helpers.analyzeRequest(baseRequestResponse)
        method = a.getMethod()
        url = str(a.getUrl())
        if (("?" in url) and (method == "GET")):
            self._stdout.println("start")
            t = AutoSqli(target=url, stdout=self._stdout, method=method)
            t.run()

    def consolidateDuplicateIssues(self, existingIssue, newIssue):
        pass

class AutoSqli(Thread):
    def __init__(self, target, stdout, method):
        self.server = "http://172.16.173.136:8775"
        self.taskid = ''
        self.target = target
        self.method = method
        self._stdout = stdout
        self.start_time = time.time()

    def task_new(self):
        self.taskid = json.loads(urllib2.urlopen(self.server + '/task/new').read())['taskid']
        self._stdout.println('Created new task: ' + self.taskid)
        if len(self.taskid) > 0:
            return True
        return False

    def task_delete(self):
        if json.loads(urllib2.urlopen(self.server + '/task/' + self.taskid + '/delete').read())['success']:
            self._stdout.println('[%s] Deleted task' % (self.taskid))
            return True
        return False
    def optionSet(self):
        headers = {'Content-Type': 'application/json'}
        payload={"level":"5","risk":"3"}
        url = self.server + '/option/' + self.taskid + '/set'
        req = urllib2.Request(url, data=json.dumps(payload), headers=headers)
        t = json.loads(urllib2.urlopen(req).read())
        self._stdout.println("set option " + self.taskid)

    def scan_start(self):
        headers = {'Content-Type': 'application/json'}
        payload = {'url': self.target}
        url = self.server + '/scan/' + self.taskid + '/start'
        # t = json.loads(requests.post(url, data=json.dumps(payload), headers=headers).text)

        req = urllib2.Request(url, data=json.dumps(payload), headers=headers)
        t = json.loads(urllib2.urlopen(req).read())
        self._stdout.println("start " + self.taskid)

        if len(str(t['engineid'])) > 0 and t['success']:
            return True
        return False

    def scan_status(self):
        status = json.loads(urllib2.urlopen(self.server + '/scan/' + self.taskid + '/status').read())['status']
        if status == 'running':
            return 'running'
        if status == 'terminated':
            return 'terminated'
        return "error"

    def scan_data(self):

        data = json.loads(urllib2.urlopen(self.server + '/scan/' + self.taskid + '/data').read())['data']
        if len(data) == 0:
            self._stdout.println('not injection:\t' + self.target)
            return False
        else:
            self._stdout.println('injection:\t' + self.target)
            return True

    def scan_kill(self):
        json.loads(rurllib2.urlopen(self.server + '/scan/' + self.taskid + '/kill').read())['success']
        self._stdout.println("%s kill") % (self.taskid)

    def scan_stop(self):
        json.loads(urllib2.urlopen(self.server + '/scan/' + self.taskid + '/stop').read())['success']
        self._stdout.println("%s stop") % (self.taskid)

    def run(self):
        try:
            if not self.task_new():
                return False
            self.optionSet()
            if not self.scan_start():
                return False
            while True:
                if self.scan_status() == 'running':
                    time.sleep(10)
                elif self.scan_status() == 'terminated':
                    break
                else:
                    break
                # print self.target + ":\t" + str(time.time() - self.start_time)
                if time.time() - self.start_time > 500:
                    self.scan_stop()
                    self.scan_kill()
                    break
            self.scan_data()
            # self.task_delete()

        except Exception as e:
            pass

再次对上面代码进行测试,可以测试出存在注入
插件的输出:

start
Created new task: 42f685742c94a985
set option 42f685742c94a985
start 42f685742c94a985
injection: http://172.16.173.136:80/sqli-labs/Less-8/?id=234


比较sqlmapapi和burpsuite scanner模块:
通过修改sqlmapapi默认设置可以提高注入检测的准确率,但是修改默认配置后sqlmapapi不见的比burpsuite scanner模块更厉害,而且sqlmapapi只能检测get请求类型,且不能检测登录后的get请求,因此看来在安全测试过程中,如果目的只是发现网站是否存在sql注入,还是使用burpsuite scanner 模块比较好用;在知道存在sql注入后,可以使用sqlmap来进行后续的操作。