burp plug-in development
The power of Burp Suite not only provides a wealth of functions that can be used by testers, but also provides support for third-party extension plug-ins, which greatly facilitates users to write their own custom plug-ins. , Burp Suite supports three plug-in types: Java, Python, and Ruby. No matter which language is implemented, developers only need to choose the language they are familiar with and implement the desired functions according to the interface specifications. Let’s take a look at how to develop a Burp Extender plug-in. The main contents covered in this chapter are:
- API description
- Preparation before writing Burp plug-in
- Writing Burp plug-in (Java language version)
api description
Open the Tab page of Burp Extender's APIs and see the interface as shown below:
Note that different versions of the API may be different. I used the latest method of using maven dependencies according to the official website. Because the latest version of BurpExtension uses an initialization method initalize that is one letter different from my current version of initalise, it took me a long time. It is recommended to use the current file for development. The api interface is exported to the project source code directory, because the maven api does not know the version correspondence at all. My version: V2022.9.
Create a new maven empty project in idea, click apis - save interface files to the src/main/java directory. The directory structure is
as follows. For
the specific use of the api, please refer to the api description or save javadoc files on the apis panel to open html to view
or go to the official website to view:
https://portswigger.github.io/burp-extensions-montoya-api/javadoc/burp/api/montoya/MontoyaApi.html
Preparation before writing plug-in
Source code reference
Burpsuite does not have detailed documentation describing how to write plug-ins. It provides many related cases, which you need to read by yourself
https://github.com/PortSwigger/burp-extensions-montoya-api-examples
java environment configuration
First, determine the jdk you need to use for your burpsuite version. For example, mine is jdk17, and jdk8 is used for daily development. Modify the
environment of the idea.
Click project structure - SDKS to add jdk17.
If the language level on Modules is not 17, just select the largest one. Here is mine. 13 is enough. This is just the version used by idea to compile the code.
Change the Project SDK on the Project to 17 and
add it in pom.xml.
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
Use maven to execute mvn package to confirm whether you are using jdk17.
Modify pom.xml and modify the packaging method to jar.
<packaging>jar</packaging>
Enable debugging
Open the Edit Configurations of idea running, create a new Remote, copy the command line arguments for remote JVM
Host is set to: localhost, and the port is set to: 50055.
Add the copied content to the bat file of burpsuite
and add the jvm parameters to
restart burpsuite
netstat - aon | findstr 50055 Check whether the port is open.
helloworld written
Create a test class: HelloWorld
package com.qj.bc;
import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.logging.Logging;
public class HelloWorld implements BurpExtension {
@Override
public void initialise(MontoyaApi api) {
Logging logging = api.logging();
// write a message to our output stream
logging.logToOutput("Hello output.");
// write a message to our error stream
logging.logToError("Hello error.");
// write a message to the Burp alerts tab
logging.raiseInfoEvent("Hello info event.");
logging.raiseDebugEvent("Hello debug event.");
logging.raiseErrorEvent("Hello error event.");
logging.raiseCriticalEvent("Hello critical event.");
// throw an exception that will appear in our error stream
throw new RuntimeException("Hello exception.");
}
}
Use mvn package to package.
Start remote debugging
. Open burpsuite - Extener - Extensions - Add and add one.
After clicking next, the breakpoint is set
. After skipping the breakpoint, view the output and error log
Errors are displayed .
Customized packet capture log display
Requirement: The packet capture log of intercepted bp is displayed in a custom table. Clicking the table can display the request and response information.
The renderings are as follows.
Official website example reference:
https://github.com/PortSwigger/burp-extensions-montoya-api-examples/tree/main/customlogger
Add request filter
package com.qj.bc.customlogger;
import burp.api.montoya.core.MessageAnnotations;
import burp.api.montoya.core.ToolSource;
import burp.api.montoya.http.HttpHandler;
import burp.api.montoya.http.RequestHandlerResult;
import burp.api.montoya.http.ResponseHandlerResult;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
public class MyHttpHandler implements HttpHandler
{
private final MyTableModel tableModel;
public MyHttpHandler(MyTableModel tableModel)
{
this.tableModel = tableModel;
}
@Override
public RequestHandlerResult handleHttpRequest(HttpRequest request, MessageAnnotations annotations, ToolSource toolSource) {
return RequestHandlerResult.from(request,annotations);
}
/**
*
* @param request 被抓包的请求信息
* @param response 返回的响应信息
* @param annotations 消息标记,比如高亮,添加注释等。
* @param toolSource 这个请求来源于那个工具,比如是proxy还是爆破,还是其他的工具,比如proxy抓包的HTTP请求,爆破自定义的请求等等
* @return
*/
@Override
public ResponseHandlerResult handleHttpResponse(HttpRequest request, HttpResponse response, MessageAnnotations annotations, ToolSource toolSource) {
tableModel.add(new HttpResponseReceived(toolSource,request,response));
return ResponseHandlerResult.from(response,annotations);
}
}
Add http request object
Because http intercepts four parameters
- request captured request information
- response the response information returned
- annotations message markup, such as highlighting, adding comments, etc.
- toolSource This request comes from which tool, such as proxy, blasting, or other tools, such as proxy capturing HTTP requests, blasting custom attack requests (click start attack to catch), etc.
Encapsulate a class to wrap these parameters, because the request response, source and other information need to be displayed on the interface.
package com.qj.bc.customlogger;
import burp.api.montoya.core.ToolSource;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
public class HttpResponseReceived {
private ToolSource toolSource;
private HttpRequest request;
private HttpResponse response;
public HttpResponseReceived(ToolSource toolSource, HttpRequest request, HttpResponse response) {
this.toolSource = toolSource;
this.request = request;
this.response = response;
}
public ToolSource toolSource(){
return toolSource;
}
public HttpRequest initiatingRequest(){
return request;
}
public HttpResponse getResponse(){
return response;
}
}
Add table data source
, this data is finally displayed in the table, define a table modal
package com.qj.bc.customlogger;
import burp.api.montoya.http.message.responses.HttpResponse;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.List;
public class MyTableModel extends AbstractTableModel
{
private final List<HttpResponseReceived> log;
public MyTableModel()
{
this.log = new ArrayList<>();
}
@Override
public synchronized int getRowCount()
{
return log.size();
}
@Override
public int getColumnCount()
{
return 2;
}
@Override
public String getColumnName(int column)
{
return switch (column)
{
case 0 -> "Tool";
case 1 -> "URL";
default -> "";
};
}
@Override
public synchronized Object getValueAt(int rowIndex, int columnIndex)
{
HttpResponseReceived responseReceived = log.get(rowIndex);
return switch (columnIndex)
{
case 0 -> responseReceived.toolSource().toolType();
case 1 -> responseReceived.initiatingRequest().url();
default -> "";
};
}
public synchronized void add(HttpResponseReceived responseReceived)
{
int index = log.size();
log.add(responseReceived);
fireTableRowsInserted(index, index);
}
public synchronized HttpResponseReceived get(int rowIndex)
{
return log.get(rowIndex);
}
}
Define burp extension
package com.qj.bc.customlogger;
import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.ui.UserInterface;
import burp.api.montoya.ui.editor.HttpRequestEditor;
import burp.api.montoya.ui.editor.HttpResponseEditor;
import javax.swing.*;
import java.awt.*;
import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY;
public class CustomLogger implements BurpExtension
{
private MontoyaApi api;
@Override
public void initialise(MontoyaApi api)
{
this.api = api;
MyTableModel tableModel = new MyTableModel();
//在burpsuite中添加一个tab页签,名字是Custom logger
api.userInterface().registerSuiteTab("Custom logger", constructLoggerTab(tableModel));
//注册一个http抓包请求拦截器,用来获取所有抓包的数据添加到表格
api.http().registerHttpHandler(new MyHttpHandler(tableModel));
}
/**
* 使用JSplitPane,添加一个左面是http请求的表格,下面是个http的请求和响应编辑器
* @param tableModel
* @return
*/
private Component constructLoggerTab(MyTableModel tableModel)
{
// main split pane
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
// tabs with request/response viewers
JTabbedPane tabs = new JTabbedPane();
UserInterface userInterface = api.userInterface();
HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY);
HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY);
tabs.addTab("Request", requestViewer.uiComponent());
tabs.addTab("Response", responseViewer.uiComponent());
splitPane.setRightComponent(tabs);
// table of log entries
JTable table = new JTable(tableModel)
{
/**
* 当表格的某个请求被选择后,需要将请求和响应的结果,设置到请求响应编辑器中
* @param rowIndex
* @param columnIndex
* @param toggle
* @param extend
*/
@Override
public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend)
{
// show the log entry for the selected row
HttpResponseReceived responseReceived = tableModel.get(rowIndex);
requestViewer.setRequest(responseReceived.initiatingRequest());
responseViewer.setResponse(responseReceived.getResponse());
super.changeSelection(rowIndex, columnIndex, toggle, extend);
}
};
JScrollPane scrollPane = new JScrollPane(table);
splitPane.setLeftComponent(scrollPane);
return splitPane;
}
}