# Burp Suite Extensions API - Montoya The Burp Extensions Montoya API is a comprehensive Java interface for building custom extensions (BApps) that extend the functionality of Burp Suite, the industry-leading web application security testing platform. This API provides programmatic access to all major Burp Suite tools including the Proxy, Scanner, Intruder, Repeater, and more, enabling developers to automate security testing workflows, create custom vulnerability checks, manipulate HTTP traffic, and integrate Burp with other security tools. The Montoya API uses a clean, modern design with immutable objects, builder patterns, and a strongly-typed interface. Extensions can intercept and modify HTTP/WebSocket traffic in real-time, register custom scanning logic for vulnerability detection, create custom UI components, persist data across sessions, leverage AI-powered analysis, import Bambdas and BChecks programmatically, and use powerful utilities for encoding, decoding, ranking responses, and executing shell commands. The API is available for both Community and Professional editions, with some advanced features like the Scanner, Collaborator, and AI restricted to Professional users. ## Core API Entry Point ### Implementing BurpExtension Interface All Burp extensions must implement the `BurpExtension` interface, which provides the `initialize()` method as the entry point. The `MontoyaApi` instance passed to this method provides access to all Burp functionality. ```java package com.example; import burp.api.montoya.BurpExtension; import burp.api.montoya.MontoyaApi; import burp.api.montoya.EnhancedCapability; import java.util.Set; public class MyExtension implements BurpExtension { private MontoyaApi api; @Override public void initialize(MontoyaApi api) { this.api = api; // Set extension name api.extension().setName("My Security Extension"); // Log to output api.logging().logToOutput("Extension loaded successfully!"); // Register HTTP handler api.http().registerHttpHandler(new MyHttpHandler(api)); // Register scanner check (Professional only) if (api.burpSuite().version().edition().toString().contains("Professional")) { api.scanner().registerActiveScanCheck( new MyScanCheck(), ScanCheckType.EXTENSION_GENERATED ); } // Add custom UI tab api.userInterface().registerSuiteTab("My Tab", new MyPanel()); } @Override public Set enhancedCapabilities() { // Declare if extension requires AI functionality return Set.of(EnhancedCapability.AI); } } ``` ## HTTP Traffic Interception and Modification ### Registering HTTP Handler to Intercept Requests and Responses The `HttpHandler` interface allows extensions to intercept and modify all HTTP traffic passing through Burp. The handler provides hooks for requests about to be sent and responses just received. ```java import burp.api.montoya.MontoyaApi; import burp.api.montoya.http.handler.*; import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.responses.HttpResponse; public class MyHttpHandler implements HttpHandler { private final MontoyaApi api; public MyHttpHandler(MontoyaApi api) { this.api = api; } @Override public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) { HttpRequest request = requestToBeSent; // Add custom header to all requests HttpRequest modifiedRequest = request.withAddedHeader("X-Custom-Header", "MyValue"); // Modify specific parameter if present if (request.hasParameter("token", HttpParameterType.URL)) { String token = request.parameterValue("token", HttpParameterType.URL); api.logging().logToOutput("Found token: " + token); // Replace parameter value modifiedRequest = modifiedRequest.withParameter( HttpParameter.urlParameter("token", "modified_" + token) ); } // Continue with modified request return RequestToBeSentAction.continueWith(modifiedRequest); // Other options: // return RequestToBeSentAction.continueWith(request); // No modification // return RequestToBeSentAction.drop(); // Drop the request } @Override public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) { HttpResponse response = responseReceived; HttpRequest request = responseReceived.initiatingRequest(); // Log responses with sensitive headers if (response.hasHeader("Authorization")) { api.logging().raiseInfoEvent( "Authorization header found in response from: " + request.url() ); } // Check for specific content in response if (response.bodyToString().contains("admin_secret")) { api.logging().raiseErrorEvent( "Sensitive data leaked in response: " + request.url() ); // Optionally modify response String modifiedBody = response.bodyToString() .replace("admin_secret", "[REDACTED]"); HttpResponse modifiedResponse = response.withBody(modifiedBody); return ResponseReceivedAction.continueWith(modifiedResponse); } return ResponseReceivedAction.continueWith(response); } } ``` ### Sending HTTP Requests Programmatically Extensions can send custom HTTP requests and analyze responses using the `Http` interface, with support for both sequential and parallel requests. ```java import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.HttpService; import burp.api.montoya.http.HttpMode; import java.util.List; import java.util.ArrayList; public class RequestSender { private final MontoyaApi api; public RequestSender(MontoyaApi api) { this.api = api; } public void sendSingleRequest() { // Create HTTP request from URL HttpRequest request = HttpRequest.httpRequestFromUrl("https://example.com/api/users"); // Add headers and body request = request .withAddedHeader("Content-Type", "application/json") .withAddedHeader("X-API-Key", "secret123") .withMethod("POST") .withBody("{\"username\":\"test\"}"); // Send request and get response HttpRequestResponse requestResponse = api.http().sendRequest(request); // Analyze response if (requestResponse.response() != null) { HttpResponse response = requestResponse.response(); api.logging().logToOutput("Status: " + response.statusCode()); api.logging().logToOutput("Body: " + response.bodyToString()); // Check for specific status code if (response.isStatusCodeClass(StatusCodeClass.SUCCESS_2XX)) { api.logging().logToOutput("Request successful!"); } } } public void sendParallelRequests() { // Create multiple requests List requests = new ArrayList<>(); for (int i = 1; i <= 10; i++) { HttpRequest request = HttpRequest.httpRequestFromUrl( "https://example.com/api/users/" + i ); requests.add(request); } // Send all requests in parallel List responses = api.http().sendRequests(requests); // Process results for (HttpRequestResponse requestResponse : responses) { if (requestResponse.response() != null) { api.logging().logToOutput( "URL: " + requestResponse.request().url() + " Status: " + requestResponse.response().statusCode() ); } } } public void sendWithOptions() { HttpRequest request = HttpRequest.httpRequestFromUrl("https://example.com/redirect"); // Send with specific request options RequestOptions options = RequestOptions.requestOptions() .withRedirectionMode(RedirectionMode.NEVER) .withUpstreamProxyHost("proxy.example.com") .withUpstreamProxyPort(8080); HttpRequestResponse requestResponse = api.http().sendRequest(request, options); // Check if redirect occurred if (requestResponse.response().statusCode() == 302) { String location = requestResponse.response().headerValue("Location"); api.logging().logToOutput("Redirect to: " + location); } } } ``` ### Building and Modifying HTTP Messages HTTP messages in the Montoya API are immutable, using builder patterns for modification. The API provides rich static factory methods and fluent interfaces. ```java import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.http.message.HttpHeader; import burp.api.montoya.http.message.params.HttpParameter; import burp.api.montoya.http.message.params.HttpParameterType; import burp.api.montoya.core.ByteArray; public class MessageBuilder { public void buildRequests() { // Build request from scratch HttpRequest request = HttpRequest.httpRequest() .withService(HttpService.httpService("example.com", 443, true)) .withPath("/api/login") .withMethod("POST") .withAddedHeader("Content-Type", "application/json") .withAddedHeader("User-Agent", "My Extension") .withBody("{\"user\":\"admin\",\"pass\":\"secret\"}"); // Build from raw HTTP text String rawRequest = "GET /test HTTP/1.1\r\n" + "Host: example.com\r\n" + "Cookie: session=abc123\r\n\r\n"; HttpRequest request2 = HttpRequest.httpRequest(rawRequest); // Modify existing request immutably HttpRequest modified = request2 .withUpdatedHeader("Cookie", "session=xyz789") .withAddedParameter(HttpParameter.urlParameter("debug", "true")) .withRemovedHeader("User-Agent") .withPath("/new-path"); // Access request components String url = request.url(); String method = request.method(); String host = request.httpService().host(); int port = request.httpService().port(); boolean isHttps = request.httpService().secure(); List headers = request.headers(); ByteArray body = request.body(); String bodyString = request.bodyToString(); // Work with parameters List params = request.parameters(); for (ParsedHttpParameter param : params) { String name = param.name(); String value = param.value(); HttpParameterType type = param.type(); // URL, BODY, COOKIE, JSON, XML, etc. } // Check for specific parameters if (request.hasParameter("id", HttpParameterType.URL)) { String id = request.parameterValue("id", HttpParameterType.URL); } } public void buildResponses() { // Build response from scratch HttpResponse response = HttpResponse.httpResponse() .withStatusCode((short) 200) .withReasonPhrase("OK") .withAddedHeader("Content-Type", "application/json") .withAddedHeader("Set-Cookie", "session=abc123; HttpOnly; Secure") .withBody("{\"status\":\"success\"}"); // Build from raw HTTP text String rawResponse = "HTTP/1.1 404 Not Found\r\n" + "Content-Type: text/html\r\n\r\n" + "Not Found"; HttpResponse response2 = HttpResponse.httpResponse(rawResponse); // Access response components short statusCode = response.statusCode(); String reasonPhrase = response.reasonPhrase(); String mimeType = response.mimeType().toString(); List cookies = response.cookies(); // Work with cookies if (response.hasCookie("session")) { Cookie sessionCookie = response.cookie("session"); String value = sessionCookie.value(); String domain = sessionCookie.domain(); } // Analyze response content if (response.isStatusCodeClass(StatusCodeClass.SUCCESS_2XX)) { // Success } else if (response.isStatusCodeClass(StatusCodeClass.CLIENT_ERROR_4XX)) { // Client error } // Search for keywords in response List counts = response.keywordCounts("password", "admin", "secret"); for (KeywordCount kc : counts) { if (kc.count() > 0) { api.logging().logToOutput("Found '" + kc.keyword() + "' " + kc.count() + " times"); } } } } ``` ## Custom Vulnerability Scanning ### Implementing Active Scan Check Active scan checks send modified requests with payloads to test for vulnerabilities. The scanner handles payload encoding and insertion automatically. ```java import burp.api.montoya.scanner.scancheck.ActiveScanCheck; import burp.api.montoya.scanner.audit.AuditResult; import burp.api.montoya.scanner.audit.AuditInsertionPoint; import burp.api.montoya.scanner.audit.issues.AuditIssue; import burp.api.montoya.scanner.audit.issues.AuditIssueSeverity; import burp.api.montoya.scanner.audit.issues.AuditIssueConfidence; import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.core.ByteArray; import java.util.List; import java.util.ArrayList; public class XssActiveScanCheck implements ActiveScanCheck { private static final List XSS_PAYLOADS = List.of( "", "", "'-alert(1)-'", "\">" ); @Override public String checkName() { return "Custom XSS Check"; } @Override public AuditResult doCheck( HttpRequestResponse baseRequestResponse, AuditInsertionPoint insertionPoint, Http http ) { List issues = new ArrayList<>(); // Get base response for comparison String baseResponse = baseRequestResponse.response().bodyToString(); // Test each XSS payload for (String payload : XSS_PAYLOADS) { // Build request with payload (insertion point handles encoding) HttpRequest attackRequest = insertionPoint.buildHttpRequestWithPayload( ByteArray.byteArray(payload) ); // Send attack request HttpRequestResponse attackResponse = http.sendRequest(attackRequest); // Check if payload reflected unencoded in response if (attackResponse.response() != null) { String responseBody = attackResponse.response().bodyToString(); if (responseBody.contains(payload)) { // Vulnerability found! AuditIssue issue = AuditIssue.auditIssue( "Reflected XSS", "The application reflects user input without proper encoding. " + "The payload '" + payload + "' was injected into parameter '" + insertionPoint.name() + "' and reflected in the response.", "Implement proper output encoding using a context-aware encoding " + "library. For HTML context, encode <>\"'/ characters.", attackRequest.url(), AuditIssueSeverity.HIGH, AuditIssueConfidence.CERTAIN, baseRequestResponse.request().url(), AuditIssueCategory.XSS, null, // No collaborator interactions attackResponse ); issues.add(issue); break; // Found issue, no need to test more payloads } } } return AuditResult.auditResult(issues); } @Override public ConsolidationAction consolidateIssues( AuditIssue existingIssue, AuditIssue newIssue ) { // Keep existing issue if same URL and insertion point if (existingIssue.baseUrl().equals(newIssue.baseUrl())) { return ConsolidationAction.KEEP_EXISTING; } return ConsolidationAction.KEEP_BOTH; } } ``` ### Implementing Passive Scan Check Passive scan checks analyze requests and responses without sending additional traffic, ideal for detecting information disclosure and security misconfigurations. ```java import burp.api.montoya.scanner.scancheck.PassiveScanCheck; import burp.api.montoya.scanner.audit.AuditResult; import burp.api.montoya.scanner.audit.issues.AuditIssue; import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.responses.HttpResponse; import java.util.List; import java.util.ArrayList; import java.util.regex.Pattern; import java.util.regex.Matcher; public class SecretExposurePassiveScanCheck implements PassiveScanCheck { private static final Pattern JWT_PATTERN = Pattern.compile( "eyJ[A-Za-z0-9_-]*\\.eyJ[A-Za-z0-9_-]*\\.[A-Za-z0-9_-]*" ); private static final Pattern API_KEY_PATTERN = Pattern.compile( "(?:api[_-]?key|apikey)['\"]?\\s*[:=]\\s*['\"]?([a-zA-Z0-9]{20,})" ); @Override public String checkName() { return "Secrets in Response"; } @Override public AuditResult doCheck(HttpRequestResponse baseRequestResponse) { List issues = new ArrayList<>(); HttpResponse response = baseRequestResponse.response(); if (response == null) { return AuditResult.auditResult(issues); } String responseBody = response.bodyToString(); String responseHeaders = response.toString(); // Check for JWT tokens Matcher jwtMatcher = JWT_PATTERN.matcher(responseBody); if (jwtMatcher.find()) { String jwt = jwtMatcher.group(); issues.add(AuditIssue.auditIssue( "JWT Token Exposed", "A JWT token was found in the HTTP response: " + jwt.substring(0, Math.min(50, jwt.length())) + "...", "Avoid exposing authentication tokens in responses. " + "Tokens should be stored securely and transmitted only when necessary.", baseRequestResponse.request().url(), AuditIssueSeverity.MEDIUM, AuditIssueConfidence.CERTAIN, baseRequestResponse.request().url(), AuditIssueCategory.AUTHENTICATION, null, baseRequestResponse )); } // Check for API keys Matcher apiKeyMatcher = API_KEY_PATTERN.matcher(responseBody); if (apiKeyMatcher.find()) { issues.add(AuditIssue.auditIssue( "API Key Exposed", "An API key was found in the HTTP response body.", "Never expose API keys in client-side code or responses.", baseRequestResponse.request().url(), AuditIssueSeverity.HIGH, AuditIssueConfidence.FIRM, baseRequestResponse.request().url(), AuditIssueCategory.INFORMATION_DISCLOSURE, null, baseRequestResponse )); } // Check for missing security headers if (!response.hasHeader("X-Content-Type-Options")) { issues.add(AuditIssue.auditIssue( "Missing X-Content-Type-Options Header", "The response does not include the X-Content-Type-Options header.", "Add 'X-Content-Type-Options: nosniff' header to prevent MIME sniffing.", baseRequestResponse.request().url(), AuditIssueSeverity.LOW, AuditIssueConfidence.CERTAIN, baseRequestResponse.request().url(), AuditIssueCategory.SECURITY_MISCONFIGURATION, null, baseRequestResponse )); } return AuditResult.auditResult(issues); } @Override public ConsolidationAction consolidateIssues( AuditIssue existingIssue, AuditIssue newIssue ) { return ConsolidationAction.KEEP_EXISTING; } } ``` ### Registering Scan Checks with Scanner Scan checks must be registered with the scanner and can specify when they should be invoked using `ScanCheckType`. ```java import burp.api.montoya.scanner.Scanner; import burp.api.montoya.scanner.scancheck.ScanCheckType; import burp.api.montoya.core.Registration; public class ScannerRegistration { public void registerChecks(MontoyaApi api) { Scanner scanner = api.scanner(); // Register active scan check - runs during active scanning Registration activeScanReg = scanner.registerActiveScanCheck( new XssActiveScanCheck(), ScanCheckType.EXTENSION_GENERATED ); // Register passive scan check - runs on all traffic Registration passiveScanReg = scanner.registerPassiveScanCheck( new SecretExposurePassiveScanCheck(), ScanCheckType.ALL_CHECKS ); api.logging().logToOutput("Scan checks registered successfully"); // Can deregister later if needed // activeScanReg.deregister(); } public void startAudit(MontoyaApi api) { // Configure and start an audit programmatically AuditConfiguration auditConfig = AuditConfiguration.auditConfiguration() .withUrl("https://example.com") .withMaxConcurrentRequests(10); Audit audit = api.scanner().startAudit(auditConfig); api.logging().logToOutput("Audit started: " + audit.id()); // Monitor audit status while (!audit.isDone()) { api.logging().logToOutput( "Audit progress: " + audit.requestCount() + " requests sent" ); Thread.sleep(5000); } // Get issues found List issues = audit.issues(); api.logging().logToOutput("Audit complete. Found " + issues.size() + " issues."); } } ``` ## AI-Powered Analysis ### Using AI Chat Functionality (Professional Only) Extensions can leverage Burp's AI capabilities to analyze HTTP traffic, generate payloads, or perform intelligent security analysis. This feature requires declaring `EnhancedCapability.AI` in your extension. ```java import burp.api.montoya.ai.Ai; import burp.api.montoya.ai.chat.Prompt; import burp.api.montoya.ai.chat.PromptResponse; import burp.api.montoya.ai.chat.PromptException; import burp.api.montoya.ai.chat.Message; import burp.api.montoya.ai.chat.PromptOptions; public class AiAnalysis { private final MontoyaApi api; public AiAnalysis(MontoyaApi api) { this.api = api; } public void analyzeResponseWithAi() { Ai ai = api.ai(); // Check if AI is available if (!ai.isEnabled()) { api.logging().logToOutput("AI functionality not available"); return; } // Get prompt interface Prompt prompt = ai.prompt(); try { // Simple prompt execution PromptResponse response = prompt.execute( "Analyze this HTTP response for potential XSS vulnerabilities", "" ); api.logging().logToOutput("AI Analysis: " + response.result()); // Execute with custom options PromptOptions options = PromptOptions.promptOptions() .withTemperature(0.7) .withMaxTokens(500); PromptResponse detailedResponse = prompt.execute( options, "Generate 5 XSS payloads for testing this input field" ); api.logging().logToOutput("AI Payloads: " + detailedResponse.result()); // Use Message objects for multi-turn conversations Message systemMsg = Message.systemMessage("You are a web security expert"); Message userMsg = Message.userMessage("What SQL injection payloads should I test?"); PromptResponse sqlResponse = prompt.execute(systemMsg, userMsg); api.logging().logToOutput("SQL Payloads: " + sqlResponse.result()); } catch (PromptException e) { api.logging().logToError("AI prompt failed: " + e.getMessage()); } } public void intelligentFuzzing(HttpRequestResponse baseRequest) { Ai ai = api.ai(); if (!ai.isEnabled()) return; try { // Use AI to generate context-aware payloads String requestContext = "URL: " + baseRequest.request().url() + "\n" + "Parameters: " + baseRequest.request().parameters(); PromptResponse payloadResponse = ai.prompt().execute( "Generate 10 intelligent fuzzing payloads for this endpoint", requestContext ); String[] payloads = payloadResponse.result().split("\n"); // Test each AI-generated payload for (String payload : payloads) { HttpRequest testRequest = baseRequest.request() .withAddedParameter(HttpParameter.urlParameter("test", payload)); HttpRequestResponse response = api.http().sendRequest(testRequest); api.logging().logToOutput("Tested payload: " + payload + " - Status: " + response.response().statusCode()); } } catch (PromptException e) { api.logging().logToError("AI fuzzing failed: " + e.getMessage()); } } } ``` ## Bambda and BCheck Integration ### Importing Bambdas Programmatically Bambdas are lightweight scripts for filtering and manipulating HTTP messages. Extensions can import Bambda scripts dynamically. ```java import burp.api.montoya.bambda.Bambda; import burp.api.montoya.bambda.BambdaImportResult; public class BambdaIntegration { private final MontoyaApi api; public BambdaIntegration(MontoyaApi api) { this.api = api; } public void importCustomBambda() { Bambda bambda = api.bambda(); // Define a Bambda script for filtering requests String bambdaScript = """ // Filter requests to only show API endpoints with authentication return requestResponse.request().hasHeader("Authorization") && requestResponse.request().url().contains("/api/"); """; // Import the Bambda (replaces if same ID exists) BambdaImportResult result = bambda.importBambda(bambdaScript); if (result.isSuccess()) { api.logging().logToOutput("Bambda imported successfully: " + result.id()); } else { api.logging().logToError("Bambda import failed: " + result.errorMessage()); } } public void importMultipleBambdas() { Bambda bambda = api.bambda(); // Bambda for SQL injection detection String sqlBambda = """ // Detect potential SQL injection attempts String body = requestResponse.request().bodyToString().toLowerCase(); return body.contains("select") || body.contains("union") || body.contains("' or '1'='1"); """; // Bambda for sensitive data exposure String sensitiveDataBambda = """ // Flag responses containing sensitive keywords String response = requestResponse.response().bodyToString().toLowerCase(); return response.contains("password") || response.contains("secret") || response.contains("api_key"); """; bambda.importBambda(sqlBambda); bambda.importBambda(sensitiveDataBambda); api.logging().logToOutput("Multiple Bambdas imported"); } } ``` ### Importing BCheck Scripts BChecks are custom vulnerability detection scripts that extend Burp's scanning capabilities. Extensions can import BCheck scripts programmatically. ```java import burp.api.montoya.scanner.bchecks.BChecks; import burp.api.montoya.scanner.bchecks.BCheckImportResult; public class BCheckIntegration { private final MontoyaApi api; public BCheckIntegration(MontoyaApi api) { this.api = api; } public void importCustomBCheck() { BChecks bchecks = api.scanner().bchecks(); // Define a BCheck script for detecting custom vulnerabilities String bcheckScript = """ metadata: language: v1-beta name: "Custom Header Injection" description: "Detects injection in custom headers" author: "Security Team" run: issue: given host then send request called check: method: GET header: "X-Custom-Header" = "{random_str}" if {check.response.status_code} == 500 then report issue and continue """; // Import and enable the BCheck BCheckImportResult result = bchecks.importBCheck(bcheckScript, true); if (result.isSuccess()) { api.logging().logToOutput("BCheck imported successfully: " + result.id()); } else { api.logging().logToError("BCheck import failed: " + result.errors()); } } public void importBCheckDisabled() { BChecks bchecks = api.scanner().bchecks(); String experimentalBCheck = """ metadata: language: v1-beta name: "Experimental Check" description: "Testing new detection method" run: issue: given host then send request """; // Import but don't enable (for testing) BCheckImportResult result = bchecks.importBCheck(experimentalBCheck, false); api.logging().logToOutput("BCheck imported (disabled): " + result.id()); } } ``` ## User Interface Integration ### Creating Custom Suite Tabs Extensions can add custom tabs to the Burp Suite interface with Swing components that follow Burp's theme. ```java import burp.api.montoya.ui.UserInterface; import burp.api.montoya.core.Registration; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; public class CustomUiTab { public void createTab(MontoyaApi api) { UserInterface ui = api.userInterface(); // Create custom panel JPanel panel = new JPanel(new BorderLayout()); // Create output area JTextArea outputArea = new JTextArea(); outputArea.setEditable(false); JScrollPane scrollPane = new JScrollPane(outputArea); // Create control panel JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); JButton scanButton = new JButton("Scan Selected Host"); JTextField hostField = new JTextField("example.com", 30); scanButton.addActionListener((ActionEvent e) -> { String host = hostField.getText(); outputArea.append("Scanning: " + host + "\n"); // Perform custom scanning logic performCustomScan(api, host, outputArea); }); controlPanel.add(new JLabel("Host:")); controlPanel.add(hostField); controlPanel.add(scanButton); panel.add(controlPanel, BorderLayout.NORTH); panel.add(scrollPane, BorderLayout.CENTER); // Apply Burp theme to components ui.applyThemeToComponent(panel); // Register the tab Registration tabReg = ui.registerSuiteTab("Custom Scanner", panel); api.logging().logToOutput("Custom tab registered"); } private void performCustomScan(MontoyaApi api, String host, JTextArea output) { SwingUtilities.invokeLater(() -> { output.append("Starting scan...\n"); HttpRequest request = HttpRequest.httpRequestFromUrl("https://" + host); HttpRequestResponse response = api.http().sendRequest(request); if (response.response() != null) { output.append("Status: " + response.response().statusCode() + "\n"); output.append("Content-Type: " + response.response().headerValue("Content-Type") + "\n"); output.append("Scan complete!\n\n"); } else { output.append("ERROR: No response received\n\n"); } }); } } ``` ### Creating Custom Settings Panels Extensions can create declarative settings panels that integrate seamlessly with Burp's Settings UI. ```java import burp.api.montoya.ui.settings.*; import burp.api.montoya.persistence.PersistedObject; public class CustomSettingsPanel { public void registerSettingsPanel(MontoyaApi api) { UserInterface ui = api.userInterface(); // Build settings panel with various input types SettingsPanelWithData settingsPanel = SettingsPanelBuilder.settingsPanel() .withTitle("My Extension Settings") .withDescription("Configure custom scanning options") .withPersistence(SettingsPanelPersistence.PROJECT) .withSetting(SettingsPanelSetting.text() .name("API Endpoint") .description("Base URL for external API integration") .defaultValue("https://api.example.com") .build()) .withSetting(SettingsPanelSetting.number() .name("Max Threads") .description("Maximum concurrent scanning threads") .defaultValue(10) .minValue(1) .maxValue(50) .build()) .withSetting(SettingsPanelSetting.checkbox() .name("Enable AI Analysis") .description("Use AI-powered vulnerability detection") .defaultValue(false) .build()) .withSetting(SettingsPanelSetting.dropdown() .name("Scan Level") .description("Intensity of vulnerability scanning") .options("Light", "Normal", "Thorough") .defaultValue("Normal") .build()) .withKeywords("scanning", "api", "custom", "settings") .build(); // Register the settings panel api.userInterface().registerSettingsPanel(settingsPanel); // Access settings data PersistedObject settingsData = settingsPanel.data(); String apiEndpoint = settingsData.getString("API Endpoint", ""); int maxThreads = settingsData.getInteger("Max Threads", 10); boolean enableAi = settingsData.getBoolean("Enable AI Analysis", false); api.logging().logToOutput("Settings loaded - API: " + apiEndpoint + ", Threads: " + maxThreads + ", AI: " + enableAi); } } ``` ### Adding Context Menu Items Extensions can add custom context menu items that appear when users right-click on HTTP messages or other items in Burp. ```java import burp.api.montoya.ui.contextmenu.ContextMenuItemsProvider; import burp.api.montoya.ui.contextmenu.ContextMenuEvent; import burp.api.montoya.http.message.HttpRequestResponse; import javax.swing.*; import java.awt.*; import java.util.List; import java.util.ArrayList; public class CustomContextMenu implements ContextMenuItemsProvider { private final MontoyaApi api; public CustomContextMenu(MontoyaApi api) { this.api = api; } @Override public List provideMenuItems(ContextMenuEvent event) { List menuItems = new ArrayList<>(); // Only show menu for HTTP messages if (event.messageEditorRequestResponse().isPresent()) { HttpRequestResponse requestResponse = event.messageEditorRequestResponse().get().requestResponse(); // Menu item: Send to custom scanner JMenuItem scanItem = new JMenuItem("Send to Custom Scanner"); scanItem.addActionListener(e -> { performCustomScan(requestResponse); }); menuItems.add(scanItem); // Menu item: Export request JMenuItem exportItem = new JMenuItem("Export Request as cURL"); exportItem.addActionListener(e -> { String curl = convertToCurl(requestResponse.request()); api.logging().logToOutput(curl); // Copy to clipboard Toolkit.getDefaultToolkit() .getSystemClipboard() .setContents(new StringSelection(curl), null); JOptionPane.showMessageDialog(null, "cURL command copied to clipboard!"); }); menuItems.add(exportItem); // Menu item: Highlight parameter values JMenuItem highlightItem = new JMenuItem("Highlight All Parameters"); highlightItem.addActionListener(e -> { highlightParameters(requestResponse); }); menuItems.add(highlightItem); } return menuItems; } private void performCustomScan(HttpRequestResponse requestResponse) { api.logging().logToOutput("Scanning: " + requestResponse.request().url()); // Perform custom scanning logic List payloads = List.of("'", "\"", ""); api.logging().logToOutput("HTML encoded: " + htmlEncoded); // Compression/decompression CompressionUtils compression = utils.compressionUtils(); ByteArray data = ByteArray.byteArray("Large data to compress..."); ByteArray compressed = compression.gzipCompress(data); api.logging().logToOutput( "Compressed: " + data.length() + " -> " + compressed.length() ); ByteArray decompressed = compression.gzipDecompress(compressed); api.logging().logToOutput("Decompressed: " + decompressed.toString()); } public void useByteArrayUtilities() { Utilities utils = api.utilities(); ByteUtils byteUtils = utils.byteUtils(); String response = "HTTP/1.1 200 OK\r\nSet-Cookie: session=abc123\r\n\r\n{\"data\":\"value\"}"; ByteArray data = ByteArray.byteArray(response); // Search for patterns ByteArray pattern = ByteArray.byteArray("session="); int offset = byteUtils.indexOf(data, pattern); if (offset >= 0) { api.logging().logToOutput("Found 'session=' at offset: " + offset); // Extract session value int endOffset = byteUtils.indexOf( data, ByteArray.byteArray("\r\n"), false, offset, data.length() ); ByteArray sessionValue = data.subArray(offset + pattern.length(), endOffset); api.logging().logToOutput("Session: " + sessionValue.toString()); } // Count occurrences int count = byteUtils.countMatches(data, ByteArray.byteArray("=")); api.logging().logToOutput("Found '=' " + count + " times"); } public void useJsonUtilities() { JsonUtils jsonUtils = api.utilities().jsonUtils(); String json = "{\"user\":\"admin\",\"roles\":[\"read\",\"write\"],\"active\":true}"; // Parse and modify JSON try { JsonNode parsed = jsonUtils.parse(json); // Access JSON properties if (parsed.isJsonObject()) { JsonObjectNode obj = parsed.asJsonObject(); String username = obj.get("user").asJsonString().value(); api.logging().logToOutput("User: " + username); } // Convert back to string String formatted = jsonUtils.toJsonString(parsed); api.logging().logToOutput("Formatted JSON: " + formatted); } catch (Exception e) { api.logging().logToError("JSON parsing failed: " + e.getMessage()); } } public void useCryptoUtilities() { CryptoUtils crypto = api.utilities().cryptoUtils(); ByteArray data = ByteArray.byteArray("Secret message"); // Generate hash ByteArray md5Hash = crypto.md5Hash(data); ByteArray sha1Hash = crypto.sha1Hash(data); ByteArray sha256Hash = crypto.sha256Hash(data); api.logging().logToOutput("MD5: " + api.utilities().base64Utils().encodeToString(md5Hash)); api.logging().logToOutput("SHA256: " + api.utilities().base64Utils().encodeToString(sha256Hash)); } public void useRandomUtilities() { RandomUtils random = api.utilities().randomUtils(); // Generate random data ByteArray randomBytes = random.randomBytes(16); String randomString = random.randomString(32); int randomInt = random.randomInt(1000); api.logging().logToOutput("Random bytes: " + api.utilities().base64Utils().encodeToString(randomBytes)); api.logging().logToOutput("Random string: " + randomString); api.logging().logToOutput("Random int: " + randomInt); } } ``` ### Ranking HTTP Request/Response Pairs The RankingUtils interface allows extensions to rank HTTP traffic based on anomaly detection or other algorithms, helping identify interesting responses. ```java import burp.api.montoya.utilities.rank.*; import burp.api.montoya.http.message.HttpRequestResponse; import java.util.List; import java.util.Collections; public class ResponseRanking { private final MontoyaApi api; public ResponseRanking(MontoyaApi api) { this.api = api; } public void rankProxyHistory() { RankingUtils rankingUtils = api.utilities().rankingUtils(); // Get all proxy history List history = api.proxy().history() .stream() .map(ProxyHttpRequestResponse::finalRequest) .collect(Collectors.toList()); // Rank responses using default anomaly-based algorithm List ranked = rankingUtils.rank(history); // Sort by rank (higher rank = more interesting) Collections.sort(ranked, (a, b) -> Double.compare(b.rank(), a.rank()) ); // Display top 10 most interesting responses api.logging().logToOutput("Top 10 most interesting responses:"); for (int i = 0; i < Math.min(10, ranked.size()); i++) { RankedHttpRequestResponse item = ranked.get(i); api.logging().logToOutput( String.format("Rank: %.2f - %s (Status: %d)", item.rank(), item.requestResponse().request().url(), item.requestResponse().response().statusCode() ) ); } } public void rankWithCustomAlgorithm() { RankingUtils rankingUtils = api.utilities().rankingUtils(); // Get site map entries List siteMap = api.siteMap().requestResponses(); // Rank using specific algorithm List ranked = rankingUtils.rank( siteMap, RankingAlgorithm.ANOMALY_BASED ); // Filter high-rank items (potential vulnerabilities) List highRank = ranked.stream() .filter(r -> r.rank() > 0.7) .collect(Collectors.toList()); api.logging().logToOutput("Found " + highRank.size() + " high-rank responses for further analysis"); // Send high-rank items to Intruder for testing for (RankedHttpRequestResponse item : highRank) { api.intruder().sendToIntruder( item.requestResponse().request(), "Auto-ranked: " + item.rank() ); } } } ``` ### Executing Shell Commands Safely The ShellUtils interface provides utilities for launching external processes with proper security controls and error handling. ```java import burp.api.montoya.utilities.shell.*; public class ShellExecution { private final MontoyaApi api; public ShellExecution(MontoyaApi api) { this.api = api; } public void executeExternalTools() { ShellUtils shell = api.utilities().shellUtils(); try { // Safe execution with separate parameters String output = shell.execute("nmap", "-sV", "-p", "80,443", "example.com"); api.logging().logToOutput("Nmap output:\n" + output); // Execute with custom options ExecuteOptions options = ExecuteOptions.executeOptions() .withTimeout(30000) // 30 second timeout .withStderrBehavior(StderrBehavior.INCLUDE) .withExitCodeBehavior(ExitCodeBehavior.THROW_ON_NON_ZERO); String curlOutput = shell.execute( options, "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "https://example.com" ); api.logging().logToOutput("HTTP Status: " + curlOutput); } catch (ProcessExecutionException e) { api.logging().logToError("Shell execution failed: " + e.getMessage()); } } public void integratePythonScript() { ShellUtils shell = api.utilities().shellUtils(); try { // Execute custom Python script for analysis String scriptPath = "/path/to/custom_analyzer.py"; String targetUrl = "https://example.com"; String result = shell.execute("python3", scriptPath, targetUrl); api.logging().logToOutput("Python analysis result:\n" + result); } catch (ProcessExecutionException e) { api.logging().logToError("Python script failed: " + e.getMessage()); } } public void dangerousExecutionExample() { ShellUtils shell = api.utilities().shellUtils(); // WARNING: Only use with trusted input // This splits on whitespace and can be vulnerable to injection try { String output = shell.dangerouslyExecute("ls -la /tmp"); api.logging().logToOutput("Directory listing:\n" + output); } catch (ProcessExecutionException e) { api.logging().logToError("Execution failed: " + e.getMessage()); } } } ``` ## Data Persistence ### Storing Extension Data Across Sessions Extensions can persist data at both the project level (stored in Burp project files) and user preferences level (survives across Burp restarts). ```java import burp.api.montoya.persistence.Persistence; import burp.api.montoya.persistence.PersistedObject; import burp.api.montoya.persistence.Preferences; import java.util.List; import java.util.ArrayList; public class DataPersistence { private final MontoyaApi api; public DataPersistence(MontoyaApi api) { this.api = api; } public void useProjectLevelPersistence() { Persistence persistence = api.persistence(); PersistedObject extensionData = persistence.extensionData(); // Store simple values extensionData.setString("api_endpoint", "https://api.example.com"); extensionData.setInteger("scan_count", 42); extensionData.setBoolean("auto_scan_enabled", true); // Store lists List domains = List.of("example.com", "test.com", "demo.com"); extensionData.setStringList("scanned_domains", domains); // Store nested objects PersistedObject scanConfig = extensionData.getChildObject("scan_config"); scanConfig.setInteger("max_threads", 10); scanConfig.setInteger("timeout", 30); scanConfig.setBoolean("follow_redirects", true); api.logging().logToOutput("Data persisted to project"); } public void readProjectLevelData() { PersistedObject extensionData = api.persistence().extensionData(); // Read simple values with defaults String endpoint = extensionData.getString("api_endpoint", "https://default.com"); int scanCount = extensionData.getInteger("scan_count", 0); boolean autoScanEnabled = extensionData.getBoolean("auto_scan_enabled", false); api.logging().logToOutput("Endpoint: " + endpoint); api.logging().logToOutput("Scan count: " + scanCount); api.logging().logToOutput("Auto scan: " + autoScanEnabled); // Read lists List domains = extensionData.getStringList("scanned_domains"); if (domains != null) { api.logging().logToOutput("Scanned domains: " + String.join(", ", domains)); } // Read nested objects PersistedObject scanConfig = extensionData.getChildObject("scan_config"); if (scanConfig != null) { int maxThreads = scanConfig.getInteger("max_threads", 5); int timeout = scanConfig.getInteger("timeout", 10); api.logging().logToOutput( "Scan config - Threads: " + maxThreads + ", Timeout: " + timeout ); } } public void usePreferences() { Preferences prefs = api.persistence().preferences(); // Store user preferences (survives Burp restarts) prefs.setString("license_key", "XXXX-XXXX-XXXX"); prefs.setBoolean("dark_mode", true); prefs.setInteger("last_scan_time", (int) System.currentTimeMillis()); // Read preferences String licenseKey = prefs.getString("license_key", ""); boolean darkMode = prefs.getBoolean("dark_mode", false); api.logging().logToOutput("License: " + licenseKey); api.logging().logToOutput("Dark mode: " + darkMode); // Delete preference prefs.deleteString("old_setting"); } public void trackScanHistory() { PersistedObject data = api.persistence().extensionData(); // Get existing scan history List history = data.getStringList("scan_history"); if (history == null) { history = new ArrayList<>(); } // Add new scan String scan = System.currentTimeMillis() + "|example.com|10 issues"; history.add(scan); // Keep only last 100 scans if (history.size() > 100) { history = history.subList(history.size() - 100, history.size()); } // Save back data.setStringList("scan_history", history); api.logging().logToOutput("Scan history updated: " + history.size() + " entries"); } } ``` ## Project Information ### Accessing Project Details Extensions can retrieve information about the current Burp project, including its name and unique identifier. ```java import burp.api.montoya.project.Project; public class ProjectInfo { private final MontoyaApi api; public ProjectInfo(MontoyaApi api) { this.api = api; } public void displayProjectInfo() { Project project = api.project(); // Get project name String projectName = project.name(); api.logging().logToOutput("Current project: " + projectName); // Get unique project identifier String projectId = project.id(); api.logging().logToOutput("Project ID: " + projectId); // Use project ID for unique file naming String reportFilename = "scan_report_" + projectId + ".html"; api.logging().logToOutput("Report will be saved as: " + reportFilename); } public void projectSpecificConfiguration() { Project project = api.project(); PersistedObject extensionData = api.persistence().extensionData(); // Store project-specific settings String projectId = project.id(); PersistedObject projectSettings = extensionData.getChildObject(projectId); projectSettings.setString("target_url", "https://example.com"); projectSettings.setInteger("scan_depth", 3); api.logging().logToOutput("Settings saved for project: " + project.name()); } } ``` ## Integrating with Other Burp Tools ### Sending Requests to Intruder, Repeater, and Other Tools Extensions can programmatically send requests to various Burp tools for further testing and analysis. ```java import burp.api.montoya.intruder.Intruder; import burp.api.montoya.intruder.HttpRequestTemplate; import burp.api.montoya.repeater.Repeater; import burp.api.montoya.comparer.Comparer; import burp.api.montoya.decoder.Decoder; import burp.api.montoya.organizer.Organizer; import burp.api.montoya.sitemap.SiteMap; public class ToolIntegration { private final MontoyaApi api; public ToolIntegration(MontoyaApi api) { this.api = api; } public void sendToIntruder() { Intruder intruder = api.intruder(); // Simple send to Intruder HttpRequest request = HttpRequest.httpRequestFromUrl( "https://example.com/api/user?id=123" ); intruder.sendToIntruder(request); // Send with custom tab name intruder.sendToIntruder(request, "Custom Intruder Test"); // Send with specific insertion points marked String requestText = "GET /api/user?id=§123§&role=§admin§ HTTP/1.1\r\n" + "Host: example.com\r\n\r\n"; HttpRequestTemplate template = HttpRequestTemplate.httpRequestTemplate(requestText); HttpService service = HttpService.httpService("example.com", 443, true); intruder.sendToIntruder(service, template, "Marked Positions"); api.logging().logToOutput("Requests sent to Intruder"); } public void sendToRepeater() { Repeater repeater = api.repeater(); HttpRequest request = HttpRequest.httpRequest() .withService(HttpService.httpService("example.com", 443, true)) .withPath("/api/test") .withMethod("POST") .withAddedHeader("Content-Type", "application/json") .withBody("{\"test\":\"data\"}"); // Send to Repeater repeater.sendToRepeater(request); // Send with custom tab name repeater.sendToRepeater(request, "API Test"); api.logging().logToOutput("Request sent to Repeater"); } public void sendToComparer() { Comparer comparer = api.comparer(); // Send multiple items to compare ByteArray data1 = ByteArray.byteArray("Response version 1"); ByteArray data2 = ByteArray.byteArray("Response version 2"); comparer.sendToComparer(data1, data2); api.logging().logToOutput("Data sent to Comparer"); } public void sendToDecoder() { Decoder decoder = api.decoder(); // Send data to Decoder tool ByteArray encodedData = ByteArray.byteArray( "YWRtaW46cGFzc3dvcmQxMjM=" ); decoder.sendToDecoder(encodedData); api.logging().logToOutput("Data sent to Decoder"); } public void sendToOrganizer() { Organizer organizer = api.organizer(); // Send request to Organizer HttpRequest request = HttpRequest.httpRequestFromUrl( "https://example.com/important" ); organizer.sendToOrganizer(request); // Send request/response pair HttpRequestResponse requestResponse = api.http().sendRequest(request); organizer.sendToOrganizer(requestResponse); // Get items from Organizer List items = organizer.items(); api.logging().logToOutput("Organizer has " + items.size() + " items"); api.logging().logToOutput("Items sent to Organizer"); } public void workWithSiteMap() { SiteMap siteMap = api.siteMap(); // Get all site map items List allItems = siteMap.requestResponses(); api.logging().logToOutput("Site map has " + allItems.size() + " items"); // Get filtered items SiteMapFilter filter = SiteMapFilter.siteMapFilter() .withUrl(".*\\.example\\.com.*") .withOnlyInScope(); List filtered = siteMap.requestResponses(filter); for (HttpRequestResponse item : filtered) { api.logging().logToOutput( "URL: " + item.request().url() + " Status: " + item.response().statusCode() ); } // Add custom item to site map HttpRequest customRequest = HttpRequest.httpRequestFromUrl( "https://example.com/custom-endpoint" ); HttpRequestResponse customResponse = api.http().sendRequest(customRequest); siteMap.add(customResponse); // Get all audit issues from site map List issues = siteMap.issues(); api.logging().logToOutput("Total issues: " + issues.size()); } } ``` ## Summary The Burp Extensions Montoya API provides a comprehensive and modern interface for extending Burp Suite's functionality through custom Java extensions. The API is organized into logical domains covering HTTP traffic manipulation, security scanning, proxy control, UI customization, WebSocket handling, AI-powered analysis, Bambda/BCheck integration, response ranking, shell execution, and integration with all major Burp tools. Extensions can intercept and modify traffic in real-time, implement custom vulnerability detection logic, create rich user interfaces with settings panels and hotkeys, persist data across sessions, and leverage powerful utility functions for encoding, decoding, ranking, and data analysis. Common integration patterns include registering handlers for HTTP/WebSocket traffic interception, implementing active and passive scan checks for custom vulnerability detection, using AI prompts for intelligent security analysis, importing Bambdas and BChecks programmatically, adding context menu items and custom tabs for UI extensions, creating declarative settings panels, ranking responses to identify anomalies, executing external security tools via ShellUtils, and programmatically sending requests to Burp tools like Intruder, Repeater, and Scanner. The API uses immutable objects with builder patterns for safety, provides comprehensive type definitions for HTTP messages and security issues, supports project-level metadata access, and includes extensive utilities for common security testing tasks. Extensions can access both Community and Professional features, with advanced capabilities like the Scanner, Collaborator, and AI functionality requiring Professional edition and enhanced capability declarations.