Working with the Burp Extension Framework

Some friends[1][2] of mine talked about their Burp extension framework at bSides a while back, so I wanted to give it a spin. It uses David Robert’s python binding for Burp and it seems to work very well.

One problem with extending Burp is that it can get troublesome trying to run various extensions at once, especially if you get them from different authors; it’s not guaranteed extensions won’t conflict and step all over each other (guaranteed they kinda will). This framework, however, let’s you write your extensions which will be organized into a separate window when Burp is run, allowing you to write event driven extensions and user others’ as well by placing them in your /extensions/ directory.

To follow along, go ahead and grab the framework. Once downloaded, you’ll need to update the burp_extended.sh or burp_extended.bat with the name / version / path of Burp you’ll be using. By default, the script refers to it as burp.jar. You’re pretty much all set to try out the framework if you want, or change directories into Lib/extensions/ to begin writing your own.

For my try-out extension, the problem I want to solve is finding out if (1) there is a different version of the target application based on whether I access it from a mobile device and (2) how different those versions might be from one another. The idea is that different site / application versions may have been developed by different devs (or possibly a third party) to be served based on the type of device making the request to the server. The level of security for these various versions may not be uniform by any means; maybe one dev team was uneducated with respect to app sec. This extension will receive a variable number of URLs that I will highlight in the Target tab, make a number of requests for each one with N User Agent strings, and give us some information about the differences so that we can make a decision about whether we should take a closer look.

Extensions live in the /Lib/extensions directory, so this is where we’ll create our MobileVersionChecker.py. As stated in the framework documentation, we’ll start off by importing the libraries we’ll need to interact with Burp as well as the extension framework and creating the basic classes and functions.

from burp import IMenuItemHandler
from burp import IBurpExtenderCallbacks
from Extension import Extension

class MobileVersionChecker(Extension):
        INFO = {
                        'Name'          :       'Mobile Version Checker',
                        'Usage'         :       '…usage info here...',
                        'Author'        :       'hackeroutfit.com'
        }

        def finishedLaunching(self):
                self.getCallBacks().registerMenuItem("Extension: Mobile Version Checker", MenuHandler(self))



class MenuHandler(IMenuItemHandler):

        def __init__(self, parent):
                self.parent = parent
                for key in ["Name", "Usage", "Author"]:
                        self.parent.printLogTab("%s: %s\n" % (key, self.parent.INFO[key]))
                self.parent.printLogTab("-"*20 + "\n")

        def menuItemClicked(self, menuItemCaption, messageInfo):
                self.parent.printLogTab("I do nothing!\n")


Now we can test this out in Burp. If we start the burp_extender.sh (or .bat) and we see a GUI window with a graphic of a girl throwing up, that means our extension had no errors. Else, if only the Burp window comes up without the extension framework window, take a look either in the Alerts tab in Burp or in your terminal for details of what the error may be. If we populate the Target window a little and right click on a URL, our extension shows up in the context menu. Clicking on it executes our printLogTab(“I do nothing\n”).

Time to add to some meaningful code to our primary menuItemClicked function. If we make code changes to our extension, they won’t be testable until after we restart Burp. [Might be a good feature request to be able to ‘refresh’ extensions]. When you close Burp, close the main window and not the extension window, else none of the cleanup or code in any applicationClosing function you may have written will get run.

Our pseudo code for the meat of the extension will be to:
1) Define a list of User Agents to test with (should be in something easy to manage and change like a file, but for this example we’ll just hard code).
2) Get the list URL(s) that our extension is called with.
3) For each URL, for each User Agent, issue a web request with that User Agent.
4) Store something about the response we can use to compare with, we’ll choose the length.

Originally, I had planned to send each response to the Burp Comparer. Unfortunately, that’s not supported yet (even though you can send to intruder or repeater) so we’ll make due in this example extension with the inspection of the response length to determine if it’s “different enough”.

Since we’ll be making HTTP requests with each User Agent, we may as well use the IBurpExtenderCallbacks makeHttpRequest method to do this. As the documentation indicates, this method requires at least 4 arguments: the (string) hostname, (int) port, (boolean) whether to use HTTPS, and (byte array) request. We’ll need to do 2 things: (1) to access the parent’s methods (IBurpExtenderCallbacks) we’ll create a local variable called mCallBacks set to self.parent.getCallBacks(). (2) For each URL passed to the extension, we’ll need to grab the hostname, port, and whether SSL is being used since web servers can be on any port. The list of URLs is passed in the messageInfo parameter, so we’ll store the length to iterate through later.

        def menuItemClicked(self, menuItemCaption, messageInfo):
                msglen = len(messageInfo)
                mCallBacks = self.parent.getCallBacks()


As I mentioned, you should read the User Agent (I’ll sometimes abbreviate as UA) strings from something like a file, or anything easier to manage, but for example purposes I’m just going to hard code them. The first UA is for a desktop browser, which we’ll use as our “control” to compare against whatever we get from the mobile device UAs. We then have a non-comprehensive list including an iPhone, iPad, Droid, and 2 Windows phone UA strings.

        def menuItemClicked(self, menuItemCaption, messageInfo):

                msglen = len(messageInfo)
                mCallBacks = self.parent.getCallBacks()

	    # We will issue a web request using the following user agents.  The first UA is for a desktop browser we'll use as a control value
                UserAgents = [
                        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:16.0) Gecko/20100101 Firefox/16.0"
                        "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
                        "Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
                        "Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
                        "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; SAMSUNG; SGH-i917)",
                        "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)"]

                httpRequestString = "GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n"

While we’re at it, we’ve also defined our standard GET request we’ll use. The URI, hostname, and User-Agent are format string placeholders since they’ll be dynamic based on whatever information we get from the list of URLs that call this extension as well as whatever User Agent we’re currently iterating through. We then start our iteration over the URLs and get the information we’ll need for our HTTP requests. The doc covers all of the methods you can call against the IHttpRequestResponse interface beyond those shown below.


        def menuItemClicked(self, menuItemCaption, messageInfo):

	    …
  	    …

                for m in range(msglen):
                        port = int(messageInfo[m].getPort())
                        host = messageInfo[m].getHost()
                        ssl = True if messageInfo[m].getProtocol() == 'https' else False

We can also build the URI by looking at the value of .getPath() and .getQuery():


                        if messageInfo[m].getUrl().getPath() is not None:
                          resource = messageInfo[m].getUrl().getPath()
                        else:
                          resource = "/"
                        if messageInfo[m].getUrl().getQuery() is not None:
                          resource = resource + "?" + messageInfo[m].getUrl().getQuery()

We now have enough information to build our HTTP requests. However, note that the documentation indicates the request body needs to be a byte array, so let’s add in a function that’ll perform the conversion for us; provide a string, get the corresponding byte array. The code in the GoogleSiteIndex extension does something similar:

import StringIO

…
…
…

        def GetRequestBytes(self, request):
                requestString = StringIO()
                requestString.write(request)
                rbytes = map(lambda x: ord(x), requestString.getvalue())
                return rbytes

Now we can issue our requests for each User Agent and print the length of the response to the Log window:

                for m in range(msglen):

                        port = int(messageInfo[m].getPort())
                        host = messageInfo[m].getHost()
                        ssl = True if messageInfo[m].getProtocol() == 'https' else False
                        
                        if messageInfo[m].getUrl().getPath() is not None:
                          resource = messageInfo[m].getUrl().getPath()
                        else:
                          resource = "/"
                        if messageInfo[m].getUrl().getQuery() is not None:
                          resource = resource + "?" + messageInfo[m].getUrl().getQuery()
                        
                        self.parent.printLogTab("Making requests\n")
                        for ua in UserAgents:
                                httpRequest = self.GetRequestBytes(httpRequestString % (resource, host, ua))
                                httpResponse = mCallBacks.makeHttpRequest(host, port, ssl, httpRequest)
                                self.parent.printLogTab(ua + "\n")
                                self.parent.printLogTab("Response Length: ")
                                self.parent.printLogTab(str(len(httpResponse)) + "\n")

The result:

At this point, you could call the doActiveScan() method on each request, but it’d be nice to have a good way to compare the responses first. In the mean time, we can accomplish that goal without using any extensions with an overly manual process using Intruder. For every URL you’re interested in, send it to intruder, specify position boundaries with the § symbol around the User Agent string, load your User Agent list from a text file, and run the attack. You can then send all of the responses to the Comparer to inspect the differences against your baseline UA (or each other). This approach may be viable for a small number of URLs and lets you leverage the power of Comparer, but gets unruly quickly if you want to check many pages on many sites during a pentest. Hopefully as extension support continues to increase, the extensions we can build will be more powerful.

The code for the extension in this blog post is here: MobileVersionChecker.py.

Advertisements