Getting the Most out of Burp Extensions. How to build a Burp extension, techniques for passive and active scanners, defining insertion points, modifying requests, and building GUI tools. This talk presents code libraries to make it easy for testers to rapidly customize Burp Suite.
5. Burp Suite
• What is Burp?
• What are extensions?
– What can I do with them? (use cases)
6. What Can I Do With Extensions?
• Passive Scanning
• Active Scanning
• Alter/append requests
• Define Insertion Points for Scanner/Intruder
• Create new payload types
• Automate Authentication
• Much, Much More
7. BApp Store
• What is it?
• How do I use it?
• A look at some useful extensions
– Logger++
– WSDL Wizard
32. Starting with a Template
• Find a starter project
• Some example projects at
https://portswigger.net/burp/extender/
• Today we’ll start with my NetbeansGUI project
found at https://github.com/monikamorrow/
Burp-Suite-Extension-Examples
– Which depends on https://github.com/augustd/burp-
suite-utils
33. Starting with a Template
• Clone Burp-Suite-Extension-Examples and
burp-suite-utils into your working directory
• Open the Burp-Suite-Extension-Examples
NetBeans project and expand folders and
resolve issues along the way
• Compile the project to resolve remaining
issues
54. How to Add?
mTab = new BurpSuiteTab
(mPluginName, mCallbacks);
mTab.add(toolsScope);
mTab.add(urlScope);
mTab.add(myJPanel);
mCallbacks.customizeUiComponent(mTab);
mCallbacks.addSuiteTab(mTab);
55. How to Get Settings?
urlScope.processAllRequests();
toolsScope.isToolSelected(toolFlag);
56. Passive Scanning
• Search responses for problematic values
• Built-in passive scans
– Credit card numbers
– Known passwords
– Missing headers
Building a Passive Scanner
57. Passive Scanning – Room for Improvement
• Error Messages
• Software Version Numbers
Building a Passive Scanner
58. Implement the IScannerCheck interface
public class PassiveScan implements IScannerCheck {
@Override
public List<IScanIssue> doPassiveScan(
IHttpRequestResponse baseRequestResponse) { … }
@Override
public List<IScanIssue> doActiveScan(
IHttpRequestResponse baseRequestResponse,
IScannerInsertionPoint insertionPoint) { … }
@Override
public int consolidateDuplicateIssues(
IScanIssue existingIssue, IScanIssue newIssue) { … }
Building a Passive Scanner
59. Register the extension as a custom scanner
@Override
protected void initialize() {
callbacks.registerScannerCheck(this);
}
Building a Passive Scanner
60. IScannerCheck.doPassiveScan()
for (MatchRule rule : rules) {
Matcher matcher =
rule.getPattern().matcher(response);
while (matcher.find()) {
matches.add(
new ScannerMatch(
matcher.start(), matcher.end(), group, rule));
Building a Passive Scanner
64. Extending from PassiveScan
@Override
protected void initPassiveScan() {
//set the extension Name
extensionName = "Error Message Checks";
//create match rules
addMatchRule(
new MatchRule(PHP_ON_LINE, 0, "PHP"));
addMatchRule(
new MatchRule(PHP_HTML_ON_LINE, 0, "PHP"));
…
Building a Passive Scanner
65. Extending from PassiveScan
@Override
protected ScanIssue getScanIssue(
IHttpRequestResponse baseRequestResponse,
List<ScannerMatch> matches, List<int[]> startStop) {
return new ScanIssue(
baseRequestResponse,
helpers,
callbacks,
startStop,
getIssueName(),
getIssueDetail(matches),
ScanIssueSeverity.MEDIUM.getName(),
ScanIssueConfidence.FIRM.getName());
Building a Passive Scanner
66. Active Scanning
• Issue requests containing attacks
• Look for indication of success in response
• Built-In Active Scans
– XSS
– SQL Injection
– Path Traversal
– etc
Building an Active Scanner
67. IScannerCheck.doActiveScan()
@Override
public List<IScanIssue> doActiveScan(
IHttpRequestResponse baseRequestResponse,
IScannerInsertionPoint insertionPoint) {
for (MatchRule rule : rules) {
// compile a request containing our
// injection test in the insertion point
byte[] testBytes = rule.getTest();
byte[] checkRequest =
insertionPoint.buildRequest(testBytes);
Building an Active Scanner
68. IScannerCheck.doActiveScan()
// issue the request
IHttpRequestResponse checkRequestResponse =
callbacks.makeHttpRequest(
httpService, checkRequest);
//get the response
String response = helpers.bytesToString(
checkRequestResponse.getResponse());
Building an Active Scanner
69. IScannerCheck.doActiveScan()
// get the offsets of the payload
// within the request, for in-UI highlighting
List<int[]> requestHighlights =
new ArrayList<int[]>(1);
requestHighlights.add(
insertionPoint.getPayloadOffsets(testBytes));
Building an Active Scanner
70. Extending from ActiveScan
@Override
protected void initActiveScan() {
//set the extension Name
extensionName = "Server Side Javascript Injection checks";
//create match rules
addMatchRule(
new MatchRule("response.end('success')", SUCCESS, 0, "response.end"));
addMatchRule(
new MatchRule("1995';return(true);var%20foo='bar", TRUE, 0, "string"));
Building an Active Scanner
71. Insertion Points
• Locations of parameters in request
• Contain data the server will act upon
Building an Active Scanner
78. Viewing Insertion Points
• Add menu option to send request to Intruder
• Implement IContextMenuFactory
– createMenuItems()
• Register as a menu factory
callbacks.registerContextMenuFactory(this);
Defining Insertion Points
80. BurpExtender.createMenuItems()
//create clickable menu item
JMenuItem item = new JMenuItem(
"Send GWT request(s) to Intruder");
item.addActionListener(new MenuItemListener(ihrrs));
//return a Collection of menu items
List<JMenuItem> menuItems =
new ArrayList<JMenuItem>();
menuItems.add(item);
return menuItems;
Defining Insertion Points
81. MenuItemListener
class MenuItemListener implements ActionListener {
private IHttpRequestResponse[] ihrrs;
public MenuItemListener(
IHttpRequestResponse[] ihrrs) {
this.ihrrs = ihrrs;
}
public void actionPerformed(ActionEvent ae) {
sendGWTToIntruder(ihrrs);
}
}
Defining Insertion Points
82. BurpExtender.sendGWTToIntruder()
public void sendGWTToIntruder(IHttpRequestResponse[] ihrrs) {
for (IHttpRequestResponse baseRR : ihrrs) {
IHttpService service = baseRR.getHttpService();
// parse the request (not shown)
if (isGWTRequest) {
// Send GWT request to Intruder
callbacks.sendToIntruder(
service.getHost(), service.getPort(),
service.getProtocol().equals("https"),
request, insertionPointOffsets);
Defining Insertion Points
100. Passive Scanning
@Override
protected void initPassiveScan() {
//set the extension Name
extensionName = "Software Version Checks";
//create a component
rulesTable = new RuleTableComponent(this,
callbacks);
//add component to Burp GUI
mTab = new BurpSuiteTab(extensionName,
callbacks);
mTab.addComponent(rulesTable);
}
Bringing it all Together
Burp Suite is the leading web application vulnerability testing tool. It is available from http://portswigger.net for $299/year –a fraction of the cost of some other commercially available web application testing tools.
Burp supports a plugin architecture which allows additional functionality to be developed and integrated with the tool. Anyone can download it and start adding new features to the tool.
I’ve spoken to some of you who are using plugins to do some truly incredible stuff like turning Burp into a full automated testing suite.
In the short time we have here today we won’t be able to get into cool stuff like that, but I want to give you the basic tools to get started writing your own extensions.
A subtab of the Extender tab of Burp the BApp Store consists of 66 Burp Extensions that can be installed from within Burp. Many can be installed with one click although extensions that aren’t built natively in Java require a small amount of configuration first.
A subtab of the Extender tab of Burp the BApp Store consists of 66 Burp Extensions that can be installed from within Burp. Many can be installed with one click although extensions that aren’t built natively in Java require a small amount of configuration first.
Installable with just one click Logger++ is one of the most broadly applicable extensions within the BApp Store.
Once an extension has been installed from the BApp Store it will show up on the Extensions subtab. Further details, an output console, and error console are available here in addition to an interface to toggle the extension on/off without uninstalling it.
Logger++ adds a Main tab to the Burp interface. It has an options tab here to configure which tools should be logged in addition to extension specific settings.
We'll talk about adding tabs to the Burp interface a little bit later
The View Logs tab of the Logger++ tab shows requests similar to the proxy tab but all requests are logged. Here you can see the tool selected can be seen in the Tool column.
Selecting an item in the Log View allows viewing of the request/response consistent with other locations in Burp.
Jython and Ruby extensions require extra configuration before being installed. Once Jython is downloaded and configured within Burp the Install button will be activated.
Java libraries locations, Jython and Ruby configurations are controlled here.
After locating the jython.jar file restart Burp.
Now Burp treats Jython extensions the same as native Java extensions and WSDL Wizard can be one click installed.
The WSDL Wizard now indicates it is installed on the BApp Store page.
Both the Logger++ and WSDL Wizard extension are installed and active. Their active state can be toggled from the Burp Extensions tab without reinstallation or reloading of Burp.
The WSDL Wizard adds a context menu in the Site map that scans for WSDL files. To use it right click on a target of interest in the Site map to access and select the custom context menu, “Scan for WSDL Files”.
The results of the scan are viewable in the WSDL Wizard output window.
Even though the BApp Store is great it is still only a small slice of what plugins CAN be used for. This is where the extensions that are fit for wide release are located.
Proprietary and one off: CSRF token changed on every request. One off extension parsed made request to obtain a new token and added the updated token to each automated request made by Burp.
Proprietary: Custom signature required for requests to be accepted by the application.
So we’ve seen how to load an extension from the BApp store. Now lets take a look at how to load a custom extension built from source.
On the Extensions subtab of the Extender tab click the “Add” button to load a custom extension by selecting its .jar, .py, or .rb file.
Select the extension type from the drop down and use the file selector to find the jar for your custom extension.
When using a NetBeans project the .jar file is located in the “dist” folder within the “BurpExtender” folder.
Change the location of the Standard Output and Standard Error if desired and press “Next” to load the extension.
If the extension is loaded successfully the Loaded checkbox will be checked and there will most likely be some text in the Output tab and no text in the Error tab.
You DO NOT need Burp Suite Pro in order to use extensions. But some features won't work unless you have pro
Java 1.6.x is the minimum requirement to run Burp, but much newer versions are available.
I like NetBeans for its ease of use, but you can use any IDE, or even a simple text editor
You can also write Burp extensions in Python using Jython, OR Ruby using Jruby, but Java is the native language of Burp Suite (and me) so that will be the focus of this talk today.
It’s helpful to name MYPROJECT with a useful name so as you mouse over your BurpExtender projects you can find the one you want
In order for Burp Suite to load your extension, all of the Java class files must be contained in a single jar file. Since we are depending on classes from other libraries, we have to update the build scripts provided by NetBeans to build a fat jar.
You should now have a functional starter project!
Passive Scanning
Passive scanning allows you to monitor responses for certain values and flag them as issues in the Burp Scanner tab.
Burp includes built in passive scanning for things like credit card numbers, previously used passwords, missing headers like X-Frame-Options, etc.
Error messages can reveal valuable details about the inner workings of an application
Software version numbers can inform you as to the overall health of an organization’s operations: When they are patched, how up to date, etc.
These things are often only revealed in error pages - things that might be responses to Scanner or Intruder requests, but not necessarily seen by a tester.
Burp has no facility to detect them on its own. Enter the Plugins!
To build a passive scanner you must implement the IScannerCheck interface and register it as a scanner check with the Extender Callbacks.
IScannerCheck requires you to implement 3 methods:
doPassiveScan will perform the meat of your scanning.
doActiveScan we are not really concerned with because this is not an active scanner. This method can simply return null.
consolidateDuplicateIssues is used to ensure that the same issue is not reported multiple times
Registering the extension as a scanner check is a simple method call to the callbacks object and can be done when the extension initializes.
Then we iterate over a list of regular expressions (contained in the MatchRule objects) attempting to match them to the response body.
When we find a match, we save it in a ScannerMatch object (just a simple Java bean defined as an inner class) which we will add to Burp’s Scanner results.
Once we have found matches of our regex, we want to add them to the Burp Scanner interface.
1. First, we need to sort the matches. This is important because in order for code highlighting to work, Burp wants all matches to be in order.
Next we create a list of ints which are the offsets, the start and stop points, within the response. These are used by Burp to do the code highlighting
Finally return a CustomScanIssue (an POJO object that extends IScanIssue) to be added to the Scanner results tab.
The ScanIssue contains all the information that will be displayed in Burp Scanner’s Advisory tab
consolidateDuplicateIssues is called by Burp to ensure that the same issue only shows up once on Burp’s Scanner list.
It essentially works like any other Java Comparable:
Return -1 to keep the old issue and discard the new one
Return 0 to report both issues
Return 1 to report the new issue and discard the old one
If that all seems overly complicated, you are not alone. Based on feedback from last year’s presentation I’ve released a set of utilities that attempt to abstract this and make it much easier to get started. These are on my GitHub, the URLs will be at the end of the presentation.
All you need to do now is extend from the abstract class com.codemagi.burp.PassiveScan and implement two methods. In initPassiveScan set the extension name and add match rules.
In getScanIssue you add your custom code to return scan issues when a match is found.
That’s it! All of the mechanics of scanning are handled for you by extending from PassiveScan
This brings us to our next topic, Active Scanning.
Active scanning is excellent for finding injection type vulnerabilities, like SQL injection, XSS and others. Active scanning is more complicated because it requires you to issue requests and look for success in the responses.
Here we will be building an example active scanner to test for server-side code execution a JavaScript-based website, for example using node.js.
doActiveScan is called for each insertion point of each request that the Burp Scanner makes. Here we iterate through our injection tests, and for each:
Compile a test request, into the checkRequest variable, a byte array
2. Now you can issue the test request to the server, and get the response.
You get the httpService object from the IHttpRequestResponse
Now it is just a matter of applying a regex to the response to look for indications that your attack worked. If any matches are found, report the issue.
If any matches are found, report the issue. This is basically the same process as a passive scan, with one exception.
Since the active scanner issued an altered request you want to highlight the data you changed in the request, as well as the matches in the response.
getPayloadOffsets returns a two position array of ints that indicate the start and stop points of the area to be highlighted in the request.
I’ve also created a base class to simplify building active scans. All you need to do now is extend from the abstract class com.codemagi.burp.ActiveScan and implement two methods. In initActiveScan set the extension name and add match rules.
The only major difference here is that our MatchRule needs to include not only the regular expression to match, but also the attack string that will be added to each insertion point in the request. The attack strings are highlighted here in orange.
Insertion Points define the locations within a request that contain data that the server will act upon.
Insertion points are used by the Active Scanner or Burp Intruder to target attack payloads.
You can see the insertion points that Burp identifies by right-clicking a request and selecting Send to Intruder.
Burp does a pretty good job defining insertion points on its own for regular HTTP requests.
But what if your request looks like this? This is a Google Web Toolkit request, and Burp’s built-in request parser doesn’t do such a good job.
Somewhere inside that huge block of condensed text, we know that there is data that the server is going to act upon. Sure, in Intruder we can actively select each one, but that is time consuming and… boring.
So how can we teach Burp to automatically know where they are?
To have your extension define insertion points, you must implement IScannerInsertionPointProvider. This consists of one method: getInsertionPoints()
You also need to register as an insertion point provider. This can be done in the registerExtenderCallbacks method when your extension initializes.
Implementing getInsertionPoints is easy. The method is passed the HTTP request. We parse that request to determine the offsets of the insertion points we want to use. In this case, I did some research and found existing parsers, but they all missed something, so I wound up writing my own. How it works is unimportant, just know that it returns a set of offsets: The start/stop index of the insertion point within the raw request.
Once we know the offsets, we create a List of IScannerInsertionPoint objects using the helpers object we got form the callbacks.
Here, insertionPointOffsets is the list of int arrays returned by the parser.
Once we know the offsets, we return a List of IScannerInsertionPoint objects using the helpers object we got form the callbacks.
getInsertionPoints() is called automatically when you send an item to the active scanner. If you send a request to the scanner, you can see that it now has 5 insertion points, rather than the 2 that Burp originally identified.
If you want to see the actual insertion points that your extension defines you have to send the request to Intruder. Burp’s own Send to Intruder option will use the built-in insertion points, so you need to add your own option to the right-click menu.
To do that you will need to implement the IContextMenuFactory interface and add the createMenuItems() method.
You also need to register as a context menu factory. This can be done in the registerExtenderCallbacks method when your extension initializes.
The createMenuItems() method is passed an Invocation object by Burp. This object contains the request or requests that were selected when the mouse was right clicked.
We want to create a new standard Swing JMenuItem and attach an ActionListener that will fire when the menu item is clicked.
This method actually wants you to return a Collection of menu items. That way your extension can define more than one menu item.
We create an ActionListener that responds to the Java Swing events that are generated when the user clicks on the menu item.
In this case, I just send the selected items to Intruder.
The method called by the MenuItemListener parses each request in turn to see if it can locate GWT insertion points.
If insertion points are found, that indicates that the request is a GWT request. Then it invokes the sendToIntruder method of the callbacks object, passing the request with the new insertion points to Intruder.
Additionally, we call setComment on the requestResponse object to add the GWT service method to the comments that appear in the Burp proxy list.
baseRR.getComment() returns the original comment for this item so we do not overwrite any comment that the tester may have already added.
Now you can right-click on a request in any of Burp’s Tools and there will be a new option in the context menu to send a GWT request to Intruder.
In Intruder you can now see the 5 new insertion points that our extension defined.
Some web services require you to send a custom header or signature with your requests.
I had to test a site that used a constantly rotating anti-CSRF token to each request. Each time a form was submitted, the application would create a new anti-CSRF token. Any attempt to scan this app would fail after the first scanner request was submitted. I needed a way to fetch a valid CSRF token and update the parameters used by the scanner for every single request.
To do that you will need to do request modification.
To setup your extension to modify requests you need to implement IHttpListener. This has one method: processHttpMessage()
You also need to register the class as HTTP listener. Again, this is done in registerExtenderCallbacks
The processHttpMessage method is called by Burp for each HTTP request before it is sent to the server, and for each response, before it is returned to the browser.
The fist thing we need to do then is determine if this is a request or response. Fortunately Burp passes the messageIsRequest boolean to this method to tell you.
Next we need to determine whether this is a request for the scanner. Remember, Burp processes this extension’s method for every request. To do that, we check whether the toolFlag parameter matches the value for the scanner tool defined in the callbacks.
If both of those things are true, we next check to see whether the request we are scanning includes a CSRF token.
First we convert the request from a byte array to a string, then use regex to look for a match of the CSRF token.
If the request contains a CSRF token then we need to hit the form page, parse out the token from a hidden field, and place the token into the request.
To issue the request, use the helpers class to build a request as an array of bytes.
Use callbacks.makeHttpRequest to issue the request to the server and get the response as bytes.
There is a bytesToString() helper method to convert the response bytes to a string.
Then it is simply a matter of using a regex pattern to find and return the CSRF token
If both of those things are true, we next check to see whether the request we are scanning includes a CSRF token.
First we convert the request from a byte array to a string, then use regex to look for a match of the CSRF token.
Then finally we set the modified request string into the messageInfo object that Burp passed in to processHttpMessage() so that Burp can send the modified scanner request to the server.
The Burp Extender API now offers methods to print Strings to the Extension’s output and error logs. This was actually a suggestion I submitted on the Burp Suite Forums.
If you want to see stack traces you can use e.printStackTrace() and the stack trace will show up in the terminal where you launched Burp.
Calling printOutput causes the message to be written to the Output tab on the Extensions panel, directly within the Burp GUI
You can still also select to output to the terminal where you launched Burp, or save it to a file, which could be useful if you want to do further analysis.
You can call printStackTrace and write a stack trace to the terminal where you opened Burp.
To show a stack trace in Burp’s own interface, you need to get the actual OutputStream from the callbacks.
Then, create a method to print an exception stack trace directly to that OutputStream.
Now stack traces will show up directly within the Burp GUI
Today we talked about using Base Classes, Passive Scanning, and GUI Building. Now let's use these techniques to solve one of the big challenges with the BApp Store: getting your updates published. With the Software Version Checks extension, I am constantly finding new patterns that need to be added to the scanner, but writing new code and asking for a new deployment each time is unreliable.
Starting with the BaseExtender class in burp-suite-util I got access to all of the callbacks and helper methods in the Burp internals. PassiveScan extends from BaseExtender. It takes care of all the details of running a scan. To the PassiveScan I added a BurpSuiteTab that does everything needed to create a new tab in the Burp Suite UI.
Using NetBeans Gui builder I created a table component, which let's you enter a URL to load your match rules from.
Now with this much code I can extend PassiveScan to create a new passive scanner.
Now, when my extension loads, I can click a button to load the set of match rules from a tab delimited file on GitHub. This solves the challenge of deploying updates to the BApp store: There is no longer a need to deploy new code to add a new match rule, I just need to update a file! You can load your own match rules as well by creating your own tab delimited files.
Now that this is in GitHub I look forward to all of your pull requests to add new match rules!