Every so often a web application comes along where a bit of customization is required in your testing strategy to test it properly. The Burp Suite proxy tool is probably one of the most used tools by penetration testers to test web applications. When a situation comes along where its normal customization menu options isn’t sufficient (e.g. using Burp Macros) we can include a custom written Burp Extension to do what we want.
An application I recently tested used an Authorization token which was unique and was checked in every request. Firing off a Burp scan resulted in hundreds of Alerts in Burp in the format:
[120] Authentication Failure from <TARGET.COM>
The number of alerts were incrementing with each scan request. Not so good! Further analysis sending the request to Burp Repeater revealed the response:
HTTP/1.1 401 Invalid token
Stepping through the HTTP requests revealed that the Authorization: Bearer header was changing with every request and getting renewed with each request. This meant that any form of automated testing would be very painful unless we could resolve the situation.
After trying a number of options with the “Session Handling Rules” in the “Project Options” tab it became apparent that I would need to combine a Burp macro with a custom Burp Extension as follows:
1) A Burp Macro would grab a new token with each request from a suitable login sequence into the application.
2) The Burp Extension would look at each request and replace the token value with the token from the macro.
I was fortunate that I’m not the only tester to have faced this challenge, so many thanks to Xenofon Vassilakopoulos and Rory McCune and their blogs for pointing me in the right direction.
I chose to write the extension in Python so this blog is a step-by-step process in getting this problem working in Burp using a Burp Macro and a Burp Extension
Burp Macro Configuration
Burp macros allow you to automatically modify the HTTP request to the application by looking for a set of parameters and making changes according to the macro rule before submitting it to the application. To get a valid authorization token from the application I performed the following:
◦ Start After Expresion: "token":"
◦ End at delimiter: ","rememberMe"
◦ This meant the followiing new token would be retrieved (obviously changing every time):
{"token":"eyJhbG<TRUNCATED>………………...bozA","rememberMe"…..
◦ This was saved as “Parameter Name: token”. The naming of the token was important as it would be used in the custom python script.
After testing the macro and ensuring a 200 OK HTTP error code was returned, the next step involved creating a BURP extension that would use this new token retrieved by the macro and replace it into the “Authorization” header in each request.
Basic Extension Tutorial
This short tutorial will cover creating the extension using Jython (Ruby and Java are other options).
To set up Burp to create Burp extensions with python perform the following:
We can now start creating our extension. To make sure we have our Burp setup correctly, we can write a basic extension and run in. “Hello world” seems appropriate. Using your text editor safe the following into a file helloworld.py.
We need to implement the Burp Extender APIs. All extensions need the IBurpExtender API (See the API tab in Burp for details). This is written in Java as a Java class and we need to convert to use as a Jython class. Reviewing the API in Burp for IburpExtender it has a registerExtenderCallbacks method. We can translate this to Jython as:
from burp import IBurpExtender
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(self, callbacks):
return
Saving it as helloworld.py this is the very basic template to build on. To import this extension into Burp go to
from burp import IBurpExtender
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(self, callbacks):
callbacks.setExtensionName("Hello World")
return
from burp import IBurpExtender
class BurpExtender(IBurpExtender):
def registerExtenderCallbacks(self, callbacks):
callbacks.setExtensionName("Hello World")
callbacks.printOutput("Hello There!")
return
Now we have a basic extension working, lets get back to our task in hand to generating an extension for our problem.
Automating Token Generation
To link a burp extension with a session action we can use the ISessionHandlingAction
API that uses the performAction method. It takes a Burp request and the result of any macro as its two inputs to perform the required action. So with that said, here’s the final script:
import json
import datetime
from java.io import PrintWriter
from burp import IBurpExtender, IBurpExtenderCallbacks, ISessionHandlingAction
class BurpExtender(IBurpExtender, ISessionHandlingAction):
NAME = "Bearer Authorization Token"
def registerExtenderCallbacks(self, callbacks):
# Make errors more readable and required for debugger burp-exceptions
sys.stdout = callbacks.getStdout()
# reference our callback objects
self.callbacks = callbacks
# obtain an extension helpers object
self.helpers = callbacks.getHelpers()
# Set the name of the extension fron NAME variable above
callbacks.setExtensionName(self.NAME)
# Register the session
self.callbacks.registerSessionHandlingAction(self)
# Use PrintWriter for all output
self.stdout = PrintWriter(callbacks.getStdout(), True)
#self.stderr = PrintWriter(callbacks.getStdout(), True)
self.stdout.println("Bearer Authorization Token \n")
self.stdout.println('starting at time : {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()))
self.stdout.println("-----------------------------------------------------------------\n\n")
return
def getActionName(self):
return self.NAME
def performAction(self, currentRequest, macroItems):
request_info = self.helpers.analyzeRequest(currentRequest)
#Extract the Bearer token from the macro response
macro_response_info = self.helpers.analyzeResponse(macroItems[0].getResponse())
macro_msg = macroItems[0].getResponse()
resp_body = macro_msg[macro_response_info.getBodyOffset():]
macro_body_string = self.helpers.bytesToString(resp_body)
bearer_token = json.loads(macro_body_string)
# The "token" is the name of the parameter rule configured in the macro
bearer = bearer_token["token"]
headers = request_info.getHeaders()
req_body = currentRequest.getRequest()[request_info.getBodyOffset():]
resp_headers = macro_response_info.getHeaders()
headers = request_info.getHeaders()
auth_to_delete = ''
# Search for the "Authorization:Bearer" in response to delete
for head in headers:
if 'Authorization: Bearer ' in head:
auth_to_delete = head
try:
headers.remove(auth_to_delete)
except:
pass
# Add new bearer token from macro
headers.add('Authorization: Bearer ' + bearer)
self.stdout.println('Header Checked at time : {:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now()))
self.stdout.println("-----------------------------------------------------------------" )
self.stdout.println("Adding new header - Authorization bearer: " + bearer)
self.stdout.println("-----------------------------------------------------------------")
self.stdout.println("Geting authorized..done\n\n")
# Build request with bypass headers
message = self.helpers.buildHttpMessage(headers, req_body)
# Update Request with New Header
currentRequest.setRequest(message)
return
FixBurpExceptions()
It essentially takes the result of the macro (the “token” variable) which is the new Authorization Bearer to include in every new request.
Getting the Macro and Extension Working Together
Bearer Authorization Token
starting at time : 2018-08-17 12:01:36
Header Checked at time : 2018-08-17 12:09:40
-----------------------------------------------------------------
Adding new header - Authorization bearer: eyJhbGci….<TRUNCATED>…….Kl6bSmicRbmsIkZ6KfKBY__mrw
-----------------------------------------------------------------
Geting authorized..done
Although it took a while to get working, it’s one of those techniques that are going to pop up more often and worth getting to know. Hopefully you found this blog post helpful, if you'd like to know more about the penetration testing services that we offer then click the link below!