Writing your own Burpsuite Extensions: Complete Guide

Aditya Verma
9 min readJun 24, 2022

Recently I had to create some extensions for Burpsuite. I tried finding resources that could help me but couldn’t find much. Most of them were very basic and didn’t talk about the details. I’ll try to cover most of the parts of writing custom extensions in python for automating your workflow and get those bugs with less manual work.

Keep an eye on the API documentation so that you can relate what I am telling here. If I miss anything then be sure that you can figure that yourself using the documentation. We’ll be making a simple Parameter Replacer which would replace all parameters with given value. Useful for finding BlindXSS and SSRFs. But I’ll be also giving info unrelated to this particular extension so that you can create whatever you want. Let’s divide this guide into few parts so it is easier to understand,

Basic Building Blocks

We start writing all the code of our extension inside BurpExtender class and define the features we are gonna use in the arguments of the class like this,

from burp import ITab                             
from burp import IProxyListener
from burp import IBurpExtender
class BurpExtender(IBurpExtender,IProxyListener, ITab)

Here we are defining IBurpExtender, IProxyListener and Itab interfaces, IBurpExtender is default in every extension, IProxyListener is used when we want to listen to the requests going through proxy and ITab is used to create your own custom tab in the Burpsuite. Don’t forget to import whichever feature you are gonna use. There are other listeners also which I would be explaining later.

Now we need to register all the features of Burp that we are gonna use, in the registerExtenderCallbacks function. This function acts as a control panel of the extension. You can also give your extension a name in this function.

def registerExtenderCallbacks(self, callbacks):
#keeping a reference to our callbacks object
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()

# set our extension name
callbacks.setExtensionName("Parameter Injector")
callbacks.addSuiteTab(self)
    # obtain our output stream
self._stdout = PrintWriter(callbacks.getStdout(), True)
    # register ourselves as a Proxy listener
callbacks.registerProxyListener(self)

Here we are also keeping a reference of helpers and callbacks object so that we can use the function inside them later on. You can see all the function that these objects provide by going to the documentation: callbacks helpers. We gave out extension a name, [addSuiteTab() later in the blog] got our output stream through which we’ll be printing out(shown in extension tab on selecting the extension)(you can also use print statement for the same but sometimes it overwrites things) and registered Proxy Listener. Now whenever a request/response goes through Burp it would go through extension. Similarly we can register other listeners like registerScannerListener which would trigger whenever any Issue is found by Burp Scanner. You can have as many listeners as you want in the extension. Use the documentation to know about other listeners.

Your code should look like this now,

If you run this you will see “Extension is Loaded” printed under the output dialog of the extension. Let’s process the messages that are going through proxy now.

Make Extension do the work

Since we have registered an IProxyListener interface in our extension and we can see in the documentation that IProxyListener interface is processed by processProxyMessage function. So we used callbacks.registerProxyListener and now whenever a message goes thorugh proxy, processProxyMessage function would be fired with appropriate arguments. Let’s define the function,

def processProxyMessage(self, messageIsRequest, message):
self._stdout.println(("Proxy request to " if messageIsRequest else "Proxy response from ") + message.getMessageInfo().getHttpService().toString())
if messageIsRequest:
messageInfo=message.getMessageInfo()
requestString = messageInfo.getRequest().tostring()

self._stdout.println("Processing")
req=self.processit(requestString,messageInfo)

func = self._callbacks.makeHttpRequest
thread = Thread(target=func, args=(messageInfo.getHttpService(), req))
thread.start()

The first argument in this function is self which is the current instance of the class and would be used to access variables/functions belonging to the class. Second argument is a bool that tells whether the message is a request or a response and last is the message which is an IInterceptedProxyMessage Object as mentioned in the description of the IProxyListener Interface. In the definition of IInterceptedProxyMessage you can see a getMessageInfo() function which would return a IHttpRequestResponse object. We can process request/response using this object. We use getRequest() function to get the bytes of the request and convert it to string.

Similarly, if you had set IScannerListener then you could define a newScanIssue() function and get all the information regarding the issue using IScanIssue object. One thing to remember while using IScanIssue object is that, getHttpMessages() function returns a list of IHttpRequestResponse object(which is shown by the square brackets in the return type in the documentation). The list contains two elements first is request that was used to find the issue and the second is the response that was recieved.

Now, let’s process the request to replace all the parameters with our payload. We have defined a processit named function to do our task. Remember that messageInfo is the IHttpRequestResponse object here and requestString is our intercepted request in string format.

def processit(self, requestString, messageInfo=None):
analyzed = self._helpers.analyzeRequest(messageInfo)
parameters = analyzed.getParameters()
modified_parameter=[]
for param in parameters:
modified_parameter.append(
self._helpers.buildParameter(param.getName(), self.payload.getText(), param.getType())
)
new_req=messageInfo.getRequest()
for param in modified_parameter:
new_req = self._helpers.updateParameter(new_req, param)
print(param.getName(),param.getValue(),param.getType())

#self._stdout.println(requestString)
return new_req

Now we’ll be using helpers to get our work easier. Helpers provide a large number of functions that can help you with the processing that you want to do. For this case we want to change all the parameters’ value. So, first we get all parameters in the request. We use analyzeRequest() function as it would give us IRequestInfo object which has getParameters() function through which we can access the parameters. There are multiple ways you can use analyze request, for this case we are passing IHttpRequestResponse object. Then using getParameters() we get the list of IParameter object. Each object contains details of a parameter. Now we are keeping a list of IParameter objects with value changed to our payload(the self.payload.getText() is used to get payload value from the custom tab of the extension, which I’ll be explaining later. You can directly pass a string in place of this for the meanwhile). Here the getType() function tells us whether the parameter is a GET,Cookie or POST parameter. It returns 0 for GET, 1 for POST and 2 for Cookie parameter.

After this, we are making a copy of the request, as we don’t want to change the parameters of original request. This is because if we change the parameters of original request, then it is possible that the requests that are coming from browser would not be successful, because the server might be using those parameters to process your request and if we change that then we won’t be able to browse properly. Our aim here is to keep browsing while all the parameters in the requests get changed with our payload without making any problem in our browsing. Again using the helpers we update the parameters in the new_req. This new_req is in bytes and we return it back.

You can directly update the request as you want by directly processing on it. Do all the processing, it would directly reflect in the request which you can cross-check by going in the History section of Proxy tab.

As I’ve already told you we aren’t processing on the original request which would be passed to server as all the instructions of processProxyMessage() are complete. But our new_req(copy of original) is not going to the server on it’s own. So we need to send it manually by using the helpers. You could directly send the request without using thread but this will cause the extension to wait while the request is being sent.

self._callbacks.makeHttpRequest(messageInfo.getHttpService,req)

So, now our code looks like,

You can run it to check for yourself, don’t forget to change the self.payload.getText() with your own payload.

To cross check whether your modified requests are going or not, you can install Logger++ extension from BApp Store and in that you can see the requests made by Extender.

User Interface

Let’s add UI to our extension so that it is easier to use as currently, you need to change the code whenever you want to change the payload. Swing is a toolkit in java which is used to create GUI for java apps. Since, Burpsuite is written in Java we’ll use the same but in the form of a Python library. I’ll be talking about only basic UI and for more you can learn it from TutorialsPoint’s Article.

So, we had used a function addSuiteTab(self) earlier in the registerExtenderCallbacks() function. It is used to add a custom tab for the extension. We name the tab using by defining the following function.

def getTabCaption(self):
# Setting extenstion tab name
return "Custom Extension"

Let’s create a very basic UI in the tab,

def getUiComponent(self):
# Returning instance of the panel as in burp's docs
x = 10 # panel padding
y = 10 # panel padding

panel = Panel()
panel.setLayout(None)
self.pyld_lbl = JLabel("Payload")
self.pyld_lbl.setBounds(x, y, 80, 20)
panel.add(self.pyld_lbl)

self.payload = JTextArea()
self.pyld_scrl = JScrollPane(self.payload)
self.pyld_scrl.setBounds(x + 100, y, 200, 20)
panel.add(self.pyld_scrl)
    return panel

Here, x and y act as coordinates with (0,0) at top-right and y increases as we move downwards, while x increases as we go from left to right. We set the base value (10,10) so that our text is not touching the border of the tab. Create a panel and then using JLabel we add a label for our input box. setBounds() function defines where we want to insert the element and what would be it’s length and breadth. Here 80 is the length while 20 is breadth. Next, we add our input box using JTextArea() and declare it as self.payload(we would be using it directly to fetch the contents inside the input box). Since, we had already put our label at x so we put input box after some gap in front of label by providing the first coordinate as x+100 and as the input box would be infront of the label so second coordinate does not changes. To get the text typed in the input box we can use self.paylaod.getText().

Don’t forget to import JTextArea, JLabel and JScrollPane from javax.swing library and Panel from java.awt library. All these libraries are already present in jython.jar so once you have provided the path of it to Burpsuite you can use them directly. One more thing to remember is that if you are creating multiple input boxes or other elements, don’t declare them with same name.

You can also add buttons but we won’t need them in this extension.

self.btn = JButton("Recieve",actionPerformed=self.doSomething)                                     self.pyld_scrl = JScrollPane(self.btn)                                     self.pyld_scrl.setBounds(x, self.y + 150, 90, 20)                                     self.panel.add(self.pyld_scrl)

Here actionPerformed provides the function name that would be fired when the button is clicked.

If you want to create a custom entry in the Right-Click menu(Context menu) of the Burp for the extension then it can be done by first registering it in the registerExtenderCallbacks function by

callbacks.registerContextMenuFactory(self)

Then you can create the menu item using the following function,

def createMenuItems(self,invocation):
menu_list = []
menu_list.append(JMenuItem("CustomEntry",None,actionPerformed= lambda x, inv=invocation:self.customName(inv)))
return menu_list

JMenuItem needs to be imported from javax.swing. “CustomEntry” is the name of the menu item in the context menu and customName is the name of function fired upon it’s click. The function would be defined as, where inv is the IContextMenuInvocation Object.

def send(self,inv):
msg=inv.getSelectedMessages()
#Do something

Make Custom Objects

This part is unrelated to our extension but using this you can do a lot more like connecting different Burpsuite instances, making them communicate with one-another, share issues. Here is one instance where this can be needed, suppose you want to minimize the resource consumption on your device(Burpsuite eats a lot of RAM) and want a burpsuite running in cloud so that it can do your active scans or other stuff.

Let’s skip the part where we configure the Burpsuite in cloud as we are talking only related to Extensions here. As you would have learnt till now that we have to deal with multiple type of objects. Objects cannot be sent as-they-are thorugh internet and either we can serialize them on cloud Burpsuite and then deserialize on the burpsuite running on your own device or we can send all the required data as POST request and use it to make custom objects which act similar to original object and use them to do further processing.

Let’s make a custom IScanIssue object through which we can add issues discovered in the cloud Burpsuite to the dashboard in our local Burpsuite and a custom IHttpRequestResponse object.

So, in our custom class for IScanIssue we have provided all the functions that a original class contains but only given context(you can give context to all functions) to those functions which would be used by the function operating on the custom object. Now, you create your custom object and pass it to (for example) helper functions just like you were using an original object.

That wraps the guide. I have tried to provide detail on every core aspect that a beginner might face issues on while working first time with the Extensibility API. Hope this helps and hit me up if you have any questions.

--

--