Tuesday, January 14, 2014

Writing and Debugging BurpSuite Extensions in Python

When I first started with Burp extensions over a year ago, I used the hiccup framework to develop my plugins.   Hiccup had a way of monitoring my custom "plugin" for changes each time it performed an action.  As a result, it appeared that any changes I made to a plugin took effect in Burp instantly.   

Well, when Burp Extender API 1.5 came out, while it greatly improved what could be done with Burp extensions, it also broke projects like Hiccup.   Not wanting to be dependent on another non PortSwigger API, I decided to spend whatever time I needed to learn how to interface with the Burp API directly.   

As I began, one frustrating thing I realized was that I had to reload my extension each time I made even the smallest change.   This process takes some time, and because I am using Jython, it sucks some memory each time the extension is reloaded.  I finally gave in and asked on the Burp Suite forum if anyone had a better way of writing and/or debugging Burp Extensions in Python.  

It turns out someone did. elespike figured out that if you move your new functionality to a second file, you could import it once, and then reload it as frequently as you like.  His guidance is in the thread listed above, but I thought it would be helpful to blog my entire solution.  Also, after he pointed me in the direction of the reload, I went back to look at hiccup, and that is in fact what what chair6 was doing to make hiccup reload the plugin.  

And it is WAY BETTER.  It saves a ton of time. 

So, with that intro, I wanted to document what I did. 

Step 1

In Burp >> Extensions >> Options, I set my "Folder for loading modules (optional)" to c:\python\lib


Step 2

I then created my Burp Extension file. Where I would normally include all of my extension logic in this file, I instead moved all of my custom functions to another file (shown in step 3):

from burp import IBurpExtender
from burp import IContextMenuFactory
from burp import IExtensionHelpers
from javax.swing import JMenuItem
from java.awt.event import ActionListener
from java.awt.event import ActionEvent
from java.awt.event import KeyEvent
import traceback
# Burp is configured to look for python modules in c:\python27\lib. 
# If the following file exists in that directory, it will be loaded
import UniqueParamValues

class BurpExtender(IBurpExtender, IContextMenuFactory, ActionListener):
    def __init__(self):
 self.menuItem = JMenuItem('Print Unique Parameter Values')
 self.menuItem.addActionListener(self)    

    def actionPerformed(self, actionEvent):
 print "*" * 60
 # Here is the reload. You can place this anywhere you wantm but you will 
 # most likely want to place this within an action (request recieved, menu
 # item clicked, scanner started, etc).  
 reload(UniqueParamValues)
 # This try statement, and the traceback included in the except, are what
 # allowed me to finally get the trace information I needed to debug my 
 # issues.  I highly recommned including these when developing Burp 
 # Extensions
 try:
     UniqueParamValues.getUniqueParams(self)
 except:
     tb = traceback.format_exc()
     print tb
 
    # implement IBurpExtender
    def registerExtenderCallbacks(self, callbacks):
    
        # keep a reference to our callbacks object (Burp Extensibility Feature)
        self._callbacks = callbacks   
        self._helpers = callbacks.getHelpers()
        # set our extension name
        callbacks.setExtensionName("Unique Parameter Values")
 callbacks.registerContextMenuFactory(self)
        return
    
    def createMenuItems(self, ctxMenuInvocation):
 self.ctxMenuInvocation = ctxMenuInvocation
 return [self.menuItem]
 
      
Step 3

And finally, I created a file that would contain the customized functions needed in my extension (UniqueParamValues.py), and dropped that file in c:\python\lib directory. 


def getUniqueParams(self):
    # Initialize list
    parameter_array = []
    parameter_string_array = []
    messages = self.ctxMenuInvocation.getSelectedMessages()
    # This for loop iterates through all of the selected messages pulling out 
    # everything Burp considers a parameter (even cookies), and putting all of 
    # the parameters in an array
    for m in messages: 
 request_byte_array=m.getRequest()
 requestInfo = self._helpers.analyzeRequest(request_byte_array)
 parameters = requestInfo.getParameters()
 parameter_array = parameter_array + parameters

    # This for loop iterates through each paramter and creates a string with the 
    # paramname=paramvalue, so that they can be compared and sorted later.  
    for p in parameter_array:
 param_string = p.getName() + "=" + p.getValue()
 #print "Param String:", param_string
 parameter_string_array.append(param_string)
    
    # After the for loop is finished, then uniquify and sort the parameters -- The main purpose of the extension
    unique_parameters = sorted(uniqify(self,parameter_string_array))
    
    print "************************************************************"
    print "******************** Unique Paramters **********************"
    print "************************************************************"
    print
    print "Number of Parameters:", len(parameter_string_array)
    print "Number of Unique Parameters :", len(unique_parameters)
    print 
    
    param_dict = {}
    for unique_param in unique_parameters:
 #print "Param: %s" % (unique_param))
 param_name = unique_param.split("=")[0]
 param_value = unique_param.split("=")[1]
  #This if statement creates a dictionary, but unlike a normal dictionary, the value of each key is a list.
  #This is so that I can use the append function.  
  #The key is the parameter name
  #The value is a list of all of unique the seen parameter values
 if not param_name in param_dict:
     param_dict[param_name] = []
 param_dict[param_name].append(param_value)
        
    for key, value in param_dict.iteritems():
 print(len(key) * "-" + "----")
 print("| %s |" % (key))
 print (len(key) * "-" + "----")            
 for item in value:
     print(item)
    print("\n\n\n\n") 

def uniqify(self, parameter_string_array):
    # not order preserving
    set = {}
    map(set.__setitem__, parameter_string_array, [])
    return set.keys()   
As you can see in the snippet above, this file does not require any additional imports. You just define the definitions, receive the arguments, process, and then optionally return the result to the caller. This extension is far from complete, and is the first python I have written in a year, so please don't judge me :).

Regardless, I wanted to put it up here as an example on how to quickly develop and debug a Burp Extension with Python.

If you are curious, at this point, the extension spits out a table in stdout that looks like this:

************************************************************
******************** Unique Paramters **********************
************************************************************


Number of Parameters: 40
Number of Unique Parameters: 12

--------------
| csrf_token |
--------------
null
------------
| board_id |
------------
2
----------
| __utmb |
----------
194279098.1.10.1389713652
----------
| __utmc |
----------
194279098
----------
| __utmz |
----------
194279098.1389713652.1.1.utmcsr
----------
| __utma |
----------
194279098.364032451.1389713652.1389713652.1389713652.1
--------
| page |
--------
2
3
4
5
6


My goal is to eventually create a window in Burp that will contain this information, as well as counts for each parameter value. 

No comments: