Recently we were assigned with a task to perform a penetration test on web application, where there was a limitation on how much time the session would be valid, enforced through authorization token. In order to bypass this restriction and allow burp suite to complete scanning and intruder tasks without failing, we had to create our own Burp extender class. Specifically, the method created, had to inspect every request for the tag ‘Authorization: Bearer ‘ in its headers and after that delete it and replace it with a new one in order for the current session to be valid. After the login request, the generated token would be included in the JSON keyword called ‘bearerToken’ inside the response body. The concept was to extract the bearer token from the response and replace it with the new existing token on the new request. The following request performs a login to the Web Application which enforces the creation of a new token in the response.
POST /some_path/rest/public/security/login HTTP/1.1
Host: ##############
Connection: close
Content-Length: 52
Accept: application/json, text/plain, */*
Origin: ################
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36
Content-Type: application/json;charset=UTF-8
Referer: ################
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8
{"userId":"pentestcommon","password":"#######4321$"}
The above request issues the following response where the bearer Token can be found in the JSON keyword ‘bearerToken’
HTTP/1.1 200 OK
Date: Thu, 01 Sep 2016 10:34:33 GMT
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block;
X-Content-Type-Options: nosniff
Expires: 0
Cache-Control: private, no-cache, no-store, must-revalidate
Pragma: no-cache
Access-Control-Allow-Origin: ################
Access-Control-Allow-Credentials: true
Content-Type: application/json
Vary: Accept-Encoding
Set-Cookie: rememberMe=deleteMe; Path=/some_path; Max-Age=0; Expires=Wed, 31-Aug-2016 10:34:33 GMT;HttpOnly;Secure
Content-Length: 5346
Connection: close
{"bearerToken":"SJHJSHDDHDLJDWIDJWDOMKSNMKSHSKLSNFLSKJKSJSDWOWOSKSJKKSKJ","status":{"id":"OK","description":"OK"},"email":"[email protected]","fullname":"Pen Test Admin","version":"1.14.10","mId":"374859230","bId":null,"termId":null,
[....]
The above request – response is used from a macro which is explained later in this document, to generate a new Token in every scanner request to the application. Apparently, to do this for every request is time consuming. The best way to achieve this solution without having overhead to the scanning task is to check whether the response headers have ‘HTTP/1.1 401 Unauthorized’ header. In such case, a customized script must change the invalid Token.
Invalid Request
GET /some_path/rest/product/all HTTP/1.1
Host: some_website.com
Connection: close
Accept: application/json, text/plain, */*
Authorization: Bearer LFSJBVNSMVOJMOPECMOEPER39I3FLKNFOEIFJEOD0EFOEKFE49I3OKE
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
Referer: https://some_website.com/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
The above request is invalid and that can be realized from the response that has HTTP/1.1 401 Unauthorized Header as shown below
Unauthorized Response
HTTP/1.1 401 Unauthorized
Date: Mon, 29 Aug 2016 07:51:53 GMT
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block;
X-Content-Type-Options: nosniff
WWW-Authenticate: Bearer realm="########"
Vary: Accept-Encoding
Set-Cookie: rememberMe=deleteMe; Path=/gmp-web; Max-Age=0; Expires=Sun, 28-Aug-2016 07:51:53 GMT;HttpOnly;Secure
Content-Length: 0
Connection: close
Content-Type: text/plain; charset=UTF-8
The above request includes a Token that has expired due to the time limitation of the application, while handling the token validity. The above Token has to be replaced with the new one which is generated from the macro. See the following request with the new Bearer Token.
Valid Request
GET /some_path/rest/product/all HTTP/1.1
Host: some_website.com
Connection: close
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
Referer: https://some_website.com/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8
Authorization: Bearer SJHJSHDDHDLJDWIDJWDOMKSNMKSHSKLSNFLSKJKSJSDWOWOSKSJKKSKJ
For the above replacement to happen, a macro has been created on Burp Suite. The macro ran before the python script in order to generate the new Token by initiating a new login to the application. After the preceding procedure was done, the upcoming requests and responses had received the new authorized token. This procedure validates the requests and the scanner executes the tests properly. In order to accomplish this task, we had to use ISessionActionHandling API which introduces the method performAction that handles session requests and responses when triggered from Burp Suite macros.
The following code snippet demonstrates how the performAction function handles and analyzes requests and responses. The argument macroItems in particular, handles the responses when the requests are triggered from a macro object on Burp Suite. The following python script is used to manipulate the array which carries the response body, while changing it into a string format as well as getting the contents offset location in the response. From there, the Bearer token can be parsed and extracted. The script below checks if the header ‘Authorization: Bearer ‘ already exists in the request and if it does. It replaces it with the new one. Afterwards the new header will be overwritten on the current request to validate the request on scanner or any other related Burp Suite tool.
performAction
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)
bearer = bearer_token["bearerToken"]
headers = request_info.getHeaders()
req_body = currentRequest.getRequest()[request_info.getBodyOffset():]
resp_headers = macro_response_info.getHeaders()
headers = request_info.getHeaders()
auth_to_delete = ''
for head in headers:
if 'Authorization: Bearer ' in head:
auth_to_delete = head
headers.remove(auth_to_delete)
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
Before the above code snippet executes, the burp API must be called, in order to initialize the communication with the python script during execution. The following code snippet provides the commands used in order for python interpreter to initialize the communication with burp API.
API Callbacks
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):
self.callbacks = callbacks
self.helpers = callbacks.getHelpers()
callbacks.setExtensionName(self.NAME)
self.callbacks.registerSessionHandlingAction(self)
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
The script below has been ported to python from an already implemented script written in ruby from Rory McCune (raesene). The ruby script created in order to manipulate JWT Tokens. Respectively, the following script was written in python to manipulate Bearer tokens:
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):
self.callbacks = callbacks
self.helpers = callbacks.getHelpers()
callbacks.setExtensionName(self.NAME)
self.callbacks.registerSessionHandlingAction(self)
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)
bearer = bearer_token["bearerToken"]
headers = request_info.getHeaders()
req_body = currentRequest.getRequest()[request_info.getBodyOffset():]
resp_headers = macro_response_info.getHeaders()
headers = request_info.getHeaders()
auth_to_delete = ''
for head in headers:
if 'Authorization: Bearer ' in head:
auth_to_delete = head
try:
headers.remove(auth_to_delete)
except:
pass
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
Bearer Authorization Token manipulation extender
For the purpose of this work we have implemented the following extender using eclipse IDE and Java. Specifically, in order to have a new tab in Burp Suite, we have used java swing. Furthermore, the following java extender will create a Tab named bearer which will have two separate columns showing the requests and responses every time the Token changes. You can find the code and the jar file here TwelveSec Github
package burp;
import java.awt.Component;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import org.json.JSONException;
import org.json.JSONObject;
public class BurpExtender extends AbstractTableModel implements ISessionHandlingAction ,ITab, IMessageEditorController
{
private static final long serialVersionUID = 1L;
private IBurpExtenderCallbacks callbacks;
private IExtensionHelpers helpers;
private PrintWriter stdout;
private String date;
ArrayList headers = new ArrayList();
private final List log = new ArrayList();
private JSplitPane splitPane;
private IMessageEditor requestViewer;
private IMessageEditor responseViewer;
private String NAME = "Bearer Token";
private IHttpRequestResponse currentlyDisplayedItem;
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks)
{
this.callbacks = callbacks;
// set our extension name
callbacks.setExtensionName("Authorization Token");
// obtain an extension helpers object
helpers = callbacks.getHelpers();
// obtain our output and error streams
stdout = new PrintWriter(callbacks.getStdout(), true);
SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy hh:mm:ss");
date = sdf.format(new Date());
// obtain our output and error streams
PrintWriter stdout = new PrintWriter(callbacks.getStdout(), true);
// write a message to our output stream
stdout.println("Authorization Token\n");
stdout.println("starting at time : " + date);
stdout.println("-----------------------------------------------------------------\n\n");
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
// main split pane
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
// table of log entries
Table logTable = new Table(BurpExtender.this);
JScrollPane scrollPane = new JScrollPane(logTable);
splitPane.setLeftComponent(scrollPane);
// tabs with request/response viewers
JTabbedPane tabs = new JTabbedPane();
requestViewer = callbacks.createMessageEditor(BurpExtender.this, false);
responseViewer = callbacks.createMessageEditor(BurpExtender.this, false);
tabs.addTab("Request", requestViewer.getComponent());
tabs.addTab("Response", responseViewer.getComponent());
splitPane.setRightComponent(tabs);
// customize our UI components
callbacks.customizeUiComponent(splitPane);
callbacks.customizeUiComponent(logTable);
callbacks.customizeUiComponent(scrollPane);
callbacks.customizeUiComponent(tabs);
// add the custom tab to Burp's UI
callbacks.addSuiteTab(BurpExtender.this);
//register the handler that does that alters the HTTP request
//this has to be enabled via the Burp Session handling options
callbacks.registerSessionHandlingAction(BurpExtender.this);
}
});
}
@Override
public void performAction(IHttpRequestResponse currentRequest, IHttpRequestResponse[] macroItems) {
IRequestInfo rqInfo = helpers.analyzeRequest(currentRequest);
IResponseInfo macro_repsonse_items = helpers.analyzeResponse(macroItems[0].getResponse());
byte[] macro_msg = macroItems[0].getResponse();
String Response = helpers.bytesToString(macro_msg);
String Resp_Body = Response.substring(macro_repsonse_items.getBodyOffset());
JSONObject json;
try {
json = new JSONObject(Resp_Body);
boolean bearer_token = json.has("bearerToken");
String Bearer = null;
if (bearer_token == true)
Bearer = json.getString("bearerToken");
// retrieve all headers
headers = (ArrayList) rqInfo.getHeaders();
// get the request
String request = new String(currentRequest.getRequest());
// get the request body
String messageBody = request.substring(rqInfo.getBodyOffset());
// go through the header and look for the one that we want to replace
for (int i = 0; i < headers.size(); i++){
if(((String) headers.get(i)).startsWith("Authorization: Bearer "))
headers.remove(i);
}
headers.add("Authorization: Bearer " + Bearer);
// create the new http message with the modified header
byte[] message = helpers.buildHttpMessage(headers, messageBody.getBytes());
// replace the current request and forward it
synchronized(log)
{
int row =log.size();
log.add(new LogEntry("adding new header - Authorization bearer: " + Bearer,"Header Checked at time : " + date, callbacks.saveBuffersToTempFiles(currentRequest)));
log.add(new LogEntry("","-----------------------------------------------------------------", callbacks.saveBuffersToTempFiles(currentRequest)));
log.add(new LogEntry("Geting authorized..done\n\n","", callbacks.saveBuffersToTempFiles(currentRequest)));
log.add(new LogEntry("-----------------------------------------------------------------\n\n","", callbacks.saveBuffersToTempFiles(currentRequest)));
fireTableRowsInserted(row,row);
}
currentRequest.setRequest(message);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public String getActionName() {
return NAME;
}
@Override
public String getColumnName(int columnIndex)
{
switch (columnIndex)
{
case 0:
return "Checked Time";
case 1:
return "Outline";
default:
return "";
}
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public int getRowCount() {
return log.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
LogEntry logEntry = (LogEntry) log.get(rowIndex);
switch (columnIndex)
{
case 0:
return logEntry.Checked_Time;
case 1:
return logEntry.Outline;
default:
return "";
}
}
@Override
public IHttpService getHttpService() {
// TODO Auto-generated method stub
return null;
}
@Override
public byte[] getRequest() {
return currentlyDisplayedItem.getRequest();
}
@Override
public byte[] getResponse() {
return currentlyDisplayedItem.getResponse();
}
@Override
public String getTabCaption() {
return "Bearer";
}
@Override
public Component getUiComponent() {
return splitPane;
}
@Override
public Class getColumnClass(int columnIndex)
{
return String.class;
}
private class Table extends JTable
{
private static final long serialVersionUID = 1L;
public Table(TableModel tableModel)
{
super(tableModel);
}
@Override
public void changeSelection(int row, int col, boolean toggle, boolean extend)
{
// show the log entry for the selected row
LogEntry logEntry = (LogEntry) log.get(row);
requestViewer.setMessage(logEntry.requestResponse.getRequest(), true);
responseViewer.setMessage(logEntry.requestResponse.getResponse(), false);
currentlyDisplayedItem = logEntry.requestResponse;
super.changeSelection(row, col, toggle, extend);
}
}
private static class LogEntry
{
final IHttpRequestResponsePersisted requestResponse;
final String Outline;
final String Checked_Time;
LogEntry(String Outline,String Checked_Time, IHttpRequestResponsePersisted requestResponse)
{
this.requestResponse = requestResponse;
this.Outline = Outline;
this.Checked_Time = Checked_Time;
}
}
}
Burp Suite
In order for the above script to work, the following steps must be performed. First Jython standalone .jar has to be installed into Burp Suite in order for the python interpreter to be able to communicate with java API. Following this, the above python script has to be installed into extenders tab as shown at the image below.
After the two preceding steps, two macro objects are created as shown below. The picture shows two macros created, each one representing a different login request with the relevant credentials from pentestadmin and pentestcommon users.
The macro editor shown below has the login request ready to run when the macro will be called.
The only thing the macro has to do is to run the above request and login into the application. Once logged in, the new Token will be generated. To be more specific, In this implementation we want the macro to run only when the session is invalid. To know when the session is invalid a new session rule has been generated as shown at the image below.
The above session handling rule ‘Bearer Authorization Token Validation’ accepts requests from the scanner and if the response headers of those requests have the Header ‘HTTP/1.1 401 Unauthorized’, then the macro will be triggered to initiate a new login. Afterwards, the python script will run to extract and replace the new Token to the current request to make it valid. The image below shows how this is implemented with a session handling rule.
In case the above scenario doesn’t work, one solution is to run the macro in every request coming from burp scanner, but this will put extra overhead into the scanning process. Running the macro in every request will be time consuming, but also will be the most accurate solution because there won’t be any possibilities of the scanner failing. This solution can be shown below using the following session handling rule:
Summarize
To summarize, the above methodology provides a solution on testing Enterprise applications that involve security Authorization tokens into every HTTP requests.Furthermore, this solution provides a better approach to solve the problem of Burp suite automated scanning failures when Authorization tokens exist. In general, the penetration tester has to be more efficient and productive during their security assessments. Additionally, approaches like this provide excellent assistance to the penetration tester while organizing their time and effort better, optimizing the quality of work. In the contrary, the only drawback to this kind of approach is that there are extra working hours to be spend in order to implement the solution.
As a conclusion to the above , penetration testing solution development practices must be identified from penetration testers in early stages of the security assessment in order to provide automation in penetration testing tasks.Therefore, Burp Suite provides a flexible and easy to use API which gives the penetration tester the ability to create tools that will produce the maximum performance on their day to day work. Consequently, any penetration testing tool that provides the ability to extend its functionality becomes beneficial from the security test perspective while increases the possibilities of accuracy as well as ameliorate the quality of penetration tester’s work. Also Tools and methodologies need to be more generic in order to be used in every relevant cases with little or no changes to accomplish better results.
Thanks a lot for this. It helped me deal with some APIs and applications I was testing in burp with tokens of a very short life span. I have been able to use this without issues in Burp Intruder and Repeater. However when i try to use it with the Burp Scanner, the extension seems to crash with an error message: Burp Extender java.net.SocketException: Connection reset. Wondering if you’ve come across this.
Hello Ronald ,
usually “java.net.SocketException: Connection reset” is caused by network problems sending from your end. Did you check if you had network issues form your side?
Hello,
Great walkthrough. Im a junior tester and have just started with tokens and came across your article. However, i get to the point where the python script is to be imported under Extender – it fails every time.
Its a big copy pasta but its a lot of java errors so i posted a image to imgur:
https://i.imgur.com/DC3tJIL.png
For line #1, if i remove that one it jumps to next one with same errors. I have successfully imported both *.jar file created from github and the jythons standlone 2.7.2.
Any advice? Greatly appreciated.
Hello Jonathan,
Thank you for reaching out. In order to use the extender you should follow the instructions provided at the README file on TwelveSec’s github and the extender will be loaded without errors. There is no reason to mess with python here, as the extender has been written in Java. Also, either you can load the pre-compiled .jar file on BurpSuite or follow the steps to compile your own .jar. As for the loading process of the BurpSuite extender, If you don’t know how it is done, you can find articles on the internet that describe the procedure step by step.
Hello,
Thanks for the reply!
I have indeed followed the github guide that links to this article. On the step above:
“In order for the above script to work, the following steps must be performed. First Jython standalone .jar has to be installed into Burp Suite in order for the python interpreter to be able to communicate with java API. Following this, the above python script has to be installed into extenders tab as shown at the image below.”
I successfully load everything, It then tells me to add the above python script and thats when the errors occur (from my previous screenshoot). Im unable to get past this step when following the guide above. But if i undertand you correctly, the *.py step mentioned in this turtorial is not needed(?).
Thank you. Appreciated