Burp VERBalyzer v1.0 Release

I’m proud to release my Burp VERBalyzer (credit to Ben for the name) plugin this week.

Introduction

On an engagement recently, I had a coworker run across a subdirectory that allowed the OPTIONS method, but / didn’t. This, plus the fact that checking options during an engagement wasn’t easy, led me to develop this plugin.

VERBalyzer will go through a list of standard HTTP Methods (other than GET, HEAD, and POST), and check whether the server responds with a 200 response.

While this tool is mostly to report methods that a server should probably have off (TRACE etc.), it can also be used to find interesting footholds on strange endpoints (PUT etc.).

Installation

First, due to the fact that VERBalyzer utilizes Active Scanner checks, you will need a copy of Burp Professional to use it.

To install VERBalyzer, you will first need to install Jython.

Once you have Jython installed, you will need to configure your Burp to point towards the jython.jar file.

Burp VERBalyzer - Jython

After Jython is installed and configured, you can install VERBalyzer.

First, go back to the “Extensions” tab and click “Add”. Change the “Extension type” to Python, and select the location of your VERBalyzer.py file.

Finally, click Next, verify that the plugin was successfully loaded, and click Close!

Burp VERBalyzer - Plugin loaded

Usage

To use VERBalyzer, you will just need to start an Active Scan against your target application.

Once the scanner is running, any non-standard responses will be reported in the “Issues” section of the “Target” tab.

That said, you will need to manually verify some responses to see whether or not the server actually supports the method.

Findings

For an example, here is a list of findings against a few of the httpbin endpoints.

Burp VERBalyzer - HTTPbin Findings

As you can see, the /patch endpoint responded to the PATCH method.

Burp VERBalyzer - PATCH

Finally, you can see the raw request and response below.

PATCH /patch HTTP/1.1
Host: httpbin.org
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3055.0 Safari/537.36 autochrome/yellow
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.8
Cookie: _gauges_unique_hour=1; _gauges_unique_day=1; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1
Connection: close
HTTP/1.1 200 OK
Connection: close
Server: gunicorn/19.7.1
Date: Thu, 13 Apr 2017 16:04:33 GMT
Content-Type: application/json
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Content-Length: 723
Via: 1.1 vegur

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", 
    "Accept-Language": "en-US,en;q=0.8", 
    "Connection": "close", 
    "Content-Length": "0", 
    "Cookie": "_gauges_unique_hour=1; _gauges_unique_day=1; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1", 
    "Host": "httpbin.org", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3055.0 Safari/537.36 autochrome/yellow"
  }, 
  "json": null, 
  "origin": "11.22.33.44", 
  "url": "http://httpbin.org/patch"
}

Technical Details

VERBalyzer adds a new insertion point to HTTP requests, that allows active scans to insert payloads into the HTTP method. That said, VERBalyzer does not allow Burp to use its payloads in that insertion point, at least for now (see Notes below).

Additionally, VERBalyzer adds a new Active Scanner check for all the possible HTTP methods included with it.

You can find the current code for Burp VERBalyzer below.

# VERBalyzer - Burp Plugin to detect HTTP Methods supported by the server
# Author: Ray Doyle (@doylersec) <https://www.doyler.net>
# Copyright 2017
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

try:
    from burp import IBurpExtender
    from burp import IScannerCheck
    from burp import IScanIssue
    from burp import IScannerInsertionPointProvider
    from burp import IScannerInsertionPoint
    from burp import IParameter
    from array import array
    from org.python.core.util import StringUtil
    import string
except ImportError:
    print "Failed to load dependencies."

VERSION = "1.0"
callbacks = None
helpers = None

methods = [
    'OPTIONS',
    #'GET',
    #'HEAD',
    #'POST',
    'PUT',
    #'DELETE',
    'TRACE',
    'CONNECT'
    'PROPFIND',
    'PROPPATCH',
    'MKCOL',
    'COPY',
    'MOVE',
    'LOCK',
    'UNLOCK',
    'VERSION-CONTROL',
    'REPORT',
    'CHECKOUT',
    'CHECKIN',
    'UNCHECKOUT',
    'MKWORKSPACE',
    'UPDATE',
    'LABEL',
    'MERGE',
    'BASELINE-CONTROL',
    'MKACTIVITY',
    'ORDERPATCH',
    'ACL',
    'SEARCH',
    'PATCH',
    'FOO'
]

class BurpExtender(IBurpExtender, IScannerInsertionPointProvider, IScannerCheck):
    def	registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()

        callbacks.setExtensionName("VERBalyzer")

        callbacks.registerScannerInsertionPointProvider(self)
        callbacks.registerScannerCheck(self)

        print "Successfully loaded VERBalyzer v" + VERSION
        return

    # helper method to search a response for occurrences of a literal match string
    # and return a list of start/end offsets
    def _get_matches(self, response, match):
        matches = []
        start = 0
        reslen = len(response)
        matchlen = len(match)
        while start < reslen:
            start = self._helpers.indexOf(response, match, True, start, reslen)
            if start == -1:
                break
            matches.append(array('i', [start, start + matchlen]))
            start += matchlen

        return matches

    # 
    # implement IScannerInsertionPointProvider
    #
    def getInsertionPoints(self, baseRequestResponse):
        requestLine = self._helpers.analyzeRequest(baseRequestResponse.getRequest()).getHeaders()[0]

        if (requestLine is None):
            return None
        
        else:
            # if the parameter is present, add a single custom insertion point for it
            return [ InsertionPoint(self._helpers, baseRequestResponse.getRequest(), requestLine) ]
        
    def doActiveScan(self, baseRequestResponse, insertionPoint):
        if 'HTTP Method' != insertionPoint.getInsertionPointName():
            return []

        issues = []
        
        for method in methods:
            checkRequest = insertionPoint.buildRequest(method)
            checkRequestResponse = self._callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), checkRequest)

            matches = self._get_matches(checkRequestResponse.getResponse(), "HTTP/1.1 200 OK")

            if len(matches) > 0:
                # get the offsets of the payload within the request, for in-UI highlighting
                requestHighlights = [insertionPoint.getPayloadOffsets(method)]

                issues.append(CustomScanIssue(
                    baseRequestResponse.getHttpService(),
                    self._helpers.analyzeRequest(baseRequestResponse).getUrl(),
                    [self._callbacks.applyMarkers(checkRequestResponse, requestHighlights, matches)],
                    "Non-standard HTTP Method Found",
                    "The following method was found to be supported by the server: " + method,
                    "Medium"))

        return issues

    def doPassiveScan(self, basePair):
        return []

    def consolidateDuplicateIssues(self, existingIssue, newIssue):
        # This method is called when multiple issues are reported for the same URL 
        # path by the same extension-provided check. The value we return from this 
        # method determines how/whether Burp consolidates the multiple issues
        # to prevent duplication
        #
        # Since the issue name is sufficient to identify our issues as different,
        # if both issues have the same name, only report the existing issue
        # otherwise report both issues
        if existingIssue.getIssueDetail() == newIssue.getIssueDetail():
            return -1
        return 0

# 
# class implementing IScannerInsertionPoint
#

class InsertionPoint(IScannerInsertionPoint):

    def __init__(self, helpers, baseRequest, requestLine):
        self._helpers = helpers
        self._baseRequest = baseRequest

        # parse the location of the input string within the decoded data
        start = 0
        self._insertionPointPrefix = requestLine[:start]
        end = string.find(requestLine, " /", start)
        if (end == -1):
            end = requestLine.length()
        self._baseValue = requestLine[start:end]
        self._insertionPointSuffix = requestLine[end:]
        return
        
    # 
    # implement IScannerInsertionPoint
    #
    def getInsertionPointName(self):
        return "HTTP Method"

    def getBaseValue(self):
        return self._baseValue

    def buildRequest(self, payload):
        # Gross workaround via Dafydd - https://support.portswigger.net/customer/portal/questions/12431820-design-of-active-scanner-plugin-vs-insertionpoints
        if payload.tostring() not in methods:
            raise Exception('Just stopping Burp from using our custom insertion point')
        else:
            requestStr = self._baseRequest.tostring()

            newRequest = requestStr.replace(self._baseValue, payload)
            newRequestB = StringUtil.toBytes(newRequest)
        
            # update the request with the new parameter value
            return newRequestB

    def getPayloadOffsets(self, payload):
        return [0, len(payload.tostring())]

    def getInsertionPointType(self):
        return INS_EXTENSION_PROVIDED

#
# class implementing IScanIssue to hold our custom scan issue details
#
class CustomScanIssue (IScanIssue):
    def __init__(self, httpService, url, httpMessages, name, detail, severity):
        self._httpService = httpService
        self._url = url
        self._httpMessages = httpMessages
        self._name = name
        self._detail = detail
        self._severity = severity

    def getUrl(self):
        return self._url

    def getIssueName(self):
        return self._name

    def getIssueType(self):
        return 0

    def getSeverity(self):
        return self._severity

    def getConfidence(self):
        return "Certain"

    def getIssueBackground(self):
        pass

    def getRemediationBackground(self):
        pass

    def getIssueDetail(self):
        return self._detail

    def getRemediationDetail(self):
        pass

    def getHttpMessages(self):
        return self._httpMessages

    def getHttpService(self):
        return self._httpService

Future Work

Some of my next steps will be to add more methods, create an actual tab in Burp for configuration and reporting, submit the plugin to the BApp Store, and fix the duplicate issue reporting.

Note: there is a current annoying issue with the plugin. If the server returns a 200 response for all methods (as opposed to 405), then there will be hundreds of findings fairly quickly.

Burp VERBalyzer - Duplicates

Notes

Also, for those of you actually looking at the code or the Error console, then you might notice a ton of exceptions being thrown.

The reason for this is that I didn’t want (or need) Burp to use my insertion point for ALL OF ITS tests.

In this case, any payload that isn’t in the predefined list will throw an exception, and not attempt to change the HTTP method. Credit to Dafydd for this dirty solution to my problem.

Conclusion

Let me know if you have any questions, comments, suggestions, or ideas!

Finally, you can find the code and updates in my GitHub repository.

doyler on Githubdoyler on Twitter
doyler
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCP, eCPPT, eWPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Senior Penetration Testing Consultant for SecureWorks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next (OSCE?!) or side project to work on, he enjoys playing video games, traveling, and watching sports.

1 Comment

Filed under Security Not Included

One Response to Burp VERBalyzer v1.0 Release

  1. Pingback: Burp VERBalyzer v1.0 Release - ZRaven Consulting

Leave a Reply

Your email address will not be published. Required fields are marked *

*