Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to MontoyaAPI #39

Merged
merged 4 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@

<dependencies>
<dependency>
<groupId>net.portswigger.burp.extender</groupId>
<artifactId>burp-extender-api</artifactId>
<version>2.3</version>
<groupId>net.portswigger.burp.extensions</groupId>
<artifactId>montoya-api</artifactId>
<version>2023.12.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
Expand Down
14 changes: 8 additions & 6 deletions src/main/java/burp/BurpExtender.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@
*/
package burp;

import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi;
import com.cys4.sensitivediscoverer.MainUI;
import com.cys4.sensitivediscoverer.Utils;

public class BurpExtender implements IBurpExtender {

public class BurpExtender implements BurpExtension {
@Override
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) {
public void initialize(MontoyaApi burpApi) {
try {
MainUI mainUI = new MainUI(callbacks);
MainUI mainUI = new MainUI(burpApi);
mainUI.initializeUI();

callbacks.setExtensionName(mainUI.getNameExtension());
burpApi.extension().setName(mainUI.getExtensionName());

callbacks.printOutput("Extension loaded successfully!%nVersion loaded: %s".formatted(Utils.getExtensionVersion()));
burpApi.logging().logToOutput("Extension loaded successfully!%nVersion loaded: %s".formatted(Utils.getExtensionVersion()));
} catch (Exception e) {
throw new RuntimeException(e);
}

}
}
39 changes: 11 additions & 28 deletions src/main/java/com/cys4/sensitivediscoverer/MainUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
*/
package com.cys4.sensitivediscoverer;

import burp.IBurpExtenderCallbacks;
import burp.ITab;
import burp.api.montoya.MontoyaApi;
import com.cys4.sensitivediscoverer.model.RegexEntity;
import com.cys4.sensitivediscoverer.model.ScannerOptions;
import com.cys4.sensitivediscoverer.tab.AboutTab;
Expand All @@ -14,30 +13,23 @@
import com.cys4.sensitivediscoverer.tab.OptionsTab;

import javax.swing.*;
import java.awt.*;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Properties;

import static com.cys4.sensitivediscoverer.Utils.loadConfigFile;

public class MainUI implements ITab {
private final IBurpExtenderCallbacks callbacks;
public class MainUI {
private final MontoyaApi burpApi;
private final List<RegexEntity> generalRegexList;
private final List<RegexEntity> extensionsRegexList;
private final Properties configProperties;
private final ScannerOptions scannerOptions;
private JTabbedPane mainPanel;
private boolean interfaceInitialized;

public MainUI(IBurpExtenderCallbacks callbacks) throws Exception {
public MainUI(MontoyaApi burpApi) throws Exception {
this.interfaceInitialized = false;
this.callbacks = callbacks;

// setup stdout/stderr
System.setOut(new PrintStream(callbacks.getStdout(), true, StandardCharsets.UTF_8));
System.setErr(new PrintStream(callbacks.getStderr(), true, StandardCharsets.UTF_8));
this.burpApi = burpApi;

// parse configurations
this.configProperties = loadConfigFile();
Expand Down Expand Up @@ -76,8 +68,8 @@ private void _initializeUI() {
ApplicationTab aboutTab = new AboutTab();
mainPanel.addTab(aboutTab.getTabName(), aboutTab.getPanel());

callbacks.customizeUiComponent(mainPanel);
callbacks.addSuiteTab(MainUI.this);
burpApi.userInterface().applyThemeToComponent(mainPanel);
burpApi.userInterface().registerSuiteTab(this.getExtensionName(), this.getMainPanel());

this.interfaceInitialized = true;
}
Expand All @@ -89,8 +81,8 @@ public JTabbedPane getMainPanel() {
return mainPanel;
}

public IBurpExtenderCallbacks getCallbacks() {
return callbacks;
public MontoyaApi getBurpApi() {
return burpApi;
}

public List<RegexEntity> getGeneralRegexList() {
Expand All @@ -102,19 +94,10 @@ public List<RegexEntity> getExtensionsRegexList() {
}

/**
* GetNameExtension return the name of the extension from the configuration file
* getExtensionName return the name of the extension from the configuration file
*/
public String getNameExtension() {
public String getExtensionName() {
return configProperties.getProperty("ui.extension_name");
}

@Override
public String getTabCaption() {
return getNameExtension();
}

@Override
public Component getUiComponent() {
return getMainPanel();
}
}
124 changes: 56 additions & 68 deletions src/main/java/com/cys4/sensitivediscoverer/RegexScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@
*/
package com.cys4.sensitivediscoverer;

import burp.*;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.message.HttpHeader;
import burp.api.montoya.http.message.MimeType;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
import burp.api.montoya.proxy.ProxyHttpRequestResponse;
import com.cys4.sensitivediscoverer.model.LogEntity;
import com.cys4.sensitivediscoverer.model.RegexEntity;
import com.cys4.sensitivediscoverer.model.ScannerOptions;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.*;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
Expand All @@ -22,34 +26,44 @@
import java.util.stream.Stream;

public class RegexScanner {
private final IExtensionHelpers helpers;
private final IBurpExtenderCallbacks callbacks;
private final MontoyaApi burpApi;
private final ScannerOptions scannerOptions;
private final List<RegexEntity> generalRegexList;
private final List<RegexEntity> extensionsRegexList;
/**
* List of MIME types to ignore if matched while scanning
*/
private final List<String> blacklistedMimeTypes;
private final Gson gson;
/**
* Flag that indicates if the scan must be interrupted.
* Used to interrupt scan before completion.
*/
private boolean interruptScan;

public RegexScanner(IBurpExtenderCallbacks burpExtenderCallbacks,
/**
* List of MIME types to ignore while scanning when the relevant option is enabled
*/
private final EnumSet<MimeType> blacklistedMimeTypes = EnumSet.of(
MimeType.APPLICATION_FLASH,
MimeType.FONT_WOFF,
MimeType.FONT_WOFF2,
MimeType.IMAGE_BMP,
MimeType.IMAGE_GIF,
MimeType.IMAGE_JPEG,
MimeType.IMAGE_PNG,
MimeType.IMAGE_SVG_XML,
MimeType.IMAGE_TIFF,
MimeType.IMAGE_UNKNOWN,
MimeType.LEGACY_SER_AMF,
MimeType.RTF,
MimeType.SOUND,
MimeType.VIDEO
);;

public RegexScanner(MontoyaApi burpApi,
ScannerOptions scannerOptions,
List<RegexEntity> generalRegexList,
List<RegexEntity> extensionsRegexList) {
this.callbacks = burpExtenderCallbacks;
this.burpApi = burpApi;
this.scannerOptions = scannerOptions;
this.helpers = callbacks.getHelpers();
this.generalRegexList = generalRegexList;
this.extensionsRegexList = extensionsRegexList;
this.blacklistedMimeTypes = new ArrayList<>();
this.interruptScan = false;
this.gson = new Gson();
}

/**
Expand All @@ -59,7 +73,7 @@ public RegexScanner(IBurpExtenderCallbacks burpExtenderCallbacks,
* @param logEntriesCallback A callback that's called for every new finding, with the LogEntity as an argument
*/
public void analyzeProxyHistory(Consumer<Integer> itemAnalyzedCallback, Consumer<LogEntity> logEntriesCallback) {
IHttpRequestResponse[] httpProxyItems = callbacks.getProxyHistory();
List<ProxyHttpRequestResponse> proxyEntries = this.burpApi.proxy().history();

// create copy of regex list to protect from changes while scanning
List<RegexEntity> allRegexListCopy = Stream
Expand All @@ -68,18 +82,15 @@ public void analyzeProxyHistory(Consumer<Integer> itemAnalyzedCallback, Consumer
.toList();

ExecutorService executor = Executors.newFixedThreadPool(scannerOptions.getConfigNumberOfThreads());
for (int i = 0; i < httpProxyItems.length; i++) {
IHttpRequestResponse httpProxyItem = httpProxyItems[i];
int reqNumber = i + 1;
proxyEntries.forEach(proxyEntry -> {
executor.execute(() -> {
analyzeSingleMessage(reqNumber, allRegexListCopy, scannerOptions, httpProxyItem, logEntriesCallback);
analyzeSingleMessage(allRegexListCopy, scannerOptions, proxyEntry, logEntriesCallback);

if (interruptScan) return;

itemAnalyzedCallback.accept(httpProxyItems.length);
itemAnalyzedCallback.accept(proxyEntries.size());
});

}
});

try {
executor.shutdown();
Expand All @@ -97,58 +108,48 @@ public void analyzeProxyHistory(Consumer<Integer> itemAnalyzedCallback, Consumer
/**
* The main method that scan for regex in the single request body
*
* @param requestNumber The request id in burp's proxy
* @param regexList list of regexes to try and match
* @param scannerOptions options for the scanner
* @param httpProxyItem the item (request/response) from burp's http proxy
* @param proxyEntry the item (request/response) from burp's http proxy
* @param logEntriesCallback A callback function where to report findings
*/
private void analyzeSingleMessage(int requestNumber,
List<RegexEntity> regexList,
private void analyzeSingleMessage(List<RegexEntity> regexList,
ScannerOptions scannerOptions,
IHttpRequestResponse httpProxyItem,
ProxyHttpRequestResponse proxyEntry,
Consumer<LogEntity> logEntriesCallback) {
// check if URL is in scope
byte[] request = httpProxyItem.getRequest();
IRequestInfo requestInfo = helpers.analyzeRequest(httpProxyItem);
if (scannerOptions.isFilterInScopeCheckbox() && (!callbacks.isInScope(requestInfo.getUrl()))) return;
HttpRequest request = proxyEntry.finalRequest();
if (scannerOptions.isFilterInScopeCheckbox() && (!request.isInScope())) return;

// skip empty responses
byte[] response = httpProxyItem.getResponse();
HttpResponse response = proxyEntry.response();
if (Objects.isNull(response)) return;
// check for max request size
if (scannerOptions.isFilterSkipMaxSizeCheckbox() && response.length > scannerOptions.getConfigMaxResponseSize())
if (scannerOptions.isFilterSkipMaxSizeCheckbox() && response.body().length() > scannerOptions.getConfigMaxResponseSize())
return;

// check for blacklisted MIME types
IResponseInfo responseInfo = helpers.analyzeResponse(response);
if (scannerOptions.isFilterSkipMediaTypeCheckbox() && isMimeTypeBlacklisted(responseInfo.getStatedMimeType(), responseInfo.getInferredMimeType()))
if (scannerOptions.isFilterSkipMediaTypeCheckbox() && isMimeTypeBlacklisted(response.statedMimeType(), response.inferredMimeType()))
return;

int requestBodyOffset = requestInfo.getBodyOffset();
String requestBody = helpers.bytesToString(Arrays.copyOfRange(request, requestBodyOffset, request.length));
String requestHeaders = String.join("\r\n", requestInfo.getHeaders());

int responseBodyOffset = responseInfo.getBodyOffset();
String responseBody = helpers.bytesToString(Arrays.copyOfRange(response, responseBodyOffset, response.length));
String responseHeaders = String.join("\r\n", responseInfo.getHeaders());
String requestBody = request.bodyToString();
String requestHeaders = String.join("\r\n", request.headers().stream().map(HttpHeader::toString).toList());

String requestUrl = requestInfo.getUrl().toString();
String responseBody = response.bodyToString();
String responseHeaders = String.join("\r\n", response.headers().stream().map(HttpHeader::toString).toList());

for (RegexEntity entry : regexList) {
if (this.interruptScan) return;

// if the box related to the regex in the Options tab of the extension is checked
if (!entry.isActive()) continue;

getRegexMatchers(entry, requestUrl, requestHeaders, requestBody, responseHeaders, responseBody)
getRegexMatchers(entry, request.url(), requestHeaders, requestBody, responseHeaders, responseBody)
.parallelStream()
.forEach(matcher -> {
while (matcher.find()) {
logEntriesCallback.accept(new LogEntity(
httpProxyItem,
requestNumber,
helpers.analyzeRequest(httpProxyItem).getUrl(),
proxyEntry,
entry,
matcher.group()));
}
Expand All @@ -164,6 +165,7 @@ private List<Matcher> getRegexMatchers(RegexEntity regex,
String responseBody) {
Pattern regexCompiled = regex.getRegexCompiled();

//TODO keep track of section where regex matched. Show the section in the logger table;
return regex.getSections()
.parallelStream()
.map(proxyItemSection -> switch (proxyItemSection) {
Expand All @@ -182,26 +184,12 @@ private List<Matcher> getRegexMatchers(RegexEntity regex,
* Checks if the MimeType is inside the list of blacklisted mime types "mime_types.json".
* If the stated mime type in the header isBlank, then the inferred mime type is used.
*
* @param statedMimeType Stated mime type from a IResponseInfo object
* @param inferredMimeType Inferred mime type from a IResponseInfo object
* @param statedMimeType Stated mime type from a HttpResponse object
* @param inferredMimeType Inferred mime type from a HttpResponse object
* @return True if the mime type is blacklisted
*/
private boolean isMimeTypeBlacklisted(String statedMimeType, String inferredMimeType) {
String mimeType = statedMimeType.isBlank() ? inferredMimeType : statedMimeType;

if (this.blacklistedMimeTypes.isEmpty()) {
Type tArrayListString = new TypeToken<ArrayList<String>>() {
}.getType();
Stream.of("mime_types.json")
.map(Utils::readResourceFile)
.<List<String>>map(mimeTypes -> gson.fromJson(mimeTypes, tArrayListString))
// if res == null, then blacklisted will remain empty, which is fine
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.forEach(blacklistedMimeTypes::add);
}

return blacklistedMimeTypes.contains(mimeType.toUpperCase());
private boolean isMimeTypeBlacklisted(MimeType statedMimeType, MimeType inferredMimeType) {
return blacklistedMimeTypes.contains(Objects.isNull(statedMimeType) ? inferredMimeType : statedMimeType);
}

/**
Expand Down
Loading
Loading