Skip to Main Content
April 16, 2020

Prepare to Write A Scanner Plugin Before Your Next Platform Test!

Written by Geoff Walton

BurpSuite is a remarkably extensible platform. While I have written a number of extensions for testing specific applications, as well as more general extensions, one type of extension I had never attempted before was creating my own BurpSuite Scanner plugin.

Because modern applications are increasingly difficult to exhaustively test for certain types of issues, I thought it might be worth implementing a scanner plugin. In the days when everything used server-side templates, we could design a payload we wanted to inject and it may have gone into a header directly. It could also be that we needed to URL encode the payload and start copy/pasting it into query strings or POST body parameters, moving through them all quickly.

With today’s applications, especially those that have grown organically as technology has shifted, it tends to be difficult to try a specific payload on all the insertion points. We are faced with a mixture of URL encoded parameters, JSON, SOAP XML, and sometimes nested things such as URL encoded JSON in POST parameters. Let’s consider the POST body below.


Thing1=someValue&Thing2=%7B%22param1%22%3A%22foo%22%2C+%22param2%22%3A%22bar%22%2C+%22param4%22%3A%22baz%22%7D 

Suppose this application allows some type of server-side template processing on some resources and we want to find out if it can be used on others. Assume we know that if templates are processed, and injection is present, a payload of {{ $Objects.getServiceVersion() }} would return something like ‘8.5.60’ as a string.

To be thorough, this is the process I need to follow:

  1. Submit my payload as the entire body
    1. {{ $Objects.getServiceVersion() }}
  2. Submit my payload URL encoded as the entire body
    1. %7B%7B+%24Objects.getServiceVersion%28%29+%7D%7D
    2. x=%7B%7B+%24Objects.getServiceVersion%28%29+%7D%7D
  3. Try it as parameter values for Thing1 and Thing1
    1. Thing1=%7B%7B+%24Objects.getServiceVersion%28%29+%7D%7D& Thing2=%7B%22param1%22%3A%22foo%22%2C+%22param2%22%3A%22bar%22%2C+%22param4%22%3A%22baz%22%7D
    2. Thing1=someValue&Thing2=%7B%7B+%24Objects.getServiceVersion%28%29+%7D%7D
  4. Try it as each of the JSON values param1, param2, param3
    1. %7B%22param1%22%3A%22%7B%7B+%24Objects.getServiceVersion%28%29+%7D%7D%22%2C+%22param2%22%3A%22bar%22%2C+%22param4%22%3A%22baz%22%7D

Are you tired yet? I know I am, and this one was easy. This did not even have content that had to be escaped for a JSON string. We did not have to deal with XML either. Oh, but wait, we still have to eyeball or search all those responses for ‘8.5.60’.

The good news is that we have a tool in BurpSuite’s Scanner that already knows how to do everything we need to do. It can locate the insertion points, including the nested ones, even to an arbitrary depth. Additionally, it can perform all the correct encoding in the correct order so we do not have deal with that tedious error-prone task. With little more than the contribution of regex pattern on our part can watch for the tells. BurpSuite’s extender documentation is pretty good as far as each interface goes, but understanding how they relate to one another in Scanner took me some time.

There are six Scanner-related interfaces that you can implement or extend. These are IScanIssue, IScanQueueItem, IScannerCheck, IScannerInsertionPoint, IScannerInsertionPointProvider, and IScannerListener. Again, this a lot of complexity that offers wonderful flexibility when needed, but it is a lot to process when you are just looking to solve a problem quickly. Fortunately, as long the application you are testing sticks to normal exchange formats (XML, JSON, traditional HTTP form encodings, etc.), there are only two of these interfaces to be concerned with - IScannerCheck and IScanIssue.

The IScanIssue interface is pretty straightforward. The concreate class we create is basically going to be a bag of properties that, among other things, includes the name of our issue, its severity, a confidence value, and various strings we want to have presented if BurpSuite’s reporting features are used. Your Scanner check will return instances of this class.

IScannerCheck is the interface that, when implemented, is the meat of your Scanner issue. It will receive instances of classes implementing the IScannerInsertionPoint interface, which it will complete with payloads, generate http requests from, and finally examine the responses.

I have created a rough template for implementing a Scanner check in Ruby, as that is simpler than Java for iteratively tweaking and editing. I have stuck to the Java-like naming and calling of things for the most part, however.

require 'java'
 java_import 'burp.IExtensionHelpers'
 java_import 'burp.IBurpExtender'
 java_import 'burp.IScannerCheck'
 java_import 'burp.IScanIssue'
 java_import 'burp.IScannerInsertionPoint'
 
 module BURPMethods
   def self.included(base)
     base.send(:include,InstanceMethods)
     base.extend(StaticMethods)
   end
 
   module InstanceMethods
     def method_missing(method, *args, &block)
       if self.class.helpers.respond_to? method
         self.class.helpers.send(method, *args, &block)
       elsif self.class.callbacks.respond_to? method
         self.class.callbacks.send(method, *args, &block)
       else
         raise NoMethodError, "undefined method `#{method}` for #{self.class.name}"
       end
     end
 
     def respond_to?(method, include_private = false)
       super || self.class.callbacks.respond_to?(method, include_private) || self.class.helpers.respond_to?(method, include_private)
     end
 
   end
 
   module StaticMethods
     def callbacks=(callbacks)
       @callbacks = callbacks
       @helpers = @callbacks.getHelpers
     end
 
     attr_reader :callbacks
     attr_reader :helpers
   end
 end
 
 class MyScannerCheck
   include IScannerCheck
   include BURPMethods
   INS_PARAM_URL = 0x00
   INS_PARAM_BODY = 0x01
   INS_PARAM_COOKIE = 0x02
   INS_PARAM_XML = 0x03
   INS_PARAM_XML_ATTR = 0x04
   INS_PARAM_MULTIPART_ATTR = 0x05
   INS_PARAM_JSON = 0x06
   INS_PARAM_AMF = 0x07
   INS_HEADER = 0x20
   INS_URL_PATH_FOLDER = 0x21
   INS_PARAM_NAME_URL = 0x22
   INS_PARAM_NAME_BODY = 0x23
   INS_ENTIRE_BODY = 0x24
   INS_URL_PATH_FILENAME = 0x25
   INS_USER_PROVIDED = 0x40
   INS_EXTENSION_PROVIDED = 0x41
   INS_UNKNOWN = 0x7f
   EXISTING_ISSUE = -1
   NEW_ISSUE = 1
   BOTH_ISSUES = 0
   IDX_START = 'TS_ASDF'
   IDX_END = 'TS_LKJH'
   INJECT = "${'#{IDX_START} ' YOUR_PAYLOAD_HERE ' #{IDX_END}'}"
 
   def doPassiveScan(baseRequestResponse)
     nil #Will not be able to spot the issue on a passive scan
   end
 
   def doActiveScan(baseRequestResponse, insertionPoint)
     issues = Array.new
     #return issues if insertionPoint.getInsertionPointType() == 0x21
 
     checkRequest = insertionPoint.buildRequest(stringToBytes(INJECT));
     checkRequestResponse = makeHttpRequest(baseRequestResponse.getHttpService(), checkRequest)
     response = bytesToString(checkRequestResponse.getResponse()).to_s #Get a Ruby String
     index_start = response.index IDX_START
     index_end = response.index IDX_END
     if index_start
 
       issue = MyScannerIssue.new
       issue.setHttpService(baseRequestResponse.getHttpService())
       issue.setURL(analyzeRequest(baseRequestResponse).getUrl())
 
       issue.setHTTPMessages([applyMarkers(checkRequestResponse, requestHighlights, nil)])
       issues << issue
     end
     issues
   end
 
   def consolidateDuplicateIssues(existingIssue, newIssue)
     return EXISTING_ISSUES if existingIssue == newIssue
     BOTH_ISSUES
   end
 end
 
 class MyScannerIssue
   include IScanIssue
   attr_accessor :getURL
   alias_method :setURL, :getURL=
   alias_method :getUrl, :getURL
   alias_method :setUrl, :setURL
   attr_reader :getConfidence
   attr_accessor :getHttpMessages
   alias_method :setHTTPMessages, :getHttpMessages=
   attr_accessor :getHttpService
   alias_method :setHttpService, :getHttpService=
 
   def ==(v)
     false #Right now never match so will report all issues; but we can put sensible looking logic elsewhere
   end
   alias_method :eql?, :==
 
   def initialize
     @getConfidence = 'Tentative'
   end
 
   def setConfidence(c)
     case c
     when :certain
       @getConfidence = 'Certain'
     when :firm
       @getConfidence = 'Firm'
     when :tentative
       @getConfidence = 'Tentative'
     else
       'Tentative'
     end
   end
   alias_method :getConfidence=, :setConfidence
 
   def getIssueBackground
     'https://telekomsecurity.github.io/2018/07/servicenow-privilege-escalation.html'
   end
   alias_method :getIssueDetail, :getIssueBackground
 
   def getRemediationBackground
     'Contact ServiceNow'
   end
   alias_method :getRemediationDetail, :getRemediationBackground
 
 
   def getIssueName
     ‘Script Injection'
   end
 
   def getIssueType
     0xFF000000
   end
 
   def getSeverity
     'High'
   end
 end
 
 class BurpExtender
   include IBurpExtender
   ExtensionName = 'YOUR EXTENSION NAME Scanner'
 
   def registerExtenderCallbacks(callbacks)
     ObjectSpace.each_object(Class).select {|klass| klass < BURPMethods }.each do |kklass|
       kklass.callbacks = callbacks
     end
     callbacks.setExtensionName ExtensionName
     callbacks.registerScannerCheck(MyScannerCheck.new)
   end
 
 end

Here is a quick explanation of what is going on here - the first thing to look at is the BurpExtender class. Every extension has to implement this and BurpSuite will trigger the registerExtenderCallbacks method when it loads the extension. This method does a few things. It first obtains the callbacks object. This object gives access to BurpSuite’s helper functions and network methods such as makeHTTPRequest, stringToBytes, etc. The ObjectSpace call identifies any user classes that have been created and that have the BURPMethods module in their inheritance tree and assigns a reference to the callbacks object as a class variable. The BURPMethods module is a quick way to leverage Ruby’s metaprogramming method lookup overrides to make the important BurpSuite methods available without passing the callbacks object around in method signatures or having long chains of receivers. This means the ScannerCheck implementationand any other classes that might be defined can get straight to business,abstracting away some BurpSuite’s object hierarchy.

MyScannerIssue is basically just a bag of values for BurpSuite’s reporting and dashboard functions and does not require much explanation. The sample here hardcodes some things like severity because of the targeted way I have been utilizing custom scan issues, but it certainly could make these properties as well.

MyScannerIssue::doActiveScan is where things get interesting. A base request and an insertionPoint are passed into the method. The first thing we do is create an empty array. The API expects us to return an array of zero or more ScannerIssue objects. It may be preferrable to skip testing for certain types of insertion points and return an empty array early. These are the defined constants in the sample class and can be compared against the return value of getInsertionPointType on the insertionPoint object. As an example, it may not make sense to test certain types of injection payloads in locations such as URL path components part of a REST API. The insertionPoint.buildRequest method enables the raw payload we want to send to be encoded and inserted into the request. Next, MakeHTTPRequest is used to send the newly formed request to the same HTTPService as the base request. After that, we obtain the response object from the RequestResponse object and convert its bytes first to a Java string with BurpSuite’s helper method and finally to a Ruby string with call to to_s.

At this point, you are really on your own. You need to inspect the response for whatever indication there is that an issue is likely present. If something is found, create a new instance of your ScannerIssue class, fill out the values, and append it to the issues array. Finally, return the array. That is all there is to it. Not hard after all, despite the number of Scanner interfaces in the BurpSuite API.

Now, all that is left to do is to register the new extension on the Extender tab.

Figure 3 - BurpSuite Extender - Add Extension Function

If you have not used JRuby extensions before, you might need to pay the Extender -> Options tab a visit and set the JRuby JAR file. You can obtain it from https://www.jruby.org. If you do not want to set up a complete JRuby environment, you can download one of the JRuby-complete jars, which includes the Ruby standard library in the JAR and will provide you with all the functionality needed for Scanner extensions. 

Now, all that is left is to get scanning and watch the findings roll in on the dashboard!