Compare commits

...

32 Commits
3.2.1 ... 4.0.2

Author SHA1 Message Date
gh0stkey
a06ef8e25e Version: 4.0.2 Update 2025-01-08 13:49:12 +08:00
EvilChen
7e53e250af Merge pull request #235 from 0Chencc/master
修复了社区版会无法正常使用HaE的问题
2025-01-08 13:47:38 +08:00
林晨
b686b5e75e 修复了社区版会无法正常使用HaE的问题
社区版由于Scanner的问题无法使用HaE,加入了一处版本判断。
2025-01-02 05:50:14 +08:00
gh0stkey
e83a6a1478 Version: 4.0.1 Update 2024-12-31 15:40:05 +08:00
gh0stkey
daacb2e146 Version: 4.0 Update 2024-12-21 15:34:45 +08:00
gh0stkey
1f1ca99f10 Update 2024-12-21 15:19:19 +08:00
gh0stkey
fa35b0a625 Version: 3.4 Update 2024-11-16 19:48:50 +08:00
gh0stkey
8ef98d20a9 Version: 3.4 Update 2024-11-16 18:42:15 +08:00
gh0stkey
e556abb6f7 Version: 3.4 Update 2024-11-16 18:06:49 +08:00
EvilChen
471aab5ea1 Merge pull request #221 from AabyssZG/master
Update Rules.yml
2024-10-24 11:48:57 +08:00
曾哥
76b475bd91 Update Rules.yml 2024-10-24 11:40:29 +08:00
gh0stkey
6014089594 Version: 3.3.4 Update 2024-10-14 16:35:20 +08:00
EvilChen
910658f2e0 Update 问题反馈.md 2024-09-23 00:05:40 +08:00
gh0stkey
8692b0a494 Version: 3.3.3 Update 2024-09-19 17:45:47 +08:00
gh0stkey
5419d4a679 Version: 3.3.3 Update 2024-09-19 17:11:55 +08:00
gh0stkey
ae8cb2fd25 Version: 3.3.3 Update 2024-09-19 17:08:46 +08:00
EvilChen
5b6bdbe5b6 Update README.md 2024-08-28 16:19:24 +08:00
EvilChen
ddb08e9a6e Update README.md 2024-08-28 16:18:25 +08:00
EvilChen
6a2f289d57 Update build.gradle 2024-08-26 10:04:57 +08:00
gh0stkey
84746a7089 Version: 3.3.2 Update 2024-08-23 22:03:31 +08:00
gh0stkey
68f0bce619 Version: 3.3.1 Update 2024-08-12 10:41:24 +08:00
gh0stkey
4f0401347c Version: 3.3.1 Update 2024-08-12 10:34:26 +08:00
gh0stkey
a7e0a2a6ce Update 2024-07-31 08:57:17 +08:00
gh0stkey
b7c5a8363d Update 2024-07-31 08:49:53 +08:00
gh0stkey
d7b4419d51 Version: 3.3 Update 2024-07-23 09:22:43 +08:00
gh0stkey
5f54d1f461 Version: 3.3 Update 2024-07-23 09:21:30 +08:00
EvilChen
e4b7f86a0c Update README.md 2024-07-19 00:24:04 +08:00
EvilChen
cc30f41bfa Add files via upload 2024-07-18 23:38:29 +08:00
EvilChen
386c562311 Delete images/config.png 2024-07-18 23:37:12 +08:00
EvilChen
a867039284 Update README.md 2024-07-18 23:36:52 +08:00
gh0stkey
3a8d9eae11 Version: 3.2.2 Update 2024-06-19 22:20:46 +08:00
gh0stkey
e5f55b6c4c Version: 3.2.2 Update 2024-06-19 22:16:57 +08:00
36 changed files with 1518 additions and 1557 deletions

View File

@@ -14,7 +14,9 @@ HaE 版本:
有无自定义规则: 有无自定义规则:
BurpSuite 版本: BurpSuite 版本:
操作系统版本: 操作系统版本:
有无仔细阅读README 是否阅读README
是否知晓注意事项:
是否查阅历史ISSUE
``` ```
## 问题详情 ## 问题详情

View File

@@ -6,22 +6,34 @@
## 项目介绍 ## 项目介绍
**HaE**是一款网络安全(数据安全)领域下的辅助型框架式项目,旨在实现对HTTP消息包含WebSocket高亮标记和信息提取。本项目通过自定义正则表达式匹配响应报文或请求报文,并对匹配成功的报文进行标记和提取。 **HaE**是一款**网络安全(数据安全)领域**下的框架式项目,采用了**乐高积木式**模块化设计理念,实现对HTTP消息包含WebSocket精细化的标记和提取。
通过运用**多引擎**的自定义正则表达式HaE能够准确匹配并处理HTTP请求与响应报文包含WebSocket对匹配成功的内容进行有效的标记和信息抽取从而提升网络安全数据安全领域下的**漏洞和数据分析效率**。
> 随着现代化Web应用采用前后端分离的开发模式日常漏洞挖掘的过程中捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**借助HaE您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。 > 随着现代化Web应用采用前后端分离的开发模式日常漏洞挖掘的过程中捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**借助HaE您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
GitHub项目地址https://github.com/gh0stkey/HaE
GitCode项目地址https://gitcode.com/gh0stkey/HaE
**所获荣誉**:
1. [入选2022年KCon兵器谱](https://mp.weixin.qq.com/s/JohMsl1WD29LHCHuLf8mVQ)
2. [入选GitCode G-Star项目](https://gitcode.com/gh0stkey/HaE)
**注意事项**: **注意事项**:
1. 由于HaE 3.0版本开始采用`Montoya API`进行开发,因此使用新版HaE需要升级你的BurpSuite版本>=2023.12.1)。 1. HaE 3.0版本开始采用`Montoya API`进行开发使用新版HaE需要升级你的BurpSuite版本>=2023.12.1)。
2. 由于HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。 2. HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
3. HaE官方规则库存放在[Github](https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml)上,因此点击`Update`升级HaE官方规则库时需使用代理BApp审核考虑安全性不允许使用CDN 3. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`
4. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`在HaE的规则中就需要变成`(rememberMe=delete)`
## 使用方法 ## 使用方法
插件装载: `Extender - Extensions - Add - Select File - Next` 插件装载: `Extender - Extensions - Add - Select File - Next`
初次装载`HaE`自动获取官方规则库`https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml`,配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下: 初次装载`HaE`从Jar包中加载离线的规则库如果更新可以点击`Reinit`进行重新初始化。内置规则库地址可以在Github上找到`https://github.com/gh0stkey/HaE/blob/master/src/main/resources/rules/Rules.yml`
配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下:
1. Linux/Mac用户的配置文件目录`~/.config/HaE/` 1. Linux/Mac用户的配置文件目录`~/.config/HaE/`
2. Windows用户的配置文件目录`%USERPROFILE%/.config/HaE/` 2. Windows用户的配置文件目录`%USERPROFILE%/.config/HaE/`
@@ -43,14 +55,13 @@ HaE目前的规则一共有8个字段详细的含义如下所示
| Color | 规则匹配颜色主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法当出现相同颜色时会自动向上升级一个颜色进行标记。 | | Color | 规则匹配颜色主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法当出现相同颜色时会自动向上升级一个颜色进行标记。 |
| Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 | | Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 |
## 优势特点 ## 优势特点
1. **功能**通过对HTTP报文的颜色高亮、注释和提取帮助使用者获取有意义的信息**聚焦高价值报文**。 1. **功能**通过对HTTP报文的颜色高亮、注释和提取帮助使用者获取有意义的信息**聚焦高价值报文**。
2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。 2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。
3. **查询**将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。 3. **查询**将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。
4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。 4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。
5. **管理**支持对数据的一键导出、导入,以**自定义`.hae`文件的方式**进行项目数据存储,**便于存储和共享项目数据** 5. **管理****融入BurpSuite的项目数据管理**当使用BurpSuite进行项目存储时HaE数据也会一并存储
6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。 6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。
| 界面名称 | 界面展示 | | 界面名称 | 界面展示 |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

@@ -10,6 +10,12 @@ public class Config {
public static String host = "gh0st.cn"; public static String host = "gh0st.cn";
public static String status = "404";
public static String size = "0";
public static String boundary = "\n\t\n";
public static String[] scope = new String[]{ public static String[] scope = new String[]{
"any", "any",
"any header", "any header",
@@ -24,6 +30,10 @@ public class Config {
"request body" "request body"
}; };
public static String scopeOptions = "Suite|Target|Proxy|Scanner|Intruder|Repeater|Logger|Sequencer|Decoder|Comparer|Extensions|Organizer|Recorded login replayer";
public static String modeStatus = "true";
public static String[] ruleFields = { public static String[] ruleFields = {
"Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive" "Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive"
}; };
@@ -51,9 +61,9 @@ public class Config {
"gray" "gray"
}; };
public static Boolean proVersionStatus = true;
public static Map<String, Object[][]> globalRules = new HashMap<>(); public static Map<String, Object[][]> globalRules = new HashMap<>();
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>(); public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
public static ConcurrentHashMap<String, Map<String, Object>> globalHostHashMap = new ConcurrentHashMap<>();
} }

View File

@@ -2,46 +2,79 @@ 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.CachePool;
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;
import hae.instances.editor.ResponseEditor; import hae.instances.editor.ResponseEditor;
import hae.instances.editor.WebSocketEditor; import hae.instances.editor.WebSocketEditor;
import hae.instances.http.HttpMessageHandler; import hae.instances.http.HttpMessagePassiveHandler;
import hae.instances.websocket.WebSocketMessageHandler; import hae.instances.websocket.WebSocketMessageHandler;
import hae.utils.ConfigLoader; import hae.utils.ConfigLoader;
import hae.utils.DataManager;
public class HaE implements BurpExtension { public class HaE implements BurpExtension {
@Override @Override
public void initialize(MontoyaApi api) { public void initialize(MontoyaApi api) {
// 设置扩展名称 // 设置扩展名称
String version = "3.2.1"; api.extension().setName("HaE - Highlighter and Extractor");
api.extension().setName(String.format("HaE (%s) - Highlighter and Extractor", version)); String version = "4.0.2";
// 加载扩展后输出的项目信息 // 加载扩展后输出的项目信息
Logging logging = api.logging(); Logging logging = api.logging();
logging.logToOutput("[ HACK THE WORLD - TO DO IT ]"); logging.logToOutput("[ HACK THE WORLD - TO DO IT ]");
logging.logToOutput("[#] Author: EvilChen && 0chencc && vaycore"); logging.logToOutput("[#] Author: EvilChen && 0chencc && vaycore");
logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE"); logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE");
logging.logToOutput("[#] Version: " + version);
// 配置文件加载 // 配置文件加载
ConfigLoader configLoader = new ConfigLoader(api); ConfigLoader configLoader = new ConfigLoader(api);
MessageTableModel messageTableModel = new MessageTableModel(api); MessageTableModel messageTableModel = new MessageTableModel(api, configLoader);
// 设置BurpSuite专业版状态
Config.proVersionStatus = getBurpSuiteProStatus(api, configLoader, messageTableModel);
// 注册Tab页用于查询数据 // 注册Tab页用于查询数据
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel)); api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
// 注册HTTP处理器
api.http().registerHttpHandler(new HttpMessageHandler(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)));
// 注册消息编辑框(用于展示数据) // 注册消息编辑框(用于展示数据)
api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader)); api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader));
api.userInterface().registerHttpResponseEditorProvider(new ResponseEditor(api, configLoader)); api.userInterface().registerHttpResponseEditorProvider(new ResponseEditor(api, configLoader));
api.userInterface().registerWebSocketMessageEditorProvider(new WebSocketEditor(api)); api.userInterface().registerWebSocketMessageEditorProvider(new WebSocketEditor(api, configLoader));
// 从BurpSuite里加载数据
DataManager dataManager = new DataManager(api);
dataManager.loadData(messageTableModel);
api.extension().registerUnloadingHandler(new ExtensionUnloadingHandler() {
@Override
public void extensionUnloaded() {
// 卸载清空数据
Config.globalDataMap.clear();
CachePool.clear();
}
});
}
private Boolean getBurpSuiteProStatus(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
boolean burpSuiteProStatus = false;
try {
burpSuiteProStatus = api.burpSuite().version().name().contains("Professional");
} catch (Exception e) {
try {
api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel)).deregister();
burpSuiteProStatus = true;
} catch (Exception ignored) {
}
}
return burpSuiteProStatus;
} }
} }

View File

@@ -0,0 +1,438 @@
package hae.component;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.Registration;
import hae.component.board.message.MessageTableModel;
import hae.component.rule.Rules;
import hae.instances.http.HttpMessageActiveHandler;
import hae.instances.http.HttpMessagePassiveHandler;
import hae.utils.ConfigLoader;
import hae.utils.UIEnhancer;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.*;
import java.util.List;
import java.util.*;
public class Config extends JPanel {
private final MontoyaApi api;
private final ConfigLoader configLoader;
private final MessageTableModel messageTableModel;
private final Rules rules;
private Registration activeHandler;
private Registration passiveHandler;
public Config(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel, Rules rules) {
this.api = api;
this.configLoader = configLoader;
this.messageTableModel = messageTableModel;
this.rules = rules;
this.activeHandler = api.http().registerHttpHandler(new HttpMessageActiveHandler(api, configLoader, messageTableModel));
this.passiveHandler = api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel));
initComponents();
}
private void initComponents() {
setLayout(new BorderLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 1.0;
constraints.fill = GridBagConstraints.HORIZONTAL;
JPanel ruleInfoPanel = new JPanel(new GridBagLayout());
ruleInfoPanel.setBorder(new EmptyBorder(10, 15, 5, 15));
JLabel ruleLabel = new JLabel("Path:");
JTextField pathTextField = new JTextField();
pathTextField.setEditable(false);
pathTextField.setText(configLoader.getRulesFilePath());
JButton reloadButton = new JButton("Reload");
JButton reinitButton = new JButton("Reinit");
ruleInfoPanel.add(ruleLabel);
ruleInfoPanel.add(pathTextField, constraints);
ruleInfoPanel.add(Box.createHorizontalStrut(5));
ruleInfoPanel.add(reinitButton);
ruleInfoPanel.add(Box.createHorizontalStrut(5));
ruleInfoPanel.add(reloadButton);
reloadButton.addActionListener(this::reloadActionPerformed);
reinitButton.addActionListener(this::reinitActionPerformed);
constraints.gridx = 1;
JTabbedPane configTabbedPanel = new JTabbedPane();
String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status"};
JPanel settingPanel = createConfigTablePanel(settingMode);
JPanel northPanel = new JPanel(new BorderLayout());
JPanel modePanel = getModePanel();
JScrollPane modeScrollPane = new JScrollPane(modePanel);
modeScrollPane.setBorder(new TitledBorder("Mode"));
JTextField limitPanel = getLimitPanel();
JScrollPane limitScrollPane = new JScrollPane(limitPanel);
limitScrollPane.setBorder(new TitledBorder("Limit Size (MB)"));
JSplitPane northTopPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, modeScrollPane, limitScrollPane);
northTopPanel.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
northTopPanel.setDividerLocation(0.5);
}
});
JPanel scopePanel = getScopePanel();
JScrollPane scopeScrollPane = new JScrollPane(scopePanel);
scopeScrollPane.setBorder(new TitledBorder("Scope"));
northPanel.add(scopeScrollPane, BorderLayout.SOUTH);
northPanel.add(northTopPanel, BorderLayout.NORTH);
settingPanel.add(northPanel, BorderLayout.NORTH);
configTabbedPanel.add("Setting", settingPanel);
add(ruleInfoPanel, BorderLayout.NORTH);
add(configTabbedPanel, BorderLayout.CENTER);
}
private JPanel getScopePanel() {
JPanel scopePanel = new JPanel();
scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.X_AXIS));
scopePanel.setBorder(new EmptyBorder(3, 0, 6, 0));
String[] scopeInit = hae.Config.scopeOptions.split("\\|");
String[] scopeMode = configLoader.getScope().split("\\|");
for (String scope : scopeInit) {
JCheckBox checkBox = new JCheckBox(scope);
scopePanel.add(checkBox);
checkBox.addActionListener(e -> updateScope(checkBox));
for (String mode : scopeMode) {
if (scope.equals(mode)) {
checkBox.setSelected(true);
}
}
updateScope(checkBox);
}
return scopePanel;
}
private JPanel getModePanel() {
JPanel modePanel = new JPanel();
modePanel.setLayout(new BoxLayout(modePanel, BoxLayout.X_AXIS));
JCheckBox checkBox = new JCheckBox("Enable active http message handler");
checkBox.setEnabled(hae.Config.proVersionStatus);
modePanel.add(checkBox);
checkBox.addActionListener(e -> updateModeStatus(checkBox));
checkBox.setSelected(configLoader.getMode());
updateModeStatus(checkBox);
return modePanel;
}
private JTextField getLimitPanel() {
JTextField limitSizeTextField = new JTextField();
limitSizeTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
onTextChange();
}
@Override
public void removeUpdate(DocumentEvent e) {
onTextChange();
}
@Override
public void changedUpdate(DocumentEvent e) {
onTextChange();
}
private void onTextChange() {
String limitSizeText = limitSizeTextField.getText();
configLoader.setLimitSize(limitSizeText);
}
});
limitSizeTextField.setText(configLoader.getLimitSize());
return limitSizeTextField;
}
private TableModelListener craeteSettingTableModelListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
return new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
String selected = (String) setTypeComboBox.getSelectedItem();
String values = getFirstColumnDataAsString(model);
if (selected.equals("Exclude suffix")) {
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
configLoader.setExcludeSuffix(values);
}
}
if (selected.equals("Block host")) {
if (!values.equals(configLoader.getBlockHost()) && !values.isEmpty()) {
configLoader.setBlockHost(values);
}
}
if (selected.equals("Exclude status")) {
if (!values.equals(configLoader.getExcludeStatus()) && !values.isEmpty()) {
configLoader.setExcludeStatus(values);
}
}
}
};
}
private ActionListener createSettingActionListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String selected = (String) setTypeComboBox.getSelectedItem();
model.setRowCount(0);
if (selected.equals("Exclude suffix")) {
addDataToTable(configLoader.getExcludeSuffix().replaceAll("\\|", "\r\n"), model);
}
if (selected.equals("Block host")) {
addDataToTable(configLoader.getBlockHost().replaceAll("\\|", "\r\n"), model);
}
if (selected.equals("Exclude status")) {
addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model);
}
}
};
}
private JPanel createConfigTablePanel(String[] mode) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 1.0;
constraints.fill = GridBagConstraints.HORIZONTAL;
JPanel settingPanel = new JPanel(new BorderLayout());
DefaultTableModel model = new DefaultTableModel();
JTable table = new JTable(model);
model.addColumn("Value");
JScrollPane scrollPane = new JScrollPane(table);
JPanel buttonPanel = new JPanel();
buttonPanel.setBorder(new EmptyBorder(0, 3, 0, 0));
GridBagLayout layout = new GridBagLayout();
layout.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0};
layout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
buttonPanel.setLayout(layout);
JPanel inputPanel = new JPanel(new BorderLayout());
JPanel inputPanelB = new JPanel(new BorderLayout());
inputPanelB.setBorder(new EmptyBorder(0, 0, 3, 0));
JButton addButton = new JButton("Add");
JButton removeButton = new JButton("Remove");
JButton pasteButton = new JButton("Paste");
JButton clearButton = new JButton("Clear");
JComboBox<String> setTypeComboBox = new JComboBox<>();
setTypeComboBox.setModel(new DefaultComboBoxModel<>(mode));
model.addTableModelListener(craeteSettingTableModelListener(setTypeComboBox, model));
setTypeComboBox.addActionListener(createSettingActionListener(setTypeComboBox, model));
setTypeComboBox.setSelectedItem(mode[0]);
constraints.insets = new Insets(0, 0, 3, 0);
constraints.gridy = 0;
buttonPanel.add(setTypeComboBox, constraints);
constraints.gridy = 1;
buttonPanel.add(addButton, constraints);
constraints.gridy = 2;
buttonPanel.add(removeButton, constraints);
constraints.gridy = 3;
buttonPanel.add(pasteButton, constraints);
constraints.gridy = 4;
buttonPanel.add(clearButton, constraints);
JTextField addTextField = new JTextField();
String defaultText = "Enter a new item";
UIEnhancer.setTextFieldPlaceholder(addTextField, defaultText);
inputPanelB.add(addTextField, BorderLayout.CENTER);
inputPanel.add(scrollPane, BorderLayout.CENTER);
inputPanel.add(inputPanelB, BorderLayout.NORTH);
settingPanel.add(buttonPanel, BorderLayout.EAST);
settingPanel.add(inputPanel, BorderLayout.CENTER);
addButton.addActionListener(e -> addActionPerformed(e, model, addTextField, setTypeComboBox.getSelectedItem().toString()));
addTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
addActionPerformed(null, model, addTextField, setTypeComboBox.getSelectedItem().toString());
}
}
});
pasteButton.addActionListener(e -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
try {
String data = (String) clipboard.getData(DataFlavor.stringFlavor);
if (data != null && !data.isEmpty()) {
addDataToTable(data, model);
}
} catch (Exception ignored) {
}
});
removeButton.addActionListener(e -> {
int selectedRow = table.getSelectedRow();
if (selectedRow != -1) {
model.removeRow(selectedRow);
}
});
clearButton.addActionListener(e -> model.setRowCount(0));
JPanel settingMainPanel = new JPanel(new BorderLayout());
settingMainPanel.setBorder(new EmptyBorder(5, 15, 10, 15));
JScrollPane settingScroller = new JScrollPane(settingPanel);
settingScroller.setBorder(new TitledBorder("Setting"));
settingMainPanel.add(settingScroller, BorderLayout.CENTER);
return settingMainPanel;
}
private String getFirstColumnDataAsString(DefaultTableModel model) {
StringBuilder firstColumnData = new StringBuilder();
int numRows = model.getRowCount();
for (int row = 0; row < numRows; row++) {
firstColumnData.append(model.getValueAt(row, 0));
if (row < numRows - 1) {
firstColumnData.append("|");
}
}
return firstColumnData.toString();
}
private void addDataToTable(String data, DefaultTableModel model) {
if (!data.isBlank()) {
String[] rows = data.split("\\r?\\n");
for (String row : rows) {
model.addRow(new String[]{row});
}
deduplicateTableData(model);
}
}
private void deduplicateTableData(DefaultTableModel model) {
// 使用 Map 存储每一行的数据,用于去重
Set<List<Object>> rowData = new LinkedHashSet<>();
int columnCount = model.getColumnCount();
// 将每一行数据作为一个列表,添加到 Set 中
for (int i = 0; i < model.getRowCount(); i++) {
List<Object> row = new ArrayList<>();
for (int j = 0; j < columnCount; j++) {
row.add(model.getValueAt(i, j));
}
rowData.add(row);
}
// 清除原始数据
model.setRowCount(0);
// 将去重后的数据添加回去
for (List<Object> uniqueRow : rowData) {
model.addRow(uniqueRow.toArray());
}
}
public void updateModeStatus(JCheckBox checkBox) {
boolean selected = checkBox.isSelected();
configLoader.setMode(selected ? "true" : "false");
if (checkBox.isSelected()) {
if (hae.Config.proVersionStatus && passiveHandler.isRegistered()) {
passiveHandler.deregister();
}
if (!activeHandler.isRegistered()) {
activeHandler = api.http().registerHttpHandler(new HttpMessageActiveHandler(api, configLoader, messageTableModel));
}
} else {
if (hae.Config.proVersionStatus && !passiveHandler.isRegistered()) {
passiveHandler = api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel));
}
if (activeHandler.isRegistered()) {
activeHandler.deregister();
}
}
}
public void updateScope(JCheckBox checkBox) {
String boxText = checkBox.getText();
boolean selected = checkBox.isSelected();
Set<String> HaEScope = new HashSet<>(Arrays.asList(configLoader.getScope().split("\\|")));
if (selected) {
HaEScope.add(boxText);
} else {
HaEScope.remove(boxText);
}
configLoader.setScope(String.join("|", HaEScope));
}
private void addActionPerformed(ActionEvent e, DefaultTableModel model, JTextField addTextField, String comboBoxSelected) {
String addTextFieldText = addTextField.getText();
if (addTextField.getForeground().equals(Color.BLACK)) {
addDataToTable(addTextFieldText, model);
addTextField.setText("");
addTextField.requestFocusInWindow();
}
}
private void reloadActionPerformed(ActionEvent e) {
rules.reloadRuleGroup();
}
private void reinitActionPerformed(ActionEvent e) {
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to reinitialize rules? This action will overwrite your existing rules.", "Info", JOptionPane.YES_NO_OPTION);
if (retCode == JOptionPane.YES_OPTION) {
boolean ret = configLoader.initRules();
if (ret) {
rules.reloadRuleGroup();
}
}
}
}

View File

@@ -3,7 +3,6 @@ package hae.component;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.component.board.Databoard; import hae.component.board.Databoard;
import hae.component.board.message.MessageTableModel; import hae.component.board.message.MessageTableModel;
import hae.component.config.Config;
import hae.component.rule.Rules; import hae.component.rule.Rules;
import hae.utils.ConfigLoader; import hae.utils.ConfigLoader;
@@ -37,26 +36,17 @@ public class Main extends JPanel {
// 新增Logo // 新增Logo
JTabbedPane HaETabbedPane = new JTabbedPane(); JTabbedPane HaETabbedPane = new JTabbedPane();
HaETabbedPane.addTab("", getImageIcon(false), mainTabbedPane); boolean isDarkBg = isDarkBg(HaETabbedPane);
HaETabbedPane.addTab("", getImageIcon(isDarkBg), mainTabbedPane);
// 中文Slogan赋能白帽高效作战 // 中文Slogan赋能白帽高效作战
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null); HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null);
HaETabbedPane.setEnabledAt(1, false); HaETabbedPane.setEnabledAt(1, false);
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() { HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
@Override @Override
public void propertyChange(PropertyChangeEvent e) { public void propertyChange(PropertyChangeEvent e) {
boolean isDarkBg = isDarkBg(); boolean isDarkBg = isDarkBg(HaETabbedPane);
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg)); HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
} }
private boolean isDarkBg() {
Color bg = HaETabbedPane.getBackground();
int r = bg.getRed();
int g = bg.getGreen();
int b = bg.getBlue();
int avg = (r + g + b) / 3;
return avg < 128;
}
}); });
add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
@@ -67,7 +57,17 @@ public class Main extends JPanel {
Rules rules = new Rules(api, configLoader); Rules rules = new Rules(api, configLoader);
mainTabbedPane.addTab("Rules", rules); mainTabbedPane.addTab("Rules", rules);
mainTabbedPane.addTab("Databoard", new Databoard(api, configLoader, messageTableModel)); mainTabbedPane.addTab("Databoard", new Databoard(api, configLoader, messageTableModel));
mainTabbedPane.addTab("Config", new Config(api, configLoader, rules)); mainTabbedPane.addTab("Config", new Config(api, configLoader, messageTableModel, rules));
}
private boolean isDarkBg(JTabbedPane HaETabbedPane) {
Color bg = HaETabbedPane.getBackground();
int r = bg.getRed();
int g = bg.getGreen();
int b = bg.getBlue();
int avg = (r + g + b) / 3;
return avg < 128;
} }
private ImageIcon getImageIcon(boolean isDark) { private ImageIcon getImageIcon(boolean isDark) {
@@ -81,7 +81,6 @@ public class Main extends JPanel {
ImageIcon originalIcon = new ImageIcon(imageURL); ImageIcon originalIcon = new ImageIcon(imageURL);
Image originalImage = originalIcon.getImage(); Image originalImage = originalIcon.getImage();
Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST); Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST);
ImageIcon scaledIcon = new ImageIcon(scaledImage); return new ImageIcon(scaledImage);
return scaledIcon;
} }
} }

View File

@@ -2,53 +2,46 @@ package hae.component.board;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.Config; import hae.Config;
import hae.component.board.message.MessageEntry;
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.instances.http.utils.RegularMatcher; import hae.component.board.table.Datatable;
import hae.utils.ConfigLoader; import hae.utils.ConfigLoader;
import hae.utils.project.ProjectProcessor; import hae.utils.UIEnhancer;
import hae.utils.project.model.HaeFileContent;
import hae.utils.string.StringProcessor; import hae.utils.string.StringProcessor;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.TableColumnModel; import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter; import javax.swing.table.TableRowSorter;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.io.File;
import java.util.List; import java.util.List;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class Databoard extends JPanel { public class Databoard extends JPanel {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader; private final ConfigLoader configLoader;
private final ProjectProcessor projectProcessor;
private final MessageTableModel messageTableModel; private final MessageTableModel messageTableModel;
private JTextField hostTextField; private JTextField hostTextField;
private JTabbedPane dataTabbedPane; private JTabbedPane dataTabbedPane;
private JSplitPane splitPane; private JSplitPane splitPane;
private MessageTable messageTable; private MessageTable messageTable;
private JProgressBar progressBar;
private static Boolean isMatchHost = false; private static Boolean isMatchHost = false;
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(); private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
private final JComboBox hostComboBox = new JComboBox(comboBoxModel); private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
private SwingWorker<Boolean, Void> currentWorker; private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker;
private SwingWorker<Void, Void> applyHostFilterWorker;
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) { public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
this.api = api; this.api = api;
this.configLoader = configLoader; this.configLoader = configLoader;
this.projectProcessor = new ProjectProcessor(api);
this.messageTableModel = messageTableModel; this.messageTableModel = messageTableModel;
initComponents(); initComponents();
@@ -57,31 +50,28 @@ public class Databoard extends JPanel {
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};
((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 25, 0}; ((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 0};
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4}; ((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 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}; ((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 1.0E-4};
JLabel hostLabel = new JLabel("Host:"); JLabel hostLabel = new JLabel("Host:");
JButton clearButton = new JButton("Clear"); JButton clearButton = new JButton("Clear");
JButton exportButton = new JButton("Export");
JButton importButton = new JButton("Import");
JButton actionButton = new JButton("Action"); JButton actionButton = new JButton("Action");
JPanel menuPanel = new JPanel(new GridLayout(3, 1, 0, 5)); JPanel menuPanel = new JPanel(new GridLayout(1, 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(clearButton);
menuPanel.add(exportButton);
menuPanel.add(importButton);
menu.add(menuPanel); menu.add(menuPanel);
hostTextField = new JTextField(); hostTextField = new JTextField();
String defaultText = "Please enter the host";
UIEnhancer.setTextFieldPlaceholder(hostTextField, defaultText);
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
dataTabbedPane.setPreferredSize(new Dimension(500, 0)); dataTabbedPane.setPreferredSize(new Dimension(500, 0));
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
splitPane.setLeftComponent(dataTabbedPane);
actionButton.addActionListener(e -> { actionButton.addActionListener(e -> {
int x = 0; int x = 0;
@@ -90,10 +80,7 @@ public class Databoard extends JPanel {
}); });
clearButton.addActionListener(this::clearActionPerformed); clearButton.addActionListener(this::clearActionPerformed);
exportButton.addActionListener(this::exportActionPerformed);
importButton.addActionListener(this::importActionPerformed);
progressBar = new JProgressBar();
splitPane.addComponentListener(new ComponentAdapter() { splitPane.addComponentListener(new ComponentAdapter() {
@Override @Override
@@ -103,7 +90,6 @@ public class Databoard extends JPanel {
}); });
splitPane.setVisible(false); splitPane.setVisible(false);
progressBar.setVisible(false);
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
@@ -112,12 +98,9 @@ public class Databoard extends JPanel {
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
add(splitPane, new GridBagConstraints(1, 1, 3, 1, 0.0, 1.0, add(splitPane, new GridBagConstraints(1, 1, 3, 2, 0.0, 1.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(0, 5, 0, 5), 0, 0)); new Insets(0, 5, 0, 5), 0, 0));
add(progressBar, new GridBagConstraints(1, 3, 3, 1, 1.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
new Insets(0, 5, 0, 5), 0, 0));
hostComboBox.setMaximumRowCount(5); hostComboBox.setMaximumRowCount(5);
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
@@ -137,19 +120,6 @@ public class Databoard extends JPanel {
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1)); columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
} }
private void setProgressBar(boolean status) {
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() {
hostComboBox.setSelectedItem(null); hostComboBox.setSelectedItem(null);
hostComboBox.addActionListener(this::handleComboBoxAction); hostComboBox.addActionListener(this::handleComboBoxAction);
@@ -182,28 +152,38 @@ public class Databoard extends JPanel {
private void handleComboBoxAction(ActionEvent e) { private void handleComboBoxAction(ActionEvent e) {
if (!isMatchHost && hostComboBox.getSelectedItem() != null) { if (!isMatchHost && hostComboBox.getSelectedItem() != null) {
progressBar.setVisible(true);
setProgressBar(true);
String selectedHost = hostComboBox.getSelectedItem().toString(); String selectedHost = hostComboBox.getSelectedItem().toString();
if (getHostByList().contains(selectedHost)) {
hostTextField.setText(selectedHost); hostTextField.setText(selectedHost);
if (currentWorker != null && !currentWorker.isDone()) { if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
currentWorker.cancel(true); handleComboBoxWorker.cancel(true);
} }
currentWorker = new SwingWorker<Boolean, Void>() { handleComboBoxWorker = new SwingWorker<Map<String, List<String>>, Void>() {
@Override @Override
protected Boolean doInBackground() { protected Map<String, List<String>> doInBackground() {
return populateTabbedPaneByHost(selectedHost); return getSelectedMapByHost(selectedHost);
} }
@Override @Override
protected void done() { protected void done() {
if (!isCancelled()) { if (!isCancelled()) {
try { try {
boolean status = get(); Map<String, List<String>> selectedDataMap = get();
if (status) { if (!selectedDataMap.isEmpty()) {
dataTabbedPane.removeAll();
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
Datatable datatablePanel = new Datatable(api, configLoader, entry.getKey(), entry.getValue());
datatablePanel.setTableListener(messageTableModel);
dataTabbedPane.addTab(tabTitle, datatablePanel);
}
JSplitPane messageSplitPane = messageTableModel.getSplitPane(); JSplitPane messageSplitPane = messageTableModel.getSplitPane();
splitPane.setLeftComponent(dataTabbedPane);
splitPane.setRightComponent(messageSplitPane); splitPane.setRightComponent(messageSplitPane);
messageTable = messageTableModel.getMessageTable(); messageTable = messageTableModel.getMessageTable();
resizePanel(); resizePanel();
@@ -220,7 +200,8 @@ public class Databoard extends JPanel {
} }
}; };
currentWorker.execute(); handleComboBoxWorker.execute();
}
} }
} }
@@ -248,13 +229,13 @@ public class Databoard extends JPanel {
isMatchHost = false; isMatchHost = false;
} }
private boolean populateTabbedPaneByHost(String selectedHost) { private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
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;
if (selectedHost.contains("*")) { if (selectedHost.contains("*")) {
selectedDataMap = new HashMap<>(); selectedDataMap = new HashMap<>();
dataMap.keySet().parallelStream().forEach(key -> { dataMap.keySet().forEach(key -> {
if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) { if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) {
Map<String, List<String>> ruleMap = dataMap.get(key); Map<String, List<String>> ruleMap = dataMap.get(key);
for (String ruleKey : ruleMap.keySet()) { for (String ruleKey : ruleMap.keySet()) {
@@ -274,19 +255,7 @@ public class Databoard extends JPanel {
selectedDataMap = dataMap.get(selectedHost); selectedDataMap = dataMap.get(selectedHost);
} }
if (!selectedDataMap.isEmpty()) { return selectedDataMap;
dataTabbedPane.removeAll();
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
Datatable datatablePanel = new Datatable(api, entry.getKey(), entry.getValue());
datatablePanel.setTableListener(messageTableModel);
dataTabbedPane.addTab(tabTitle, datatablePanel);
}
return true;
}
return false;
} }
private void filterComboBoxList() { private void filterComboBoxList() {
@@ -316,7 +285,11 @@ public class Databoard extends JPanel {
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter(); TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", ""); String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
new SwingWorker<Void, Void>() { if (applyHostFilterWorker != null && !applyHostFilterWorker.isDone()) {
applyHostFilterWorker.cancel(true);
}
applyHostFilterWorker = new SwingWorker<Void, Void>() {
@Override @Override
protected Void doInBackground() throws Exception { protected Void doInBackground() throws Exception {
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() { RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
@@ -335,13 +308,9 @@ public class Databoard extends JPanel {
return null; return null;
} }
};
@Override applyHostFilterWorker.execute();
protected void done() {
setProgressBar(false);
}
}.execute();
} }
private List<String> getHostByList() { private List<String> getHostByList() {
@@ -351,230 +320,6 @@ public class Databoard extends JPanel {
return new ArrayList<>(); return new ArrayList<>();
} }
private void exportActionPerformed(ActionEvent e) {
String selectedHost = hostTextField.getText().trim();
if (selectedHost.isEmpty()) {
return;
}
String exportDir = selectDirectory(true);
if (exportDir.isEmpty()) {
return;
}
new SwingWorker<List<String>, Void>() {
@Override
protected List<String> doInBackground() {
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
return exportData(selectedHost, exportDir, dataMap);
}
@Override
protected void done() {
try {
List<String> taskStatusList = get();
if (!taskStatusList.isEmpty()) {
String exportStatusMessage = String.format("Exported File List Status:\n%s", String.join("\n", taskStatusList));
JOptionPane.showConfirmDialog(Databoard.this, exportStatusMessage, "Info", JOptionPane.YES_OPTION);
}
} catch (Exception ignored) {
}
}
}.execute();
}
private List<String> exportData(String selectedHost, String exportDir, Map<String, Map<String, List<String>>> dataMap) {
return dataMap.entrySet().stream()
.filter(entry -> selectedHost.equals("*") || StringProcessor.matchesHostPattern(entry.getKey(), selectedHost))
.filter(entry -> !entry.getKey().contains("*"))
.map(entry -> exportEntry(entry, exportDir))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private String exportEntry(Map.Entry<String, Map<String, List<String>>> entry, String exportDir) {
String key = entry.getKey();
Map<String, List<String>> ruleMap = entry.getValue();
if (ruleMap == null || ruleMap.isEmpty()) {
return null;
}
List<MessageEntry> messageEntryList = messageTableModel.getLogs();
Map<MessageEntry, String> entryUUIDMap = messageEntryList.stream()
.collect(Collectors.toMap(
messageEntry -> messageEntry,
messageEntry -> StringProcessor.getRandomUUID(),
(existing, replacement) -> existing // 在冲突时保留现有的映射
));
Map<String, Map<String, Object>> httpMap = processEntries(
messageEntryList,
key,
entryUUIDMap,
this::createHttpItemMap
);
Map<String, Map<String, Object>> urlMap = processEntries(
messageEntryList,
key,
entryUUIDMap,
this::creteUrlItemMap
);
String hostName = key.replace(":", "_");
String filename = String.format("%s/%s-%s.hae", exportDir, StringProcessor.getCurrentTime(), hostName);
boolean createdStatus = projectProcessor.createHaeFile(filename, key, ruleMap, urlMap, httpMap);
return String.format("Filename: %s, Status: %s", filename, createdStatus);
}
private Map<String, Map<String, Object>> processEntries(List<MessageEntry> messageEntryList, String key, Map<MessageEntry, String> entryUUIDMap, Function<MessageEntry, Map<String, Object>> mapFunction) {
return messageEntryList.stream()
.filter(messageEntry -> !StringProcessor.getHostByUrl(messageEntry.getUrl()).isEmpty())
.filter(messageEntry -> StringProcessor.getHostByUrl(messageEntry.getUrl()).equals(key))
.collect(Collectors.toMap(
entryUUIDMap::get,
mapFunction,
(existing, replacement) -> existing
));
}
private Map<String, Object> creteUrlItemMap(MessageEntry entry) {
Map<String, Object> urlItemMap = new LinkedHashMap<>();
urlItemMap.put("url", entry.getUrl());
urlItemMap.put("method", entry.getMethod());
urlItemMap.put("status", entry.getStatus());
urlItemMap.put("length", entry.getLength());
urlItemMap.put("comment", entry.getComment());
urlItemMap.put("color", entry.getColor());
urlItemMap.put("size", String.valueOf(entry.getRequestResponse().request().toByteArray().length()));
return urlItemMap;
}
private Map<String, Object> createHttpItemMap(MessageEntry entry) {
Map<String, Object> httpItemMap = new LinkedHashMap<>();
httpItemMap.put("request", entry.getRequestResponse().request().toByteArray().getBytes());
httpItemMap.put("response", entry.getRequestResponse().response().toByteArray().getBytes());
return httpItemMap;
}
private void importActionPerformed(ActionEvent e) {
String exportDir = selectDirectory(false);
if (exportDir.isEmpty()) {
return;
}
new SwingWorker<List<String>, Void>() {
@Override
protected List<String> doInBackground() {
List<String> filesWithExtension = findFilesWithExtension(new File(exportDir), ".hae");
return filesWithExtension.stream()
.map(Databoard.this::importData)
.collect(Collectors.toList());
}
@Override
protected void done() {
try {
List<String> taskStatusList = get();
if (!taskStatusList.isEmpty()) {
String importStatusMessage = "Imported File List Status:\n" + String.join("\n", taskStatusList);
JOptionPane.showConfirmDialog(Databoard.this, importStatusMessage, "Info", JOptionPane.YES_OPTION);
}
} catch (Exception ignored) {
}
}
}.execute();
}
private String importData(String filename) {
HaeFileContent haeFileContent = projectProcessor.readHaeFile(filename);
boolean readStatus = haeFileContent != null;
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
List<Future<?>> futures = new ArrayList<>();
if (readStatus) {
try {
String host = haeFileContent.getHost();
haeFileContent.getDataMap().forEach((key, value) -> RegularMatcher.putDataToGlobalMap(host, key, value));
haeFileContent.getUrlMap().forEach((key, urlItemMap) -> {
Future<?> future = executor.submit(() -> {
String url = urlItemMap.get("url");
String comment = urlItemMap.get("comment");
String color = urlItemMap.get("color");
String length = urlItemMap.get("length");
String method = urlItemMap.get("method");
String status = urlItemMap.get("status");
String path = haeFileContent.getHttpPath();
messageTableModel.add(null, url, method, status, length, comment, color, key, path);
});
futures.add(future);
});
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
}
}
} catch (Exception e) {
api.logging().logToError("importData: " + e.getMessage());
} finally {
executor.shutdown();
}
}
return String.format("Filename: %s, Status: %s", filename, readStatus);
}
private List<String> findFilesWithExtension(File directory, String extension) {
List<String> filePaths = new ArrayList<>();
if (directory.isDirectory()) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
filePaths.addAll(findFilesWithExtension(file, extension));
} else if (file.isFile() && file.getName().toLowerCase().endsWith(extension)) {
filePaths.add(file.getAbsolutePath());
}
}
}
} else {
filePaths.add(directory.getAbsolutePath());
}
return filePaths;
}
private String selectDirectory(boolean forDirectories) {
JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(new java.io.File(configLoader.getRulesFilePath()));
chooser.setDialogTitle(String.format("Select a Directory%s", forDirectories ? "" : " or File"));
FileNameExtensionFilter filter = new FileNameExtensionFilter(".hae Files", "hae");
chooser.addChoosableFileFilter(filter);
chooser.setFileFilter(filter);
chooser.setFileSelectionMode(forDirectories ? JFileChooser.DIRECTORIES_ONLY : JFileChooser.FILES_AND_DIRECTORIES);
chooser.setAcceptAllFileFilterUsed(!forDirectories);
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
File selectedDirectory = chooser.getSelectedFile();
return selectedDirectory.getAbsolutePath();
}
return "";
}
private void clearActionPerformed(ActionEvent e) { private void clearActionPerformed(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);
@@ -582,7 +327,6 @@ public class Databoard extends JPanel {
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) { if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
dataTabbedPane.removeAll(); dataTabbedPane.removeAll();
splitPane.setVisible(false); splitPane.setVisible(false);
progressBar.setVisible(false);
Config.globalDataMap.keySet().parallelStream().forEach(key -> { Config.globalDataMap.keySet().parallelStream().forEach(key -> {
if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) { if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) {
@@ -590,24 +334,32 @@ public class Databoard extends JPanel {
} }
}); });
if (!StringProcessor.matchHostIsIp(host) && !host.contains("*.")) { // 删除无用的数据
String baseDomain = StringProcessor.getBaseDomain(StringProcessor.extractHostname(host)); Set<String> wildcardKeys = Config.globalDataMap.keySet().stream()
.filter(key -> key.startsWith("*."))
.collect(Collectors.toSet());
long count = Config.globalDataMap.keySet().stream() Set<String> existingSuffixes = Config.globalDataMap.keySet().stream()
.filter(k -> !k.equals("*." + baseDomain)) .filter(key -> !key.startsWith("*."))
.filter(k -> StringProcessor.matchFromEnd(k, baseDomain)) .map(key -> {
.count(); int dotIndex = key.indexOf(".");
return dotIndex != -1 ? key.substring(dotIndex) : "";
})
.collect(Collectors.toSet());
if (count == 0) { Set<String> keysToRemove = wildcardKeys.stream()
Config.globalDataMap.remove("*." + baseDomain); .filter(key -> !existingSuffixes.contains(key.substring(1)))
} .collect(Collectors.toSet());
}
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.contains("*"))) { keysToRemove.forEach(Config.globalDataMap::remove);
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
Config.globalDataMap.keySet().remove("*"); Config.globalDataMap.keySet().remove("*");
} }
messageTableModel.deleteByHost(host); messageTableModel.deleteByHost(host);
hostTextField.setText("");
} }
} }
} }

View File

@@ -1,207 +0,0 @@
package hae.component.board;
import burp.api.montoya.MontoyaApi;
import hae.component.board.message.MessageTableModel;
import hae.utils.UIEnhancer;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
public class Datatable extends JPanel {
private final MontoyaApi api;
private final JTable dataTable;
private final DefaultTableModel dataTableModel;
private final JTextField searchField;
private final TableRowSorter<DefaultTableModel> sorter;
private final JCheckBox searchMode = new JCheckBox("Reverse search");
private final String tabName;
public Datatable(MontoyaApi api, String tabName, List<String> dataList) {
this.api = api;
this.tabName = tabName;
String[] columnNames = {"#", "Information"};
dataTableModel = new DefaultTableModel(columnNames, 0);
dataTable = new JTable(dataTableModel);
sorter = new TableRowSorter<>(dataTableModel);
searchField = new JTextField();
initComponents(dataList);
}
private void initComponents(List<String> dataList) {
// 设置ID排序
sorter.setComparator(0, new Comparator<Integer>() {
@Override
public int compare(Integer s1, Integer s2) {
return s1.compareTo(s2);
}
});
dataTable.setRowSorter(sorter);
TableColumn idColumn = dataTable.getColumnModel().getColumn(0);
idColumn.setMaxWidth(50);
for (String item : dataList) {
if (!item.isEmpty()) {
addRowToTable(new Object[]{item});
}
}
// 设置灰色默认文本
String searchText = "Search";
UIEnhancer.setTextFieldPlaceholder(searchField, searchText);
// 监听输入框内容输入、更新、删除
searchField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
performSearch();
}
@Override
public void removeUpdate(DocumentEvent e) {
performSearch();
}
@Override
public void changedUpdate(DocumentEvent e) {
performSearch();
}
});
// 设置布局
JScrollPane scrollPane = new JScrollPane(dataTable);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
searchMode.addItemListener(e -> performSearch());
setLayout(new BorderLayout(0, 5));
JPanel optionsPanel = new JPanel();
optionsPanel.setBorder(BorderFactory.createEmptyBorder(2, 3, 5, 5));
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.X_AXIS));
// 新增复选框要在这修改rows
JPanel menuPanel = new JPanel(new GridLayout(1, 1));
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
JPopupMenu menu = new JPopupMenu();
menuPanel.add(searchMode);
menu.add(menuPanel);
JButton settingsButton = new JButton("Settings");
settingsButton.addActionListener(e -> {
int x = settingsButton.getX();
int y = settingsButton.getY() - menu.getPreferredSize().height;
menu.show(settingsButton, x, y);
});
optionsPanel.add(settingsButton);
optionsPanel.add(Box.createHorizontalStrut(5));
optionsPanel.add(searchField);
add(scrollPane, BorderLayout.CENTER);
add(optionsPanel, BorderLayout.SOUTH);
}
private void addRowToTable(Object[] data) {
int rowCount = dataTableModel.getRowCount();
int id = rowCount > 0 ? (Integer) dataTableModel.getValueAt(rowCount - 1, 0) + 1 : 1;
Object[] rowData = new Object[data.length + 1];
rowData[0] = id;
System.arraycopy(data, 0, rowData, 1, data.length);
dataTableModel.addRow(rowData);
}
private void performSearch() {
if (searchField.getForeground().equals(Color.BLACK)) {
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
public boolean include(Entry<?, ?> entry) {
String searchFieldTextText = searchField.getText();
Pattern pattern = null;
try {
pattern = Pattern.compile(searchFieldTextText, Pattern.CASE_INSENSITIVE);
} catch (Exception ignored) {
}
String entryValue = ((String) entry.getValue(1)).toLowerCase();
searchFieldTextText = searchFieldTextText.toLowerCase();
if (pattern != null) {
return searchFieldTextText.isEmpty() || pattern.matcher(entryValue).find() != searchMode.isSelected();
} else {
return searchFieldTextText.isEmpty() || entryValue.contains(searchFieldTextText) != searchMode.isSelected();
}
}
};
sorter.setRowFilter(rowFilter);
}
}
public void setTableListener(MessageTableModel messagePanel) {
// 表格复制功能
dataTable.setTransferHandler(new TransferHandler() {
@Override
public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException {
if (comp instanceof JTable) {
StringSelection stringSelection = new StringSelection(getSelectedDataAtTable((JTable) comp));
clip.setContents(stringSelection, null);
} else {
super.exportToClipboard(comp, clip, action);
}
}
});
dataTable.setDefaultEditor(Object.class, null);
// 表格内容双击事件
dataTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
int selectedRow = dataTable.getSelectedRow();
if (selectedRow != -1) {
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
messagePanel.applyMessageFilter(tabName, rowData);
}
}
}
});
}
public String getSelectedDataAtTable(JTable table) {
int[] selectRows = table.getSelectedRows();
StringBuilder selectData = new StringBuilder();
for (int row : selectRows) {
selectData.append(table.getValueAt(row, 1).toString()).append("\n");
}
// 便于单行复制,去除最后一个换行符
if (!selectData.isEmpty()) {
selectData.deleteCharAt(selectData.length() - 1);
return selectData.toString();
} else {
return "";
}
}
public JTable getDataTable() {
return this.dataTable;
}
}

View File

@@ -11,10 +11,8 @@ public class MessageEntry {
private final String status; private final String status;
private final String color; private final String color;
private final String method; private final String method;
private final String hash;
private final String path;
MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status, String hash, String path) { MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status) {
this.requestResponse = requestResponse; this.requestResponse = requestResponse;
this.method = method; this.method = method;
this.url = url; this.url = url;
@@ -22,8 +20,6 @@ public class MessageEntry {
this.length = length; this.length = length;
this.color = color; this.color = color;
this.status = status; this.status = status;
this.hash = hash;
this.path = path;
} }
public String getColor() { public String getColor() {
@@ -53,12 +49,4 @@ public class MessageEntry {
public HttpRequestResponse getRequestResponse() { public HttpRequestResponse getRequestResponse() {
return this.requestResponse; return this.requestResponse;
} }
public String getHash() {
return this.hash;
}
public String getPath() {
return this.path;
}
} }

View File

@@ -4,16 +4,16 @@ import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*; import java.awt.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
public class MessageRenderer extends DefaultTableCellRenderer { public class MessageRenderer extends DefaultTableCellRenderer {
private final List<MessageEntry> log; private final LinkedList<MessageEntry> log;
private final Map<String, Color> colorMap = new HashMap<>(); private final Map<String, Color> colorMap = new HashMap<>();
private final JTable table; // 保存对表格的引用 private final JTable table; // 保存对表格的引用
public MessageRenderer(List<MessageEntry> log, JTable table) { public MessageRenderer(LinkedList<MessageEntry> log, JTable table) {
this.log = log; this.log = log;
// 与BurpSuite的颜色保持一致 // 与BurpSuite的颜色保持一致
this.colorMap.put("red", new Color(0xFF, 0x64, 0x64)); this.colorMap.put("red", new Color(0xFF, 0x64, 0x64));

View File

@@ -1,17 +1,18 @@
package hae.component.board.message; package hae.component.board.message;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.ByteArray;
import burp.api.montoya.http.message.HttpHeader; import burp.api.montoya.http.message.HttpHeader;
import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.HttpRequestResponse;
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 burp.api.montoya.persistence.PersistedObject;
import burp.api.montoya.ui.UserInterface; 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.CachePool; import hae.cache.CachePool;
import hae.utils.project.FileProcessor; import hae.utils.ConfigLoader;
import hae.utils.DataManager;
import hae.utils.string.HashCalculator; import hae.utils.string.HashCalculator;
import hae.utils.string.StringProcessor; import hae.utils.string.StringProcessor;
@@ -23,10 +24,8 @@ import javax.swing.table.TableRowSorter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutionException;
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;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -34,17 +33,19 @@ import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY;
public class MessageTableModel extends AbstractTableModel { public class MessageTableModel extends AbstractTableModel {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
private final MessageTable messageTable; private final MessageTable messageTable;
private final JTabbedPane messageTab;
private final JSplitPane splitPane; private final JSplitPane splitPane;
private final LinkedList<MessageEntry> log = new LinkedList<>(); private final LinkedList<MessageEntry> log = new LinkedList<>();
private final LinkedList<MessageEntry> filteredLog; private final LinkedList<MessageEntry> filteredLog;
private SwingWorker<Void, Void> currentWorker;
public MessageTableModel(MontoyaApi api) { public MessageTableModel(MontoyaApi api, ConfigLoader configLoader) {
this.filteredLog = new LinkedList<>(); this.filteredLog = new LinkedList<>();
this.api = api; this.api = api;
this.configLoader = configLoader;
messageTab = new JTabbedPane(); JTabbedPane messageTab = new JTabbedPane();
UserInterface userInterface = api.userInterface(); UserInterface userInterface = api.userInterface();
HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY); HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY);
HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY); HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY);
@@ -97,10 +98,10 @@ public class MessageTableModel extends AbstractTableModel {
splitPane.setRightComponent(messageTab); splitPane.setRightComponent(messageTab);
} }
public void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, String hash, String path) { public 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; boolean isDuplicate = false;
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status, hash, path); MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
byte[] reqByteA = new byte[0]; byte[] reqByteA = new byte[0];
byte[] resByteA = new byte[0]; byte[] resByteA = new byte[0];
@@ -134,6 +135,22 @@ public class MessageTableModel extends AbstractTableModel {
} }
if (!isDuplicate) { if (!isDuplicate) {
if (flag) {
try {
DataManager dataManager = new DataManager(api);
// 数据存储在BurpSuite空间内
PersistedObject persistedObject = PersistedObject.persistedObject();
persistedObject.setHttpRequestResponse("messageInfo", messageInfo);
persistedObject.setString("comment", comment);
persistedObject.setString("color", color);
String uuidIndex = StringProcessor.getRandomUUID();
dataManager.putData("message", uuidIndex, persistedObject);
} catch (Exception ignored) {
}
}
// 添加进日志
log.add(logEntry); log.add(logEntry);
} }
} }
@@ -144,7 +161,11 @@ public class MessageTableModel extends AbstractTableModel {
filteredLog.clear(); filteredLog.clear();
List<Integer> rowsToRemove = new ArrayList<>(); List<Integer> rowsToRemove = new ArrayList<>();
new SwingWorker<Void, Void>() { if (currentWorker != null && !currentWorker.isDone()) {
currentWorker.cancel(true);
}
currentWorker = new SwingWorker<Void, Void>() {
@Override @Override
protected Void doInBackground() { protected Void doInBackground() {
for (int i = 0; i < log.size(); i++) { for (int i = 0; i < log.size(); i++) {
@@ -164,92 +185,35 @@ public class MessageTableModel extends AbstractTableModel {
return null; return null;
} }
};
@Override currentWorker.execute();
protected void done() {
if (!rowsToRemove.isEmpty()) {
int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray();
fireTableRowsDeleted(rows[0], rows[rows.length - 1]);
}
}
}.execute();
} }
public void applyHostFilter(String filterText) { public void applyHostFilter(String filterText) {
filteredLog.clear(); filteredLog.clear();
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); log.forEach(entry -> {
List<Future<?>> futures = new ArrayList<>(); String host = StringProcessor.getHostByUrl(entry.getUrl());
try {
log.parallelStream().forEach(entry -> {
Future<?> future = executor.submit(() -> {
MessageEntry finalEntry = getEntryByFile(entry);
String host = StringProcessor.getHostByUrl(finalEntry.getUrl());
if (!host.isEmpty()) { if (!host.isEmpty()) {
synchronized (filteredLog) {
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) { if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
filteredLog.add(finalEntry); filteredLog.add(entry);
}
} }
} }
}); });
futures.add(future);
});
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
}
}
} catch (Exception e) {
api.logging().logToError("applyHostFilter: " + e.getMessage());
} finally {
executor.shutdown();
fireTableDataChanged(); fireTableDataChanged();
} }
}
private MessageEntry getEntryByFile(MessageEntry entry) {
HttpRequestResponse requestResponse = entry.getRequestResponse();
if (requestResponse == null) {
String url = entry.getUrl();
String method = entry.getMethod();
String status = entry.getStatus();
String comment = entry.getComment();
String color = entry.getColor();
String path = entry.getPath();
String hash = entry.getHash();
int length = Integer.parseInt(entry.getLength());
byte[] contents = FileProcessor.readFileContent(path, hash);
if (contents.length > length) {
byte[] response = Arrays.copyOf(contents, length);
byte[] request = Arrays.copyOfRange(contents, length, contents.length);
requestResponse = StringProcessor.createHttpRequestResponse(url, request, response);
int index = log.indexOf(entry);
entry = new MessageEntry(requestResponse, method, url, comment, String.valueOf(length), color, status, "", "");
log.set(index, entry);
}
}
return entry;
}
public void applyMessageFilter(String tableName, String filterText) { public void applyMessageFilter(String tableName, String filterText) {
filteredLog.clear(); filteredLog.clear();
for (MessageEntry entry : log) { for (MessageEntry entry : log) {
HttpRequestResponse requestResponse = entry.getRequestResponse();
// 标志变量,表示是否满足过滤条件 // 标志变量,表示是否满足过滤条件
AtomicBoolean isMatched = new AtomicBoolean(false); AtomicBoolean isMatched = new AtomicBoolean(false);
HttpRequest httpRequest = entry.getRequestResponse().request(); HttpRequestResponse requestResponse = entry.getRequestResponse();
HttpResponse httpResponse = entry.getRequestResponse().response(); HttpRequest httpRequest = requestResponse.request();
HttpResponse httpResponse = requestResponse.response();
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8); String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8); String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
@@ -263,7 +227,6 @@ public class MessageTableModel extends AbstractTableModel {
.map(HttpHeader::toString) .map(HttpHeader::toString)
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
MessageEntry finalEntry = entry;
Config.globalRules.keySet().forEach(i -> { Config.globalRules.keySet().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) { for (Object[] objects : Config.globalRules.get(i)) {
String name = objects[1].toString(); String name = objects[1].toString();
@@ -271,7 +234,7 @@ public class MessageTableModel extends AbstractTableModel {
String scope = objects[6].toString(); String scope = objects[6].toString();
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间 // 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
if (finalEntry.getComment().contains(name)) { if (entry.getComment().contains(name)) {
if (name.equals(tableName)) { if (name.equals(tableName)) {
// 标志变量,表示当前规则是否匹配 // 标志变量,表示当前规则是否匹配
boolean isMatch = false; boolean isMatch = false;
@@ -410,7 +373,7 @@ public class MessageTableModel extends AbstractTableModel {
return messageTable; return messageTable;
} }
public List<MessageEntry> getLogs() { public LinkedList<MessageEntry> getLogs() {
return log; return log;
} }
@@ -426,12 +389,11 @@ public class MessageTableModel extends AbstractTableModel {
@Override @Override
public Object getValueAt(int rowIndex, int columnIndex) { public Object getValueAt(int rowIndex, int columnIndex) {
if (filteredLog.isEmpty()) { if (!filteredLog.isEmpty()) {
return ""; try {
}
MessageEntry messageEntry = filteredLog.get(rowIndex); MessageEntry messageEntry = filteredLog.get(rowIndex);
if (messageEntry != null) {
return switch (columnIndex) { return switch (columnIndex) {
case 0 -> messageEntry.getMethod(); case 0 -> messageEntry.getMethod();
case 1 -> messageEntry.getUrl(); case 1 -> messageEntry.getUrl();
@@ -442,6 +404,13 @@ public class MessageTableModel extends AbstractTableModel {
default -> ""; default -> "";
}; };
} }
} catch (Exception e) {
api.logging().logToError("getValueAt: " + e.getMessage());
}
}
return "";
}
@Override @Override
public String getColumnName(int columnIndex) { public String getColumnName(int columnIndex) {
@@ -458,9 +427,7 @@ public class MessageTableModel extends AbstractTableModel {
public class MessageTable extends JTable { public class MessageTable extends JTable {
private MessageEntry messageEntry; private MessageEntry messageEntry;
private SwingWorker<Object, Void> currentWorker; private final ExecutorService executorService;
// 设置响应报文返回的最大长度
private final int MAX_LENGTH = 5242880;
private int lastSelectedIndex = -1; private int lastSelectedIndex = -1;
private final HttpRequestEditor requestEditor; private final HttpRequestEditor requestEditor;
private final HttpResponseEditor responseEditor; private final HttpResponseEditor responseEditor;
@@ -469,46 +436,31 @@ public class MessageTableModel extends AbstractTableModel {
super(messageTableModel); super(messageTableModel);
this.requestEditor = requestEditor; this.requestEditor = requestEditor;
this.responseEditor = responseEditor; this.responseEditor = responseEditor;
this.executorService = Executors.newSingleThreadExecutor();
} }
@Override @Override
public void changeSelection(int row, int col, boolean toggle, boolean extend) { public void changeSelection(int row, int col, boolean toggle, boolean extend) {
super.changeSelection(row, col, toggle, extend); super.changeSelection(row, col, toggle, extend);
requestEditor.setRequest(HttpRequest.httpRequest("Loading..."));
responseEditor.setResponse(HttpResponse.httpResponse("Loading..."));
if (currentWorker != null && !currentWorker.isDone()) {
currentWorker.cancel(true);
}
currentWorker = new SwingWorker<>() {
@Override
protected Void doInBackground() {
int selectedIndex = convertRowIndexToModel(row); int selectedIndex = convertRowIndexToModel(row);
if (lastSelectedIndex != selectedIndex) { if (lastSelectedIndex != selectedIndex) {
lastSelectedIndex = selectedIndex; lastSelectedIndex = selectedIndex;
messageEntry = filteredLog.get(selectedIndex); executorService.execute(this::getSelectedMessage);
}
}
private void getSelectedMessage() {
messageEntry = filteredLog.get(lastSelectedIndex);
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse(); HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
ByteArray requestByte = httpRequestResponse.request().toByteArray(); requestEditor.setRequest(HttpRequest.httpRequest(messageEntry.getRequestResponse().httpService(), httpRequestResponse.request().toByteArray()));
ByteArray responseByte = httpRequestResponse.response().toByteArray(); int responseSizeWithMb = httpRequestResponse.response().toString().length() / 1024 / 1024;
if ((responseSizeWithMb < Integer.parseInt(configLoader.getLimitSize())) || configLoader.getLimitSize().equals("0")) {
if (responseByte.length() > MAX_LENGTH) { responseEditor.setResponse(httpRequestResponse.response());
String ellipsis = "\r\n......"; } else {
responseByte = responseByte.subArray(0, MAX_LENGTH).withAppended(ellipsis); responseEditor.setResponse(HttpResponse.httpResponse("Exceeds length limit."));
} }
requestEditor.setRequest(HttpRequest.httpRequest(messageEntry.getRequestResponse().httpService(), requestByte));
responseEditor.setResponse(HttpResponse.httpResponse(responseByte));
}
return null;
}
};
currentWorker.execute();
} }
} }
} }

View File

@@ -0,0 +1,289 @@
package hae.component.board.table;
import burp.api.montoya.MontoyaApi;
import hae.component.board.message.MessageTableModel;
import hae.utils.ConfigLoader;
import hae.utils.UIEnhancer;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
public class Datatable extends JPanel {
private final MontoyaApi api;
private final ConfigLoader configLoader;
private final JTable dataTable;
private final DefaultTableModel dataTableModel;
private final JTextField searchField;
private final JTextField secondSearchField;
private final TableRowSorter<DefaultTableModel> sorter;
private final JCheckBox searchMode = new JCheckBox("Reverse search");
private final String tabName;
private final JPanel footerPanel;
public Datatable(MontoyaApi api, ConfigLoader configLoader, String tabName, List<String> dataList) {
this.api = api;
this.configLoader = configLoader;
this.tabName = tabName;
String[] columnNames = {"#", "Information"};
this.dataTableModel = new DefaultTableModel(columnNames, 0);
this.dataTable = new JTable(dataTableModel);
this.sorter = new TableRowSorter<>(dataTableModel);
this.searchField = new JTextField(10);
this.secondSearchField = new JTextField(10);
this.footerPanel = new JPanel(new BorderLayout(0, 5));
initComponents(dataList);
}
private void initComponents(List<String> dataList) {
// 设置ID排序
sorter.setComparator(0, new Comparator<Integer>() {
@Override
public int compare(Integer s1, Integer s2) {
return s1.compareTo(s2);
}
});
for (String item : dataList) {
if (!item.isEmpty()) {
addRowToTable(new Object[]{item});
}
}
UIEnhancer.setTextFieldPlaceholder(searchField, "Search");
searchField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
performSearch();
}
@Override
public void removeUpdate(DocumentEvent e) {
performSearch();
}
@Override
public void changedUpdate(DocumentEvent e) {
performSearch();
}
});
UIEnhancer.setTextFieldPlaceholder(secondSearchField, "Second search");
secondSearchField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
performSearch();
}
@Override
public void removeUpdate(DocumentEvent e) {
performSearch();
}
@Override
public void changedUpdate(DocumentEvent e) {
performSearch();
}
});
// 设置布局
JScrollPane scrollPane = new JScrollPane(dataTable);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
dataTable.setRowSorter(sorter);
TableColumn idColumn = dataTable.getColumnModel().getColumn(0);
idColumn.setPreferredWidth(50);
idColumn.setMaxWidth(100);
setLayout(new BorderLayout(0, 5));
JPanel optionsPanel = new JPanel();
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.X_AXIS));
// Settings按钮
JPanel settingMenuPanel = new JPanel(new GridLayout(1, 1));
settingMenuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
JPopupMenu settingMenu = new JPopupMenu();
settingMenuPanel.add(searchMode);
searchMode.addItemListener(e -> performSearch());
settingMenu.add(settingMenuPanel);
JButton settingsButton = new JButton("Settings");
setMenuShow(settingMenu, settingsButton);
optionsPanel.add(settingsButton);
optionsPanel.add(Box.createHorizontalStrut(5));
optionsPanel.add(searchField);
optionsPanel.add(Box.createHorizontalStrut(5));
optionsPanel.add(secondSearchField);
footerPanel.setBorder(BorderFactory.createEmptyBorder(2, 3, 5, 3));
footerPanel.add(optionsPanel, BorderLayout.CENTER);
add(scrollPane, BorderLayout.CENTER);
add(footerPanel, BorderLayout.SOUTH);
}
private void setMenuShow(JPopupMenu menu, JButton button) {
button.addActionListener(e -> {
Point buttonLocation = button.getLocationOnScreen();
Dimension menuSize = menu.getPreferredSize();
int x = buttonLocation.x + (button.getWidth() - menuSize.width) / 2;
int y = buttonLocation.y - menuSize.height;
menu.show(button, x - buttonLocation.x, y - buttonLocation.y);
});
}
private void addRowToTable(Object[] data) {
int rowCount = dataTableModel.getRowCount();
int id = rowCount > 0 ? (Integer) dataTableModel.getValueAt(rowCount - 1, 0) + 1 : 1;
Object[] rowData = new Object[data.length + 1];
rowData[0] = id;
System.arraycopy(data, 0, rowData, 1, data.length);
dataTableModel.addRow(rowData);
}
private void performSearch() {
RowFilter<Object, Object> firstRowFilter = applyFirstSearchFilter();
RowFilter<Object, Object> secondRowFilter = applySecondFilter();
if (searchField.getForeground().equals(Color.BLACK)) {
sorter.setRowFilter(firstRowFilter);
if (secondSearchField.getForeground().equals(Color.BLACK)) {
List<RowFilter<Object, Object>> filters = new ArrayList<>();
filters.add(firstRowFilter);
filters.add(secondRowFilter);
sorter.setRowFilter(RowFilter.andFilter(filters));
}
}
}
private RowFilter<Object, Object> applyFirstSearchFilter() {
return new RowFilter<Object, Object>() {
public boolean include(Entry<?, ?> entry) {
String searchFieldTextText = searchField.getText();
Pattern pattern = null;
try {
pattern = Pattern.compile(searchFieldTextText, Pattern.CASE_INSENSITIVE);
} catch (Exception ignored) {
}
String entryValue = ((String) entry.getValue(1)).toLowerCase();
searchFieldTextText = searchFieldTextText.toLowerCase();
if (pattern != null) {
return searchFieldTextText.isEmpty() || pattern.matcher(entryValue).find() != searchMode.isSelected();
} else {
return searchFieldTextText.isEmpty() || entryValue.contains(searchFieldTextText) != searchMode.isSelected();
}
}
};
}
private RowFilter<Object, Object> applySecondFilter() {
return new RowFilter<Object, Object>() {
public boolean include(Entry<?, ?> entry) {
String searchFieldTextText = secondSearchField.getText();
Pattern pattern = null;
try {
pattern = Pattern.compile(searchFieldTextText, Pattern.CASE_INSENSITIVE);
} catch (Exception ignored) {
}
String entryValue = ((String) entry.getValue(1)).toLowerCase();
searchFieldTextText = searchFieldTextText.toLowerCase();
if (pattern != null) {
return searchFieldTextText.isEmpty() || pattern.matcher(entryValue).find();
} else {
return searchFieldTextText.isEmpty() || entryValue.contains(searchFieldTextText);
}
}
};
}
public void setTableListener(MessageTableModel messagePanel) {
// 表格复制功能
dataTable.setTransferHandler(new TransferHandler() {
@Override
public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException {
if (comp instanceof JTable) {
StringSelection stringSelection = new StringSelection(getSelectedDataAtTable((JTable) comp).replace("\0", "").replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", ""));
clip.setContents(stringSelection, null);
} else {
super.exportToClipboard(comp, clip, action);
}
}
});
dataTable.setDefaultEditor(Object.class, null);
// 表格内容双击事件
dataTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
int selectedRow = dataTable.getSelectedRow();
if (selectedRow != -1) {
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
messagePanel.applyMessageFilter(tabName, rowData);
}
}
}
});
}
private String getTableData(JTable table) {
StringBuilder selectData = new StringBuilder();
int rowCount = table.getRowCount();
for (int i = 0; i < rowCount; i++) {
selectData.append(table.getValueAt(i, 1).toString()).append("\r\n");
}
if (!selectData.isEmpty()) {
selectData.delete(selectData.length() - 2, selectData.length());
} else {
return "";
}
return selectData.toString();
}
public String getSelectedDataAtTable(JTable table) {
int[] selectRows = table.getSelectedRows();
StringBuilder selectData = new StringBuilder();
for (int row : selectRows) {
selectData.append(table.getValueAt(row, 1).toString()).append("\r\n");
}
if (!selectData.isEmpty()) {
selectData.delete(selectData.length() - 2, selectData.length());
} else {
return "";
}
return selectData.toString();
}
public JTable getDataTable() {
return this.dataTable;
}
}

View File

@@ -1,263 +0,0 @@
package hae.component.config;
import burp.api.montoya.MontoyaApi;
import hae.component.rule.Rules;
import hae.utils.ConfigLoader;
import hae.utils.UIEnhancer;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class Config extends JPanel {
private final MontoyaApi api;
private final ConfigLoader configLoader;
private final Rules rules;
private JTextField addTextField;
private final String defaultText = "Enter a new item";
public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) {
this.api = api;
this.configLoader = configLoader;
this.rules = rules;
initComponents();
}
private void initComponents() {
setLayout(new BorderLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 1.0;
constraints.fill = GridBagConstraints.HORIZONTAL;
JPanel ruleInfoPanel = new JPanel(new GridBagLayout());
ruleInfoPanel.setBorder(new EmptyBorder(10, 15, 5, 15));
JLabel ruleLabel = new JLabel("Path:");
JTextField pathTextField = new JTextField();
pathTextField.setEditable(false);
pathTextField.setText(configLoader.getRulesFilePath());
JButton reloadButton = new JButton("Reload");
JButton updateButton = new JButton("Update");
ruleInfoPanel.add(ruleLabel);
ruleInfoPanel.add(pathTextField, constraints);
ruleInfoPanel.add(Box.createHorizontalStrut(5));
ruleInfoPanel.add(reloadButton);
ruleInfoPanel.add(Box.createHorizontalStrut(5));
ruleInfoPanel.add(updateButton);
reloadButton.addActionListener(this::reloadActionPerformed);
updateButton.addActionListener(this::onlineUpdateActionPerformed);
JPanel settingPanel = new JPanel(new BorderLayout());
DefaultTableModel model = new DefaultTableModel();
JTable table = new JTable(model);
model.addColumn("Value");
JScrollPane scrollPane = new JScrollPane(table);
JPanel buttonPanel = new JPanel();
buttonPanel.setBorder(new EmptyBorder(0, 3, 0, 0));
GridBagLayout layout = new GridBagLayout();
layout.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0};
layout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
buttonPanel.setLayout(layout);
JPanel inputPanel = new JPanel(new BorderLayout());
JPanel inputPanelB = new JPanel(new BorderLayout());
inputPanelB.setBorder(new EmptyBorder(0, 0, 3, 0));
constraints.gridx = 1;
JButton addButton = new JButton("Add");
JButton removeButton = new JButton("Remove");
JButton pasteButton = new JButton("Paste");
JButton clearButton = new JButton("Clear");
JComboBox<String> setTypeComboBox = new JComboBox<>();
String[] mode = new String[]{"Exclude suffix", "Block host"};
setTypeComboBox.setModel(new DefaultComboBoxModel<>(mode));
setTypeComboBox.addActionListener(e -> {
String selected = (String) setTypeComboBox.getSelectedItem();
model.setRowCount(0);
if (selected.equals("Exclude suffix")) {
addDataToTable(configLoader.getExcludeSuffix().replaceAll("\\|", "\r\n"), model);
}
if (selected.equals("Block host")) {
addDataToTable(configLoader.getBlockHost().replaceAll("\\|", "\r\n"), model);
}
});
setTypeComboBox.setSelectedItem("Exclude suffix");
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
String selected = (String) setTypeComboBox.getSelectedItem();
String values = getFirstColumnDataAsString(model);
if (selected.equals("Exclude suffix")) {
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
configLoader.setExcludeSuffix(values);
}
}
if (selected.equals("Block host")) {
if (!values.equals(configLoader.getBlockHost()) && !values.isEmpty()) {
configLoader.setBlockHost(values);
}
}
}
});
constraints.insets = new Insets(0, 0, 3, 0);
constraints.gridy = 0;
buttonPanel.add(setTypeComboBox, constraints);
constraints.gridy = 1;
buttonPanel.add(addButton, constraints);
constraints.gridy = 2;
buttonPanel.add(removeButton, constraints);
constraints.gridy = 3;
buttonPanel.add(pasteButton, constraints);
constraints.gridy = 4;
buttonPanel.add(clearButton, constraints);
addTextField = new JTextField();
UIEnhancer.setTextFieldPlaceholder(addTextField, defaultText);
inputPanelB.add(addTextField, BorderLayout.CENTER);
inputPanel.add(scrollPane, BorderLayout.CENTER);
inputPanel.add(inputPanelB, BorderLayout.NORTH);
settingPanel.add(buttonPanel, BorderLayout.EAST);
settingPanel.add(inputPanel, BorderLayout.CENTER);
addButton.addActionListener(e -> addActionPerformedAction(e, model));
addTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
addActionPerformedAction(null, model);
}
}
});
pasteButton.addActionListener(e -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
try {
String data = (String) clipboard.getData(DataFlavor.stringFlavor);
if (data != null && !data.isEmpty()) {
addDataToTable(data, model);
}
} catch (Exception ignored) {
}
});
removeButton.addActionListener(e -> {
int selectedRow = table.getSelectedRow();
if (selectedRow != -1) {
model.removeRow(selectedRow);
}
});
clearButton.addActionListener(e -> model.setRowCount(0));
JPanel settingMainPanel = new JPanel(new BorderLayout());
JLabel settingLabel = new JLabel("Setting:");
JPanel settingLabelPanel = new JPanel(new BorderLayout());
settingLabelPanel.add(settingLabel, BorderLayout.WEST);
settingMainPanel.setBorder(new EmptyBorder(0, 15, 10, 15));
settingMainPanel.add(settingLabelPanel, BorderLayout.NORTH);
settingMainPanel.add(settingPanel, BorderLayout.CENTER);
add(ruleInfoPanel, BorderLayout.NORTH);
add(settingMainPanel, BorderLayout.CENTER);
}
private String getFirstColumnDataAsString(DefaultTableModel model) {
StringBuilder firstColumnData = new StringBuilder();
int numRows = model.getRowCount();
for (int row = 0; row < numRows; row++) {
firstColumnData.append(model.getValueAt(row, 0));
if (row < numRows - 1) {
firstColumnData.append("|");
}
}
return firstColumnData.toString();
}
private void addDataToTable(String data, DefaultTableModel model) {
if (!data.isBlank()) {
String[] rows = data.split("\\r?\\n");
for (String row : rows) {
model.addRow(new String[]{row});
}
deduplicateTableData(model);
}
}
private void deduplicateTableData(DefaultTableModel model) {
// 使用 Map 存储每一行的数据,用于去重
Set<List<Object>> rowData = new LinkedHashSet<>();
int columnCount = model.getColumnCount();
// 将每一行数据作为一个列表,添加到 Set 中
for (int i = 0; i < model.getRowCount(); i++) {
List<Object> row = new ArrayList<>();
for (int j = 0; j < columnCount; j++) {
row.add(model.getValueAt(i, j));
}
rowData.add(row);
}
// 清除原始数据
model.setRowCount(0);
// 将去重后的数据添加回去
for (List<Object> uniqueRow : rowData) {
model.addRow(uniqueRow.toArray());
}
}
private void addActionPerformedAction(ActionEvent e, DefaultTableModel model) {
String addTextFieldText = addTextField.getText();
if (!addTextFieldText.equals(defaultText)) {
addDataToTable(addTextFieldText, model);
}
addTextField.setText("");
addTextField.requestFocusInWindow();
}
private void onlineUpdateActionPerformed(ActionEvent e) {
// 添加提示框防止用户误触导致配置更新
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
if (retCode == JOptionPane.YES_OPTION) {
configLoader.initRulesByNet();
reloadActionPerformed(null);
}
}
private void reloadActionPerformed(ActionEvent e) {
rules.reloadRuleGroup();
}
}

View File

@@ -9,9 +9,11 @@ import burp.api.montoya.ui.Selection;
import burp.api.montoya.ui.editor.extension.EditorCreationContext; import burp.api.montoya.ui.editor.extension.EditorCreationContext;
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor; import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor;
import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider; import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider;
import hae.component.board.Datatable; import hae.Config;
import hae.component.board.table.Datatable;
import hae.instances.http.utils.MessageProcessor; import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader; import hae.utils.ConfigLoader;
import hae.utils.http.HttpUtils;
import hae.utils.string.StringProcessor; import hae.utils.string.StringProcessor;
import javax.swing.*; import javax.swing.*;
@@ -37,6 +39,7 @@ public class RequestEditor implements HttpRequestEditorProvider {
private static class Editor implements ExtensionProvidedHttpRequestEditor { private static class Editor implements ExtensionProvidedHttpRequestEditor {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader; private final ConfigLoader configLoader;
private final HttpUtils httpUtils;
private final EditorCreationContext creationContext; private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
private HttpRequestResponse requestResponse; private HttpRequestResponse requestResponse;
@@ -47,6 +50,7 @@ public class RequestEditor implements HttpRequestEditorProvider {
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) { public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api; this.api = api;
this.configLoader = configLoader; this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader);
this.creationContext = creationContext; this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api);
} }
@@ -59,7 +63,7 @@ public class RequestEditor implements HttpRequestEditorProvider {
@Override @Override
public void setRequestResponse(HttpRequestResponse requestResponse) { public void setRequestResponse(HttpRequestResponse requestResponse) {
this.requestResponse = requestResponse; this.requestResponse = requestResponse;
generateTabbedPaneFromResultMap(api, jTabbedPane, this.dataList); generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
} }
@Override @Override
@@ -69,13 +73,10 @@ public class RequestEditor implements HttpRequestEditorProvider {
try { try {
String host = StringProcessor.getHostByUrl(request.url()); String host = StringProcessor.getHostByUrl(request.url());
if (!host.isEmpty()) { if (!host.isEmpty()) {
String[] hostList = configLoader.getBlockHost().split("\\|"); String toolType = creationContext.toolSource().toolType().toolName();
boolean isBlockHost = isBlockHost(hostList, host); boolean matches = httpUtils.verifyHttpRequestResponse(requestResponse, toolType);
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|")); if (!matches) {
boolean matches = suffixList.contains(request.fileExtension().toLowerCase()) || isBlockHost;
if (!matches && !request.bodyToString().equals("Loading...")) {
this.dataList = messageProcessor.processRequest("", request, false); this.dataList = messageProcessor.processRequest("", request, false);
return isListHasData(this.dataList); return isListHasData(this.dataList);
} }
@@ -118,19 +119,6 @@ public class RequestEditor implements HttpRequestEditorProvider {
} }
} }
public static boolean isBlockHost(String[] hostList, String host) {
boolean isBlockHost = false;
for (String hostName : hostList) {
String cleanedHost = StringProcessor.replaceFirstOccurrence(hostName, "*.", "");
if (hostName.contains("*.") && StringProcessor.matchFromEnd(host, cleanedHost)) {
isBlockHost = true;
} else if (host.equals(hostName) || hostName.equals("*")) {
isBlockHost = true;
}
}
return isBlockHost;
}
public static boolean isListHasData(List<Map<String, String>> dataList) { public static boolean isListHasData(List<Map<String, String>> dataList) {
if (dataList != null && !dataList.isEmpty()) { if (dataList != null && !dataList.isEmpty()) {
Map<String, String> dataMap = dataList.get(0); Map<String, String> dataMap = dataList.get(0);
@@ -139,14 +127,14 @@ public class RequestEditor implements HttpRequestEditorProvider {
return false; return false;
} }
public static void generateTabbedPaneFromResultMap(MontoyaApi api, JTabbedPane tabbedPane, List<Map<String, String>> result) { public static void generateTabbedPaneFromResultMap(MontoyaApi api, ConfigLoader configLoader, JTabbedPane tabbedPane, List<Map<String, String>> result) {
tabbedPane.removeAll(); tabbedPane.removeAll();
if (result != null && !result.isEmpty()) { if (result != null && !result.isEmpty()) {
Map<String, String> dataMap = result.get(0); Map<String, String> dataMap = result.get(0);
if (dataMap != null && !dataMap.isEmpty()) { if (dataMap != null && !dataMap.isEmpty()) {
dataMap.keySet().forEach(i -> { dataMap.keySet().forEach(i -> {
String[] extractData = dataMap.get(i).split("\n"); String[] extractData = dataMap.get(i).split(Config.boundary);
Datatable dataPanel = new Datatable(api, i, Arrays.asList(extractData)); Datatable dataPanel = new Datatable(api, configLoader, i, Arrays.asList(extractData));
tabbedPane.addTab(i, dataPanel); tabbedPane.addTab(i, dataPanel);
}); });
} }

View File

@@ -10,14 +10,14 @@ import burp.api.montoya.ui.Selection;
import burp.api.montoya.ui.editor.extension.EditorCreationContext; import burp.api.montoya.ui.editor.extension.EditorCreationContext;
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor; import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor;
import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider; import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider;
import hae.component.board.Datatable; import hae.component.board.table.Datatable;
import hae.instances.http.utils.MessageProcessor; import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader; import hae.utils.ConfigLoader;
import hae.utils.http.HttpUtils;
import hae.utils.string.StringProcessor; import hae.utils.string.StringProcessor;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -38,6 +38,7 @@ public class ResponseEditor implements HttpResponseEditorProvider {
private static class Editor implements ExtensionProvidedHttpResponseEditor { private static class Editor implements ExtensionProvidedHttpResponseEditor {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader; private final ConfigLoader configLoader;
private final HttpUtils httpUtils;
private final EditorCreationContext creationContext; private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
private HttpRequestResponse requestResponse; private HttpRequestResponse requestResponse;
@@ -48,6 +49,7 @@ public class ResponseEditor implements HttpResponseEditorProvider {
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) { public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api; this.api = api;
this.configLoader = configLoader; this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader);
this.creationContext = creationContext; this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api);
} }
@@ -60,7 +62,7 @@ public class ResponseEditor implements HttpResponseEditorProvider {
@Override @Override
public void setRequestResponse(HttpRequestResponse requestResponse) { public void setRequestResponse(HttpRequestResponse requestResponse) {
this.requestResponse = requestResponse; this.requestResponse = requestResponse;
RequestEditor.generateTabbedPaneFromResultMap(api, jTabbedPane, this.dataList); RequestEditor.generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
} }
@Override @Override
@@ -75,17 +77,14 @@ public class ResponseEditor implements HttpResponseEditorProvider {
try { try {
String host = StringProcessor.getHostByUrl(request.url()); String host = StringProcessor.getHostByUrl(request.url());
if (!host.isEmpty()) { if (!host.isEmpty()) {
String[] hostList = configLoader.getBlockHost().split("\\|"); String toolType = creationContext.toolSource().toolType().toolName();
boolean isBlockHost = RequestEditor.isBlockHost(hostList, host); matches = httpUtils.verifyHttpRequestResponse(requestResponse, toolType);
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
matches = suffixList.contains(request.fileExtension().toLowerCase()) || isBlockHost;
} }
} catch (Exception ignored) { } catch (Exception ignored) {
} }
} }
if (!matches && !response.bodyToString().equals("Loading...")) { if (!matches) {
this.dataList = messageProcessor.processResponse("", response, false); this.dataList = messageProcessor.processResponse("", response, false);
return RequestEditor.isListHasData(this.dataList); return RequestEditor.isListHasData(this.dataList);
} }

View File

@@ -8,8 +8,9 @@ import burp.api.montoya.ui.contextmenu.WebSocketMessage;
import burp.api.montoya.ui.editor.extension.EditorCreationContext; import burp.api.montoya.ui.editor.extension.EditorCreationContext;
import burp.api.montoya.ui.editor.extension.ExtensionProvidedWebSocketMessageEditor; import burp.api.montoya.ui.editor.extension.ExtensionProvidedWebSocketMessageEditor;
import burp.api.montoya.ui.editor.extension.WebSocketMessageEditorProvider; import burp.api.montoya.ui.editor.extension.WebSocketMessageEditorProvider;
import hae.component.board.Datatable; import hae.component.board.table.Datatable;
import hae.instances.http.utils.MessageProcessor; import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -18,18 +19,21 @@ import java.util.Map;
public class WebSocketEditor implements WebSocketMessageEditorProvider { public class WebSocketEditor implements WebSocketMessageEditorProvider {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
public WebSocketEditor(MontoyaApi api) { public WebSocketEditor(MontoyaApi api, ConfigLoader configLoader) {
this.api = api; this.api = api;
this.configLoader = configLoader;
} }
@Override @Override
public ExtensionProvidedWebSocketMessageEditor provideMessageEditor(EditorCreationContext editorCreationContext) { public ExtensionProvidedWebSocketMessageEditor provideMessageEditor(EditorCreationContext editorCreationContext) {
return new Editor(api, editorCreationContext); return new Editor(api, configLoader, editorCreationContext);
} }
private static class Editor implements ExtensionProvidedWebSocketMessageEditor { private static class Editor implements ExtensionProvidedWebSocketMessageEditor {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
private final EditorCreationContext creationContext; private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
private ByteArray message; private ByteArray message;
@@ -37,8 +41,9 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
private final JTabbedPane jTabbedPane = new JTabbedPane(); private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, EditorCreationContext creationContext) { public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api; this.api = api;
this.configLoader = configLoader;
this.creationContext = creationContext; this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api);
} }
@@ -51,7 +56,7 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
@Override @Override
public void setMessage(WebSocketMessage webSocketMessage) { public void setMessage(WebSocketMessage webSocketMessage) {
this.message = webSocketMessage.payload(); this.message = webSocketMessage.payload();
RequestEditor.generateTabbedPaneFromResultMap(api, jTabbedPane, this.dataList); RequestEditor.generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
} }
@Override @Override

View File

@@ -0,0 +1,106 @@
package hae.instances.http;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.Annotations;
import burp.api.montoya.core.HighlightColor;
import burp.api.montoya.http.handler.*;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import hae.component.board.message.MessageTableModel;
import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader;
import hae.utils.http.HttpUtils;
import hae.utils.string.StringProcessor;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpMessageActiveHandler implements HttpHandler {
private final MontoyaApi api;
private final ConfigLoader configLoader;
private final HttpUtils httpUtils;
private final MessageTableModel messageTableModel;
private final MessageProcessor messageProcessor;
// Montoya API对HTTP消息的处理分为了请求和响应因此此处设置高亮和标记需要使用全局变量的方式以此兼顾请求和响应
// 同时采用 ThreadLocal 来保证多线程并发的情况下全局变量的安全性
private final ThreadLocal<String> host = ThreadLocal.withInitial(() -> "");
private final ThreadLocal<List<String>> colorList = ThreadLocal.withInitial(ArrayList::new);
private final ThreadLocal<List<String>> commentList = ThreadLocal.withInitial(ArrayList::new);
public HttpMessageActiveHandler(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
this.api = api;
this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader);
this.messageTableModel = messageTableModel;
this.messageProcessor = new MessageProcessor(api);
}
@Override
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent httpRequestToBeSent) {
colorList.get().clear();
commentList.get().clear();
Annotations annotations = httpRequestToBeSent.annotations();
try {
host.set(StringProcessor.getHostByUrl(httpRequestToBeSent.url()));
} catch (Exception e) {
api.logging().logToError("handleHttpRequestToBeSent: " + e.getMessage());
}
return RequestToBeSentAction.continueWith(httpRequestToBeSent, annotations);
}
@Override
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived httpResponseReceived) {
Annotations annotations = httpResponseReceived.annotations();
HttpRequest request = httpResponseReceived.initiatingRequest();
HttpRequestResponse requestResponse = HttpRequestResponse.httpRequestResponse(request, httpResponseReceived);
String toolType = httpResponseReceived.toolSource().toolType().toolName();
boolean matches = httpUtils.verifyHttpRequestResponse(requestResponse, toolType);
if (!matches) {
try {
setColorAndCommentList(messageProcessor.processRequest(host.get(), request, true));
setColorAndCommentList(messageProcessor.processResponse(host.get(), httpResponseReceived, true));
if (!colorList.get().isEmpty() && !commentList.get().isEmpty()) {
HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(request, httpResponseReceived);
String color = messageProcessor.retrieveFinalColor(messageProcessor.retrieveColorIndices(colorList.get()));
annotations.setHighlightColor(HighlightColor.highlightColor(color));
String comment = StringProcessor.mergeComment(String.join(", ", commentList.get()));
annotations.setNotes(comment);
String method = request.method();
String url = request.url();
String status = String.valueOf(httpResponseReceived.statusCode());
String length = String.valueOf(httpResponseReceived.toByteArray().length());
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() {
messageTableModel.add(httpRequestResponse, url, method, status, length, comment, color, true);
return null;
}
}.execute();
}
} catch (Exception e) {
api.logging().logToError("handleHttpResponseReceived: " + e.getMessage());
}
}
return ResponseReceivedAction.continueWith(httpResponseReceived, annotations);
}
private void setColorAndCommentList(List<Map<String, String>> result) {
if (result != null && !result.isEmpty()) {
colorList.get().add(result.get(0).get("color"));
commentList.get().add(result.get(1).get("comment"));
}
}
}

View File

@@ -1,101 +0,0 @@
package hae.instances.http;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.Annotations;
import burp.api.montoya.core.HighlightColor;
import burp.api.montoya.http.handler.*;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import hae.component.board.message.MessageTableModel;
import hae.instances.editor.RequestEditor;
import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader;
import hae.utils.string.StringProcessor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class HttpMessageHandler implements HttpHandler {
private final MontoyaApi api;
private final ConfigLoader configLoader;
private final MessageTableModel messageTableModel;
private final MessageProcessor messageProcessor;
// Montoya API对HTTP消息的处理分为了请求和响应因此此处设置高亮和标记需要使用全局变量的方式以此兼顾请求和响应
// 同时采用 ThreadLocal 来保证多线程并发的情况下全局变量的安全性
private final ThreadLocal<String> host = ThreadLocal.withInitial(() -> "");
private final ThreadLocal<List<String>> colorList = ThreadLocal.withInitial(ArrayList::new);
private final ThreadLocal<List<String>> commentList = ThreadLocal.withInitial(ArrayList::new);
private final ThreadLocal<Boolean> matches = ThreadLocal.withInitial(() -> false);
private final ThreadLocal<HttpRequest> httpRequest = new ThreadLocal<>();
public HttpMessageHandler(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
this.api = api;
this.configLoader = configLoader;
this.messageTableModel = messageTableModel;
this.messageProcessor = new MessageProcessor(api);
}
@Override
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent httpRequestToBeSent) {
colorList.get().clear();
commentList.get().clear();
Annotations annotations = httpRequestToBeSent.annotations();
httpRequest.set(httpRequestToBeSent);
host.set(StringProcessor.getHostByUrl(httpRequestToBeSent.url()));
String[] hostList = configLoader.getBlockHost().split("\\|");
boolean isBlockHost = RequestEditor.isBlockHost(hostList, host.get());
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
matches.set(suffixList.contains(httpRequestToBeSent.fileExtension().toLowerCase()) || isBlockHost);
if (!matches.get()) {
List<Map<String, String>> result = messageProcessor.processRequest(host.get(), httpRequestToBeSent, true);
setColorAndCommentList(result);
}
return RequestToBeSentAction.continueWith(httpRequestToBeSent, annotations);
}
@Override
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived httpResponseReceived) {
Annotations annotations = httpResponseReceived.annotations();
if (!matches.get()) {
List<Map<String, String>> result = messageProcessor.processResponse(host.get(), httpResponseReceived, true);
setColorAndCommentList(result);
// 设置高亮颜色和注释
if (!colorList.get().isEmpty() && !commentList.get().isEmpty()) {
String color = messageProcessor.retrieveFinalColor(messageProcessor.retrieveColorIndices(colorList.get()));
annotations.setHighlightColor(HighlightColor.highlightColor(color));
String comment = StringProcessor.mergeComment(String.join(", ", commentList.get()));
annotations.setNotes(comment);
HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(httpRequest.get(), httpResponseReceived);
// 添加到Databoard
String method = httpRequest.get().method();
String url = httpRequest.get().url();
String status = String.valueOf(httpResponseReceived.statusCode());
String length = String.valueOf(httpResponseReceived.toByteArray().length());
messageTableModel.add(httpRequestResponse, url, method, status, length, comment, color, "", "");
}
}
return ResponseReceivedAction.continueWith(httpResponseReceived, annotations);
}
private void setColorAndCommentList(List<Map<String, String>> result) {
if (result != null && !result.isEmpty() && result.size() > 0) {
colorList.get().add(result.get(0).get("color"));
commentList.get().add(result.get(1).get("comment"));
}
}
}

View File

@@ -0,0 +1,97 @@
package hae.instances.http;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
import burp.api.montoya.scanner.AuditResult;
import burp.api.montoya.scanner.ConsolidationAction;
import burp.api.montoya.scanner.ScanCheck;
import burp.api.montoya.scanner.audit.insertionpoint.AuditInsertionPoint;
import burp.api.montoya.scanner.audit.issues.AuditIssue;
import hae.component.board.message.MessageTableModel;
import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader;
import hae.utils.http.HttpUtils;
import hae.utils.string.StringProcessor;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static burp.api.montoya.scanner.AuditResult.auditResult;
import static burp.api.montoya.scanner.ConsolidationAction.KEEP_BOTH;
import static burp.api.montoya.scanner.ConsolidationAction.KEEP_EXISTING;
import static java.util.Collections.emptyList;
public class HttpMessagePassiveHandler implements ScanCheck {
private final MontoyaApi api;
private final ConfigLoader configLoader;
private final HttpUtils httpUtils;
private final MessageTableModel messageTableModel;
private final MessageProcessor messageProcessor;
public HttpMessagePassiveHandler(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
this.api = api;
this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader);
this.messageTableModel = messageTableModel;
this.messageProcessor = new MessageProcessor(api);
}
@Override
public AuditResult activeAudit(HttpRequestResponse httpRequestResponse, AuditInsertionPoint auditInsertionPoint) {
return auditResult(emptyList());
}
@Override
public AuditResult passiveAudit(HttpRequestResponse httpRequestResponse) {
List<String> colorList = new ArrayList<>();
List<String> commentList = new ArrayList<>();
HttpRequest request = httpRequestResponse.request();
HttpResponse response = httpRequestResponse.response();
boolean matches = httpUtils.verifyHttpRequestResponse(httpRequestResponse, "Proxy");
if (!matches) {
try {
String host = StringProcessor.getHostByUrl(request.url());
setColorAndCommentList(messageProcessor.processRequest(host, request, true), colorList, commentList);
setColorAndCommentList(messageProcessor.processResponse(host, response, true), colorList, commentList);
String url = request.url();
String method = request.method();
String status = String.valueOf(response.statusCode());
String color = messageProcessor.retrieveFinalColor(messageProcessor.retrieveColorIndices(colorList));
String comment = StringProcessor.mergeComment(String.join(", ", commentList));
String length = String.valueOf(response.toByteArray().length());
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() {
messageTableModel.add(httpRequestResponse, url, method, status, length, comment, color, true);
return null;
}
}.execute();
} catch (Exception e) {
api.logging().logToError("passiveAudit: " + e.getMessage());
}
}
return auditResult(emptyList());
}
private void setColorAndCommentList(List<Map<String, String>> result, List<String> colorList, List<String> commentList) {
if (result != null && !result.isEmpty()) {
colorList.add(result.get(0).get("color"));
commentList.add(result.get(1).get("comment"));
}
}
@Override
public ConsolidationAction consolidateIssues(AuditIssue newIssue, AuditIssue existingIssue) {
return existingIssue.name().equals(newIssue.name()) ? KEEP_EXISTING : KEEP_BOTH;
}
}

View File

@@ -71,7 +71,7 @@ public class MessageProcessor {
List<Map<String, String>> highlightList = new ArrayList<>(); List<Map<String, String>> highlightList = new ArrayList<>();
List<Map<String, String>> extractList = new ArrayList<>(); List<Map<String, String>> extractList = new ArrayList<>();
if (obj != null && !obj.isEmpty() && obj.size() > 0) { if (obj != null && !obj.isEmpty()) {
if (actionFlag) { if (actionFlag) {
List<List<String>> resultList = extractColorsAndComments(obj); List<List<String>> resultList = extractColorsAndComments(obj);
List<String> colorList = resultList.get(0); List<String> colorList = resultList.get(0);

View File

@@ -1,12 +1,15 @@
package hae.instances.http.utils; package hae.instances.http.utils;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.persistence.PersistedList;
import burp.api.montoya.persistence.PersistedObject;
import dk.brics.automaton.Automaton; import dk.brics.automaton.Automaton;
import dk.brics.automaton.AutomatonMatcher; 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.CachePool; import hae.cache.CachePool;
import hae.utils.DataManager;
import hae.utils.string.HashCalculator; import hae.utils.string.HashCalculator;
import hae.utils.string.StringProcessor; import hae.utils.string.StringProcessor;
@@ -38,7 +41,7 @@ public class RegularMatcher {
// 多线程执行,一定程度上减少阻塞现象 // 多线程执行,一定程度上减少阻塞现象
String matchContent = ""; String matchContent = "";
// 遍历获取规则 // 遍历获取规则
List<String> result = new ArrayList<>(); List<String> result;
Map<String, Object> tmpMap = new HashMap<>(); Map<String, Object> tmpMap = new HashMap<>();
boolean loaded = (Boolean) objects[0]; boolean loaded = (Boolean) objects[0];
@@ -78,7 +81,7 @@ public class RegularMatcher {
} }
try { try {
result.addAll(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive)); result = new ArrayList<>(matchByRegex(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());
@@ -92,13 +95,13 @@ public class RegularMatcher {
if (!result.isEmpty()) { if (!result.isEmpty()) {
tmpMap.put("color", color); tmpMap.put("color", color);
String dataStr = String.join("\n", result); String dataStr = String.join(Config.boundary, result);
tmpMap.put("data", dataStr); tmpMap.put("data", dataStr);
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(host, name, result); putDataToGlobalMap(api, host, name, result, true);
} }
} }
} }
@@ -108,7 +111,7 @@ public class RegularMatcher {
} }
} }
public static void putDataToGlobalMap(String host, String name, List<String> dataList) { public synchronized static void putDataToGlobalMap(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) -> {
@@ -120,6 +123,21 @@ public class RegularMatcher {
return new ArrayList<>(combinedSet); return new ArrayList<>(combinedSet);
}); });
if (flag) {
// 数据存储在BurpSuite空间内
try {
DataManager dataManager = new DataManager(api);
PersistedObject persistedObject = PersistedObject.persistedObject();
gRuleMap.forEach((kName, vList) -> {
PersistedList<String> persistedList = PersistedList.persistedStringList();
persistedList.addAll(vList);
persistedObject.setStringList(kName, persistedList);
});
dataManager.putData("data", host, persistedObject);
} catch (Exception ignored) {
}
}
return gRuleMap; return gRuleMap;
}); });
@@ -128,7 +146,7 @@ public class RegularMatcher {
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : ""; String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
if (!Config.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) { if (!Config.globalDataMap.containsKey(anyHost) && !anyHost.isEmpty()) {
// 添加通配符Host实际数据从查询哪里将所有数据提取 // 添加通配符Host实际数据从查询哪里将所有数据提取
Config.globalDataMap.put(anyHost, new HashMap<>()); Config.globalDataMap.put(anyHost, new HashMap<>());
} }

View File

@@ -22,7 +22,7 @@ public class WebSocketMessageHandler implements ProxyMessageHandler {
String message = interceptedTextMessage.payload(); String message = interceptedTextMessage.payload();
List<Map<String, String>> result = messageProcessor.processMessage("", message, true); List<Map<String, String>> result = messageProcessor.processMessage("", message, true);
if (result != null && !result.isEmpty() && result.size() > 0) { if (result != null && !result.isEmpty()) {
interceptedTextMessage.annotations().setHighlightColor(HighlightColor.highlightColor(result.get(0).get("color"))); interceptedTextMessage.annotations().setHighlightColor(HighlightColor.highlightColor(result.get(0).get("color")));
interceptedTextMessage.annotations().setNotes(result.get(1).get("comment")); interceptedTextMessage.annotations().setNotes(result.get(1).get("comment"));
} }

View File

@@ -1,9 +1,6 @@
package hae.utils; package hae.utils;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.RequestOptions;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import hae.Config; import hae.Config;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
@@ -46,7 +43,7 @@ public class ConfigLoader {
File rulesFilePath = new File(this.rulesFilePath); File rulesFilePath = new File(this.rulesFilePath);
if (!(rulesFilePath.exists() && rulesFilePath.isFile())) { if (!(rulesFilePath.exists() && rulesFilePath.isFile())) {
initRulesByRes(); initRules();
} }
Config.globalRules = getRules(); Config.globalRules = getRules();
@@ -77,8 +74,11 @@ public class ConfigLoader {
public void initConfig() { public void initConfig() {
Map<String, Object> r = new LinkedHashMap<>(); Map<String, Object> r = new LinkedHashMap<>();
r.put("excludeSuffix", getExcludeSuffix()); r.put("ExcludeSuffix", getExcludeSuffix());
r.put("blockHost", getBlockHost()); r.put("BlockHost", getBlockHost());
r.put("ExcludeStatus", getExcludeStatus());
r.put("LimitSize", getLimitSize());
r.put("HaEScope", getScope());
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);
@@ -135,39 +135,79 @@ public class ConfigLoader {
} }
public String getBlockHost() { public String getBlockHost() {
File yamlSetting = new File(configFilePath); return getValueFromConfig("BlockHost", Config.host);
if (!yamlSetting.exists() || !yamlSetting.isFile()) {
return Config.host;
}
try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) {
Map<String, Object> r = new Yaml().load(inorder);
if (r.containsKey("blockHost")) {
return r.get("blockHost").toString();
}
} catch (Exception ignored) {
}
return Config.host;
} }
public String getExcludeSuffix() { public String getExcludeSuffix() {
return getValueFromConfig("ExcludeSuffix", Config.suffix);
}
public String getExcludeStatus() {
return getValueFromConfig("ExcludeStatus", Config.status);
}
public String getLimitSize() {
return getValueFromConfig("LimitSize", Config.size);
}
public String getScope() {
return getValueFromConfig("HaEScope", Config.scopeOptions);
}
public boolean getMode() {
return getValueFromConfig("HaEModeStatus", Config.modeStatus).equals("true");
}
private String getValueFromConfig(String name, String defaultValue) {
File yamlSetting = new File(configFilePath); File yamlSetting = new File(configFilePath);
if (!yamlSetting.exists() || !yamlSetting.isFile()) { if (!yamlSetting.exists() || !yamlSetting.isFile()) {
return Config.suffix; return defaultValue;
} }
try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) { try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) {
Map<String, Object> r = new Yaml().load(inorder); Map<String, Object> r = new Yaml().load(inorder);
if (r.containsKey("excludeSuffix")) { if (r.containsKey(name)) {
return r.get("excludeSuffix").toString(); return r.get(name).toString();
} }
} catch (Exception ignored) { } catch (Exception ignored) {
} }
return Config.suffix; return defaultValue;
}
public void setExcludeSuffix(String excludeSuffix) {
setValueToConfig("ExcludeSuffix", excludeSuffix);
}
public void setBlockHost(String blockHost) {
setValueToConfig("BlockHost", blockHost);
}
public void setExcludeStatus(String status) {
setValueToConfig("ExcludeStatus", status);
}
public void setLimitSize(String size) {
setValueToConfig("LimitSize", size);
}
public void setScope(String scope) {
setValueToConfig("HaEScope", scope);
}
public void setMode(String mode) {
setValueToConfig("HaEModeStatus", mode);
}
private void setValueToConfig(String name, String value) {
Map<String, Object> currentConfig = loadCurrentConfig();
currentConfig.put(name, value);
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
yaml.dump(currentConfig, ws);
} catch (Exception ignored) {
}
} }
private Map<String, Object> loadCurrentConfig() { private Map<String, Object> loadCurrentConfig() {
@@ -183,31 +223,12 @@ public class ConfigLoader {
} }
} }
public void setExcludeSuffix(String excludeSuffix) { public boolean initRules() {
Map<String, Object> currentConfig = loadCurrentConfig(); boolean ret = copyRulesToFile(this.rulesFilePath);
currentConfig.put("excludeSuffix", excludeSuffix); // 更新配置 if (!ret) {
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
yaml.dump(currentConfig, ws);
} catch (Exception ignored) {
}
}
public void setBlockHost(String blockHost) {
Map<String, Object> currentConfig = loadCurrentConfig();
currentConfig.put("blockHost", blockHost); // 更新配置
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
yaml.dump(currentConfig, ws);
} catch (Exception ignored) {
}
}
public void initRulesByRes() {
boolean isCopySuccess = copyRulesToFile(this.rulesFilePath);
if (!isCopySuccess) {
api.extension().unload(); api.extension().unload();
} }
return ret;
} }
private boolean copyRulesToFile(String targetFilePath) { private boolean copyRulesToFile(String targetFilePath) {
@@ -230,33 +251,4 @@ public class ConfigLoader {
return false; return false;
} }
public void initRulesByNet() {
Thread t = new Thread() {
public void run() {
pullRules();
}
};
t.start();
try {
t.join(10000);
} catch (Exception ignored) {
}
}
private void pullRules() {
try {
String url = "https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml";
HttpRequest httpRequest = HttpRequest.httpRequestFromUrl(url);
HttpRequestResponse requestResponse = api.http().sendRequest(httpRequest, RequestOptions.requestOptions().withUpstreamTLSVerification());
String responseBody = requestResponse.response().bodyToString();
if (responseBody.contains("rules")) {
FileOutputStream fileOutputStream = new FileOutputStream(rulesFilePath);
fileOutputStream.write(responseBody.getBytes());
fileOutputStream.close();
}
} catch (Exception ignored) {
api.extension().unload();
}
}
} }

View File

@@ -0,0 +1,90 @@
package hae.utils;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
import burp.api.montoya.persistence.PersistedList;
import burp.api.montoya.persistence.PersistedObject;
import burp.api.montoya.persistence.Persistence;
import hae.component.board.message.MessageTableModel;
import hae.instances.http.utils.RegularMatcher;
public class DataManager {
private final MontoyaApi api;
private final Persistence persistence;
public DataManager(MontoyaApi api) {
this.api = api;
this.persistence = api.persistence();
}
public synchronized void putData(String dataType, String dataName, PersistedObject persistedObject) {
if (persistence.extensionData().getChildObject(dataName) != null) {
persistence.extensionData().deleteChildObject(dataName);
}
persistence.extensionData().setChildObject(dataName, persistedObject);
saveIndex(dataType, dataName);
}
public void loadData(MessageTableModel messageTableModel) {
// 1. 获取索引
PersistedList<String> dataIndex = persistence.extensionData().getStringList("data"); // 数据索引
PersistedList<String> messageIndex = persistence.extensionData().getStringList("message"); // 消息索引
// 2. 从索引获取数据
loadHaEData(dataIndex);
loadMessageData(messageIndex, messageTableModel);
}
private void saveIndex(String indexName, String indexValue) {
PersistedList<String> indexList = persistence.extensionData().getStringList(indexName);
if (indexList != null && !indexList.isEmpty()) {
persistence.extensionData().deleteStringList(indexName);
} else {
indexList = PersistedList.persistedStringList();
}
if (!indexList.contains(indexValue)) {
indexList.add(indexValue);
}
persistence.extensionData().setStringList(indexName, indexList);
}
private void loadHaEData(PersistedList<String> dataIndex) {
if (dataIndex != null && !dataIndex.isEmpty()) {
dataIndex.parallelStream().forEach(index -> {
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
try {
dataObj.stringListKeys().forEach(dataKey -> {
RegularMatcher.putDataToGlobalMap(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false);
});
} catch (Exception e) {
api.logging().logToOutput("loadHaEData:" + e.getMessage());
}
});
}
}
private void loadMessageData(PersistedList<String> messageIndex, MessageTableModel messageTableModel) {
if (messageIndex != null && !messageIndex.isEmpty()) {
messageIndex.parallelStream().forEach(index -> {
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
if (dataObj != null) {
HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo");
String comment = dataObj.getString("comment");
String color = dataObj.getString("color");
HttpRequest request = messageInfo.request();
HttpResponse response = messageInfo.response();
String method = request.method();
String url = request.url();
String status = String.valueOf(response.statusCode());
String length = String.valueOf(response.toByteArray().length());
messageTableModel.add(messageInfo, url, method, status, length, comment, color, false);
}
});
}
}
}

View File

@@ -7,24 +7,38 @@ import java.awt.event.FocusListener;
public class UIEnhancer { public class UIEnhancer {
public static void setTextFieldPlaceholder(JTextField textField, String placeholderText) { public static void setTextFieldPlaceholder(JTextField textField, String placeholderText) {
textField.setForeground(Color.GRAY); // 使用客户端属性来存储占位符文本和占位符状态
textField.setText(placeholderText); textField.putClientProperty("placeholderText", placeholderText);
textField.putClientProperty("isPlaceholder", true);
// 设置占位符文本和颜色
setPlaceholderText(textField);
textField.addFocusListener(new FocusListener() { textField.addFocusListener(new FocusListener() {
@Override @Override
public void focusGained(FocusEvent e) { public void focusGained(FocusEvent e) {
if (textField.getText().equals(placeholderText)) { // 当获得焦点且文本是占位符时,清除文本并更改颜色
if ((boolean) textField.getClientProperty("isPlaceholder")) {
textField.setText(""); textField.setText("");
textField.setForeground(Color.BLACK); textField.setForeground(Color.BLACK);
textField.putClientProperty("isPlaceholder", false);
} }
} }
@Override @Override
public void focusLost(FocusEvent e) { public void focusLost(FocusEvent e) {
// 当失去焦点且文本为空时,设置占位符文本和颜色
if (textField.getText().isEmpty()) { if (textField.getText().isEmpty()) {
textField.setForeground(Color.GRAY); setPlaceholderText(textField);
textField.setText(placeholderText);
} }
} }
}); });
} }
private static void setPlaceholderText(JTextField textField) {
String placeholderText = (String) textField.getClientProperty("placeholderText");
textField.setForeground(Color.GRAY);
textField.setText(placeholderText);
textField.putClientProperty("isPlaceholder", true);
}
} }

View File

@@ -0,0 +1,58 @@
package hae.utils.http;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
import hae.utils.ConfigLoader;
import hae.utils.string.StringProcessor;
import java.util.Arrays;
import java.util.List;
public class HttpUtils {
private final MontoyaApi api;
private final ConfigLoader configLoader;
public HttpUtils(MontoyaApi api, ConfigLoader configLoader) {
this.api = api;
this.configLoader = configLoader;
}
public boolean verifyHttpRequestResponse(HttpRequestResponse requestResponse, String toolType) {
HttpRequest request = requestResponse.request();
HttpResponse response = requestResponse.response();
boolean retStatus = false;
try {
String host = StringProcessor.getHostByUrl(request.url());
String[] hostList = configLoader.getBlockHost().split("\\|");
boolean isBlockHost = isBlockHost(hostList, host);
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
boolean isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase());
boolean isToolScope = !configLoader.getScope().contains(toolType);
List<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|"));
boolean isExcludeStatus = statusList.contains(String.valueOf(response.statusCode()));
retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus;
} catch (Exception ignored) {
}
return retStatus;
}
private boolean isBlockHost(String[] hostList, String host) {
boolean isBlockHost = false;
for (String hostName : hostList) {
String cleanedHost = StringProcessor.replaceFirstOccurrence(hostName, "*.", "");
if (hostName.contains("*.") && StringProcessor.matchFromEnd(host, cleanedHost)) {
isBlockHost = true;
} else if (host.equals(hostName) || hostName.equals("*")) {
isBlockHost = true;
}
}
return isBlockHost;
}
}

View File

@@ -1,47 +0,0 @@
package hae.utils.project;
import java.io.File;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
public class FileProcessor {
public static void deleteDirectoryWithContents(Path pathToBeDeleted) {
if (pathToBeDeleted != null) {
try {
Files.walk(pathToBeDeleted)
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
} catch (Exception ignored) {
}
}
}
public static byte[] readFileContent(String basePath, String fileName) {
Path filePath = Paths.get(basePath, fileName);
Path path = Paths.get(basePath);
try {
byte[] fileContent = Files.readAllBytes(filePath);
Files.deleteIfExists(filePath);
boolean isEmpty = isDirectoryEmpty(path);
if (isEmpty) {
Files.deleteIfExists(path);
}
return fileContent;
} catch (Exception e) {
return new byte[0];
}
}
private static boolean isDirectoryEmpty(Path directory) throws Exception {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(directory)) {
return !dirStream.iterator().hasNext();
}
}
}

View File

@@ -1,194 +0,0 @@
package hae.utils.project;
import burp.api.montoya.MontoyaApi;
import hae.utils.project.model.HaeFileContent;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class ProjectProcessor {
private final MontoyaApi api;
public ProjectProcessor(MontoyaApi api) {
this.api = api;
}
public boolean createHaeFile(String haeFilePath, String host, Map<String, List<String>> dataMap, Map<String, Map<String, Object>> urlMap, Map<String, Map<String, Object>> httpMap) {
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
List<Future<?>> futures = new ArrayList<>();
ByteArrayOutputStream dataYamlStream = new ByteArrayOutputStream();
ByteArrayOutputStream urlYamlStream = new ByteArrayOutputStream();
Yaml yaml = new Yaml();
yaml.dump(dataMap, new OutputStreamWriter(dataYamlStream, StandardCharsets.UTF_8));
yaml.dump(urlMap, new OutputStreamWriter(urlYamlStream, StandardCharsets.UTF_8));
try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(haeFilePath))) {
zipOut.putNextEntry(new ZipEntry("info"));
zipOut.write(host.getBytes(StandardCharsets.UTF_8));
zipOut.closeEntry();
zipOut.putNextEntry(new ZipEntry("data"));
zipOut.write(dataYamlStream.toByteArray());
zipOut.closeEntry();
zipOut.putNextEntry(new ZipEntry("url"));
zipOut.write(urlYamlStream.toByteArray());
zipOut.closeEntry();
for (String httpHash : httpMap.keySet()) {
Map<String, Object> httpItem = httpMap.get(httpHash);
futures.add(executorService.submit(() -> {
try {
ByteArrayOutputStream httpOutStream = new ByteArrayOutputStream();
byte[] request = (byte[]) httpItem.get("request");
byte[] response = (byte[]) httpItem.get("response");
httpOutStream.write(response);
httpOutStream.write(request);
synchronized (zipOut) {
zipOut.putNextEntry(new ZipEntry(String.format("http/%s", httpHash)));
zipOut.write(httpOutStream.toByteArray());
zipOut.closeEntry();
}
} catch (Exception e) {
api.logging().logToError("createHaeFile: " + e.getMessage());
}
}));
}
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
}
}
} catch (Exception e) {
api.logging().logToError("createHaeFile: " + e.getMessage());
return false;
} finally {
executorService.shutdown();
}
return true;
}
public HaeFileContent readHaeFile(String haeFilePath) {
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
List<Future<?>> futures = new ArrayList<>();
HaeFileContent haeFileContent = new HaeFileContent(api);
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setMaxAliasesForCollections(Integer.MAX_VALUE);
loaderOptions.setCodePointLimit(Integer.MAX_VALUE);
Yaml yaml = new Yaml(loaderOptions);
Path tempDirectory = null;
try {
if (hasValidStructure(haeFilePath)) {
tempDirectory = Files.createTempDirectory("hae");
haeFileContent.setHttpPath(tempDirectory.toString());
try (ZipFile zipFile = new ZipFile(haeFilePath)) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String fileName = entry.getName();
if (fileName.startsWith("http/")) {
Path filePath = tempDirectory.resolve(fileName.substring("http/".length()));
futures.add(executorService.submit(() -> {
try (InputStream in = zipFile.getInputStream(entry)) {
Files.copy(in, filePath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
api.logging().logToError("readHaeFile: " + e.getMessage());
}
}));
} else {
try (InputStream in = zipFile.getInputStream(entry)) {
switch (fileName) {
case "info" ->
haeFileContent.setHost(new String(in.readAllBytes(), StandardCharsets.UTF_8));
case "data" ->
haeFileContent.setDataMap(yaml.load(new InputStreamReader(in, StandardCharsets.UTF_8)));
case "url" ->
haeFileContent.setUrlMap(yaml.load(new InputStreamReader(in, StandardCharsets.UTF_8)));
}
}
}
}
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt();
}
}
}
}
} catch (Exception e) {
api.logging().logToError("readHaeFile: " + e.getMessage());
if (tempDirectory != null) {
FileProcessor.deleteDirectoryWithContents(tempDirectory);
}
haeFileContent = null;
} finally {
executorService.shutdown();
}
return haeFileContent;
}
private boolean hasValidStructure(String zipFilePath) {
Set<String> requiredRootEntries = new HashSet<>();
requiredRootEntries.add("info");
requiredRootEntries.add("data");
requiredRootEntries.add("url");
boolean hasHttpDirectoryWithFiles = false;
try {
ZipFile zipFile = new ZipFile(zipFilePath);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (!entry.isDirectory() && !name.contains("/")) {
requiredRootEntries.remove(name);
}
if (name.startsWith("http/") && !entry.isDirectory()) {
hasHttpDirectoryWithFiles = true;
}
if (requiredRootEntries.isEmpty() && hasHttpDirectoryWithFiles) {
break;
}
}
zipFile.close();
} catch (Exception ignored) {
}
return requiredRootEntries.isEmpty() && hasHttpDirectoryWithFiles;
}
}

View File

@@ -1,76 +0,0 @@
package hae.utils.project.model;
import burp.api.montoya.MontoyaApi;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HaeFileContent {
private final MontoyaApi api;
private String host;
private String httpPath;
private final Map<String, List<String>> dataMap;
private final Map<String, Map<String, String>> urlMap;
public HaeFileContent(MontoyaApi api) {
this.api = api;
this.dataMap = new HashMap<>();
this.urlMap = new HashMap<>();
}
public String getHost() {
return host;
}
public Map<String, List<String>> getDataMap() {
return dataMap;
}
public Map<String, Map<String, String>> getUrlMap() {
return urlMap;
}
public String getHttpPath() {
return httpPath;
}
public void setHost(String host) {
this.host = host;
}
public void setHttpPath(String path) {
this.httpPath = path;
}
public void setDataMap(Map<String, List<Object>> dataMap) {
for (Map.Entry<String, List<Object>> entry : dataMap.entrySet()) {
List<String> values = new ArrayList<>();
for (Object value : entry.getValue()) {
try {
values.add(new String((byte[]) value, StandardCharsets.UTF_8));
} catch (Exception e) {
values.add(value.toString());
}
}
this.dataMap.put(entry.getKey(), values);
}
}
public void setUrlMap(Map<String, Map<String, Object>> urlMap) {
for (Map.Entry<String, Map<String, Object>> entry : urlMap.entrySet()) {
Map<String, String> newValues = new HashMap<>();
Map<String, Object> values = entry.getValue();
for (String key : values.keySet()) {
try {
newValues.put(key, new String((byte[]) values.get(key), StandardCharsets.UTF_8));
} catch (Exception e) {
newValues.put(key, values.get(key).toString());
}
}
this.urlMap.put(entry.getKey(), newValues);
}
}
}

View File

@@ -119,17 +119,6 @@ public class StringProcessor {
return host; return host;
} }
public static String getBaseDomain(String host) {
int lastIndex = host.lastIndexOf('.');
if (lastIndex > 0) {
int secondLastIndex = host.substring(0, lastIndex).lastIndexOf('.');
if (secondLastIndex >= 0) {
return host.substring(secondLastIndex + 1);
}
}
return host;
}
public static boolean matchHostIsIp(String host) { public static boolean matchHostIsIp(String host) {
return host.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"); return host.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b");
} }

View File

@@ -115,7 +115,7 @@ rules:
sensitive: true sensitive: true
- name: Chinese Mobile Number - name: Chinese Mobile Number
loaded: true loaded: true
f_regex: '[^\w]((?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8})[^\w]' f_regex: '[^\w]((?:(?:\+|0{0,2})86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8})[^\w]'
s_regex: '' s_regex: ''
format: '{0}' format: '{0}'
color: orange color: orange
@@ -153,7 +153,7 @@ rules:
sensitive: false sensitive: false
- name: Windows File/Dir Path - name: Windows File/Dir Path
loaded: true loaded: true
f_regex: '[^\w](([a-zA-Z]:\\(?:\w+\\?)*)|([a-zA-Z]:\\(?:\w+\\)*\w+\.\w+))' f_regex: '[^\w]([a-zA-Z]:\\\\?(?:[^<>:/\\|?*]+\\\\?)*)([^<>:/\\|?*]+(?:\.[^<>:/\\|?*]+)?)'
s_regex: '' s_regex: ''
format: '{0}' format: '{0}'
color: green color: green
@@ -162,8 +162,8 @@ rules:
sensitive: true sensitive: true
- name: Password Field - name: Password Field
loaded: true loaded: true
f_regex: ((|'|")(|[\w]{1,10})([p](ass|wd|asswd|assword))(|[\w]{1,10})(|'|")(:|=)( f_regex: ((|\\)(|'|")(|[\w]{1,10})([p](ass|wd|asswd|assword))(|[\w]{1,10})(|\\)(|'|")(:|=|\)\.val\()(
|)('|")(.*?)('|")(|,)) |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))
s_regex: '' s_regex: ''
format: '{0}' format: '{0}'
color: yellow color: yellow
@@ -172,8 +172,8 @@ rules:
sensitive: false sensitive: false
- name: Username Field - name: Username Field
loaded: true loaded: true
f_regex: ((|'|")(|[\w]{1,10})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\w]{1,10})(|'|")(:|=)( f_regex: ((|\\)(|'|")(|[\w]{1,10})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\w]{1,10})(|\\)(|'|")(:|=|\)\.val\()(
|)('|")(.*?)('|")(|,)) |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))
s_regex: '' s_regex: ''
format: '{0}' format: '{0}'
color: green color: green
@@ -209,19 +209,29 @@ rules:
sensitive: false sensitive: false
- name: Sensitive Field - name: Sensitive Field
loaded: true loaded: true
f_regex: ((\[)?('|")?([\w]{0,10})((key)|(secret)|(token)|(config)|(auth)|(access)|(admin))([\w]{0,10})('|")?(\])?( f_regex: ((\[)?('|")?([\w]{0,10})((key)|(secret)|(token)|(config)|(auth)|(access)|(admin)|(ticket))([\w]{0,10})('|")?(\])?(
|)(:|=)( |)('|")(.*?)('|")(|,)) |)(:|=|\)\.val\()( |)('|")([^'"]+?)('|")(|,|\)))
s_regex: '' s_regex: ''
format: '{0}' format: '{0}'
color: yellow color: yellow
scope: response scope: response
engine: nfa engine: nfa
sensitive: false sensitive: false
- name: Mobile Number Field
loaded: true
f_regex: ((|\\)(|'|")(|[\w]{1,10})(mobile|phone|sjh|shoujihao|concat)(|[\w]{1,10})(|\\)(|'|")(:|=|\)\.val\()(
|)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))
s_regex: ''
format: '{0}'
color: green
scope: response body
engine: nfa
sensitive: false
- group: Other - group: Other
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-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,}|)))(?:"|') 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,})?(?:"|')
s_regex: '' s_regex: ''
format: '{0}' format: '{0}'
color: gray color: gray
@@ -237,18 +247,9 @@ rules:
scope: response body scope: response body
engine: dfa engine: dfa
sensitive: false sensitive: false
- name: HTML Notes
loaded: true
f_regex: (<!--.*?-->)
s_regex: ''
format: '{0}'
color: magenta
scope: response body
engine: nfa
sensitive: false
- name: Create Script - name: Create Script
loaded: true loaded: true
f_regex: (\+\{.*?\}\[[a-zA-Z]\]\+".*?\.js") f_regex: (\{[^{}]*\}\s*\[[^\s]*\]\s*\+\s*"[^\s]*\.js")
s_regex: '"?([\w].*?)"?:"(.*?)"' s_regex: '"?([\w].*?)"?:"(.*?)"'
format: '{0}.{1}' format: '{0}.{1}'
color: green color: green
@@ -257,7 +258,7 @@ rules:
sensitive: false sensitive: false
- name: URL Schemes - name: URL Schemes
loaded: true loaded: true
f_regex: ((?![http]|[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})://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]))
s_regex: '' s_regex: ''
format: '{0}' format: '{0}'
color: yellow color: yellow
@@ -282,3 +283,21 @@ rules:
scope: response body scope: response body
engine: nfa engine: nfa
sensitive: true sensitive: true
- name: Request URI
loaded: true
f_regex: ' ((?!.*\.js(\?.*)?$)(.*?[^.js$])) '
s_regex: ''
format: '{0}'
color: gray
scope: request line
engine: nfa
sensitive: false
- name: 302 Location
loaded: true
f_regex: 'Location: (.*?)\n'
s_regex: ''
format: '{0}'
color: gray
scope: response header
engine: nfa
sensitive: false