Burp Extension Python Tutorial

This post provides step by step instructions for writing a Burp extension in Python. The end result will be an extension that decodes the credentials in an Authorization header using Basic Authentication. More importantly, I’ll go through the code that implements the following processes:

  • Importing the required modules
  • Hooking into the Burp Extender API to access all of the base classes and useful methods
  • Implementing an easier debugger
  • Creating a message tab and accessing the HTTP request headers
  • Processing the headers and displaying them in the message tab

While decoding the Authorization value isn’t anything too special, the goal is to show the steps to implement the code in order to create custom extensions to suit the needs of any future test.

Much of this code is inspired by an nVisium video tutorial, which involved writing an extension to decode a cookie value. While that walkthrough is great, it does move quickly, so what follows is my attempt at a gentler introduction to writing an extension. Hopefully you will come away understanding how the code works, so you can write your own extension according to your needs.

Initial setup

  • Create a directory to store your extensions – I named mine burp-extensions
  • Download the Jython standalone JAR file – Place into the burp-extensions folder
  • Download exceptions_fix.py  to the burp-extensions folder – This will make debugging much easier
  • Configure Burp to use Jython – Extender > Options > Python Environment > Select file…

Create a new file (make sure you save it in your burp_extensions folder) in your favorite text editor and start importing the required modules:

Importing the required modules


from burp import IBurpExtender               # Required for all extensions
from burp import IMessageEditorTab           # Used to create custom tabs within the Burp HTTP message editors
from burp import IMessageEditorTabFactory    # Provides rendering or editing of HTTP messages, within within the created tab
import base64                                # Required to decode Base64 encoded header value
from exceptions_fix import FixBurpExceptions # Used to make the error messages easier to debug
import sys                                   # Used to write exceptions for exceptions_fix.py debugging

The IBurpExtender module is required for all extensions, while the IMessageEditorTab and IMessageEditorTabFactory will be used to display messages in Burp’s message tab. The base64 module will be used to decode the basic authorization header, and the FixBurpExceptions and sys modules will be used for debugging, which I’ll cover shortly.

Hook into the Burp Extender API to access all of the base classes and useful methods


class BurpExtender(IBurpExtender, IMessageEditorTabFactory):
    ''' Implements IBurpExtender for hook into burp and inherit base classes.
     Implement IMessageEditorTabFactory to access createNewInstance.
    '''
    def registerExtenderCallbacks(self, callbacks):

        # required for debugger: https://github.com/securityMB/burp-exceptions
        sys.stdout = callbacks.getStdout()

        # keep a reference to our callbacks object
        self._callbacks = callbacks

        # obtain an extension helpers object
        # This method is used to obtain an IExtensionHelpers object, which can be used by the extension to perform numerous useful tasks
        self._helpers = callbacks.getHelpers()

        # set our extension name
        callbacks.setExtensionName("Decode Basic Auth")

        # register ourselves as a message editor tab factory
        callbacks.registerMessageEditorTabFactory(self)

        return
        
    def createNewInstance(self, controller, editable):
        ''' Allows us to create a tab in the http tabs. Returns 
        an instance of a class that implements the iMessageEditorTab class
        '''
        return DisplayValues(self, controller, editable)

This class implements IBurpExtender, which is required for all extensions and must be called BurpExtender. Within the required method, registerExtendedCallbacks, the lines self._callbacks and self._helpers assign useful methods from other classes. The callbacks.setExtensionName gives the extension a name, and the callbacks.registerMessageEditorTabFactory is required to implement a new tab. The createNewInstance method is required to create a new HTTP tab. The controller parameter is an IMessageEditorController object, which the new tab can query to retrieve details about the currently displayed message. The editable parameter is a Boolean value that indicates whether the tab is editable or read-only.

Now we can save the file, and load the extension into Burp, which will cause an error.

Load the file: Extender > Extensions > Add > Extension Details > Extension Type: Python > Select file…

Click Next, and it should produce an ugly error.

From this error message, it is difficult for me to figure out exactly what the problem is, but luckily @SecurityMB wrote some magical code to make this readable.

Implement nicer looking error messages

To make the error messages readable, add the following to the code:

In the registerExtenderCallbacks method:

and at the end of the script:

Now the errors should make more sense. To reload the extension, just click the loaded checkbox, unload the extension, and click again to load it.

Now the errors tab should look something like this:

The error specifically mentions that with the createNewInstance method the global name DisplayValues is not defined. This error is of course expected since we have not yet created that class, which we will do now. At this point, your script should look like this:


# Decode the value of Authorization: Basic header
# Author: Jake Miller (@LaconicWolf)

from burp import IBurpExtender               # Required for all extensions
from burp import IMessageEditorTab           # Used to create custom tabs within the Burp HTTP message editors
from burp import IMessageEditorTabFactory    # Provides rendering or editing of HTTP messages, within within the created tab
import base64                                # Required to decode Base64 encoded header value
from exceptions_fix import FixBurpExceptions # Used to make the error messages easier to debug
import sys                                   # Used to write exceptions for exceptions_fix.py debugging


class BurpExtender(IBurpExtender, IMessageEditorTabFactory):
    ''' Implements IBurpExtender for hook into burp and inherit base classes.
     Implement IMessageEditorTabFactory to access createNewInstance.
    '''
    def registerExtenderCallbacks(self, callbacks):

        # required for debugger: https://github.com/securityMB/burp-exceptions
        sys.stdout = callbacks.getStdout()

        # keep a reference to our callbacks object
        self._callbacks = callbacks

        # obtain an extension helpers object
        # This method is used to obtain an IExtensionHelpers object, which can be used by the extension to perform numerous useful tasks
        self._helpers = callbacks.getHelpers()

        # set our extension name
        callbacks.setExtensionName("Decode Basic Auth")

        # register ourselves as a message editor tab factory
        callbacks.registerMessageEditorTabFactory(self)

        return
        
    def createNewInstance(self, controller, editable):
        ''' Allows us to create a tab in the http tabs. Returns 
        an instance of a class that implements the iMessageEditorTab class
        '''
        return DisplayValues(self, controller, editable)

FixBurpExceptions()

Create a message tab and access the HTTP headers

The DisplayValues class uses Burp’s IMessageEditorTab to create the custom tab, and ultimately controls the logic for whether the tab gets displayed and its message. This class requires several methods to be implemented for it to work. Here is the code that will create a tab and display all of the request headers:


class DisplayValues(IMessageEditorTab):
    ''' Creates a message tab, and controls the logic of which portion
    of the HTTP message is processed.
    '''
    def __init__(self, extender, controller, editable):
        ''' Extender is a instance of IBurpExtender class.
        Controller is a instance of the IMessageController class.
        Editable is boolean value which determines if the text editor is editable.
        '''
        self._txtInput = extender._callbacks.createTextEditor()
        self._extender = extender

    def getUiComponent(self):
        ''' Must be invoked before the editor displays the new HTTP message,
        so that the custom tab can indicate whether it should be enabled for
        that message.
        '''
        return self._txtInput.getComponent()
    
    def getTabCaption(self):
        ''' Returns the name of the custom tab
        '''
        return "Decoded Authorization Header"
        
    def isEnabled(self, content, isRequest):
        ''' Determines whether a tab shows up on an HTTP message
        '''
        if isRequest == True:
            requestInfo = self._extender._helpers.analyzeRequest(content)
            headers = requestInfo.getHeaders();
            headers = [header for header in headers]
            self._headers = '\n'.join(headers)
        return isRequest and self._headers
        
    def setMessage(self, content, isRequest):
        ''' Shows the message in the tab if not none
        '''
        if (content is None):
            self._txtInput.setText(None)
            self._txtInput.setEditable(False)
        else:
            self._txtInput.setText(self._headers)
        return

If you are following along, paste this code after the BurpExtender class you just created, but be sure to make the FixBurpExceptions() the last line of the script. The comments explain the methods, so I’m only going to focus on the isEnabled and setMessage methods. For more info on this class, you can look at the IMessageEditorTab in the Burp Extender API.

The isEnabled method accepts message contents and the isRequest parameter (which determines whether the message is a request or a response). If the message is a request, the extender helpers extract the request headers, which for the example purposes I assign to the headers variable via a list comprehension and then assign to self._headers as a string (this needs to be a string). I then return the isRequest and self._headers. In the setMessage method, the content will be received and displayed in a new tab. If you reload this extension and make a request, you should now have a new message tab that is displaying the request headers from the requests you make.

Process the headers and populate the message tab

Now that we have access to the headers, you can go ahead and process the headers as you see fit. In this example, we will look for the Authorization: Basic header, and decode it if it is present. We need to make a few changes to the isEnabled and setMessage methods.

isEnabled:


    def isEnabled(self, content, isRequest):
        ''' Determines whether a tab shows up on an HTTP message
        '''
        if isRequest == True:
            requestInfo = self._extender._helpers.analyzeRequest(content)
            headers = requestInfo.getHeaders();
            authorizationHeader = [header for header in headers if header.find("Authorization: Basic") != -1]
            if authorizationHeader:
                encHeaderValue = authorizationHeader[0].split()[-1]
                try:
                    self._decodedAuthorizationHeader = base64.b64decode(encHeaderValue)
                except Exception as e:
                    print e
                    self._decodedAuthorizationHeader = ""
            else:
                 self._decodedAuthorizationHeader = ""
        return isRequest and self._decodedAuthorizationHeader

The changes we are making looks for the header and decodes it. Otherwise it returns an empty string.

setMessage:


    def setMessage(self, content, isRequest):
        ''' Shows the message in the tab if not none
        '''
        if (content is None):
            self._txtInput.setText(None)
            self._txtInput.setEditable(False)
        else:
            self._txtInput.setText(self._decodedAuthorizationHeader)
        return

The only change made here is displaying the decoded authorization header (self._txtInput.setText(self._decodedAuthorizationHeader)).

Test run

Once you reload the extension, you should have a functional extension which will display a new HTTP message tab if you visit a site requiring Basic Authentication. To test it out, header over to https://httpbin.org/basic-auth/user/passwd and enter in some fake credentials:

Conclusion

Hopefully this walkthrough was a helpful introduction to writing Burp extensions. Below is the full script. If you don’t understand how it works, I urge you to play around with it, putting in print statements in various places so you can experiment. You print statements will appear in the output subtab within the extender tab.

Full script:


# Decode the value of Authorization: Basic header
# Author: Jake Miller (@LaconicWolf)

from burp import IBurpExtender               # Required for all extensions
from burp import IMessageEditorTab           # Used to create custom tabs within the Burp HTTP message editors
from burp import IMessageEditorTabFactory    # Provides rendering or editing of HTTP messages, within within the created tab
import base64                                # Required to decode Base64 encoded header value
from exceptions_fix import FixBurpExceptions # Used to make the error messages easier to debug
import sys                                   # Used to write exceptions for exceptions_fix.py debugging


class BurpExtender(IBurpExtender, IMessageEditorTabFactory):
    ''' Implements IBurpExtender for hook into burp and inherit base classes.
     Implement IMessageEditorTabFactory to access createNewInstance.
    '''
    def registerExtenderCallbacks(self, callbacks):

        # required for debugger: https://github.com/securityMB/burp-exceptions
        sys.stdout = callbacks.getStdout()

        # keep a reference to our callbacks object
        self._callbacks = callbacks

        # obtain an extension helpers object
        # This method is used to obtain an IExtensionHelpers object, which can be used by the extension to perform numerous useful tasks
        self._helpers = callbacks.getHelpers()

        # set our extension name
        callbacks.setExtensionName("Decode Basic Auth")

        # register ourselves as a message editor tab factory
        callbacks.registerMessageEditorTabFactory(self)

        return
        
    def createNewInstance(self, controller, editable):
        ''' Allows us to create a tab in the http tabs. Returns 
        an instance of a class that implements the iMessageEditorTab class
        '''
        return DisplayValues(self, controller, editable)

FixBurpExceptions()


class DisplayValues(IMessageEditorTab):
    ''' Creates a message tab, and controls the logic of which portion
    of the HTTP message is processed.
    '''
    def __init__(self, extender, controller, editable):
        ''' Extender is a instance of IBurpExtender class.
        Controller is a instance of the IMessageController class.
        Editable is boolean value which determines if the text editor is editable.
        '''
        self._txtInput = extender._callbacks.createTextEditor()
        self._extender = extender

    def getUiComponent(self):
        ''' Must be invoked before the editor displays the new HTTP message,
        so that the custom tab can indicate whether it should be enabled for
        that message.
        '''
        return self._txtInput.getComponent()
    
    def getTabCaption(self):
        ''' Returns the name of the custom tab
        '''
        return "Decoded Authorization Header"
        
    def isEnabled(self, content, isRequest):
        ''' Determines whether a tab shows up on an HTTP message
        '''
        if isRequest == True:
            requestInfo = self._extender._helpers.analyzeRequest(content)
            headers = requestInfo.getHeaders();
            authorizationHeader = [header for header in headers if header.find("Authorization: Basic") != -1]
            if authorizationHeader:
                encHeaderValue = authorizationHeader[0].split()[-1]
                try:
                    self._decodedAuthorizationHeader = base64.b64decode(encHeaderValue)
                except Exception as e:
                    print e
                    self._decodedAuthorizationHeader = ""
            else:
                 self._decodedAuthorizationHeader = ""
        return isRequest and self._decodedAuthorizationHeader
        
    def setMessage(self, content, isRequest):
        ''' Shows the message in the tab if not none
        '''
        if (content is None):
            self._txtInput.setText(None)
            self._txtInput.setEditable(False)
        else:
            self._txtInput.setText(self._decodedAuthorizationHeader)
        return

Leave a Reply

Your email address will not be published. Required fields are marked *