Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daacb2e146 | ||
|
|
1f1ca99f10 | ||
|
|
fa35b0a625 | ||
|
|
8ef98d20a9 | ||
|
|
e556abb6f7 | ||
|
|
471aab5ea1 | ||
|
|
76b475bd91 | ||
|
|
6014089594 | ||
|
|
910658f2e0 | ||
|
|
8692b0a494 | ||
|
|
5419d4a679 | ||
|
|
ae8cb2fd25 | ||
|
|
5b6bdbe5b6 | ||
|
|
ddb08e9a6e | ||
|
|
6a2f289d57 | ||
|
|
84746a7089 | ||
|
|
68f0bce619 | ||
|
|
4f0401347c | ||
|
|
a7e0a2a6ce | ||
|
|
b7c5a8363d | ||
|
|
d7b4419d51 | ||
|
|
5f54d1f461 | ||
|
|
e4b7f86a0c | ||
|
|
cc30f41bfa | ||
|
|
386c562311 | ||
|
|
a867039284 | ||
|
|
3a8d9eae11 | ||
|
|
e5f55b6c4c | ||
|
|
54973d9f4f | ||
|
|
fb347a8dc6 | ||
|
|
04b6652b03 | ||
|
|
6d4abae898 | ||
|
|
97172fab45 | ||
|
|
ba3b206acf |
4
.github/ISSUE_TEMPLATE/问题反馈.md
vendored
@@ -14,7 +14,9 @@ HaE 版本:
|
|||||||
有无自定义规则:
|
有无自定义规则:
|
||||||
BurpSuite 版本:
|
BurpSuite 版本:
|
||||||
操作系统版本:
|
操作系统版本:
|
||||||
有无仔细阅读README:
|
是否阅读README:
|
||||||
|
是否知晓注意事项:
|
||||||
|
是否查阅历史ISSUE:
|
||||||
```
|
```
|
||||||
|
|
||||||
## 问题详情
|
## 问题详情
|
||||||
|
|||||||
47
README.md
@@ -1,27 +1,39 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="images/logo.png" style="width: 20%" />
|
<img src="images/logo.png" style="width: 20%" />
|
||||||
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
|
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
|
||||||
<h5>第一作者: <a href="https://github.com/gh0stkey">EvilChen</a>(中孚信息元亨实验室), 第二作者: <a href="https://github.com/0chencc">0chencc</a>(米斯特安全团队)</h5>
|
<h5>第一作者: <a href="https://github.com/gh0stkey">EvilChen</a>(中孚信息元亨实验室)<br>第二作者: <a href="https://github.com/0chencc">0chencc</a>(米斯特安全团队)<br>第三作者: <a href="https://github.com/vaycore">vaycore</a>(独立安全研究员)</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 项目介绍
|
## 项目介绍
|
||||||
|
|
||||||
**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
|
||||||
|
|
||||||
1. 由于HaE 3.0版本开始采用`Montoya API`进行开发,因此使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。
|
GitCode项目地址:https://gitcode.com/gh0stkey/HaE
|
||||||
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)上,因此默认加载HaE官方规则库需使用代理(BApp审核不允许使用CDN)。
|
**所获荣誉**:
|
||||||
4. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。
|
|
||||||
|
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)。
|
||||||
|
2. HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
|
||||||
|
3. 自定义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,17 +55,14 @@ HaE目前的规则一共有8个字段,详细的含义如下所示:
|
|||||||
| Color | 规则匹配颜色,主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法,当出现相同颜色时会自动向上升级一个颜色进行标记。 |
|
| Color | 规则匹配颜色,主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法,当出现相同颜色时会自动向上升级一个颜色进行标记。 |
|
||||||
| Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 |
|
| Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 |
|
||||||
|
|
||||||
|
|
||||||
## 优势特点
|
## 优势特点
|
||||||
|
|
||||||
1. 精细配置:高度自由的配置选项,以满足各类精细化场景需求。
|
1. **功能**:通过对HTTP报文的颜色高亮、注释和提取,帮助使用者获取有意义的信息,**聚焦高价值报文**。
|
||||||
2. 分类标签:使用标签对规则进行分类,便于管理和组织规则。
|
2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。
|
||||||
3. 高亮标记:在HTTP History页面,通过颜色高亮和注释判断请求的价值。
|
3. **查询**:将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。
|
||||||
4. 易读配置:使用易读的YAML格式存储配置文件,方便阅读和修改。
|
4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。
|
||||||
5. 数据集合:将匹配到的数据、请求和响应集中在数据面板中,提高测试和梳理效率。
|
5. **管理**:**融入BurpSuite的项目数据管理**,当使用BurpSuite进行项目存储时HaE数据也会一并存储。
|
||||||
6. 简洁可视:清晰可视的界面设计,更轻松地了解和配置HaE,操作简单、使用便捷。
|
6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。
|
||||||
7. 颜色升级:内置颜色升级算法,避免“屠龙者终成恶龙”场景,突出最具价值的请求。
|
|
||||||
8. 实战规则:官方规则库是基于实战化场景总结输出,提升数据发现的有效性、精准性。
|
|
||||||
|
|
||||||
| 界面名称 | 界面展示 |
|
| 界面名称 | 界面展示 |
|
||||||
| ------------------------ | ---------------------------------------------------- |
|
| ------------------------ | ---------------------------------------------------- |
|
||||||
@@ -62,9 +71,7 @@ HaE目前的规则一共有8个字段,详细的含义如下所示:
|
|||||||
| Databoard(数据集合) | <img src="images/databoard.png" style="width: 80%" /> |
|
| Databoard(数据集合) | <img src="images/databoard.png" style="width: 80%" /> |
|
||||||
| MarkInfo(数据展示) | <img src="images/markinfo.png" style="width: 80%" /> |
|
| MarkInfo(数据展示) | <img src="images/markinfo.png" style="width: 80%" /> |
|
||||||
|
|
||||||
## 文末随笔
|
## 支持项目
|
||||||
|
|
||||||
正义感是一个不可丢失的东西。
|
|
||||||
|
|
||||||
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ sourceSets {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1'
|
implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1'
|
||||||
implementation 'org.yaml:snakeyaml:2.0'
|
implementation 'org.yaml:snakeyaml:2.0'
|
||||||
implementation 'net.sourceforge.jregex:jregex:1.2_01'
|
|
||||||
implementation 'dk.brics.automaton:automaton:1.11-8'
|
implementation 'dk.brics.automaton:automaton:1.11-8'
|
||||||
|
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 54 KiB |
BIN
images/rules.png
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 128 KiB |
@@ -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"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,46 +2,59 @@ 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.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";
|
String version = "4.0";
|
||||||
api.extension().setName(String.format("HaE (%s) - Highlighter and Extractor", version));
|
api.extension().setName("HaE - Highlighter and Extractor");
|
||||||
|
|
||||||
// 加载扩展后输出的项目信息
|
// 加载扩展后输出的项目信息
|
||||||
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");
|
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);
|
||||||
|
|
||||||
// 注册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();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
437
src/main/java/hae/component/Config.java
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
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");
|
||||||
|
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 (passiveHandler.isRegistered()) {
|
||||||
|
passiveHandler.deregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeHandler.isRegistered()) {
|
||||||
|
activeHandler = api.http().registerHttpHandler(new HttpMessageActiveHandler(api, configLoader, messageTableModel));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,22 @@
|
|||||||
package hae.component.board;
|
package hae.component.board;
|
||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import burp.api.montoya.http.HttpService;
|
|
||||||
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.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.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -33,8 +25,8 @@ 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;
|
||||||
@@ -44,10 +36,12 @@ public class Databoard extends JPanel {
|
|||||||
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<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();
|
||||||
@@ -63,20 +57,21 @@ public class Databoard extends JPanel {
|
|||||||
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.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||||
|
|
||||||
actionButton.addActionListener(e -> {
|
actionButton.addActionListener(e -> {
|
||||||
int x = 0;
|
int x = 0;
|
||||||
@@ -85,8 +80,7 @@ public class Databoard extends JPanel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
clearButton.addActionListener(this::clearActionPerformed);
|
clearButton.addActionListener(this::clearActionPerformed);
|
||||||
exportButton.addActionListener(this::exportActionPerformed);
|
|
||||||
importButton.addActionListener(this::importActionPerformed);
|
|
||||||
|
|
||||||
splitPane.addComponentListener(new ComponentAdapter() {
|
splitPane.addComponentListener(new ComponentAdapter() {
|
||||||
@Override
|
@Override
|
||||||
@@ -103,9 +97,10 @@ public class Databoard extends JPanel {
|
|||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
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, 3, 0.0, 0.0,
|
|
||||||
|
add(splitPane, new GridBagConstraints(1, 1, 3, 2, 0.0, 1.0,
|
||||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
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));
|
||||||
@@ -158,8 +153,55 @@ 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) {
|
||||||
String selectedHost = hostComboBox.getSelectedItem().toString();
|
String selectedHost = hostComboBox.getSelectedItem().toString();
|
||||||
hostTextField.setText(selectedHost);
|
|
||||||
populateTabbedPaneByHost(selectedHost);
|
if (getHostByList().contains(selectedHost)) {
|
||||||
|
hostTextField.setText(selectedHost);
|
||||||
|
|
||||||
|
if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
|
||||||
|
handleComboBoxWorker.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleComboBoxWorker = new SwingWorker<Map<String, List<String>>, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Map<String, List<String>> doInBackground() {
|
||||||
|
return getSelectedMapByHost(selectedHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
if (!isCancelled()) {
|
||||||
|
try {
|
||||||
|
Map<String, List<String>> selectedDataMap = get();
|
||||||
|
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();
|
||||||
|
splitPane.setLeftComponent(dataTabbedPane);
|
||||||
|
splitPane.setRightComponent(messageSplitPane);
|
||||||
|
messageTable = messageTableModel.getMessageTable();
|
||||||
|
resizePanel();
|
||||||
|
|
||||||
|
splitPane.setVisible(true);
|
||||||
|
hostTextField.setText(selectedHost);
|
||||||
|
|
||||||
|
hostComboBox.setPopupVisible(false);
|
||||||
|
applyHostFilter(selectedHost);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleComboBoxWorker.execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +220,6 @@ public class Databoard extends JPanel {
|
|||||||
if (keyCode == KeyEvent.VK_ENTER) {
|
if (keyCode == KeyEvent.VK_ENTER) {
|
||||||
isMatchHost = false;
|
isMatchHost = false;
|
||||||
handleComboBoxAction(null);
|
handleComboBoxAction(null);
|
||||||
hostComboBox.setPopupVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyCode == KeyEvent.VK_ESCAPE) {
|
if (keyCode == KeyEvent.VK_ESCAPE) {
|
||||||
@@ -188,10 +229,40 @@ public class Databoard extends JPanel {
|
|||||||
isMatchHost = false;
|
isMatchHost = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
|
||||||
|
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||||
|
Map<String, List<String>> selectedDataMap;
|
||||||
|
|
||||||
|
if (selectedHost.contains("*")) {
|
||||||
|
selectedDataMap = new HashMap<>();
|
||||||
|
dataMap.keySet().forEach(key -> {
|
||||||
|
if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) {
|
||||||
|
Map<String, List<String>> ruleMap = dataMap.get(key);
|
||||||
|
for (String ruleKey : ruleMap.keySet()) {
|
||||||
|
List<String> dataList = ruleMap.get(ruleKey);
|
||||||
|
if (selectedDataMap.containsKey(ruleKey)) {
|
||||||
|
List<String> mergedList = new ArrayList<>(selectedDataMap.get(ruleKey));
|
||||||
|
mergedList.addAll(dataList);
|
||||||
|
HashSet<String> uniqueSet = new HashSet<>(mergedList);
|
||||||
|
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
|
||||||
|
} else {
|
||||||
|
selectedDataMap.put(ruleKey, dataList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
selectedDataMap = dataMap.get(selectedHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedDataMap;
|
||||||
|
}
|
||||||
|
|
||||||
private void filterComboBoxList() {
|
private void filterComboBoxList() {
|
||||||
isMatchHost = true;
|
isMatchHost = true;
|
||||||
comboBoxModel.removeAllElements();
|
comboBoxModel.removeAllElements();
|
||||||
String input = hostTextField.getText().toLowerCase();
|
String input = hostTextField.getText().toLowerCase();
|
||||||
|
|
||||||
if (!input.isEmpty()) {
|
if (!input.isEmpty()) {
|
||||||
for (String host : getHostByList()) {
|
for (String host : getHostByList()) {
|
||||||
String lowerCaseHost = host.toLowerCase();
|
String lowerCaseHost = host.toLowerCase();
|
||||||
@@ -210,250 +281,85 @@ public class Databoard extends JPanel {
|
|||||||
isMatchHost = false;
|
isMatchHost = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateTabbedPaneByHost(String selectedHost) {
|
|
||||||
if (!Objects.equals(selectedHost, "")) {
|
|
||||||
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
|
||||||
Map<String, List<String>> selectedDataMap;
|
|
||||||
|
|
||||||
dataTabbedPane.removeAll();
|
|
||||||
dataTabbedPane.setPreferredSize(new Dimension(500, 0));
|
|
||||||
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
|
||||||
splitPane.setLeftComponent(dataTabbedPane);
|
|
||||||
|
|
||||||
if (selectedHost.contains("*")) {
|
|
||||||
// 通配符数据
|
|
||||||
selectedDataMap = new HashMap<>();
|
|
||||||
for (String key : dataMap.keySet()) {
|
|
||||||
if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) {
|
|
||||||
Map<String, List<String>> ruleMap = dataMap.get(key);
|
|
||||||
for (String ruleKey : ruleMap.keySet()) {
|
|
||||||
List<String> dataList = ruleMap.get(ruleKey);
|
|
||||||
if (selectedDataMap.containsKey(ruleKey)) {
|
|
||||||
List<String> mergedList = new ArrayList<>(selectedDataMap.get(ruleKey));
|
|
||||||
mergedList.addAll(dataList);
|
|
||||||
HashSet<String> uniqueSet = new HashSet<>(mergedList);
|
|
||||||
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
|
|
||||||
} else {
|
|
||||||
selectedDataMap.put(ruleKey, dataList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selectedDataMap = dataMap.get(selectedHost);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 展示请求消息表单
|
|
||||||
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
|
|
||||||
this.splitPane.setRightComponent(messageSplitPane);
|
|
||||||
messageTable = messageTableModel.getMessageTable();
|
|
||||||
|
|
||||||
resizePanel();
|
|
||||||
splitPane.setVisible(true);
|
|
||||||
|
|
||||||
applyHostFilter(selectedHost);
|
|
||||||
hostTextField.setText(selectedHost);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyHostFilter(String filterText) {
|
private void applyHostFilter(String filterText) {
|
||||||
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
|
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
|
||||||
|
|
||||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
||||||
|
|
||||||
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
if (applyHostFilterWorker != null && !applyHostFilterWorker.isDone()) {
|
||||||
public boolean include(Entry<?, ?> entry) {
|
applyHostFilterWorker.cancel(true);
|
||||||
if (cleanedText.equals("*")) {
|
}
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
String host = StringProcessor.getHostByUrl((String) entry.getValue(1));
|
|
||||||
|
|
||||||
return StringProcessor.matchesHostPattern(host, filterText);
|
applyHostFilterWorker = new SwingWorker<Void, Void>() {
|
||||||
}
|
@Override
|
||||||
|
protected Void doInBackground() throws Exception {
|
||||||
|
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
||||||
|
public boolean include(Entry<?, ?> entry) {
|
||||||
|
if (cleanedText.equals("*")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
String host = StringProcessor.getHostByUrl((String) entry.getValue(1));
|
||||||
|
return StringProcessor.matchesHostPattern(host, filterText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sorter.setRowFilter(rowFilter);
|
||||||
|
messageTableModel.applyHostFilter(filterText);
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
sorter.setRowFilter(rowFilter);
|
applyHostFilterWorker.execute();
|
||||||
|
|
||||||
messageTableModel.applyHostFilter(filterText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getHostByList() {
|
private List<String> getHostByList() {
|
||||||
if (!(Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.contains("*")))) {
|
if (!Config.globalDataMap.keySet().isEmpty()) {
|
||||||
return new ArrayList<>(Config.globalDataMap.keySet());
|
return new ArrayList<>(Config.globalDataMap.keySet());
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
|
||||||
List<String> taskStatusList = exportData(selectedHost, exportDir, dataMap);
|
|
||||||
|
|
||||||
if (!taskStatusList.isEmpty()) {
|
|
||||||
String exportStatusMessage = String.format("Exported File List Status:\n%s", String.join("\n", taskStatusList));
|
|
||||||
JOptionPane.showConfirmDialog(null, exportStatusMessage, "Info", JOptionPane.YES_OPTION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<String, Map<String, String>> httpMap = messageEntryList.stream()
|
|
||||||
.filter(messageEntry -> !StringProcessor.getHostByUrl(messageEntry.getUrl()).isEmpty())
|
|
||||||
.filter(messageEntry -> StringProcessor.getHostByUrl(messageEntry.getUrl()).equals(key))
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
MessageEntry::getUrl,
|
|
||||||
this::createHttpItemMap,
|
|
||||||
(existing, replacement) -> existing
|
|
||||||
));
|
|
||||||
|
|
||||||
String hostName = key.replace(":", "_");
|
|
||||||
String filename = String.format("%s/%s.hae", exportDir, hostName);
|
|
||||||
boolean createdStatus = projectProcessor.createHaeFile(filename, key, ruleMap, httpMap);
|
|
||||||
|
|
||||||
return String.format("Filename: %s, Status: %s", filename, createdStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> createHttpItemMap(MessageEntry entry) {
|
|
||||||
Map<String, String> httpItemMap = new HashMap<>();
|
|
||||||
httpItemMap.put("comment", entry.getComment());
|
|
||||||
httpItemMap.put("color", entry.getColor());
|
|
||||||
httpItemMap.put("request", entry.getRequestResponse().request().toString());
|
|
||||||
httpItemMap.put("response", entry.getRequestResponse().response().toString());
|
|
||||||
return httpItemMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void importActionPerformed(ActionEvent e) {
|
|
||||||
String exportDir = selectDirectory(false);
|
|
||||||
if (exportDir.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> filesWithExtension = findFilesWithExtension(new File(exportDir), ".hae");
|
|
||||||
List<String> taskStatusList = filesWithExtension.stream()
|
|
||||||
.map(this::importData)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (!taskStatusList.isEmpty()) {
|
|
||||||
String importStatusMessage = "Imported File List Status:\n" + String.join("\n", taskStatusList);
|
|
||||||
JOptionPane.showConfirmDialog(null, importStatusMessage, "Info", JOptionPane.YES_OPTION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String importData(String filename) {
|
|
||||||
HaeFileContent haeFileContent = projectProcessor.readHaeFile(filename);
|
|
||||||
boolean readStatus = haeFileContent != null;
|
|
||||||
|
|
||||||
if (readStatus) {
|
|
||||||
String host = haeFileContent.getHost();
|
|
||||||
haeFileContent.getDataMap().forEach((key, value) -> RegularMatcher.putDataToGlobalMap(host, key, value));
|
|
||||||
|
|
||||||
haeFileContent.getHttpMap().forEach((key, httpItemMap) -> {
|
|
||||||
String comment = httpItemMap.get("comment");
|
|
||||||
String color = httpItemMap.get("color");
|
|
||||||
HttpRequestResponse httpRequestResponse = createHttpRequestResponse(key, httpItemMap);
|
|
||||||
messageTableModel.add(httpRequestResponse, comment, color);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.format("Filename: %s, Status: %s", filename, readStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpRequestResponse createHttpRequestResponse(String key, Map<String, String> httpItemMap) {
|
|
||||||
HttpService httpService = HttpService.httpService(key);
|
|
||||||
HttpRequest httpRequest = HttpRequest.httpRequest(httpService, httpItemMap.get("request"));
|
|
||||||
HttpResponse httpResponse = HttpResponse.httpResponse(httpItemMap.get("response"));
|
|
||||||
return HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(null, "Do you want to clear data?", "Info",
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
String host = hostTextField.getText();
|
String host = hostTextField.getText();
|
||||||
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
||||||
dataTabbedPane.removeAll();
|
dataTabbedPane.removeAll();
|
||||||
splitPane.setVisible(false);
|
splitPane.setVisible(false);
|
||||||
|
|
||||||
String cleanedHost = StringProcessor.replaceFirstOccurrence(host, "*.", "");
|
Config.globalDataMap.keySet().parallelStream().forEach(key -> {
|
||||||
|
if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) {
|
||||||
|
Config.globalDataMap.remove(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (host.contains("*")) {
|
// 删除无用的数据
|
||||||
Config.globalDataMap.keySet().removeIf(i -> i.contains(cleanedHost) || cleanedHost.contains("*"));
|
Set<String> wildcardKeys = Config.globalDataMap.keySet().stream()
|
||||||
} else {
|
.filter(key -> key.startsWith("*."))
|
||||||
Config.globalDataMap.remove(host);
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Set<String> existingSuffixes = Config.globalDataMap.keySet().stream()
|
||||||
|
.filter(key -> !key.startsWith("*."))
|
||||||
|
.map(key -> {
|
||||||
|
int dotIndex = key.indexOf(".");
|
||||||
|
return dotIndex != -1 ? key.substring(dotIndex) : "";
|
||||||
|
})
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Set<String> keysToRemove = wildcardKeys.stream()
|
||||||
|
.filter(key -> !existingSuffixes.contains(key.substring(1)))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
keysToRemove.forEach(Config.globalDataMap::remove);
|
||||||
|
|
||||||
|
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
|
||||||
|
Config.globalDataMap.keySet().remove("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
messageTableModel.deleteByHost(host);
|
messageTableModel.deleteByHost(host);
|
||||||
|
|
||||||
|
hostTextField.setText("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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));
|
||||||
|
|||||||
@@ -1,16 +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.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;
|
||||||
|
|
||||||
@@ -22,6 +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.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -29,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 List<MessageEntry> log = new ArrayList<MessageEntry>();
|
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);
|
||||||
@@ -92,25 +98,25 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
splitPane.setRightComponent(messageTab);
|
splitPane.setRightComponent(messageTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(HttpRequestResponse messageInfo, String comment, String color) {
|
public void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
|
||||||
synchronized (log) {
|
synchronized (log) {
|
||||||
HttpRequest httpRequest = messageInfo.request();
|
boolean isDuplicate = false;
|
||||||
String url = httpRequest.url();
|
|
||||||
String method = httpRequest.method();
|
|
||||||
|
|
||||||
HttpResponse httpResponse = messageInfo.response();
|
|
||||||
String status = String.valueOf(httpResponse.statusCode());
|
|
||||||
String length = String.valueOf(httpResponse.body().length());
|
|
||||||
|
|
||||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
||||||
|
|
||||||
try {
|
byte[] reqByteA = new byte[0];
|
||||||
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
byte[] resByteA = new byte[0];
|
||||||
byte[] reqByteA = httpRequest.toByteArray().getBytes();
|
|
||||||
byte[] resByteA = httpResponse.toByteArray().getBytes();
|
|
||||||
boolean isDuplicate = false;
|
|
||||||
|
|
||||||
if (log.size() > 0) {
|
if (messageInfo != null) {
|
||||||
|
HttpRequest httpRequest = messageInfo.request();
|
||||||
|
HttpResponse httpResponse = messageInfo.response();
|
||||||
|
|
||||||
|
reqByteA = httpRequest.toByteArray().getBytes();
|
||||||
|
resByteA = httpResponse.toByteArray().getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
||||||
|
try {
|
||||||
|
if (!log.isEmpty()) {
|
||||||
for (MessageEntry entry : log) {
|
for (MessageEntry entry : log) {
|
||||||
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
||||||
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
||||||
@@ -125,12 +131,24 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDuplicate) {
|
|
||||||
log.add(logEntry);
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isDuplicate) {
|
||||||
|
if (flag) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加进日志
|
||||||
|
log.add(logEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -138,42 +156,47 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
public void deleteByHost(String filterText) {
|
public void deleteByHost(String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
List<Integer> rowsToRemove = new ArrayList<>();
|
List<Integer> rowsToRemove = new ArrayList<>();
|
||||||
for (int i = 0; i < log.size(); i++) {
|
|
||||||
MessageEntry entry = log.get(i);
|
if (currentWorker != null && !currentWorker.isDone()) {
|
||||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
currentWorker.cancel(true);
|
||||||
if (!host.isEmpty()) {
|
}
|
||||||
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
|
|
||||||
rowsToRemove.add(i);
|
currentWorker = new SwingWorker<Void, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() {
|
||||||
|
for (int i = 0; i < log.size(); i++) {
|
||||||
|
MessageEntry entry = log.get(i);
|
||||||
|
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||||
|
if (!host.isEmpty()) {
|
||||||
|
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.equals("*")) {
|
||||||
|
rowsToRemove.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
|
||||||
|
int row = rowsToRemove.get(i);
|
||||||
|
log.remove(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
|
currentWorker.execute();
|
||||||
int row = rowsToRemove.get(i);
|
|
||||||
log.remove(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rowsToRemove.isEmpty()) {
|
|
||||||
int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray();
|
|
||||||
fireTableRowsDeleted(rows[0], rows[rows.length - 1]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void applyHostFilter(String filterText) {
|
public void applyHostFilter(String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
fireTableDataChanged();
|
|
||||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
|
||||||
|
|
||||||
for (MessageEntry entry : log) {
|
log.forEach(entry -> {
|
||||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||||
if (!host.isEmpty()) {
|
if (!host.isEmpty()) {
|
||||||
if (filterText.contains("*.") && StringProcessor.matchFromEnd(StringProcessor.extractHostname(host), cleanedText)) {
|
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
|
||||||
filteredLog.add(entry);
|
|
||||||
} else if (host.equals(filterText) || filterText.contains("*")) {
|
|
||||||
filteredLog.add(entry);
|
filteredLog.add(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
}
|
}
|
||||||
@@ -181,6 +204,9 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
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) {
|
||||||
|
// 标志变量,表示是否满足过滤条件
|
||||||
|
AtomicBoolean isMatched = new AtomicBoolean(false);
|
||||||
|
|
||||||
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||||
HttpRequest httpRequest = requestResponse.request();
|
HttpRequest httpRequest = requestResponse.request();
|
||||||
HttpResponse httpResponse = requestResponse.response();
|
HttpResponse httpResponse = requestResponse.response();
|
||||||
@@ -197,9 +223,6 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
.map(HttpHeader::toString)
|
.map(HttpHeader::toString)
|
||||||
.collect(Collectors.joining("\n"));
|
.collect(Collectors.joining("\n"));
|
||||||
|
|
||||||
// 标志变量,表示是否满足过滤条件
|
|
||||||
AtomicBoolean isMatched = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
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();
|
||||||
@@ -305,7 +328,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
if (!map2.containsKey(key)) {
|
if (!map2.containsKey(key)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
if (areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,30 +338,29 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
|
|
||||||
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
||||||
if (innerMap1.size() != innerMap2.size()) {
|
if (innerMap1.size() != innerMap2.size()) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (String key : innerMap1.keySet()) {
|
for (String key : innerMap1.keySet()) {
|
||||||
if (!innerMap2.containsKey(key)) {
|
if (!innerMap2.containsKey(key)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
Object value1 = innerMap1.get(key);
|
Object value1 = innerMap1.get(key);
|
||||||
Object value2 = innerMap2.get(key);
|
Object value2 = innerMap2.get(key);
|
||||||
|
|
||||||
// 如果值是Map,则递归对比
|
// 如果值是Map,则递归对比
|
||||||
if (value1 instanceof Map && value2 instanceof Map) {
|
if (value1 instanceof Map && value2 instanceof Map) {
|
||||||
if (!areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
if (areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (!value1.equals(value2)) {
|
} else if (!value1.equals(value2)) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public JSplitPane getSplitPane() {
|
public JSplitPane getSplitPane() {
|
||||||
return splitPane;
|
return splitPane;
|
||||||
}
|
}
|
||||||
@@ -347,7 +369,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
return messageTable;
|
return messageTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MessageEntry> getLogs() {
|
public LinkedList<MessageEntry> getLogs() {
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,20 +385,27 @@ 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);
|
|
||||||
|
|
||||||
return switch (columnIndex) {
|
if (messageEntry != null) {
|
||||||
case 0 -> messageEntry.getMethod();
|
return switch (columnIndex) {
|
||||||
case 1 -> messageEntry.getUrl();
|
case 0 -> messageEntry.getMethod();
|
||||||
case 2 -> messageEntry.getComment();
|
case 1 -> messageEntry.getUrl();
|
||||||
case 3 -> messageEntry.getStatus();
|
case 2 -> messageEntry.getComment();
|
||||||
case 4 -> messageEntry.getLength();
|
case 3 -> messageEntry.getStatus();
|
||||||
case 5 -> messageEntry.getColor();
|
case 4 -> messageEntry.getLength();
|
||||||
default -> "";
|
case 5 -> messageEntry.getColor();
|
||||||
};
|
default -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("getValueAt: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -393,10 +422,8 @@ 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;
|
||||||
@@ -405,6 +432,7 @@ 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
|
||||||
@@ -413,42 +441,21 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
requestEditor.setRequest(HttpRequest.httpRequest("Loading..."));
|
private void getSelectedMessage() {
|
||||||
responseEditor.setResponse(HttpResponse.httpResponse("Loading..."));
|
messageEntry = filteredLog.get(lastSelectedIndex);
|
||||||
|
|
||||||
if (currentWorker != null && !currentWorker.isDone()) {
|
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
|
||||||
currentWorker.cancel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentWorker = new SwingWorker<>() {
|
requestEditor.setRequest(HttpRequest.httpRequest(messageEntry.getRequestResponse().httpService(), httpRequestResponse.request().toByteArray()));
|
||||||
@Override
|
int responseSizeWithMb = httpRequestResponse.response().toString().length() / 1024 / 1024;
|
||||||
protected ByteArray[] doInBackground() {
|
if ((responseSizeWithMb < Integer.parseInt(configLoader.getLimitSize())) || configLoader.getLimitSize().equals("0")) {
|
||||||
ByteArray requestByte = MessageEntry.getRequestResponse().request().toByteArray();
|
responseEditor.setResponse(httpRequestResponse.response());
|
||||||
ByteArray responseByte = MessageEntry.getRequestResponse().response().toByteArray();
|
} else {
|
||||||
|
responseEditor.setResponse(HttpResponse.httpResponse("Exceeds length limit."));
|
||||||
if (responseByte.length() > MAX_LENGTH) {
|
|
||||||
String ellipsis = "\r\n......";
|
|
||||||
responseByte = responseByte.subArray(0, MAX_LENGTH).withAppended(ellipsis);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ByteArray[]{requestByte, responseByte};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
if (!isCancelled()) {
|
|
||||||
try {
|
|
||||||
ByteArray[] result = (ByteArray[]) get();
|
|
||||||
requestEditor.setRequest(HttpRequest.httpRequest(MessageEntry.getRequestResponse().httpService(), result[0]));
|
|
||||||
responseEditor.setResponse(HttpResponse.httpResponse(result[1]));
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
currentWorker.execute();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
289
src/main/java/hae/component/board/table/Datatable.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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(null, "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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -97,7 +97,7 @@ public class Rule extends JPanel {
|
|||||||
Display ruleDisplay = new Display();
|
Display ruleDisplay = new Display();
|
||||||
ruleDisplay.formatTextField.setText("{0}");
|
ruleDisplay.formatTextField.setText("{0}");
|
||||||
|
|
||||||
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
|
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
|
||||||
if (showState == YES_OPTION) {
|
if (showState == YES_OPTION) {
|
||||||
Vector<Object> ruleData = new Vector<>();
|
Vector<Object> ruleData = new Vector<>();
|
||||||
ruleData.add(false);
|
ruleData.add(false);
|
||||||
@@ -132,7 +132,7 @@ public class Rule extends JPanel {
|
|||||||
|
|
||||||
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
||||||
|
|
||||||
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
|
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
|
||||||
if (showState == 0) {
|
if (showState == 0) {
|
||||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||||
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
||||||
@@ -151,7 +151,7 @@ public class Rule extends JPanel {
|
|||||||
|
|
||||||
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||||
if (ruleTable.getSelectedRowCount() >= 1) {
|
if (ruleTable.getSelectedRowCount() >= 1) {
|
||||||
if (JOptionPane.showConfirmDialog(null, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
|
if (JOptionPane.showConfirmDialog(this, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
|
||||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public class Rules extends JTabbedPane {
|
|||||||
|
|
||||||
private void deleteRuleGroupActionPerformed(ActionEvent e) {
|
private void deleteRuleGroupActionPerformed(ActionEvent e) {
|
||||||
if (getTabCount() > 2) {
|
if (getTabCount() > 2) {
|
||||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to delete this rule group?", "Info",
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to delete this rule group?", "Info",
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
if (retCode == JOptionPane.YES_OPTION) {
|
if (retCode == JOptionPane.YES_OPTION) {
|
||||||
String title = getTitleAt(getSelectedIndex());
|
String title = getTitleAt(getSelectedIndex());
|
||||||
|
|||||||
@@ -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,17 +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 (StringProcessor.matchFromEnd(host, cleanedHost)) {
|
|
||||||
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);
|
||||||
@@ -137,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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
106
src/main/java/hae/instances/http/HttpMessageActiveHandler.java
Normal 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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,96 +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
|
|
||||||
messageTableModel.add(httpRequestResponse, 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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 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,15 +123,27 @@ public class RegularMatcher {
|
|||||||
return new ArrayList<>(combinedSet);
|
return new ArrayList<>(combinedSet);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (flag) {
|
||||||
|
// 数据存储在BurpSuite空间内
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
return gRuleMap;
|
return gRuleMap;
|
||||||
});
|
});
|
||||||
|
|
||||||
String[] splitHost = host.split("\\.");
|
String[] splitHost = host.split("\\.");
|
||||||
String onlyHost = host.split(":")[0];
|
String onlyHost = host.split(":")[0];
|
||||||
|
|
||||||
String anyHost = (splitHost.length > 2 && !onlyHost.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b")) ? 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<>());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
85
src/main/java/hae/utils/DataManager.java
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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 loadHaEData(PersistedList<String> dataIndex) {
|
||||||
|
if (dataIndex != null && !dataIndex.isEmpty()) {
|
||||||
|
dataIndex.parallelStream().forEach(index -> {
|
||||||
|
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||||
|
dataObj.stringListKeys().forEach(dataKey -> {
|
||||||
|
RegularMatcher.putDataToGlobalMap(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMessageData(PersistedList<String> messageIndex, MessageTableModel messageTableModel) {
|
||||||
|
if (messageIndex != null && !messageIndex.isEmpty()) {
|
||||||
|
messageIndex.parallelStream().forEach(index -> {
|
||||||
|
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
src/main/java/hae/utils/http/HttpUtils.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package hae.utils.project;
|
|
||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
|
||||||
import hae.utils.project.model.HaeFileContent;
|
|
||||||
import org.yaml.snakeyaml.Yaml;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
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, String>> httpMap) {
|
|
||||||
ByteArrayOutputStream dataYamlStream = new ByteArrayOutputStream();
|
|
||||||
ByteArrayOutputStream httpYamlStream = new ByteArrayOutputStream();
|
|
||||||
Yaml yaml = new Yaml();
|
|
||||||
|
|
||||||
yaml.dump(dataMap, new OutputStreamWriter(dataYamlStream, StandardCharsets.UTF_8));
|
|
||||||
yaml.dump(httpMap, new OutputStreamWriter(httpYamlStream, 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.yml"));
|
|
||||||
zipOut.write(dataYamlStream.toByteArray());
|
|
||||||
zipOut.closeEntry();
|
|
||||||
|
|
||||||
zipOut.putNextEntry(new ZipEntry("http.yml"));
|
|
||||||
zipOut.write(httpYamlStream.toByteArray());
|
|
||||||
zipOut.closeEntry();
|
|
||||||
} catch (Exception e) {
|
|
||||||
api.logging().logToOutput(e.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HaeFileContent readHaeFile(String haeFilePath) {
|
|
||||||
HaeFileContent haeFileContent = new HaeFileContent(api);
|
|
||||||
Yaml yaml = new Yaml();
|
|
||||||
|
|
||||||
try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(haeFilePath))) {
|
|
||||||
ZipEntry entry;
|
|
||||||
while ((entry = zipIn.getNextEntry()) != null) {
|
|
||||||
switch (entry.getName()) {
|
|
||||||
case "info":
|
|
||||||
haeFileContent.setHost(new String(zipIn.readAllBytes(), StandardCharsets.UTF_8));
|
|
||||||
break;
|
|
||||||
case "data.yml":
|
|
||||||
haeFileContent.setDataMap(yaml.load(new InputStreamReader(zipIn, StandardCharsets.UTF_8)));
|
|
||||||
break;
|
|
||||||
case "http.yml":
|
|
||||||
haeFileContent.setHttpMap(yaml.load(new InputStreamReader(zipIn, StandardCharsets.UTF_8)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
zipIn.closeEntry();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
api.logging().logToOutput(e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return haeFileContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,67 +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 final Map<String, List<String>> dataMap;
|
|
||||||
private final Map<String, Map<String, String>> httpMap;
|
|
||||||
|
|
||||||
public HaeFileContent(MontoyaApi api) {
|
|
||||||
this.api = api;
|
|
||||||
this.dataMap = new HashMap<>();
|
|
||||||
this.httpMap = new HashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHost() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, List<String>> getDataMap() {
|
|
||||||
return dataMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Map<String, String>> getHttpMap() {
|
|
||||||
return httpMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHost(String host) {
|
|
||||||
this.host = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 setHttpMap(Map<String, Map<String, Object>> httpMap) {
|
|
||||||
for (Map.Entry<String, Map<String, Object>> entry : httpMap.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.httpMap.put(entry.getKey(), newValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
package hae.utils.string;
|
package hae.utils.string;
|
||||||
|
|
||||||
|
import burp.api.montoya.core.ByteArray;
|
||||||
|
import burp.api.montoya.http.HttpService;
|
||||||
|
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||||
|
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||||
|
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class StringProcessor {
|
public class StringProcessor {
|
||||||
public static String replaceFirstOccurrence(String original, String find, String replace) {
|
public static String replaceFirstOccurrence(String original, String find, String replace) {
|
||||||
@@ -55,6 +64,24 @@ public class StringProcessor {
|
|||||||
return matchesDirectly || matchesPattern;
|
return matchesDirectly || matchesPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HttpRequestResponse createHttpRequestResponse(String url, byte[] request, byte[] response) {
|
||||||
|
HttpService httpService = HttpService.httpService(url);
|
||||||
|
HttpRequest httpRequest = HttpRequest.httpRequest(httpService, ByteArray.byteArray(request));
|
||||||
|
HttpResponse httpResponse = HttpResponse.httpResponse(ByteArray.byteArray(response));
|
||||||
|
return HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCurrentTime() {
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
return now.format(formatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRandomUUID() {
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
return uuid.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static String mergeComment(String comment) {
|
public static String mergeComment(String comment) {
|
||||||
if (!comment.contains(",")) {
|
if (!comment.contains(",")) {
|
||||||
return comment;
|
return comment;
|
||||||
@@ -92,6 +119,10 @@ public class StringProcessor {
|
|||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean matchHostIsIp(String host) {
|
||||||
|
return host.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b");
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<String, Integer> getStringIntegerMap(String comment) {
|
private static Map<String, Integer> getStringIntegerMap(String comment) {
|
||||||
Map<String, Integer> itemCounts = new HashMap<>();
|
Map<String, Integer> itemCounts = new HashMap<>();
|
||||||
String[] items = comment.split(", ");
|
String[] items = comment.split(", ");
|
||||||
|
|||||||
@@ -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,14 +209,24 @@ 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
|
||||||
@@ -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
|
||||||
|
|||||||