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.
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:
- You need to download the zipfile attached at the end of this article.
- You need to unzip the content in a dedicated folder.
- You need to copy the burpsuite jarfile in this folder (something like
burpsuite_pro_v1.3.07.jar or
burpsuite_v1.3.03.jar)
- The python extension (
BurpExtender.py) needs to be placed in
the Lib subfolder.
- 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,
- Add the capability of using several python and java extensions at the same
time and link them together
- Add the capability of dynamically reload a python extension without having
to stop-restart Burp
- Put the project on a tracking version system like GitHub
- Add more Demo that could leverage on the numerous Python libraries that
already exist. UPDATE: please see the w3af
extension
- ..