SBN

A Step-by-Step Guide to Writing Extensions for API Pentesting in BurpSuite

API pentesting is an integral part of any security assessment, and BurpSuite can be a powerful tool for testing APIs. But did you know that you can extend the capabilities of BurpSuite even further by writing your own extensions?

This article will provide a step-by-step guide to writing custom burp suite extensions to help with API pentesting. We’ll cover everything from setting up your environment to working with the extension interfaces so that you have all the tools necessary to build an effective extension explicitly tailored to your needs.

So let’s get started!

Getting set up

So you can write custom BurpSuite extensions in several languages, including Java, Python, and Ruby. Personally, I am a big fan of writing them in Python as it is much simpler and easier to set up and get working than trying to set up a Java development environment. All you really need is a code editor (hell, even vi will do) and Jython.

What is Jython?

Jython is an implementation of the Python programming language designed to run on the Java platform. This is needed because BurpSuite is written in Java, so your extension will need to leverage Jython to interface with the APIs PortSwigger exposes to you within BurpSuite.

Setting up Jython

Setting up Jython to work with BurpSuite is relatively trivial.

Here are the steps:

  1. Download the Jython standalone jar. Place it somewhere safe and easy to access. I put mine in a burp folder in my home directory.
  2. Open up BurpSuite
  3. Click the Extensions tab
  4. Click the Options sub-tab
  5. Under Python Environment, click the Select file … button
  6. Browse for your Jython standalone jar, and then click Open

Writing the most basic BurpSuite extension in Python

So to get started, we need to understand the API PortSwigger makes available to us. Right in BurpSuite, we can find all the exposed APIs documented under the APIs sub-tab in Extensions. You can also find more detailed documentation online here.

So let’s write our first “Hello World” extension, and then I’ll walk you through it. Open your favorite code editor and create a new python file with the .py extension, like hello_world.py.

Now put this code in there and save the file:

from burp import IBurpExtender

class BurpExtender(IBurpExtender):
	def registerExtenderCallbacks(self, callbacks):
		self._callbacks = callbacks
		self._helpers = callbacks.getHelpers()
		callbacks.setExtensionName("Hello World")
		print( "Hello World extension loaded." )
		return

OK, so let’s walk through this code.

  • The first thing we do is import IBurpExtender from the burp module. All Burp extensions MUST implement this interface.
  • We then create our extension class and implement IBurpExtender.
  • Within that extension class, we need to implement a method called registerExtenderCallbacks(), which is exposed through the IBurpExtender interface. This method is invoked when the extension is loaded. It registers an instance of the IBurpExtenderCallbacks interface, providing methods that the extension may invoke to perform various actions. Don’t worry if that seems confusing… I’ll clear it up later we need to take advantage of it.
  • Inside the registerExtenderCallbacks() method, we create internal references to the callbacks and their helpers. While it’s not actually needed for this “Hello World” example, any real-world extension will ultimately rely on these when they need to do real work in other methods.
  • We set the extension name.
  • We then print a string that will end up in the output screen of the Burp Extensions loader. This is useful to ensure the extension loads properly and gives us immediate feedback if something isn’t right.

Alright, let’s load this extension and see what happens!

Loading your first custom extension

Now that you have written your first extension let’s load it in Burp and see what happens:

  1. Click on the Extensions tab
  2. Click on the Installed sub-tab
  3. Under the Burp Extensions section, click the Add button
  4. Under Extension Details, select the extension type of Python and then click the Select file … button
  5. Browse for the extension you wrote in Python, select it, and then click Open.
  6. Click Next.
  7. If everything works, you will see your text in the Output tab. If something goes wrong, you can check the Errors tab.
  8. Click Close.

At this point, you have the first custom extension you’ve ever written running in BurpSuite. Congratulations!

Building a valuable extension for API pentesting

Now that you have your first extension done, let’s build something together that is useful when hacking APIs.

If you are a follower of my blog, you might recall my article about attacking predictable GUIDs. If you still need to read that, I recommend doing it. Don’t worry… I’ll wait…

OK, so we know using v1 UUIDs could be a useful attack vector in the proper context. It all starts by discovering such UUIDs in the first place. Instead of manually looking for these, why not let BurpSuite do it for us while conducting its passive scans?

And that’s precisely what we are going to write together right now.

Setting up UUID Checker extension

To start, copy your hello_world.py extension to uuid_checker.py and open it in your favorite code editor.

Our goal for this extension will be to scan the body of any HTTP response and look for patterns that match a v1 UUID. If we detect that, we want to create an issue and register it under “Issue Activity” in the Dashboard of BurpSuite.

Since we want to detect this during a passive scan, we will need to instruct BurpSuite that we want to be notified when a scanner check occurs. This can be done by implementing the IScannerCheck interface.

To be able to write to the Issue Activity report view, we will need to implement our own class based on the IScanIssue interface.

Finally, to actually look for v1 UUID patterns, we will leverage Python’s regex module.

Got all that? OK, let’s update the extension code accordingly.

Update the imports

Change the imports at the top of the file to this:

from burp import IBurpExtender, IScannerCheck, IScanIssue
import re

Update the class implementation

Change your extension class to implement IScannerCheck:

class BurpExtender(IBurpExtender, IScannerCheck):

Now that you have implemented IScannerCheck, you will need to implement the following additional methods within our extension class:

def doPassiveScan(self, baseRequestResponse):
	pass
	
def doActiveScan(self, baseRequestResponse, insertionPoint):
	pass

def consolidateDuplicateIssues(self, existingIssue, newIssue):
	if existingIssue.getIssueName() == newIssue.getIssueName():
		return -1
	return 0

Later on, we will do most of our work in doPassiveScan(). Just set up the scaffolding for now.

Update registration for the scanner

Now that we want to instruct Burp to let us know when a scanner check is going on, we have to update registerExtenderCallbacks(). Here is what that should look like now:

def registerExtenderCallbacks(self, callbacks):
	self._callbacks = callbacks
	self._helpers = callbacks.getHelpers()
	callbacks.setExtensionName("UUID Inspector")
	callbacks.registerScannerCheck(self)
	callbacks.issueAlert("Registered UUID Inspector...")
	print( "UUID Inspector extension loaded." )
	return

Notice a few changes:

  • Same basic scaffolding as Hello World, but with a new extension name.
  • We registered for the scanner check, which will tell Burp to call doPassiveScan() when the time is right.
  • We issued an alert to the dashboard that the extension loaded. This gets written to the Event Log; it’s helpful to know it loaded correctly whenever Burp restarts. We do this since we are not implementing any UI elements in this extension, like our own custom tabs or menu items.

Create a helper method for parsing HTTP responses

So the default response object that comes from Burp is done through a more complex byte[] array that is more challenging to work with in Python. So let’s create a helper method that extracts the header and body and return it in something we can more easily parse:

def getResponseHeadersAndBody(self, content):
	response = content.getResponse()
	response_data = self._helpers.analyzeResponse(response)
	headers = list(response_data.getHeaders())
	body = response[response_data.getBodyOffset():].tostring()
	return headers, body

What you can notice in this code is that this is the first time we are leveraging the self._helpers to analyze the response. We then take the extracted data, parse out a list of headers, find the offset of the data where the body exists in the byte array, and convert that to a string.

We then return the headers and body so we can process them at will.

This is a useful reusable helper that you will probably leverage quite a bit in your extensions in Python.

Create a method to detect valid v1 UUIDs

So detecting v1 UUIDs is relatively easy. We basically want to look for a pattern of characters that match the following conditions:

  • A group of 8 hexadecimal characters
  • Followed by a dash
  • Followed by a group of 4 hexadecimal characters
  • Followed by a dash
  • Followed by a 1 (which represents a v1 UUID)
  • Followed by 3 additional hexadecimal characters
  • Followed by a dash
  • Followed by 4 additional hexadecimal characters
  • Followed by a dash
  • Followed by 12 additional hexadecimal characters

Here is a method to do that using Python’s regex module to search for such a pattern:

def valid_v1_uuid(self, uuid):
	# To look for v1 UUID we check if a valid pattern exists for a UUID 
	# where the number after the second dash is a '1'.
	pattern = '[a-f0-9]{8}-?[a-f0-9]{4}-?1[a-f0-9]{3}-?[a-f0-9]{4}-?[a-f0-9]{12}'
	match = re.search(pattern, uuid, flags=re.IGNORECASE)
	return bool(match)

TIP: Notice the use of re.IGNORECASE. This allows us to check for UUIDs ignoring if it’s upper or lowercase. Another way to accomplish that would be to use a pattern like “[a-fA-F0-9]”. This way is just more human-readable for those who don’t work with regex often.

Create a new class based on IScanIssue

When we want to be informed that we detected a valid v1 UUID, we need to insert a record under Issue Activity on the Dashboard. We can build our own class based on IScanIssue that accomplishes that:

class UUIDScanIssue(IScanIssue):
    def __init__(self, httpService, url, httpMessages):
        self._httpService = httpService
        self._url = url
        self._httpMessages = httpMessages

    def getUrl(self):
        return self._url

    def getIssueName(self):
        return "Possible v1 UUID detected"

    def getIssueType(self):
        return 0

    def getSeverity(self):
        return "Medium"

    def getConfidence(self):
        return "Certain"

    def getIssueBackground(self):
        pass

    def getRemediationBackground(self):
        pass

    def getIssueDetail(self):
        return "The response contains a potential v1 UUID, which is prone to attack. See: https://danaepp.com/attacking-predictable-guids-when-hacking-apis"

    def getRemediationDetail(self):
        pass

    def getHttpMessages(self):
         return self._httpMessages

    def getHttpService(self):
        return self._httpService

Putting it all together in doPassiveScan()

Now that we have implemented all the extra bits let’s do some work to find these valid UUIDs.

Let me show you the implementation of the method, and then I’ll explain it:

def doPassiveScan(self, baseRequestResponse):
	issues = []
	headers, body = self.getResponseHeadersAndBody(baseRequestResponse)
        
	# Test the body for GUIDs
	if self.valid_v1_uuid(body):
		self._callbacks.issueAlert("Found potential valid v1 UUID in body. Check Issue Activity!")
			
		# report the issue
		issues.append( UUIDScanIssue(
			baseRequestResponse.getHttpService(),
			self._helpers.analyzeRequest(baseRequestResponse).getUrl(),
			[baseRequestResponse]
		))

	if not issues:
		issues = None 

	return issues

So here is what the code does:

  • It extracts the headers and body from the response. While we are only testing the body here, scanning all the headers for possible UUIDs would be a good addition. But that’s an exercise I will leave to you to practice.
  • It takes the body and scans for a valid v1 UUID pattern. If it finds one, it writes an alert to the event log. It also appends a UUIDScanIssue object to the issues list, which will be pushed to the Scanner Issue Activity log on return from this method call.

The full extension code

Breaking up the details into sections was helpful as we walked through it. I hope you’ve followed along and updated your extension accordingly.

Just in case though, here is the complete UUID Inspector code:

from burp import IBurpExtender, IScannerCheck, IScanIssue
import re

class BurpExtender(IBurpExtender, IScannerCheck):
	def registerExtenderCallbacks(self, callbacks):
		self._callbacks = callbacks
		self._helpers = callbacks.getHelpers()
		callbacks.setExtensionName("UUID Inspector")
		callbacks.registerScannerCheck(self)
		callbacks.issueAlert("Registered UUID Inspector...")
		print( "UUID Inspector extension loaded." )
		return

	def getResponseHeadersAndBody(self, content):
		response = content.getResponse()
		response_data = self._helpers.analyzeResponse(response)
		headers = list(response_data.getHeaders())
		body = response[response_data.getBodyOffset():].tostring()
		return headers, body

	def valid_v1_uuid(self, uuid):
		# To look for v1 UUID we check if a valid pattern exists for a UUID 
		# where the number after the second dash is a '1'.
		pattern = '[a-f0-9]{8}-?[a-f0-9]{4}-?1[a-f0-9]{3}-?[a-f0-9]{4}-?[a-f0-9]{12}'
		match = re.search(pattern, uuid, flags=re.IGNORECASE)
		return bool(match)

	def doPassiveScan(self, baseRequestResponse):
		issues = []
		headers, body = self.getResponseHeadersAndBody(baseRequestResponse)
        
		# Test the body for GUIDs
		if self.valid_v1_uuid(body):
			self._callbacks.issueAlert("Found potential valid v1 UUID in body. Check Issue Activity!")
			
			# report the issue
			issues.append( UUIDScanIssue(
				baseRequestResponse.getHttpService(),
				self._helpers.analyzeRequest(baseRequestResponse).getUrl(),
				[baseRequestResponse]
			))

		if not issues:
			issues = None 

		return issues
	
	def doActiveScan(self, baseRequestResponse, insertionPoint):
		pass

	def consolidateDuplicateIssues(self, existingIssue, newIssue):
		if existingIssue.getIssueName() == newIssue.getIssueName():
			return -1
		return 0
		
class UUIDScanIssue(IScanIssue):
    def __init__(self, httpService, url, httpMessages):
        self._httpService = httpService
        self._url = url
        self._httpMessages = httpMessages

    def getUrl(self):
        return self._url

    def getIssueName(self):
        return "Possible v1 UUID detected"

    def getIssueType(self):
        return 0

    def getSeverity(self):
        return "Medium"

    def getConfidence(self):
        return "Certain"

    def getIssueBackground(self):
        pass

    def getRemediationBackground(self):
        pass

    def getIssueDetail(self):
        return "The response contains a potential v1 UUID, which is prone to attack. See: https://danaepp.com/attacking-predictable-guids-when-hacking-apis"

    def getRemediationDetail(self):
        pass

    def getHttpMessages(self):
         return self._httpMessages

    def getHttpService(self):
        return self._httpService

Seeing your extension in action

Alright. You’ve got your extension to help you hunt for v1 UUIDs ready to go. Let’s load it up in BurpSuite, and give it a try.

  1. Click on the Extensions tab
  2. Click on the Installed sub-tab
  3. Under the Burp Extensions section, click the Add button
  4. Under Extension Details, select the extension type of Python and then click the Select file … button
  5. Browse for the extension you wrote in Python (uuid_checker.py), select it, and then click Open.
  6. Click Next.
  7. If everything works, you will see your text in the Output tab. If something goes wrong, you can check the Errors tab.
  8. Click Close.

If everything works, you should see an alert in the event log on the Dashboard of BurpSuite:

Now using a browser that is proxying through Burp, go somewhere that a v1 UUID exists. Here is a simple example.

You should quickly see an Issue pushed into the Issue Activity on the Dashboard. You will also find it under Issues in the Site Map if you click on the Target tab and then click on the Site map sub-tab. It should look something like this:

And there you go. You have written your first custom extension to help you during your API pentesting. You literally have to do nothing… when a v1 UUID is detected in the body of a response, it will alert you. You can then click on the issue, select the Response tab and look through the data for the potential vulnerable UUID and determine if it’s exploitable.

Writing more complex extensions

Now that you have a great foundation and have built your first BurpSuite extension in Python, don’t stop there. There is so much more you can do. A great starting place is to check out PortSwigger’s GitHub repository and filter for Python. You can find a great deal of example code there. If you search the repo for “example,” you will find several key examples with code in Java, Python, and Ruby.

Conclusion

Writing BurpSuite extensions in Python can be a great way to help automate some of your API pentesting tasks. This article provided step-by-step instructions on how to write your own extension and even gave you an example of a simple extension that can look for predictable GUIDs that you might be able to attack.

I hope you found this helpful and a good starting point for writing your own extensions. But it’s only the beginning. Imagine the possibilities!

Want more resources like this to help improve your API hacking tradecraft? Then make sure you download my free ebook on The Ultimate Guide of API Hacking Resources.

Hack hard!

The post A Step-by-Step Guide to Writing Extensions for API Pentesting in BurpSuite appeared first on Dana Epp's Blog.

*** This is a Security Bloggers Network syndicated blog from Dana Epp's Blog authored by Dana Epp. Read the original post at: https://danaepp.com/a-step-by-step-guide-to-writing-extensions-for-api-pentesting-in-burpsuite