In a previous post, I wrote about creating a Burp Suite extension in Java using the IBurpExtender interface. When performing web application security testing, I often need to write small pieces of code to help me in automating some tasks and the code is generally specific the the application I am testing. Whereas I like Java, I think that dynamically typed languages are more efficient for creating small pieces of code quickly and efficiently. However, don't misquote me, dynamically typed languages like Python can also be (and are) used for very large development projects.

python Having used Python for about 8 years now, I found very interesting the idea of creating a Python binding for the Burp Suite. Since Burp is written in Java, I obviously used Jython, the java implementation of Python.

My goal was to allow anyone to write the Burp extensions directly in Python using the same BurpExtender interface. Therefore, if you wrote Burp extensions in Java, you already know how to write them in Python.

First example

This very simple extension replaces the string "java" to "python" in all http responses received by the Burp. This is useless; but it is just to show how easy it is to write an extension in Python. Only those few lines of code are needed:

from burp import IBurpExtender

class BurpExtender(IBurpExtender):
    
    def processProxyMessage(self,messageReference, messageIsRequest, remoteHost, remotePort,
                            serviceIsHttps, httpMethod, url, resourceType, statusCode,
                            responseContentType, message, interceptAction):
        if not messageIsRequest:
            message = message.tostring().replace("java","python")
        return message


Embedding an interactive python interpreter

Let's look at something a bit more interesting, using an interactive python console to work on some messages proceeded by Burp:

from burp import IBurpExtender
from java.net import URL
from code import InteractiveConsole

class BurpExtender(IBurpExtender):
    def processProxyMessage(self,messageReference, messageIsRequest, remoteHost, remotePort,
                            serviceIsHttps, httpMethod, url, resourceType, statusCode,
                            responseContentType, message, interceptAction):
        if not messageIsRequest:
            uUrl = URL("HTTPS" if serviceIsHttps else "HTTP", remoteHost, remotePort, url)
            if self.mCallBacks.isInScope(uUrl):
                message = message.tostring()
                from pprint import pprint
                loc=dict(locals())
                c = InteractiveConsole(locals=loc)
                c.interact("Interactive python interpreter")
                for key in loc:
                    if key != '__builtins__':
                        exec "%s = loc[%r]" % (key, key)
        return message

    def registerExtenderCallbacks(self, callbacks):
        self.mCallBacks = callbacks

What this code does basically is: launch a Python interpreter, make all the python namespace available (you can access and modify any field and method that is offered by the BurpExtender object). Is this not cool?

Only messages that are in the Burp Suite scope will be intercepted and made available interactively (Target/Scope tab in Burp). This is done by the line:

 if self.mCallBacks.isInScope(uUrl):

isInScope is a callback function, the mCallBack object is registered by the registerExtenderCallbacks python method.

Below is an example on what is available with the interactive shell. The shell is available on the console used to start Burp suite. When a message is in the scope, the shell is launched.

First, we are within the scope of the processProxyMessage method and have direct access to the different fields.

Interactive python interpreter
>>> pprint(dir())
['httpMethod',
 'interceptAction',
 'message',
 'messageIsRequest',
 'messageReference',
 'pprint',
 'remoteHost',
 'remotePort',
 'resourceType',
 'responseContentType',
 'self',
 'serviceIsHttps',
 'statusCode',
 'uUrl',
 'url']

>>> pprint(message)
'HTTP/1.1 200 OK\r\nDate: Mon, 30 Aug 2010 12:16:40 GMT\r\nServer: Apache/2.2.9 (Fedora)\r\nLast-Modified: Mon, 30 Aug 2010 11:12:52 GMT\r\nETag: "2aa3a-4d-48f088ba1f500"\r\nAccept-Ranges: bytes\r\nContent-Length: 77\r\nConnection: close\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<html>\n<head>\n<title>Test!</title>\n</head>\n<body>\nHello all!\n</body>\n</html>\n'

>>> print resourceType, responseContentType, statusCode
html text/html; charset=utf-8 200

It is also possible to interact with all the BurpExtender fields and methods:

>>> pprint(dir(self))
['ACTION_DONT_INTERCEPT',
[..]
 'applicationClosing',
 'class',
 'classDictInit',
 'clone',
 'commandLineArgs',
 'equals',
 'finalize',
 'getClass',
 'hashCode',
 'mCallBacks',
 'newScanIssue',
 'notify',
 'notifyAll',
 'processHttpMessage',
 'processProxyMessage',
 'registerExtenderCallbacks',
 'setCommandLineArgs',
 'toString',
 'wait']
>>> 

It is possible for example to call any Burp method provided by the callback object:

>>> for message in self.mCallBacks.getProxyHistory():
...     message.getRequest().tostring()
... 
'GET /test.html HTTP/1.1\r\nHost: 127.0.0.1\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.5) Gecko/2008121622 Fedora/3.0.5-1.fc9 Firefox/3.0.5\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nProxy-Connection: keep-alive\r\nCache-Control: max-age=0\r\n\r\n'


Adding new options in Burp Suite menus

This only works with the professional version of Burp Suite (minimum 1.3.07)

Now I am going to show how to create a new menu item within Burp that will call new functions written in Python. This code below adds a "Compare parameters" item in the Burp Suite contextual menu. In the Proxy/History tab, you can select two messages, right click and select the new compare function. This code is just an example of what can be done, it compares GET and POST parameters between two requests and tells the differences. It can be useful though because the Burp Suite comparer is not great to compare requests.

from burp import IBurpExtender
from burp import IMenuItemHandler

from cgi import parse_qs

class BurpExtender(IBurpExtender):
    def registerExtenderCallbacks(self, callbacks):
        self.mCallBacks = callbacks
        self.mCallBacks.registerMenuItem("Compare parameters", ArgsDiffMenuItem())

class ArgsDiffMenuItem(IMenuItemHandler):
    def menuItemClicked(self, menuItemCaption, messageInfo):
        print "--- Diff on arguments ---"
        if len(messageInfo) == 2:
            # We can do a diff
            request1=HttpRequest(messageInfo[0].getRequest())
            request2=HttpRequest(messageInfo[1].getRequest())
            print "Diff in GET parameters:"
            self.diff(request1.query_params,request2.query_params)
            print "Diff in POST parameters:"
            self.diff(request1.body_params,request2.body_params)
        else:
            print "You need to select two messages to do an argument diff"
        print "\n\n"

    def diff(self, params1, params2):
            for param in params1:
                if param not in params2:
                    print "Param %s=%s is not is the second request" % \
                          (param, params1[param])
                    continue
                if params1[param] != params2[param]:
                    print "Request1 %s=%s Request2 %s=%s" % \
                            (param, params1[param], param, params2[param])
            for param in params2:
                if param not in params1:
                    print "Param %s=%s is not is the first request" % \
                          (param, params2[param])

class HttpRequest:
    def __init__(self, request):
        self.request=request.tostring().splitlines()
        self.query_params={}
        self.getParameters()

    def getParameters(self):
        # get url parameters
        try:
            self.query_params=parse_qs(\
            ''.join(self.request[0].split()[1].split("?")[1:]))
        except:
            self.query_params={}

        # get body parameters
        try:
            index=++self.request.index('')
            self.body_params=parse_qs(\
            ''.join(self.request[index:]))
        except:
            self.body_params={}


How to use the python extension

You need the burppython.jar extension. I have created a jar file that contains the jython interpreter so you don't need to install anything else.

Steps:

  1. You need to download the zipfile attached at the end of this article.
  2. You need to unzip the content in a dedicated folder.
  3. You need to copy the burpsuite jarfile in this folder (something like burpsuite_pro_v1.3.07.jar or burpsuite_v1.3.03.jar)
  4. The python extension (BurpExtender.py) needs to be placed in the Lib subfolder.
  5. You can launch the burp suite using suite.bat or suite.sh

Please send me an email to david@ombrepixel.com for any questions

To be done

A lot needs to be done,

  1. Add the capability of using several python and java extensions at the same time and link them together
  2. Add the capability of dynamically reload a python extension without having to stop-restart Burp
  3. Put the project on a tracking version system like GitHub
  4. Add more Demo that could leverage on the numerous Python libraries that already exist. UPDATE: please see the w3af extension
  5. ..