123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- #
- # BurpSQLTruncationScanner - Scan for potential SQL truncation attack vectors.
- #
- # Copyright (c) 2020 Frans Hendrik Botes (InitRoot)
- # Verions 0.1
- #
- from burp import IBurpExtender, IScannerCheck, IScanIssue, ITab, IBurpExtenderCallbacks, IExtensionHelpers, IContextMenuFactory, IContextMenuInvocation, IHttpRequestResponse
- from java.io import PrintWriter
- from java.net import URL
- from java.util import ArrayList, List
- from java.util.regex import Matcher, Pattern
- import binascii
- from javax import swing
- from java.awt import Font, Color
- import sys
- import time
- import threading
- import base64
- import re
- from array import array
- import json
- #Global Issue List
- issueList = ArrayList()
- class BurpExtender(IBurpExtender, IScannerCheck, IContextMenuFactory, IHttpRequestResponse, IBurpExtenderCallbacks):
- def registerExtenderCallbacks(self, callbacks):
- sys.stdout = callbacks.getStdout()
- self._callbacks = callbacks
- self._helpers = callbacks.getHelpers()
- callbacks.setExtensionName("SQLTruncScanner")
- callbacks.issueAlert("SQL Truncation Scanner Enabled")
- stdout = PrintWriter(callbacks.getStdout(), True)
- stderr = PrintWriter(callbacks.getStderr(), True)
- callbacks.registerContextMenuFactory(self)
- print ("SQL Truncation Scanner loaded.")
- print ("Copyright (c) 2020 Frans Hendrik Botes (InitRoot)")
- self.httpTraffic = None
- self.resp = None
-
- #Set Parameters for Original Reqquest to Restruct
- self.orgHost = None
- self.orgPort = None
- self.orgProtoChoice = None
- self.orgHeaders = None
- self.orgParams = None
- self.orgMethod = None
- self.orgURLPath = None
- self.orgContType = None
- self.orgBaseline = None
- #Create context menu entry
- def createMenuItems(self, invocation):
- self.context = invocation
- itemContext = invocation.getSelectedMessages()
- if itemContext > 0:
- menuList = ArrayList()
- menuItem = swing.JMenuItem(
- "Scan with SQLTruncScanner", None, actionPerformed=self.start_scan)
- menuList.add(menuItem)
- return menuList
- return None
- # We are ready to start a scan for the specific request
- def start_scan(self, event):
- try:
- #For later use lets get what user selected.
- #Let's get what the user selected, only compatible with one item at a time. ADD check for multiple items and throw error
- scanIssues = self._callbacks.getScanIssues(None)
- httpTraffic = self.context.getSelectedMessages()
- print (len(httpTraffic))
- httpRequest = [item.request.tostring()
- for item in httpTraffic]
- orignalRequest = ''.join(httpRequest)
- #Rebuild the request and fetch response to calculate baseline value
- self.buildRequest(orignalRequest, httpTraffic)
- #We have the baseline, time to start fuzzing
- paramFuzzer = fuzzParams(httpTraffic, self._helpers, self._callbacks)
- thread = threading.Thread(target=paramFuzzer.fuzzParams(self.orgHost, self.orgPort, self.orgProtoChoice,
- self.orgHeaders, self.orgParams, self.orgMethod, self.orgURLPath, self.orgContType, self.orgBaseline), args=())
- thread.daemon = True
- thread.start()
- print (str(len(issueList)))
-
- # issue = ScanIssue(httpTraffic[0], self._helpers)
- #self._callbacks.addScanIssue(issue)
- #paramFuzzer.fuzzParams(self.orgHost,self.orgPort,self.orgProtoChoice,self.orgHeaders,self.orgParams,self.orgMethod,self.orgURLPath,self.orgContType,self.orgBaseline)
-
-
- except UnicodeEncodeError:
- print("Error in URL decode.")
- return None
- def buildRequest(self, strRequest, httpTraffMsg):
- stdout = PrintWriter(self._callbacks.getStdout(), True)
- strorignalRequest = strRequest
-
- #Get data about the request that was right clicked
- for item in httpTraffMsg:
- try:
- httpService = item.getHttpService()
- self.httpTraffic = httpService
- host = httpService.host
- port = httpService.port
- protocol = httpService.protocol
- protoChoice = True if protocol.lower() == 'https' else False
- #Parse the text area that should contain an HTTP request.
- requestInfo = self._helpers.analyzeRequest(strorignalRequest)
- #Request data
- headers = requestInfo.getHeaders()
- bodyOffset = requestInfo.bodyOffset
- body = strorignalRequest[bodyOffset:]
- for (i, header) in enumerate(headers):
- if header.lower().startswith("content-type:"):
- content_type = header.split(":")[1].lower().strip()
- method = headers[0].split(" ")[0]
- urlpath = headers[0].split(" ")[1]
- #Debugging area for output and parsing
- #stdout.println(str(body))
- #stdout.println(str(headers))
- #stdout.println(str(method))
- #stdout.println(str(content_type))
- #stdout.println(str(urlpath))
- #Identify and parse parameters in the request
- if method == "GET":
- stdout.println("[!] GET REQUEST IDENTIFIED")
- body = urlpath.split("?")[1]
- #print(body)
- params = dict(x.split('=') for x in body.split('&'))
- else:
- #Add logic here for the handling parameters in body and JSON content
- if "json" in str(content_type) or "JSON" in str(content_type):
- stdout.println("[!] JSON REQUEST IDENTIFIED")
- #print(body)
- #print("[!] BODY DONE")
- params = json.loads(body)
- #print(body)
- #print(params)
- else:
- stdout.println("[!] POST REQUEST IDENTIFIED")
- #print(body)
- params = dict(x.split('=') for x in body.split('&'))
- #print(params)
- stdout.println("[!] PARAMETERS IDENTIFIED!")
- stdout.println(params)
- stdout.println("[!] DETERMINING BASELINE")
- baselineInt = self.baseline(host, port, protoChoice, headers, params, method, urlpath, content_type)
- #Assign Parameters for Fuzzing
- self.orgHost = host
- self.orgPort = port
- self.orgProtoChoice = protoChoice
- self.orgHeaders = headers
- self.orgParams = params
- self.orgMethod = method
- self.orgURLPath = urlpath
- self.orgContType = content_type
- self.orgBaseline = baselineInt
- except Exception as ex:
- stdout.println("Problem parsing the request data" + "\n")
- stdout.println(ex)
- return
- def consolidateDuplicateIssues(self, existingIssue, newIssue):
- if (existingIssue.getIssueName() == newIssue.getIssueName()):
- return -1
- else:
- return 0
- def extensionUnloaded(self):
- print ("SQL Truncation Scanner unloaded")
- return
- def postRequest(self, headers, body, args_):
- #Needed: args=[host,port,protoChoice,request]
- stdout = PrintWriter(self._callbacks.getStdout(), True)
- request = self._helpers.buildHttpMessage(headers, body)
- args_.append(request)
- t = threading.Thread(target=self.makeRequest, args=args_)
- t.daemon = True
- t.start()
- t.join()
- def makeRequest(self, host, port, protoChoice, request):
- stdout = PrintWriter(self._callbacks.getStdout(), True)
- try:
- self.resp = self._callbacks.makeHttpRequest(
- host,
- port,
- protoChoice,
- request
- )
- except Exception as ex:
- stdout.println(ex)
- def baseline(self, host, port, protoChoice, headers, body, method, urlpath, content_type):
- if "json" not in content_type.lower():
- new_body = ""
- new_body += '&'.join("%s=%s" % (key, str(val)) for (key, val) in body.iteritems())
- #print(new_body)
-
- if method == "GET":
- url1 = urlpath.split("?")[0]
- url2 = "?" + str(new_body)
- headers[0] = "GET " + str(url1) + str(url2) + " HTTP/1.1"
- self.getRequest(headers, [host, port, protoChoice])
- else:
- self.postRequest(headers, new_body, [host, port, protoChoice])
- #Here we take the lengh and status code of the body returned as a baseline
- originalReq = self._helpers.analyzeRequest(self.resp)
- reqResponse = self._helpers.analyzeResponse(self.resp)
- reqRespTxt = self.resp.tostring()
- respStatusCode = reqResponse.getStatusCode()
- resbodyOffset = reqResponse.getBodyOffset()
- respbodyLen = len(reqRespTxt[resbodyOffset:])
- baselineData = str(respStatusCode) + str(respbodyLen)
- print(baselineData)
- return baselineData
- class fuzzParams():
- def __init__(self, reqres, helpers, callbacks):
- self.helpers = helpers
- self.reqres = reqres
- self.callbacks = callbacks
-
- def postRequest(self, headers, body, args_):
- #Needed: args=[host,port,protoChoice,request]
- stdout = PrintWriter(self.callbacks.getStdout(), True)
- request = self.helpers.buildHttpMessage(headers, body)
- args_.append(request)
- t = threading.Thread(target=self.makeRequest, args=args_)
- t.daemon = True
- t.start()
- t.join()
- def makeRequest(self, host, port, protoChoice, request):
- stdout = PrintWriter(self.callbacks.getStdout(), True)
- try:
- self.resp = self.callbacks.makeHttpRequest(
- host,
- port,
- protoChoice,
- request
- )
- except Exception as ex:
- stdout.println(ex)
- def fuzzParams(self, host, port, protoChoice, headers, body, method, urlpath, content_type, baseline):
- stdout = PrintWriter(self.callbacks.getStdout(), True)
- stdout.println("[!] FUZZING "+ str(len(body)) + " PARAMETERS")
- issueList.clear()
- payloadSet = {"5": ' 00', "10": ' 00', "15": ' 00', "20": ' 00', "30": ' 00', "40": ' 00'}
- #Let's loop through each parameter
- for param in body:
- stdout.println(" [-] FUZZING: " + str(param))
- fuzzParameter = str(param)
- for payLSD in payloadSet:
- stdout.println(" [-] PAYLOAD: " + payLSD)
- payload = payloadSet[payLSD]
- bodd = body
- bodd[fuzzParameter] = bodd[fuzzParameter] + payload
- if "json" not in content_type.lower():
- new_body = ""
- new_body += '&'.join("%s=%s" % (key, str(val))
- for (key, val) in bodd.iteritems())
- #print(" " + new_body)
- if method == "GET":
- url1 = urlpath.split("?")[0]
- url2 = "?" + str(new_body)
- headers[0] = "GET " + str(url1) + str(url2) + " HTTP/1.1"
- self.getRequest(headers, [host, port, protoChoice])
- else:
- self.postRequest(headers, new_body, [host, port, protoChoice])
- #Here we take the lengh and status code of the body returned as a baseline
- reqFuzzResponse = self.helpers.analyzeResponse(self.resp)
- reqFuzzReq = self.helpers.analyzeRequest(self.resp)
- reqFuzzRespTxt = self.resp.tostring()
- respFuzzStatusCode = reqFuzzResponse.getStatusCode()
- resFuzzbodyOffset = reqFuzzResponse.getBodyOffset()
- respFuzzbodyLen = len(reqFuzzRespTxt[resFuzzbodyOffset:])
- fuzzResponseData = str(respFuzzStatusCode) + str(respFuzzbodyLen)
- print(" " + fuzzResponseData)
- if fuzzResponseData != baseline:
- stdout.println(" [+] POSSIBLE INJECTION DETECTED")
- issue = ScanIssue(
- self.reqres[0], reqFuzzReq, "SQL Truncation Scanner", fuzzParameter + " | " + payLSD, "High")
- self.callbacks.addScanIssue(issue)
- return
|