Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95e1cb4dc1 | ||
|
|
baa7270f46 | ||
|
|
0b1d502f79 | ||
|
|
8c7ac8f47d | ||
|
|
ec4a10753f | ||
|
|
ed698b9861 | ||
|
|
c81094eb30 | ||
|
|
3608c3dca8 | ||
|
|
124e4c14fd |
37
README.md
37
README.md
@@ -65,22 +65,27 @@ We appreciate everyone's support for the project. The following list is sorted b
|
|||||||
|
|
||||||
| ID | Amount |
|
| ID | Amount |
|
||||||
| -------- | -------- |
|
| -------- | -------- |
|
||||||
| 毁三观大人 | 200.00¥ |
|
| 毁三观大人 | 200.00 CNY |
|
||||||
| ttt | 50.00¥ |
|
| ttt | 50.00 CNY |
|
||||||
| C_soon5 | 66.66¥ |
|
| C_soon5 | 66.66 CNY |
|
||||||
| 1wtbb | 25.00¥ |
|
| 1wtbb | 25.00 CNY |
|
||||||
| Deep | 66.66¥ |
|
| Deep | 66.66 CNY |
|
||||||
| NaTsUk0 | 50.00¥ |
|
| NaTsUk0 | 50.00 CNY |
|
||||||
| Kite | 48.00¥ |
|
| Kite | 48.00 CNY |
|
||||||
| 红色键盘 | 99.99¥ |
|
| 红色键盘 | 99.99 CNY |
|
||||||
| 曾哥 | 188.88¥ |
|
| 曾哥 | 188.88 CNY |
|
||||||
| NOP Team | 200.00¥ |
|
| NOP Team | 200.00 CNY |
|
||||||
| vaycore | 188.88¥ |
|
| vaycore | 188.88 CNY |
|
||||||
| xccc | 168.00¥ |
|
| xccc | 168.00 CNY |
|
||||||
| 柯林斯-民间新秀 | 1000.00¥ |
|
| 柯林斯-民间新秀 | 1000.00 CNY |
|
||||||
| Cuber | 100.00¥ |
|
| Cuber | 100.00 CNY |
|
||||||
| 时光难逆 | 50.00¥ |
|
| 时光难逆 | 50.00 CNY |
|
||||||
| Celvin | 66.00¥ |
|
| Celvin | 132.00 CNY |
|
||||||
|
| 呱呱 | 18.80 CNY |
|
||||||
|
| 红炉点雪 | 50.00 CNY |
|
||||||
|
| 王傑 | 100.00 CNY |
|
||||||
|
| 联系不到我请拨打我手机号码 | 200.00 CNY |
|
||||||
|
| Shu2e | 50.00 CNY |
|
||||||
|
|
||||||
## Support the Project
|
## Support the Project
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,12 @@ HaE目前的规则一共有8个字段,详细的含义如下所示:
|
|||||||
| 柯林斯-民间新秀 | 1000.00元 |
|
| 柯林斯-民间新秀 | 1000.00元 |
|
||||||
| Cuber | 100.00元 |
|
| Cuber | 100.00元 |
|
||||||
| 时光难逆 | 50.00元 |
|
| 时光难逆 | 50.00元 |
|
||||||
| Celvin | 66.00元 |
|
| Celvin | 132.00元 |
|
||||||
|
| 呱呱 | 18.80元 |
|
||||||
|
| 红炉点雪 | 50.00元 |
|
||||||
|
| 王傑 | 100.00元 |
|
||||||
|
| 联系不到我请拨打我手机号码 | 200.00元 |
|
||||||
|
| Shu2e | 50.00元 |
|
||||||
|
|
||||||
## 支持项目
|
## 支持项目
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public class Config {
|
|||||||
|
|
||||||
public static String status = "404";
|
public static String status = "404";
|
||||||
|
|
||||||
|
public static String header = "Last-Modified|Date|Connection|ETag";
|
||||||
|
|
||||||
public static String size = "0";
|
public static String size = "0";
|
||||||
|
|
||||||
public static String boundary = "\n\t\n";
|
public static String boundary = "\n\t\n";
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ package hae;
|
|||||||
|
|
||||||
import burp.api.montoya.BurpExtension;
|
import burp.api.montoya.BurpExtension;
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import burp.api.montoya.extension.ExtensionUnloadingHandler;
|
|
||||||
import burp.api.montoya.logging.Logging;
|
import burp.api.montoya.logging.Logging;
|
||||||
import hae.cache.MessageCache;
|
import hae.cache.DataCache;
|
||||||
import hae.component.Main;
|
import hae.component.Main;
|
||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.instances.editor.RequestEditor;
|
import hae.instances.editor.RequestEditor;
|
||||||
@@ -20,7 +19,7 @@ public class HaE implements BurpExtension {
|
|||||||
public void initialize(MontoyaApi api) {
|
public void initialize(MontoyaApi api) {
|
||||||
// 设置扩展名称
|
// 设置扩展名称
|
||||||
api.extension().setName("HaE - Highlighter and Extractor");
|
api.extension().setName("HaE - Highlighter and Extractor");
|
||||||
String version = "4.1";
|
String version = "4.2";
|
||||||
|
|
||||||
// 加载扩展后输出的项目信息
|
// 加载扩展后输出的项目信息
|
||||||
Logging logging = api.logging();
|
Logging logging = api.logging();
|
||||||
@@ -41,7 +40,7 @@ public class HaE implements BurpExtension {
|
|||||||
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
|
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
|
||||||
|
|
||||||
// 注册WebSocket处理器
|
// 注册WebSocket处理器
|
||||||
api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api)));
|
api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api, configLoader)));
|
||||||
|
|
||||||
// 注册消息编辑框(用于展示数据)
|
// 注册消息编辑框(用于展示数据)
|
||||||
api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader));
|
api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader));
|
||||||
@@ -52,21 +51,17 @@ public class HaE implements BurpExtension {
|
|||||||
DataManager dataManager = new DataManager(api);
|
DataManager dataManager = new DataManager(api);
|
||||||
dataManager.loadData(messageTableModel);
|
dataManager.loadData(messageTableModel);
|
||||||
|
|
||||||
|
api.extension().registerUnloadingHandler(() -> {
|
||||||
api.extension().registerUnloadingHandler(new ExtensionUnloadingHandler() {
|
|
||||||
@Override
|
|
||||||
public void extensionUnloaded() {
|
|
||||||
// 卸载清空数据
|
// 卸载清空数据
|
||||||
Config.globalDataMap.clear();
|
Config.globalDataMap.clear();
|
||||||
MessageCache.clear();
|
DataCache.clear();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean getBurpSuiteProStatus(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
private Boolean getBurpSuiteProStatus(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||||
boolean burpSuiteProStatus = false;
|
boolean burpSuiteProStatus = false;
|
||||||
try {
|
try {
|
||||||
burpSuiteProStatus = api.burpSuite().version().name().contains("Professional");
|
burpSuiteProStatus = api.burpSuite().version().edition().displayName().equals("Professional");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
try {
|
try {
|
||||||
api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel)).deregister();
|
api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel)).deregister();
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import com.github.benmanes.caffeine.cache.Caffeine;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class MessageCache {
|
public class DataCache {
|
||||||
private static final int MAX_SIZE = 100000;
|
private static final int MAX_SIZE = 100000;
|
||||||
private static final int EXPIRE_DURATION = 5;
|
private static final int EXPIRE_DURATION = 4;
|
||||||
|
|
||||||
private static final Cache<String, Map<String, Map<String, Object>>> cache =
|
private static final Cache<String, Map<String, Map<String, Object>>> cache =
|
||||||
Caffeine.newBuilder()
|
Caffeine.newBuilder()
|
||||||
@@ -24,10 +24,6 @@ public class MessageCache {
|
|||||||
return cache.getIfPresent(key);
|
return cache.getIfPresent(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void remove(String key) {
|
|
||||||
cache.invalidate(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear() {
|
public static void clear() {
|
||||||
cache.invalidateAll();
|
cache.invalidateAll();
|
||||||
}
|
}
|
||||||
46
src/main/java/hae/cache/DataQueryCache.java
vendored
46
src/main/java/hae/cache/DataQueryCache.java
vendored
@@ -1,46 +0,0 @@
|
|||||||
package hae.cache;
|
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class DataQueryCache {
|
|
||||||
private static final int MAX_SIZE = 1000;
|
|
||||||
private static final int EXPIRE_DURATION = 30;
|
|
||||||
|
|
||||||
private static final Cache<String, Map<String, List<String>>> hostQueryCache =
|
|
||||||
Caffeine.newBuilder()
|
|
||||||
.maximumSize(MAX_SIZE)
|
|
||||||
.expireAfterWrite(EXPIRE_DURATION, TimeUnit.MINUTES)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private static final Cache<String, List<String>> hostFilterCache =
|
|
||||||
Caffeine.newBuilder()
|
|
||||||
.maximumSize(MAX_SIZE)
|
|
||||||
.expireAfterWrite(EXPIRE_DURATION, TimeUnit.MINUTES)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public static void putHostQueryResult(String host, Map<String, List<String>> result) {
|
|
||||||
hostQueryCache.put(host, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, List<String>> getHostQueryResult(String host) {
|
|
||||||
return hostQueryCache.getIfPresent(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void putHostFilterResult(String input, List<String> result) {
|
|
||||||
hostFilterCache.put(input, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> getHostFilterResult(String input) {
|
|
||||||
return hostFilterCache.getIfPresent(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearCache() {
|
|
||||||
hostQueryCache.invalidateAll();
|
|
||||||
hostFilterCache.invalidateAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,6 @@ import javax.swing.border.EmptyBorder;
|
|||||||
import javax.swing.border.TitledBorder;
|
import javax.swing.border.TitledBorder;
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.DocumentListener;
|
import javax.swing.event.DocumentListener;
|
||||||
import javax.swing.event.TableModelEvent;
|
|
||||||
import javax.swing.event.TableModelListener;
|
import javax.swing.event.TableModelListener;
|
||||||
import javax.swing.table.DefaultTableModel;
|
import javax.swing.table.DefaultTableModel;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -74,7 +73,7 @@ public class Config extends JPanel {
|
|||||||
constraints.gridx = 1;
|
constraints.gridx = 1;
|
||||||
JTabbedPane configTabbedPanel = new JTabbedPane();
|
JTabbedPane configTabbedPanel = new JTabbedPane();
|
||||||
|
|
||||||
String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status"};
|
String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status", "Dynamic Header"};
|
||||||
JPanel settingPanel = createConfigTablePanel(settingMode);
|
JPanel settingPanel = createConfigTablePanel(settingMode);
|
||||||
|
|
||||||
JPanel northPanel = new JPanel(new BorderLayout());
|
JPanel northPanel = new JPanel(new BorderLayout());
|
||||||
@@ -174,12 +173,10 @@ public class Config extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TableModelListener craeteSettingTableModelListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
private TableModelListener craeteSettingTableModelListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||||
return new TableModelListener() {
|
return e -> {
|
||||||
@Override
|
|
||||||
public void tableChanged(TableModelEvent e) {
|
|
||||||
String selected = (String) setTypeComboBox.getSelectedItem();
|
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||||
String values = getFirstColumnDataAsString(model);
|
String values = getFirstColumnDataAsString(model);
|
||||||
|
if (selected != null) {
|
||||||
if (selected.equals("Exclude suffix")) {
|
if (selected.equals("Exclude suffix")) {
|
||||||
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
|
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
|
||||||
configLoader.setExcludeSuffix(values);
|
configLoader.setExcludeSuffix(values);
|
||||||
@@ -198,17 +195,20 @@ public class Config extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selected.equals("Dynamic Header")) {
|
||||||
|
if (!values.equals(configLoader.getExcludeStatus()) && !values.isEmpty()) {
|
||||||
|
configLoader.setDynamicHeader(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private ActionListener createSettingActionListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
private ActionListener createSettingActionListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||||
return new ActionListener() {
|
return e -> {
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
String selected = (String) setTypeComboBox.getSelectedItem();
|
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||||
model.setRowCount(0);
|
model.setRowCount(0);
|
||||||
|
if (selected != null) {
|
||||||
if (selected.equals("Exclude suffix")) {
|
if (selected.equals("Exclude suffix")) {
|
||||||
addDataToTable(configLoader.getExcludeSuffix().replaceAll("\\|", "\r\n"), model);
|
addDataToTable(configLoader.getExcludeSuffix().replaceAll("\\|", "\r\n"), model);
|
||||||
}
|
}
|
||||||
@@ -220,11 +220,14 @@ public class Config extends JPanel {
|
|||||||
if (selected.equals("Exclude status")) {
|
if (selected.equals("Exclude status")) {
|
||||||
addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model);
|
addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selected.equals("Dynamic Header")) {
|
||||||
|
addDataToTable(configLoader.getDynamicHeader().replaceAll("\\|", "\r\n"), model);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private JPanel createConfigTablePanel(String[] mode) {
|
private JPanel createConfigTablePanel(String[] mode) {
|
||||||
GridBagConstraints constraints = new GridBagConstraints();
|
GridBagConstraints constraints = new GridBagConstraints();
|
||||||
constraints.weightx = 1.0;
|
constraints.weightx = 1.0;
|
||||||
@@ -286,13 +289,13 @@ public class Config extends JPanel {
|
|||||||
settingPanel.add(inputPanel, BorderLayout.CENTER);
|
settingPanel.add(inputPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
|
||||||
addButton.addActionListener(e -> addActionPerformed(e, model, addTextField, setTypeComboBox.getSelectedItem().toString()));
|
addButton.addActionListener(e -> addActionPerformed(e, model, addTextField));
|
||||||
|
|
||||||
addTextField.addKeyListener(new KeyAdapter() {
|
addTextField.addKeyListener(new KeyAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
addActionPerformed(null, model, addTextField, setTypeComboBox.getSelectedItem().toString());
|
addActionPerformed(null, model, addTextField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -413,7 +416,7 @@ public class Config extends JPanel {
|
|||||||
configLoader.setScope(String.join("|", HaEScope));
|
configLoader.setScope(String.join("|", HaEScope));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addActionPerformed(ActionEvent e, DefaultTableModel model, JTextField addTextField, String comboBoxSelected) {
|
private void addActionPerformed(ActionEvent e, DefaultTableModel model, JTextField addTextField) {
|
||||||
String addTextFieldText = addTextField.getText();
|
String addTextFieldText = addTextField.getText();
|
||||||
if (addTextField.getForeground().equals(Color.BLACK)) {
|
if (addTextField.getForeground().equals(Color.BLACK)) {
|
||||||
addDataToTable(addTextFieldText, model);
|
addDataToTable(addTextFieldText, model);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package hae.component.board;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.cache.DataQueryCache;
|
import hae.cache.DataCache;
|
||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.component.board.message.MessageTableModel.MessageTable;
|
import hae.component.board.message.MessageTableModel.MessageTable;
|
||||||
import hae.component.board.table.Datatable;
|
import hae.component.board.table.Datatable;
|
||||||
@@ -46,19 +46,6 @@ public class Databoard extends JPanel {
|
|||||||
initComponents();
|
initComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setProgressBar(boolean status, JProgressBar progressBar, String showString) {
|
|
||||||
progressBar.setIndeterminate(status);
|
|
||||||
if (!status) {
|
|
||||||
progressBar.setMaximum(100);
|
|
||||||
progressBar.setString("OK");
|
|
||||||
progressBar.setStringPainted(true);
|
|
||||||
progressBar.setValue(progressBar.getMaximum());
|
|
||||||
} else {
|
|
||||||
progressBar.setString(showString);
|
|
||||||
progressBar.setStringPainted(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
setLayout(new GridBagLayout());
|
setLayout(new GridBagLayout());
|
||||||
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
|
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
|
||||||
@@ -67,12 +54,14 @@ public class Databoard extends JPanel {
|
|||||||
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||||
JLabel hostLabel = new JLabel("Host:");
|
JLabel hostLabel = new JLabel("Host:");
|
||||||
|
|
||||||
JButton clearButton = new JButton("Clear");
|
JButton clearDataButton = new JButton("Clear data");
|
||||||
|
JButton clearCacheButton = new JButton("Clear cache");
|
||||||
JButton actionButton = new JButton("Action");
|
JButton actionButton = new JButton("Action");
|
||||||
JPanel menuPanel = new JPanel(new GridLayout(1, 1, 0, 5));
|
JPanel menuPanel = new JPanel(new GridLayout(2, 1, 0, 5));
|
||||||
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||||
JPopupMenu menu = new JPopupMenu();
|
JPopupMenu menu = new JPopupMenu();
|
||||||
menuPanel.add(clearButton);
|
menuPanel.add(clearDataButton);
|
||||||
|
menuPanel.add(clearCacheButton);
|
||||||
menu.add(menuPanel);
|
menu.add(menuPanel);
|
||||||
|
|
||||||
hostTextField = new JTextField();
|
hostTextField = new JTextField();
|
||||||
@@ -90,7 +79,8 @@ public class Databoard extends JPanel {
|
|||||||
menu.show(actionButton, x, y);
|
menu.show(actionButton, x, y);
|
||||||
});
|
});
|
||||||
|
|
||||||
clearButton.addActionListener(this::clearActionPerformed);
|
clearDataButton.addActionListener(this::clearDataActionPerformed);
|
||||||
|
clearCacheButton.addActionListener(this::clearCacheActionPerformed);
|
||||||
|
|
||||||
progressBar = new JProgressBar();
|
progressBar = new JProgressBar();
|
||||||
splitPane.addComponentListener(new ComponentAdapter() {
|
splitPane.addComponentListener(new ComponentAdapter() {
|
||||||
@@ -136,7 +126,16 @@ public class Databoard extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setProgressBar(boolean status) {
|
private void setProgressBar(boolean status) {
|
||||||
setProgressBar(status, progressBar, "Loading ...");
|
progressBar.setIndeterminate(status);
|
||||||
|
if (!status) {
|
||||||
|
progressBar.setMaximum(100);
|
||||||
|
progressBar.setString("OK");
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
progressBar.setValue(progressBar.getMaximum());
|
||||||
|
} else {
|
||||||
|
progressBar.setString("Loading...");
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAutoMatch() {
|
private void setAutoMatch() {
|
||||||
@@ -182,7 +181,7 @@ public class Databoard extends JPanel {
|
|||||||
handleComboBoxWorker.cancel(true);
|
handleComboBoxWorker.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleComboBoxWorker = new SwingWorker<Map<String, List<String>>, Void>() {
|
handleComboBoxWorker = new SwingWorker<>() {
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, List<String>> doInBackground() {
|
protected Map<String, List<String>> doInBackground() {
|
||||||
return getSelectedMapByHost(selectedHost);
|
return getSelectedMapByHost(selectedHost);
|
||||||
@@ -253,12 +252,6 @@ public class Databoard extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
|
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
|
||||||
// 先尝试从缓存获取结果
|
|
||||||
Map<String, List<String>> cachedResult = DataQueryCache.getHostQueryResult(selectedHost);
|
|
||||||
if (cachedResult != null) {
|
|
||||||
return cachedResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||||
Map<String, List<String>> selectedDataMap;
|
Map<String, List<String>> selectedDataMap;
|
||||||
|
|
||||||
@@ -284,11 +277,6 @@ public class Databoard extends JPanel {
|
|||||||
selectedDataMap = dataMap.get(selectedHost);
|
selectedDataMap = dataMap.get(selectedHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将结果存入缓存
|
|
||||||
if (selectedDataMap != null) {
|
|
||||||
DataQueryCache.putHostQueryResult(selectedHost, selectedDataMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedDataMap;
|
return selectedDataMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,10 +311,10 @@ public class Databoard extends JPanel {
|
|||||||
applyHostFilterWorker.cancel(true);
|
applyHostFilterWorker.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyHostFilterWorker = new SwingWorker<Void, Void>() {
|
applyHostFilterWorker = new SwingWorker<>() {
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground() throws Exception {
|
protected Void doInBackground() {
|
||||||
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
RowFilter<Object, Object> rowFilter = new RowFilter<>() {
|
||||||
public boolean include(Entry<?, ?> entry) {
|
public boolean include(Entry<?, ?> entry) {
|
||||||
if (cleanedText.equals("*")) {
|
if (cleanedText.equals("*")) {
|
||||||
return true;
|
return true;
|
||||||
@@ -348,24 +336,23 @@ public class Databoard extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getHostByList() {
|
private List<String> getHostByList() {
|
||||||
// 先尝试从缓存获取结果
|
|
||||||
List<String> cachedResult = DataQueryCache.getHostFilterResult("all_hosts");
|
|
||||||
if (cachedResult != null) {
|
|
||||||
return cachedResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> result = new ArrayList<>();
|
List<String> result = new ArrayList<>();
|
||||||
if (!Config.globalDataMap.isEmpty()) {
|
if (!Config.globalDataMap.isEmpty()) {
|
||||||
result = new ArrayList<>(Config.globalDataMap.keySet());
|
result = new ArrayList<>(Config.globalDataMap.keySet());
|
||||||
// 将结果存入缓存
|
|
||||||
DataQueryCache.putHostFilterResult("all_hosts", result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearActionPerformed(ActionEvent e) {
|
private void clearCacheActionPerformed(ActionEvent e) {
|
||||||
// 清除缓存
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear cache?", "Info",
|
||||||
DataQueryCache.clearCache();
|
JOptionPane.YES_NO_OPTION);
|
||||||
|
if (retCode == JOptionPane.YES_OPTION) {
|
||||||
|
DataCache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearDataActionPerformed(ActionEvent e) {
|
||||||
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
String host = hostTextField.getText();
|
String host = hostTextField.getText();
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ import burp.api.montoya.ui.UserInterface;
|
|||||||
import burp.api.montoya.ui.editor.HttpRequestEditor;
|
import burp.api.montoya.ui.editor.HttpRequestEditor;
|
||||||
import burp.api.montoya.ui.editor.HttpResponseEditor;
|
import burp.api.montoya.ui.editor.HttpResponseEditor;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.cache.MessageCache;
|
|
||||||
import hae.utils.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.DataManager;
|
import hae.utils.DataManager;
|
||||||
import hae.utils.string.HashCalculator;
|
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
@@ -58,14 +56,25 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
messageTable.setAutoCreateRowSorter(true);
|
messageTable.setAutoCreateRowSorter(true);
|
||||||
|
|
||||||
// Length字段根据大小进行排序
|
// Length字段根据大小进行排序
|
||||||
|
TableRowSorter<DefaultTableModel> sorter = getDefaultTableModelTableRowSorter();
|
||||||
|
messageTable.setRowSorter(sorter);
|
||||||
|
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||||
|
|
||||||
|
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||||
|
// 请求/响应文本框
|
||||||
|
JScrollPane scrollPane = new JScrollPane(messageTable);
|
||||||
|
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
||||||
|
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||||
|
splitPane.setLeftComponent(scrollPane);
|
||||||
|
splitPane.setRightComponent(messageTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TableRowSorter<DefaultTableModel> getDefaultTableModelTableRowSorter() {
|
||||||
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) messageTable.getRowSorter();
|
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) messageTable.getRowSorter();
|
||||||
sorter.setComparator(4, new Comparator<String>() {
|
sorter.setComparator(4, (Comparator<String>) (s1, s2) -> {
|
||||||
@Override
|
|
||||||
public int compare(String s1, String s2) {
|
|
||||||
Integer age1 = Integer.parseInt(s1);
|
Integer age1 = Integer.parseInt(s1);
|
||||||
Integer age2 = Integer.parseInt(s2);
|
Integer age2 = Integer.parseInt(s2);
|
||||||
return age1.compareTo(age2);
|
return age1.compareTo(age2);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Color字段根据颜色顺序进行排序
|
// Color字段根据颜色顺序进行排序
|
||||||
@@ -86,48 +95,31 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
messageTable.setRowSorter(sorter);
|
return sorter;
|
||||||
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
|
||||||
|
|
||||||
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
|
||||||
// 请求/响应文本框
|
|
||||||
JScrollPane scrollPane = new JScrollPane(messageTable);
|
|
||||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
|
||||||
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
|
||||||
splitPane.setLeftComponent(scrollPane);
|
|
||||||
splitPane.setRightComponent(messageTab);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
|
public synchronized void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
|
||||||
synchronized (log) {
|
synchronized (log) {
|
||||||
boolean isDuplicate = false;
|
if (messageInfo == null) {
|
||||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
return;
|
||||||
|
|
||||||
byte[] reqByteA = new byte[0];
|
|
||||||
byte[] resByteA = new byte[0];
|
|
||||||
|
|
||||||
if (messageInfo != null) {
|
|
||||||
HttpRequest httpRequest = messageInfo.request();
|
|
||||||
HttpResponse httpResponse = messageInfo.response();
|
|
||||||
|
|
||||||
reqByteA = httpRequest.toByteArray().getBytes();
|
|
||||||
resByteA = httpResponse.toByteArray().getBytes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
boolean isDuplicate = false;
|
||||||
try {
|
try {
|
||||||
if (!log.isEmpty()) {
|
if (!log.isEmpty() && flag) {
|
||||||
|
String host = StringProcessor.getHostByUrl(url);
|
||||||
|
|
||||||
for (MessageEntry entry : log) {
|
for (MessageEntry entry : log) {
|
||||||
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
if (host.equals(StringProcessor.getHostByUrl(entry.getUrl()))) {
|
||||||
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
if (isRequestDuplicate(
|
||||||
byte[] resByteB = reqResMessage.response().toByteArray().getBytes();
|
messageInfo, entry.getRequestResponse(),
|
||||||
try {
|
url, entry.getUrl(),
|
||||||
// 通过URL、请求和响应报文、匹配数据内容,多维度进行对比
|
comment, entry.getComment(),
|
||||||
if ((entry.getUrl().equals(url) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) {
|
color, entry.getColor()
|
||||||
|
)) {
|
||||||
isDuplicate = true;
|
isDuplicate = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,42 +128,82 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
|
|
||||||
if (!isDuplicate) {
|
if (!isDuplicate) {
|
||||||
if (flag) {
|
if (flag) {
|
||||||
|
persistData(messageInfo, comment, color);
|
||||||
|
}
|
||||||
|
log.add(new MessageEntry(messageInfo, method, url, comment, length, color, status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRequestDuplicate(
|
||||||
|
HttpRequestResponse newReq, HttpRequestResponse existingReq,
|
||||||
|
String newUrl, String existingUrl,
|
||||||
|
String newComment, String existingComment,
|
||||||
|
String newColor, String existingColor) {
|
||||||
|
try {
|
||||||
|
// 基础属性匹配
|
||||||
|
String normalizedNewUrl = normalizeUrl(newUrl);
|
||||||
|
String normalizedExistingUrl = normalizeUrl(existingUrl);
|
||||||
|
boolean basicMatch = normalizedNewUrl.equals(normalizedExistingUrl);
|
||||||
|
|
||||||
|
// 请求响应内容匹配
|
||||||
|
byte[] newReqBytes = newReq.request().toByteArray().getBytes();
|
||||||
|
byte[] newResBytes = newReq.response().toByteArray().getBytes();
|
||||||
|
byte[] existingReqBytes = existingReq.request().toByteArray().getBytes();
|
||||||
|
byte[] existingResBytes = existingReq.response().toByteArray().getBytes();
|
||||||
|
boolean contentMatch = Arrays.equals(newReqBytes, existingReqBytes) &&
|
||||||
|
Arrays.equals(newResBytes, existingResBytes);
|
||||||
|
|
||||||
|
// 注释和颜色匹配
|
||||||
|
boolean metadataMatch = areCommentsEqual(newComment, existingComment) &&
|
||||||
|
newColor.equals(existingColor);
|
||||||
|
|
||||||
|
return (basicMatch || contentMatch) && metadataMatch;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeUrl(String url) {
|
||||||
|
if (url == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = url.trim().toLowerCase();
|
||||||
|
while (normalized.endsWith("/")) {
|
||||||
|
normalized = normalized.substring(0, normalized.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized.replaceAll("//", "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean areCommentsEqual(String comment1, String comment2) {
|
||||||
|
if (comment1 == null || comment2 == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 将注释按规则拆分并排序
|
||||||
|
Set<String> rules1 = new TreeSet<>(Arrays.asList(comment1.split(", ")));
|
||||||
|
Set<String> rules2 = new TreeSet<>(Arrays.asList(comment2.split(", ")));
|
||||||
|
|
||||||
|
return rules1.equals(rules2);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistData(HttpRequestResponse messageInfo, String comment, String color) {
|
||||||
try {
|
try {
|
||||||
DataManager dataManager = new DataManager(api);
|
DataManager dataManager = new DataManager(api);
|
||||||
// 数据存储在BurpSuite空间内
|
|
||||||
PersistedObject persistedObject = PersistedObject.persistedObject();
|
PersistedObject persistedObject = PersistedObject.persistedObject();
|
||||||
persistedObject.setHttpRequestResponse("messageInfo", messageInfo);
|
persistedObject.setHttpRequestResponse("messageInfo", messageInfo);
|
||||||
persistedObject.setString("comment", comment);
|
persistedObject.setString("comment", comment);
|
||||||
persistedObject.setString("color", color);
|
persistedObject.setString("color", color);
|
||||||
String uuidIndex = StringProcessor.getRandomUUID();
|
String uuidIndex = StringProcessor.getRandomUUID();
|
||||||
dataManager.putData("message", uuidIndex, persistedObject);
|
dataManager.putData("message", uuidIndex, persistedObject);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("Data persistence error: " + e.getMessage());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加进日志
|
|
||||||
log.add(logEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void addBatch(List<Object[]> batchData) {
|
|
||||||
synchronized (log) {
|
|
||||||
for (Object[] data : batchData) {
|
|
||||||
HttpRequestResponse messageInfo = (HttpRequestResponse) data[0];
|
|
||||||
String url = (String) data[1];
|
|
||||||
String method = (String) data[2];
|
|
||||||
String status = (String) data[3];
|
|
||||||
String length = (String) data[4];
|
|
||||||
String comment = (String) data[5];
|
|
||||||
String color = (String) data[6];
|
|
||||||
|
|
||||||
// 复用现有的 add 方法逻辑,但跳过重复检查
|
|
||||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
|
||||||
log.add(logEntry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +215,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
currentWorker.cancel(true);
|
currentWorker.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentWorker = new SwingWorker<Void, Void>() {
|
currentWorker = new SwingWorker<>() {
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground() {
|
protected Void doInBackground() {
|
||||||
for (int i = 0; i < log.size(); i++) {
|
for (int i = 0; i < log.size(); i++) {
|
||||||
@@ -210,17 +242,40 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
|
|
||||||
public void applyHostFilter(String filterText) {
|
public void applyHostFilter(String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
|
|
||||||
log.forEach(entry -> {
|
|
||||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
|
||||||
if (!host.isEmpty()) {
|
|
||||||
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
|
|
||||||
filteredLog.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
|
|
||||||
|
int batchSize = 500;
|
||||||
|
|
||||||
|
// 分批处理数据
|
||||||
|
List<MessageEntry> batch = new ArrayList<>(batchSize);
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (MessageEntry entry : log) {
|
||||||
|
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||||
|
if (!host.isEmpty() && (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*"))) {
|
||||||
|
batch.add(entry);
|
||||||
|
count++;
|
||||||
|
|
||||||
|
// 当批次达到指定大小时,更新UI
|
||||||
|
if (count % batchSize == 0) {
|
||||||
|
final List<MessageEntry> currentBatch = new ArrayList<>(batch);
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
filteredLog.addAll(currentBatch);
|
||||||
|
fireTableDataChanged();
|
||||||
|
});
|
||||||
|
batch.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理最后一批
|
||||||
|
if (!batch.isEmpty()) {
|
||||||
|
final List<MessageEntry> finalBatch = new ArrayList<>(batch);
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
filteredLog.addAll(finalBatch);
|
||||||
|
fireTableDataChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyMessageFilter(String tableName, String filterText) {
|
public void applyMessageFilter(String tableName, String filterText) {
|
||||||
@@ -237,13 +292,13 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||||
String requestHeaders = httpRequest.headers().stream()
|
String requestHeaders = httpRequest.headers().stream()
|
||||||
.map(HttpHeader::toString)
|
.map(HttpHeader::toString)
|
||||||
.collect(Collectors.joining("\n"));
|
.collect(Collectors.joining("\r\n"));
|
||||||
|
|
||||||
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||||
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
||||||
String responseHeaders = httpResponse.headers().stream()
|
String responseHeaders = httpResponse.headers().stream()
|
||||||
.map(HttpHeader::toString)
|
.map(HttpHeader::toString)
|
||||||
.collect(Collectors.joining("\n"));
|
.collect(Collectors.joining("\r\n"));
|
||||||
|
|
||||||
Config.globalRules.keySet().forEach(i -> {
|
Config.globalRules.keySet().forEach(i -> {
|
||||||
for (Object[] objects : Config.globalRules.get(i)) {
|
for (Object[] objects : Config.globalRules.get(i)) {
|
||||||
@@ -333,56 +388,6 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
return isMatch;
|
return isMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Map<String, Object>> getCacheData(byte[] content) {
|
|
||||||
String hashIndex = HashCalculator.calculateHash(content);
|
|
||||||
return MessageCache.get(hashIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
|
|
||||||
if (map1 == null || map2 == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (map1.size() != map2.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String key : map1.keySet()) {
|
|
||||||
if (!map2.containsKey(key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
|
||||||
if (innerMap1.size() != innerMap2.size()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String key : innerMap1.keySet()) {
|
|
||||||
if (!innerMap2.containsKey(key)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Object value1 = innerMap1.get(key);
|
|
||||||
Object value2 = innerMap2.get(key);
|
|
||||||
|
|
||||||
// 如果值是Map,则递归对比
|
|
||||||
if (value1 instanceof Map && value2 instanceof Map) {
|
|
||||||
if (areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (!value1.equals(value2)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSplitPane getSplitPane() {
|
public JSplitPane getSplitPane() {
|
||||||
return splitPane;
|
return splitPane;
|
||||||
}
|
}
|
||||||
@@ -391,10 +396,6 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
return messageTable;
|
return messageTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinkedList<MessageEntry> getLogs() {
|
|
||||||
return log;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return filteredLog.size();
|
return filteredLog.size();
|
||||||
@@ -447,7 +448,6 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
private final ExecutorService executorService;
|
private final ExecutorService executorService;
|
||||||
private final HttpRequestEditor requestEditor;
|
private final HttpRequestEditor requestEditor;
|
||||||
private final HttpResponseEditor responseEditor;
|
private final HttpResponseEditor responseEditor;
|
||||||
private MessageEntry messageEntry;
|
|
||||||
private int lastSelectedIndex = -1;
|
private int lastSelectedIndex = -1;
|
||||||
|
|
||||||
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
|
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
|
||||||
@@ -468,7 +468,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void getSelectedMessage() {
|
private void getSelectedMessage() {
|
||||||
messageEntry = filteredLog.get(lastSelectedIndex);
|
MessageEntry messageEntry = filteredLog.get(lastSelectedIndex);
|
||||||
|
|
||||||
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
|
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public class Datatable extends JPanel {
|
|||||||
private final JCheckBox regexMode = new JCheckBox("Regex mode");
|
private final JCheckBox regexMode = new JCheckBox("Regex mode");
|
||||||
private final String tabName;
|
private final String tabName;
|
||||||
private final JPanel footerPanel;
|
private final JPanel footerPanel;
|
||||||
|
private SwingWorker<Void, Void> doubleClickWorker;
|
||||||
|
|
||||||
public Datatable(MontoyaApi api, ConfigLoader configLoader, String tabName, List<String> dataList) {
|
public Datatable(MontoyaApi api, ConfigLoader configLoader, String tabName, List<String> dataList) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
@@ -55,12 +56,7 @@ public class Datatable extends JPanel {
|
|||||||
dataTable.setRowSorter(sorter);
|
dataTable.setRowSorter(sorter);
|
||||||
|
|
||||||
// 设置ID排序
|
// 设置ID排序
|
||||||
sorter.setComparator(0, new Comparator<Integer>() {
|
sorter.setComparator(0, (Comparator<Integer>) Integer::compareTo);
|
||||||
@Override
|
|
||||||
public int compare(Integer s1, Integer s2) {
|
|
||||||
return s1.compareTo(s2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (String item : dataList) {
|
for (String item : dataList) {
|
||||||
if (!item.isEmpty()) {
|
if (!item.isEmpty()) {
|
||||||
@@ -180,7 +176,7 @@ public class Datatable extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RowFilter<Object, Object> getObjectObjectRowFilter(JTextField searchField, boolean firstFlag) {
|
private RowFilter<Object, Object> getObjectObjectRowFilter(JTextField searchField, boolean firstFlag) {
|
||||||
return new RowFilter<Object, Object>() {
|
return new RowFilter<>() {
|
||||||
public boolean include(Entry<?, ?> entry) {
|
public boolean include(Entry<?, ?> entry) {
|
||||||
String searchFieldTextText = searchField.getText();
|
String searchFieldTextText = searchField.getText();
|
||||||
searchFieldTextText = searchFieldTextText.toLowerCase();
|
searchFieldTextText = searchFieldTextText.toLowerCase();
|
||||||
@@ -206,6 +202,26 @@ public class Datatable extends JPanel {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleDoubleClick(int selectedRow, MessageTableModel messagePanel) {
|
||||||
|
if (doubleClickWorker != null && !doubleClickWorker.isDone()) {
|
||||||
|
doubleClickWorker.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
doubleClickWorker = new SwingWorker<>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() {
|
||||||
|
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
if (!isCancelled()) {
|
||||||
|
messagePanel.applyMessageFilter(tabName, rowData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
doubleClickWorker.execute();
|
||||||
|
}
|
||||||
|
|
||||||
public void setTableListener(MessageTableModel messagePanel) {
|
public void setTableListener(MessageTableModel messagePanel) {
|
||||||
// 表格复制功能
|
// 表格复制功能
|
||||||
dataTable.setTransferHandler(new TransferHandler() {
|
dataTable.setTransferHandler(new TransferHandler() {
|
||||||
@@ -229,8 +245,7 @@ public class Datatable extends JPanel {
|
|||||||
if (e.getClickCount() == 2) {
|
if (e.getClickCount() == 2) {
|
||||||
int selectedRow = dataTable.getSelectedRow();
|
int selectedRow = dataTable.getSelectedRow();
|
||||||
if (selectedRow != -1) {
|
if (selectedRow != -1) {
|
||||||
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
|
handleDoubleClick(selectedRow, messagePanel);
|
||||||
messagePanel.applyMessageFilter(tabName, rowData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public class Rule extends JPanel {
|
|||||||
Display ruleDisplay = new Display();
|
Display ruleDisplay = new Display();
|
||||||
ruleDisplay.formatTextField.setText("{0}");
|
ruleDisplay.formatTextField.setText("{0}");
|
||||||
|
|
||||||
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
|
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Add Rule", JOptionPane.YES_NO_OPTION);
|
||||||
if (showState == YES_OPTION) {
|
if (showState == YES_OPTION) {
|
||||||
Vector<Object> ruleData = new Vector<>();
|
Vector<Object> ruleData = new Vector<>();
|
||||||
ruleData.add(false);
|
ruleData.add(false);
|
||||||
@@ -132,7 +132,7 @@ public class Rule extends JPanel {
|
|||||||
|
|
||||||
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
||||||
|
|
||||||
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
|
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Edit Rule", JOptionPane.YES_NO_OPTION);
|
||||||
if (showState == 0) {
|
if (showState == 0) {
|
||||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||||
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
||||||
|
|||||||
@@ -59,8 +59,6 @@ public class Rules extends JTabbedPane {
|
|||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
reloadRuleGroup();
|
reloadRuleGroup();
|
||||||
|
|
||||||
JTabbedPane tabbedPane = this;
|
|
||||||
|
|
||||||
JMenuItem deleteMenuItem = new JMenuItem("Delete");
|
JMenuItem deleteMenuItem = new JMenuItem("Delete");
|
||||||
JPopupMenu popupMenu = new JPopupMenu();
|
JPopupMenu popupMenu = new JPopupMenu();
|
||||||
popupMenu.add(deleteMenuItem);
|
popupMenu.add(deleteMenuItem);
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
this.httpUtils = new HttpUtils(api, configLoader);
|
this.httpUtils = new HttpUtils(api, configLoader);
|
||||||
this.creationContext = creationContext;
|
this.creationContext = creationContext;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class ResponseEditor implements HttpResponseEditorProvider {
|
|||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
this.httpUtils = new HttpUtils(api, configLoader);
|
this.httpUtils = new HttpUtils(api, configLoader);
|
||||||
this.creationContext = creationContext;
|
this.creationContext = creationContext;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
|
|||||||
this.api = api;
|
this.api = api;
|
||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
this.creationContext = creationContext;
|
this.creationContext = creationContext;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class HttpMessageActiveHandler implements HttpHandler {
|
|||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
this.httpUtils = new HttpUtils(api, configLoader);
|
this.httpUtils = new HttpUtils(api, configLoader);
|
||||||
this.messageTableModel = messageTableModel;
|
this.messageTableModel = messageTableModel;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class HttpMessagePassiveHandler implements ScanCheck {
|
|||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
this.httpUtils = new HttpUtils(api, configLoader);
|
this.httpUtils = new HttpUtils(api, configLoader);
|
||||||
this.messageTableModel = messageTableModel;
|
this.messageTableModel = messageTableModel;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import burp.api.montoya.http.message.HttpHeader;
|
|||||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||||
import burp.api.montoya.http.message.responses.HttpResponse;
|
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -14,18 +15,16 @@ public class MessageProcessor {
|
|||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final RegularMatcher regularMatcher;
|
private final RegularMatcher regularMatcher;
|
||||||
|
|
||||||
private String finalColor = "";
|
public MessageProcessor(MontoyaApi api, ConfigLoader configLoader) {
|
||||||
|
|
||||||
public MessageProcessor(MontoyaApi api) {
|
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.regularMatcher = new RegularMatcher(api);
|
this.regularMatcher = new RegularMatcher(api, configLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Map<String, String>> processMessage(String host, String message, boolean flag) {
|
public List<Map<String, String>> processMessage(String host, String message, boolean flag) {
|
||||||
Map<String, Map<String, Object>> obj = null;
|
Map<String, Map<String, Object>> obj = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
obj = regularMatcher.match(host, "any", message, message, message);
|
obj = regularMatcher.performRegexMatching(host, "any", message, message, message);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,9 +39,9 @@ public class MessageProcessor {
|
|||||||
String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
||||||
String header = httpResponse.headers().stream()
|
String header = httpResponse.headers().stream()
|
||||||
.map(HttpHeader::toString)
|
.map(HttpHeader::toString)
|
||||||
.collect(Collectors.joining("\n"));
|
.collect(Collectors.joining("\r\n"));
|
||||||
|
|
||||||
obj = regularMatcher.match(host, "response", response, header, body);
|
obj = regularMatcher.performRegexMatching(host, "response", response, header, body);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +56,9 @@ public class MessageProcessor {
|
|||||||
String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||||
String header = httpRequest.headers().stream()
|
String header = httpRequest.headers().stream()
|
||||||
.map(HttpHeader::toString)
|
.map(HttpHeader::toString)
|
||||||
.collect(Collectors.joining("\n"));
|
.collect(Collectors.joining("\r\n"));
|
||||||
|
|
||||||
obj = regularMatcher.match(host, "request", request, header, body);
|
obj = regularMatcher.performRegexMatching(host, "request", request, header, body);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,10 +76,10 @@ public class MessageProcessor {
|
|||||||
List<String> commentList = resultList.get(1);
|
List<String> commentList = resultList.get(1);
|
||||||
if (!colorList.isEmpty() && !commentList.isEmpty()) {
|
if (!colorList.isEmpty() && !commentList.isEmpty()) {
|
||||||
String color = retrieveFinalColor(retrieveColorIndices(colorList));
|
String color = retrieveFinalColor(retrieveColorIndices(colorList));
|
||||||
Map<String, String> colorMap = new HashMap<String, String>() {{
|
Map<String, String> colorMap = new HashMap<>() {{
|
||||||
put("color", color);
|
put("color", color);
|
||||||
}};
|
}};
|
||||||
Map<String, String> commentMap = new HashMap<String, String>() {{
|
Map<String, String> commentMap = new HashMap<>() {{
|
||||||
put("comment", String.join(", ", commentList));
|
put("comment", String.join(", ", commentList));
|
||||||
}};
|
}};
|
||||||
highlightList.add(colorMap);
|
highlightList.add(colorMap);
|
||||||
@@ -137,41 +136,38 @@ public class MessageProcessor {
|
|||||||
return indices;
|
return indices;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void upgradeColors(List<Integer> colorList) {
|
private String upgradeColors(List<Integer> colorList) {
|
||||||
int colorSize = colorList.size();
|
if (colorList == null || colorList.isEmpty()) {
|
||||||
String[] colorArray = Config.color;
|
return Config.color[0];
|
||||||
colorList.sort(Comparator.comparingInt(Integer::intValue));
|
}
|
||||||
int i = 0;
|
|
||||||
List<Integer> stack = new ArrayList<>();
|
// 创建副本避免修改原始数据
|
||||||
while (i < colorSize) {
|
List<Integer> indices = new ArrayList<>(colorList);
|
||||||
if (stack.isEmpty()) {
|
indices.sort(Comparator.comparingInt(Integer::intValue));
|
||||||
stack.add(colorList.get(i));
|
|
||||||
} else {
|
// 处理颜色升级
|
||||||
if (!Objects.equals(colorList.get(i), stack.stream().reduce((first, second) -> second).orElse(99999999))) {
|
for (int i = 1; i < indices.size(); i++) {
|
||||||
stack.add(colorList.get(i));
|
if (indices.get(i).equals(indices.get(i - 1))) {
|
||||||
} else {
|
// 如果发现重复的颜色索引,将当前索引降级
|
||||||
stack.set(stack.size() - 1, stack.get(stack.size() - 1) - 1);
|
indices.set(i - 1, indices.get(i - 1) - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
}
|
// 获取最终的颜色索引
|
||||||
// 利用HashSet删除重复元素
|
int finalIndex = indices.stream()
|
||||||
HashSet tmpList = new HashSet(stack);
|
.min(Integer::compareTo)
|
||||||
if (stack.size() == tmpList.size()) {
|
.orElse(0);
|
||||||
stack.sort(Comparator.comparingInt(Integer::intValue));
|
|
||||||
if (stack.get(0) < 0) {
|
// 处理负数索引情况
|
||||||
finalColor = colorArray[0];
|
if (finalIndex < 0) {
|
||||||
} else {
|
return Config.color[0];
|
||||||
finalColor = colorArray[stack.get(0)];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
upgradeColors(stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Config.color[finalIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
public String retrieveFinalColor(List<Integer> colorList) {
|
public String retrieveFinalColor(List<Integer> colorList) {
|
||||||
upgradeColors(colorList);
|
return upgradeColors(colorList);
|
||||||
return finalColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import dk.brics.automaton.AutomatonMatcher;
|
|||||||
import dk.brics.automaton.RegExp;
|
import dk.brics.automaton.RegExp;
|
||||||
import dk.brics.automaton.RunAutomaton;
|
import dk.brics.automaton.RunAutomaton;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import hae.cache.MessageCache;
|
import hae.cache.DataCache;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.DataManager;
|
import hae.utils.DataManager;
|
||||||
import hae.utils.string.HashCalculator;
|
import hae.utils.string.HashCalculator;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
@@ -20,14 +21,17 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class RegularMatcher {
|
public class RegularMatcher {
|
||||||
|
private static final Map<String, Pattern> nfaPatternCache = new ConcurrentHashMap<>();
|
||||||
|
private static final Map<String, RunAutomaton> dfaAutomatonCache = new ConcurrentHashMap<>();
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
|
private final ConfigLoader configLoader;
|
||||||
|
|
||||||
public RegularMatcher(MontoyaApi api) {
|
public RegularMatcher(MontoyaApi api, ConfigLoader configLoader) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
this.configLoader = configLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
|
public synchronized static void updateGlobalMatchCache(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
|
||||||
// 添加到全局变量中,便于Databoard检索
|
// 添加到全局变量中,便于Databoard检索
|
||||||
if (!Objects.equals(host, "") && host != null) {
|
if (!Objects.equals(host, "") && host != null) {
|
||||||
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
||||||
@@ -74,18 +78,41 @@ public class RegularMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
|
public Map<String, Map<String, Object>> performRegexMatching(String host, String type, String message, String header, String body) {
|
||||||
// 先从缓存池里判断是否有已经匹配好的结果
|
// 删除动态响应头再进行存储
|
||||||
|
String originalMessage = message;
|
||||||
|
String dynamicHeader = configLoader.getDynamicHeader();
|
||||||
|
|
||||||
|
if (!dynamicHeader.isBlank()) {
|
||||||
|
String modifiedHeader = header.replaceAll(String.format("(%s):.*?\r\n", configLoader.getDynamicHeader()), "");
|
||||||
|
message = message.replace(header, modifiedHeader);
|
||||||
|
}
|
||||||
|
|
||||||
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
||||||
Map<String, Map<String, Object>> map = MessageCache.get(messageIndex);
|
|
||||||
if (map != null) {
|
// 从数据缓存中读取
|
||||||
return map;
|
Map<String, Map<String, Object>> dataCacheMap = DataCache.get(messageIndex);
|
||||||
} else {
|
|
||||||
|
// 存在则返回
|
||||||
|
if (dataCacheMap != null) {
|
||||||
|
return dataCacheMap;
|
||||||
|
}
|
||||||
|
|
||||||
// 最终返回的结果
|
// 最终返回的结果
|
||||||
|
String firstLine = originalMessage.split("\\r?\\n", 2)[0];
|
||||||
|
Map<String, Map<String, Object>> finalMap = applyMatchingRules(host, type, originalMessage, firstLine, header, body);
|
||||||
|
|
||||||
|
// 数据缓存写入,有可能是空值,当作匹配过的索引不再匹配
|
||||||
|
DataCache.put(messageIndex, finalMap);
|
||||||
|
|
||||||
|
return finalMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Map<String, Object>> applyMatchingRules(String host, String type, String message, String firstLine, String header, String body) {
|
||||||
Map<String, Map<String, Object>> finalMap = new HashMap<>();
|
Map<String, Map<String, Object>> finalMap = new HashMap<>();
|
||||||
|
|
||||||
Config.globalRules.keySet().parallelStream().forEach(i -> {
|
Config.globalRules.keySet().parallelStream().forEach(i -> {
|
||||||
for (Object[] objects : Config.globalRules.get(i)) {
|
for (Object[] objects : Config.globalRules.get(i)) {
|
||||||
// 多线程执行,一定程度上减少阻塞现象
|
|
||||||
String matchContent = "";
|
String matchContent = "";
|
||||||
// 遍历获取规则
|
// 遍历获取规则
|
||||||
List<String> result;
|
List<String> result;
|
||||||
@@ -103,6 +130,7 @@ public class RegularMatcher {
|
|||||||
|
|
||||||
// 判断规则是否开启与作用域
|
// 判断规则是否开启与作用域
|
||||||
if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) {
|
if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) {
|
||||||
|
// 在此处检查内容是否缓存,缓存则返回为空
|
||||||
switch (scope) {
|
switch (scope) {
|
||||||
case "any":
|
case "any":
|
||||||
case "request":
|
case "request":
|
||||||
@@ -121,14 +149,19 @@ public class RegularMatcher {
|
|||||||
break;
|
break;
|
||||||
case "request line":
|
case "request line":
|
||||||
case "response line":
|
case "response line":
|
||||||
matchContent = message.split("\\r?\\n", 2)[0];
|
matchContent = firstLine;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 匹配内容为空则跳出
|
||||||
|
if (matchContent.isBlank()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = new ArrayList<>(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive));
|
result = new ArrayList<>(executeRegexEngine(f_regex, s_regex, matchContent, format, engine, sensitive));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
|
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
|
||||||
api.logging().logToError(e.getMessage());
|
api.logging().logToError(e.getMessage());
|
||||||
@@ -148,21 +181,20 @@ public class RegularMatcher {
|
|||||||
String nameAndSize = String.format("%s (%s)", name, result.size());
|
String nameAndSize = String.format("%s (%s)", name, result.size());
|
||||||
finalMap.put(nameAndSize, tmpMap);
|
finalMap.put(nameAndSize, tmpMap);
|
||||||
|
|
||||||
putDataToGlobalMap(api, host, name, result, true);
|
updateGlobalMatchCache(api, host, name, result, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
MessageCache.put(messageIndex, finalMap);
|
|
||||||
return finalMap;
|
return finalMap;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
private List<String> executeRegexEngine(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
||||||
List<String> retList = new ArrayList<>();
|
List<String> retList = new ArrayList<>();
|
||||||
if ("nfa".equals(engine)) {
|
if ("nfa".equals(engine)) {
|
||||||
Matcher matcher = createPatternMatcher(f_regex, content, sensitive);
|
Matcher matcher = createPatternMatcher(f_regex, content, sensitive);
|
||||||
retList.addAll(extractMatches(s_regex, format, sensitive, matcher));
|
retList.addAll(extractRegexMatchResults(s_regex, format, sensitive, matcher));
|
||||||
} else {
|
} else {
|
||||||
// DFA不支持格式化输出,因此不关注format
|
// DFA不支持格式化输出,因此不关注format
|
||||||
String newContent = content;
|
String newContent = content;
|
||||||
@@ -172,44 +204,44 @@ public class RegularMatcher {
|
|||||||
newFirstRegex = f_regex.toLowerCase();
|
newFirstRegex = f_regex.toLowerCase();
|
||||||
}
|
}
|
||||||
AutomatonMatcher autoMatcher = createAutomatonMatcher(newFirstRegex, newContent);
|
AutomatonMatcher autoMatcher = createAutomatonMatcher(newFirstRegex, newContent);
|
||||||
retList.addAll(extractMatches(s_regex, autoMatcher, content));
|
retList.addAll(extractRegexMatchResults(s_regex, autoMatcher, content));
|
||||||
}
|
}
|
||||||
return retList;
|
return retList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> extractMatches(String s_regex, String format, boolean sensitive, Matcher matcher) {
|
private List<String> extractRegexMatchResults(String s_regex, String format, boolean sensitive, Matcher matcher) {
|
||||||
List<String> matches = new ArrayList<>();
|
List<String> matches = new ArrayList<>();
|
||||||
if (s_regex.isEmpty()) {
|
if (s_regex.isEmpty()) {
|
||||||
matches.addAll(getFormatString(matcher, format));
|
matches.addAll(formatMatchResults(matcher, format));
|
||||||
} else {
|
} else {
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String matchContent = matcher.group(1);
|
String matchContent = matcher.group(1);
|
||||||
if (!matchContent.isEmpty()) {
|
if (!matchContent.isEmpty()) {
|
||||||
matcher = createPatternMatcher(s_regex, matchContent, sensitive);
|
matcher = createPatternMatcher(s_regex, matchContent, sensitive);
|
||||||
matches.addAll(getFormatString(matcher, format));
|
matches.addAll(formatMatchResults(matcher, format));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> extractMatches(String s_regex, AutomatonMatcher autoMatcher, String content) {
|
private List<String> extractRegexMatchResults(String s_regex, AutomatonMatcher autoMatcher, String content) {
|
||||||
List<String> matches = new ArrayList<>();
|
List<String> matches = new ArrayList<>();
|
||||||
if (s_regex.isEmpty()) {
|
if (s_regex.isEmpty()) {
|
||||||
matches.addAll(getFormatString(autoMatcher, content));
|
matches.addAll(formatMatchResults(autoMatcher, content));
|
||||||
} else {
|
} else {
|
||||||
while (autoMatcher.find()) {
|
while (autoMatcher.find()) {
|
||||||
String s = autoMatcher.group();
|
String s = autoMatcher.group();
|
||||||
if (!s.isEmpty()) {
|
if (!s.isEmpty()) {
|
||||||
autoMatcher = createAutomatonMatcher(s_regex, getSubString(content, s));
|
autoMatcher = createAutomatonMatcher(s_regex, extractMatchedContent(content, s));
|
||||||
matches.addAll(getFormatString(autoMatcher, content));
|
matches.addAll(formatMatchResults(autoMatcher, content));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getFormatString(Matcher matcher, String format) {
|
private List<String> formatMatchResults(Matcher matcher, String format) {
|
||||||
List<Integer> indexList = parseIndexesFromString(format);
|
List<Integer> indexList = parseIndexesFromString(format);
|
||||||
List<String> stringList = new ArrayList<>();
|
List<String> stringList = new ArrayList<>();
|
||||||
|
|
||||||
@@ -222,20 +254,20 @@ public class RegularMatcher {
|
|||||||
return "";
|
return "";
|
||||||
}).toArray();
|
}).toArray();
|
||||||
|
|
||||||
stringList.add(MessageFormat.format(reorderIndex(format), params));
|
stringList.add(MessageFormat.format(normalizeFormatIndexes(format), params));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringList;
|
return stringList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getFormatString(AutomatonMatcher matcher, String content) {
|
private List<String> formatMatchResults(AutomatonMatcher matcher, String content) {
|
||||||
List<String> stringList = new ArrayList<>();
|
List<String> stringList = new ArrayList<>();
|
||||||
|
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String s = matcher.group(0);
|
String s = matcher.group(0);
|
||||||
if (!s.isEmpty()) {
|
if (!s.isEmpty()) {
|
||||||
stringList.add(getSubString(content, s));
|
stringList.add(extractMatchedContent(content, s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,14 +275,21 @@ public class RegularMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Matcher createPatternMatcher(String regex, String content, boolean sensitive) {
|
private Matcher createPatternMatcher(String regex, String content, boolean sensitive) {
|
||||||
Pattern pattern = sensitive ? Pattern.compile(regex) : Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
Pattern pattern = nfaPatternCache.computeIfAbsent(regex, k -> {
|
||||||
|
int flags = sensitive ? 0 : Pattern.CASE_INSENSITIVE;
|
||||||
|
return Pattern.compile(regex, flags);
|
||||||
|
});
|
||||||
|
|
||||||
return pattern.matcher(content);
|
return pattern.matcher(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AutomatonMatcher createAutomatonMatcher(String regex, String content) {
|
private AutomatonMatcher createAutomatonMatcher(String regex, String content) {
|
||||||
|
RunAutomaton runAuto = dfaAutomatonCache.computeIfAbsent(regex, k -> {
|
||||||
RegExp regexp = new RegExp(regex);
|
RegExp regexp = new RegExp(regex);
|
||||||
Automaton auto = regexp.toAutomaton();
|
Automaton auto = regexp.toAutomaton();
|
||||||
RunAutomaton runAuto = new RunAutomaton(auto, true);
|
return new RunAutomaton(auto, true);
|
||||||
|
});
|
||||||
|
|
||||||
return runAuto.newMatcher(content);
|
return runAuto.newMatcher(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,15 +308,16 @@ public class RegularMatcher {
|
|||||||
return indexes;
|
return indexes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSubString(String content, String s) {
|
private String extractMatchedContent(String content, String s) {
|
||||||
byte[] contentByte = api.utilities().byteUtils().convertFromString(content);
|
byte[] contentByte = api.utilities().byteUtils().convertFromString(content);
|
||||||
byte[] sByte = api.utilities().byteUtils().convertFromString(s);
|
byte[] sByte = api.utilities().byteUtils().convertFromString(s);
|
||||||
int startIndex = api.utilities().byteUtils().indexOf(contentByte, sByte, false, 1, contentByte.length);
|
int startIndex = api.utilities().byteUtils().indexOf(contentByte, sByte, false, 1, contentByte.length);
|
||||||
int endIndex = startIndex + s.length();
|
int endIndex = startIndex + s.length();
|
||||||
|
|
||||||
return content.substring(startIndex, endIndex);
|
return content.substring(startIndex, endIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String reorderIndex(String format) {
|
private String normalizeFormatIndexes(String format) {
|
||||||
Pattern pattern = Pattern.compile("\\{(\\d+)}");
|
Pattern pattern = Pattern.compile("\\{(\\d+)}");
|
||||||
Matcher matcher = pattern.matcher(format);
|
Matcher matcher = pattern.matcher(format);
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -287,6 +327,7 @@ public class RegularMatcher {
|
|||||||
format = format.replace(matchStr, newStr);
|
format = format.replace(matchStr, newStr);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import burp.api.montoya.MontoyaApi;
|
|||||||
import burp.api.montoya.core.HighlightColor;
|
import burp.api.montoya.core.HighlightColor;
|
||||||
import burp.api.montoya.proxy.websocket.*;
|
import burp.api.montoya.proxy.websocket.*;
|
||||||
import hae.instances.http.utils.MessageProcessor;
|
import hae.instances.http.utils.MessageProcessor;
|
||||||
|
import hae.utils.ConfigLoader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -12,9 +13,9 @@ public class WebSocketMessageHandler implements ProxyMessageHandler {
|
|||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final MessageProcessor messageProcessor;
|
private final MessageProcessor messageProcessor;
|
||||||
|
|
||||||
public WebSocketMessageHandler(MontoyaApi api) {
|
public WebSocketMessageHandler(MontoyaApi api, ConfigLoader configLoader) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
this.messageProcessor = new MessageProcessor(api);
|
this.messageProcessor = new MessageProcessor(api, configLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package hae.utils;
|
|||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
import org.yaml.snakeyaml.DumperOptions;
|
import org.yaml.snakeyaml.DumperOptions;
|
||||||
|
import org.yaml.snakeyaml.LoaderOptions;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
import org.yaml.snakeyaml.representer.Representer;
|
import org.yaml.snakeyaml.representer.Representer;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@@ -21,10 +23,7 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
public ConfigLoader(MontoyaApi api) {
|
public ConfigLoader(MontoyaApi api) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
DumperOptions dop = new DumperOptions();
|
this.yaml = createSecureYaml();
|
||||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
|
||||||
Representer representer = new Representer(dop);
|
|
||||||
this.yaml = new Yaml(representer, dop);
|
|
||||||
|
|
||||||
String configPath = determineConfigPath();
|
String configPath = determineConfigPath();
|
||||||
this.configFilePath = String.format("%s/%s", configPath, "Config.yml");
|
this.configFilePath = String.format("%s/%s", configPath, "Config.yml");
|
||||||
@@ -54,6 +53,25 @@ public class ConfigLoader {
|
|||||||
return configPathFile.exists() && configPathFile.isDirectory();
|
return configPathFile.exists() && configPathFile.isDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Yaml createSecureYaml() {
|
||||||
|
// 配置 LoaderOptions 进行安全限制
|
||||||
|
LoaderOptions loaderOptions = new LoaderOptions();
|
||||||
|
// 禁用注释处理
|
||||||
|
loaderOptions.setProcessComments(false);
|
||||||
|
// 禁止递归键
|
||||||
|
loaderOptions.setAllowRecursiveKeys(false);
|
||||||
|
|
||||||
|
// 配置 DumperOptions 控制输出格式
|
||||||
|
DumperOptions dop = new DumperOptions();
|
||||||
|
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||||
|
|
||||||
|
// 创建 Representer
|
||||||
|
Representer representer = new Representer(dop);
|
||||||
|
|
||||||
|
// 使用 SafeConstructor创建安全的 YAML 实例
|
||||||
|
return new Yaml(new SafeConstructor(loaderOptions), representer, dop);
|
||||||
|
}
|
||||||
|
|
||||||
private String determineConfigPath() {
|
private String determineConfigPath() {
|
||||||
// 优先级1:用户根目录
|
// 优先级1:用户根目录
|
||||||
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
|
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
|
||||||
@@ -79,6 +97,8 @@ public class ConfigLoader {
|
|||||||
r.put("ExcludeStatus", getExcludeStatus());
|
r.put("ExcludeStatus", getExcludeStatus());
|
||||||
r.put("LimitSize", getLimitSize());
|
r.put("LimitSize", getLimitSize());
|
||||||
r.put("HaEScope", getScope());
|
r.put("HaEScope", getScope());
|
||||||
|
r.put("DynamicHeader", getDynamicHeader());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
|
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
|
||||||
yaml.dump(r, ws);
|
yaml.dump(r, ws);
|
||||||
@@ -97,10 +117,7 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = Files.newInputStream(Paths.get(getRulesFilePath()));
|
InputStream inputStream = Files.newInputStream(Paths.get(getRulesFilePath()));
|
||||||
DumperOptions dop = new DumperOptions();
|
Map<String, Object> rulesMap = yaml.load(inputStream);
|
||||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
|
||||||
Representer representer = new Representer(dop);
|
|
||||||
Map<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream);
|
|
||||||
|
|
||||||
Object rulesObj = rulesMap.get("rules");
|
Object rulesObj = rulesMap.get("rules");
|
||||||
if (rulesObj instanceof List) {
|
if (rulesObj instanceof List) {
|
||||||
@@ -156,6 +173,14 @@ public class ConfigLoader {
|
|||||||
setValueToConfig("ExcludeStatus", status);
|
setValueToConfig("ExcludeStatus", status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDynamicHeader() {
|
||||||
|
return getValueFromConfig("DynamicHeader", Config.header);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDynamicHeader(String header) {
|
||||||
|
setValueToConfig("DynamicHeader", header);
|
||||||
|
}
|
||||||
|
|
||||||
public String getLimitSize() {
|
public String getLimitSize() {
|
||||||
return getValueFromConfig("LimitSize", Config.size);
|
return getValueFromConfig("LimitSize", Config.size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ import burp.api.montoya.persistence.Persistence;
|
|||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.instances.http.utils.RegularMatcher;
|
import hae.instances.http.utils.RegularMatcher;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
public class DataManager {
|
public class DataManager {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
@@ -65,9 +64,7 @@ public class DataManager {
|
|||||||
dataIndex.forEach(index -> {
|
dataIndex.forEach(index -> {
|
||||||
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||||
try {
|
try {
|
||||||
dataObj.stringListKeys().forEach(dataKey -> {
|
dataObj.stringListKeys().forEach(dataKey -> RegularMatcher.updateGlobalMatchCache(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false));
|
||||||
RegularMatcher.putDataToGlobalMap(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false);
|
|
||||||
});
|
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -79,69 +76,54 @@ public class DataManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> indexList = new ArrayList<>();
|
// 直接转换为List,简化处理
|
||||||
for (Object item : messageIndex) {
|
List<String> indexList = messageIndex.stream()
|
||||||
try {
|
.filter(Objects::nonNull)
|
||||||
if (item != null) {
|
.map(Object::toString)
|
||||||
indexList.add(item.toString());
|
.toList();
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
if (indexList.isEmpty()) {
|
||||||
api.logging().logToError("转换索引时出错: " + e.getMessage());
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final int batchSize = 2000; // 增加批处理大小
|
final int batchSize = 2000;
|
||||||
final int threadCount = Math.max(8, Runtime.getRuntime().availableProcessors() * 2); // 增加线程数
|
final int threadCount = Math.max(8, Runtime.getRuntime().availableProcessors() * 2);
|
||||||
int totalSize = indexList.size();
|
|
||||||
|
|
||||||
// 使用更高效的线程池
|
|
||||||
ExecutorService executorService = Executors.newWorkStealingPool(threadCount);
|
ExecutorService executorService = Executors.newWorkStealingPool(threadCount);
|
||||||
List<Future<List<Object[]>>> futures = new ArrayList<>();
|
|
||||||
|
|
||||||
// 分批并行处理数据
|
try {
|
||||||
for (int i = 0; i < totalSize; i += batchSize) {
|
// 分批处理
|
||||||
int endIndex = Math.min(i + batchSize, totalSize);
|
for (int i = 0; i < indexList.size(); i += batchSize) {
|
||||||
|
int endIndex = Math.min(i + batchSize, indexList.size());
|
||||||
List<String> batch = indexList.subList(i, endIndex);
|
List<String> batch = indexList.subList(i, endIndex);
|
||||||
|
|
||||||
Future<List<Object[]>> future = executorService.submit(() -> processBatchParallel(batch));
|
processBatch(batch, messageTableModel);
|
||||||
futures.add(future);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量添加数据到模型
|
|
||||||
try {
|
|
||||||
for (Future<List<Object[]>> future : futures) {
|
|
||||||
List<Object[]> batchData = future.get();
|
|
||||||
messageTableModel.addBatch(batchData);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
api.logging().logToError("批量添加数据时出错: " + e.getMessage());
|
|
||||||
} finally {
|
} finally {
|
||||||
executorService.shutdown();
|
executorService.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Object[]> processBatchParallel(List<String> batch) {
|
private void processBatch(List<String> batch, MessageTableModel messageTableModel) {
|
||||||
List<Object[]> batchData = new ArrayList<>();
|
batch.forEach(index -> {
|
||||||
for (String index : batch) {
|
|
||||||
try {
|
try {
|
||||||
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||||
if (dataObj != null) {
|
if (dataObj != null) {
|
||||||
HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo");
|
HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo");
|
||||||
if (messageInfo != null) {
|
if (messageInfo != null) {
|
||||||
batchData.add(prepareMessageData(messageInfo, dataObj));
|
addMessageToModel(messageInfo, dataObj, messageTableModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
api.logging().logToError("处理消息数据时出错: " + e.getMessage() + ", index: " + index);
|
api.logging().logToError("processBatch: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return batchData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object[] prepareMessageData(HttpRequestResponse messageInfo, PersistedObject dataObj) {
|
private void addMessageToModel(HttpRequestResponse messageInfo, PersistedObject dataObj, MessageTableModel messageTableModel) {
|
||||||
HttpRequest request = messageInfo.request();
|
HttpRequest request = messageInfo.request();
|
||||||
HttpResponse response = messageInfo.response();
|
HttpResponse response = messageInfo.response();
|
||||||
return new Object[]{
|
|
||||||
|
messageTableModel.add(
|
||||||
messageInfo,
|
messageInfo,
|
||||||
request.url(),
|
request.url(),
|
||||||
request.method(),
|
request.method(),
|
||||||
@@ -150,6 +132,6 @@ public class DataManager {
|
|||||||
dataObj.getString("comment"),
|
dataObj.getString("comment"),
|
||||||
dataObj.getString("color"),
|
dataObj.getString("color"),
|
||||||
false
|
false
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,16 +25,29 @@ public class HttpUtils {
|
|||||||
boolean retStatus = false;
|
boolean retStatus = false;
|
||||||
try {
|
try {
|
||||||
String host = StringProcessor.getHostByUrl(request.url());
|
String host = StringProcessor.getHostByUrl(request.url());
|
||||||
String[] hostList = configLoader.getBlockHost().split("\\|");
|
|
||||||
boolean isBlockHost = isBlockHost(hostList, host);
|
|
||||||
|
|
||||||
|
boolean isBlockHost = false;
|
||||||
|
String blockHost = configLoader.getBlockHost();
|
||||||
|
if (!blockHost.isBlank()) {
|
||||||
|
String[] hostList = configLoader.getBlockHost().split("\\|");
|
||||||
|
isBlockHost = isBlockHost(hostList, host);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isExcludeSuffix = false;
|
||||||
|
String suffix = configLoader.getExcludeSuffix();
|
||||||
|
if (!suffix.isBlank()) {
|
||||||
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
|
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
|
||||||
boolean isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase());
|
isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
boolean isToolScope = !configLoader.getScope().contains(toolType);
|
boolean isToolScope = !configLoader.getScope().contains(toolType);
|
||||||
|
|
||||||
|
boolean isExcludeStatus = false;
|
||||||
|
String status = configLoader.getExcludeStatus();
|
||||||
|
if (!status.isBlank()) {
|
||||||
List<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|"));
|
List<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|"));
|
||||||
boolean isExcludeStatus = statusList.contains(String.valueOf(response.statusCode()));
|
isExcludeStatus = statusList.contains(String.valueOf(response.statusCode()));
|
||||||
|
}
|
||||||
|
|
||||||
retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus;
|
retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus;
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package hae.utils.rule;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
|
import hae.cache.DataCache;
|
||||||
import hae.utils.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.rule.model.Group;
|
import hae.utils.rule.model.Group;
|
||||||
import hae.utils.rule.model.Info;
|
import hae.utils.rule.model.Info;
|
||||||
@@ -27,6 +28,8 @@ public class RuleProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void rulesFormatAndSave() {
|
public void rulesFormatAndSave() {
|
||||||
|
DataCache.clear();
|
||||||
|
|
||||||
DumperOptions dop = new DumperOptions();
|
DumperOptions dop = new DumperOptions();
|
||||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||||
Representer representer = new Representer(dop);
|
Representer representer = new Representer(dop);
|
||||||
|
|||||||
@@ -55,6 +55,15 @@ rules:
|
|||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
|
- name: Vite DevMode
|
||||||
|
loaded: true
|
||||||
|
f_regex: (/@vite/client)
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: red
|
||||||
|
scope: response body
|
||||||
|
engine: dfa
|
||||||
|
sensitive: true
|
||||||
- group: Maybe Vulnerability
|
- group: Maybe Vulnerability
|
||||||
rule:
|
rule:
|
||||||
- name: Java Deserialization
|
- name: Java Deserialization
|
||||||
@@ -102,6 +111,24 @@ rules:
|
|||||||
scope: request
|
scope: request
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
|
- name: Passwd File
|
||||||
|
loaded: true
|
||||||
|
f_regex: (/root:/bin/bash)
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: red
|
||||||
|
scope: response body
|
||||||
|
engine: dfa
|
||||||
|
sensitive: true
|
||||||
|
- name: Win.ini File
|
||||||
|
loaded: true
|
||||||
|
f_regex: (for 16-bit app)
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: red
|
||||||
|
scope: response body
|
||||||
|
engine: dfa
|
||||||
|
sensitive: true
|
||||||
- group: Basic Information
|
- group: Basic Information
|
||||||
rule:
|
rule:
|
||||||
- name: Email
|
- name: Email
|
||||||
@@ -244,7 +271,7 @@ rules:
|
|||||||
rule:
|
rule:
|
||||||
- name: Linkfinder
|
- name: Linkfinder
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (?:"|')((?:(?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|(?:(?:(?:/|\.\./|\./)?[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}\.[a-zA-Z]{1,4})|(?:(?:/|\.\./|\./)?[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}/[^"'><,;|()]{1,}(?:\.[a-zA-Z]{1,4}|action)?)))(?:[\?|#][^"|']{0,})?(?:"|')
|
f_regex: (?:"|')(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|')
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: gray
|
color: gray
|
||||||
@@ -271,7 +298,7 @@ rules:
|
|||||||
sensitive: false
|
sensitive: false
|
||||||
- name: URL Schemes
|
- name: URL Schemes
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: (\b(?![\w]{0,10}?https?://)(([-A-Za-z0-9]{1,20})://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]))
|
f_regex: (\b(?![\w]{0,10}?https?://)(([A-Za-z0-9-\.]{1,20})://([-\w+&@#/%?=~_|!:,.;]*[-\w+&@#/%=~_|])?))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
|
|||||||
Reference in New Issue
Block a user