Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6adf30f25c | ||
|
|
4fbd241ebe | ||
|
|
20afa30822 | ||
|
|
1a5ed2a6a3 | ||
|
|
1bf2b461ba | ||
|
|
79e2e58d48 | ||
|
|
bf0b7f0016 | ||
|
|
69c2b59c8c | ||
|
|
79655def48 | ||
|
|
116aec0848 | ||
|
|
c5de042b4b | ||
|
|
5bc592c6f9 | ||
|
|
be2df6472b | ||
|
|
4a53f20649 | ||
|
|
704e760912 | ||
|
|
3ccfee5a02 | ||
|
|
819ef820f8 | ||
|
|
0c795af101 | ||
|
|
5977e82ca6 | ||
|
|
452f297f55 | ||
|
|
a06ef8e25e | ||
|
|
7e53e250af | ||
|
|
b686b5e75e |
119
README.md
119
README.md
@@ -1,79 +1,90 @@
|
|||||||
<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/">Empower ethical hacker for efficient operations.</a></h4>
|
||||||
<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>
|
<h5>First Author:: <a href="https://github.com/gh0stkey">EvilChen</a>(Zhongfu Information Yuanheng Laboratory)<br>Second Author: <a href="https://github.com/0chencc">0chencc</a>(Mystery Security Team)<br>Third Author: <a href="https://github.com/vaycore">vaycore</a>(Independent Security Researcher)</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 项目介绍
|
README Version: \[[English](README.md) | [简体中文](README_CN.md)\]
|
||||||
|
|
||||||
**HaE**是一款**网络安全(数据安全)领域**下的框架式项目,采用了**乐高积木式**模块化设计理念,实现对HTTP消息(包含WebSocket)精细化的标记和提取。
|
## Project Introduction
|
||||||
|
|
||||||
通过运用**多引擎**的自定义正则表达式,HaE能够准确匹配并处理HTTP请求与响应报文(包含WebSocket),对匹配成功的内容进行有效的标记和信息抽取,从而提升网络安全(数据安全)领域下的**漏洞和数据分析效率**。
|
**HaE** is a framework-style project in the field of **cybersecurity (data security)**, adopting a **Lego brick-style** modular design philosophy to achieve fine-grained tagging and extraction of HTTP messages (including WebSocket).
|
||||||
|
|
||||||
> 随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
|
By utilizing **multi-engine** customized regular expressions, HaE can accurately match and process HTTP requests and response messages (including WebSocket), effectively tagging and extracting information from successfully matched content. This enhances the **efficiency of vulnerability and data analysis** in the field of cybersecurity (data security).
|
||||||
|
|
||||||
GitHub项目地址:https://github.com/gh0stkey/HaE
|
> With the adoption of front-end and back-end separation development models in modern web applications, the amount of captured HTTP request traffic during routine vulnerability discovery has correspondingly increased. Fully assessing a web application often requires spending considerable time on irrelevant messages. **The emergence of HaE aims to address such situations**, by using HaE, you can **effectively reduce** testing time, focusing more effort on **valuable and meaningful** messages, thus **improving the efficiency of vulnerability discovery**.
|
||||||
|
|
||||||
GitCode项目地址:https://gitcode.com/gh0stkey/HaE
|
GitHub project address: https://github.com/gh0stkey/HaE
|
||||||
|
|
||||||
**所获荣誉**:
|
GitCode project address: https://gitcode.com/gh0stkey/HaE
|
||||||
|
|
||||||
1. [入选2022年KCon兵器谱](https://mp.weixin.qq.com/s/JohMsl1WD29LHCHuLf8mVQ)
|
**Awards and Recognitions**:
|
||||||
2. [入选GitCode G-Star项目](https://gitcode.com/gh0stkey/HaE)
|
|
||||||
|
|
||||||
**注意事项**:
|
1. [Selected for the 2022 KCon Arsenal](https://mp.weixin.qq.com/s/JohMsl1WD29LHCHuLf8mVQ)
|
||||||
|
2. [Recognized as a GitCode G-Star Project](https://gitcode.com/gh0stkey/HaE)
|
||||||
|
|
||||||
1. HaE 3.0版本开始采用`Montoya API`进行开发,使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。
|
**Notes and Precautions**:
|
||||||
2. HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
|
|
||||||
3. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。
|
|
||||||
|
|
||||||
## 使用方法
|
1. Starting with HaE version 3.0, development is done using the `Montoya API`. To use the new version of HaE, you need to upgrade your BurpSuite version (>=2023.12.1).
|
||||||
|
2. Custom HaE rules must enclose the expressions to be extracted within parentheses `()`. For example, if you want to match a response message from a **Shiro application**, the normal matching rule would be `rememberMe=delete`, but in HaE's rule format, it needs to be written as `(rememberMe=delete)`.
|
||||||
|
|
||||||
插件装载: `Extender - Extensions - Add - Select File - Next`
|
### Rule Definitions
|
||||||
|
|
||||||
初次装载`HaE`会从Jar包中加载离线的规则库,如果更新可以点击`Reinit`进行重新初始化。内置规则库地址可以在Github上找到:`https://github.com/gh0stkey/HaE/blob/master/src/main/resources/rules/Rules.yml`。
|
Currently, HaE rules consist of 8 fields, with detailed meanings as follows:
|
||||||
|
|
||||||
配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下:
|
| Field | Meaning |
|
||||||
|
| --------- | ------------------------------------------------------------ |
|
||||||
|
| Name | Rule name, primarily used to briefly summarize the purpose of the current rule. |
|
||||||
|
| F-Regex | Rule regex, mainly used for entering regular expressions. In HaE, any content that needs to be extracted and matched should be enclosed within `(` and `)`. |
|
||||||
|
| S-Regex | Rule regex, with the same usage as F-Regex. S-Regex is a secondary regex, which can be used for further matching and extraction from the data results matched by F-Regex. Can be left empty if not needed. |
|
||||||
|
| Format | Formatted output; in NFA engine regular expressions, we can use `{0}`, `{1}`, `{2}`... to format and output captured groups. By default, using `{0}` is sufficient. |
|
||||||
|
| Scope | Rule scope, indicating which part of the HTTP message the current rule applies to. Supports request/response lines, headers, bodies, and complete messages. |
|
||||||
|
| Engine | Regex engine, indicating which engine the current rule's regular expression uses. **DFA engine**: scans each character in the text string only once, fast speed, fewer features; **NFA engine**: repeatedly marks and unmarks characters, slower but richer features (e.g., grouping, replacement, splitting). |
|
||||||
|
| Color | Match color, indicating the highlight color to mark when the current rule matches the corresponding HTTP message. HaE has a color upgrade algorithm that automatically upgrades the marking color when the same color appears. |
|
||||||
|
| Sensitive | Case sensitivity, indicating whether the current rule is case-sensitive. If sensitive (`True`), it strictly matches the case; if insensitive (`False`), it does not consider case differences. |
|
||||||
|
|
||||||
1. Linux/Mac用户的配置文件目录:`~/.config/HaE/`
|
## Key Features and Advantages
|
||||||
2. Windows用户的配置文件目录:`%USERPROFILE%/.config/HaE/`
|
|
||||||
|
|
||||||
除此之外,您也可以选择将配置文件存放在`HaE Jar包`的同级目录下的`/.config/HaE/`中,**以便于离线携带**。
|
1. **Functionality**: By highlighting, annotating, and extracting information from HTTP messages, it helps users obtain meaningful insights, **focusing on high-value messages**.
|
||||||
|
2. **Interface**: With a clear and visually intuitive design, and **simple interface interactions**, users can more easily understand and configure the project, **avoiding the complexity of a `multitude of buttons`**.
|
||||||
|
3. **Query**: Highlights, annotations, and extracted information from HTTP messages are **centralized in a single data panel**, allowing for one-click queries and extraction of information, thereby improving testing and analysis efficiency.
|
||||||
|
4. **Algorithm**: Built-in color upgrade algorithm automatically upgrades the marking color by one level when the same color appears, **preventing the scenario where `the dragon slayer becomes the dragon`**.
|
||||||
|
5. **Management**: **Integrated with BurpSuite's project data management**, HaE data is stored along with BurpSuite project data when saving projects.
|
||||||
|
6. **Practical Application**: The official rule library and rule field functionalities are **summarized and output based on real-world scenarios**, **thereby enhancing the effectiveness and accuracy of data discovery**.
|
||||||
|
|
||||||
### 规则释义
|
| Name | Display |
|
||||||
|
|
||||||
HaE目前的规则一共有8个字段,详细的含义如下所示:
|
|
||||||
|
|
||||||
| 字段 | 含义 |
|
|
||||||
|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| Name | 规则名称,主要用于简短概括当前规则的作用。 |
|
|
||||||
| F-Regex | 规则正则,主要用于填写正则表达式。在HaE中所需提取匹配的内容需要用`(`、`)`将正则表达式进行包裹。|
|
|
||||||
| S-Regex | 规则正则,作用及使用同F-Regex。S-Regex为二次正则,可以用于对F-Regex匹配的数据结果进行二次的匹配提取,如不需要的情况下可以留空。|
|
|
||||||
| Format | 格式化输出,在NFA引擎的正则表达式中,我们可以通过`{0}`、`{1}`、`{2}`…的方式进行取分组格式化输出。默认情况下使用`{0}`即可。 |
|
|
||||||
| Scope | 规则作用域,主要用于表示当前规则作用于HTTP报文的哪个部分。支持请求、响应的行、头、体,以及完整的报文。 |
|
|
||||||
| Engine | 正则引擎,主要用于表示当前规则的正则表达式所使用的引擎。**DFA引擎**:对于文本串里的每一个字符只需扫描一次,速度快、特性少;**NFA引擎**:要翻来覆去标注字符、取消标注字符,速度慢,但是特性(如:分组、替换、分割)丰富。 |
|
|
||||||
| Color | 规则匹配颜色,主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法,当出现相同颜色时会自动向上升级一个颜色进行标记。 |
|
|
||||||
| Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 |
|
|
||||||
|
|
||||||
## 优势特点
|
|
||||||
|
|
||||||
1. **功能**:通过对HTTP报文的颜色高亮、注释和提取,帮助使用者获取有意义的信息,**聚焦高价值报文**。
|
|
||||||
2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。
|
|
||||||
3. **查询**:将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。
|
|
||||||
4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。
|
|
||||||
5. **管理**:**融入BurpSuite的项目数据管理**,当使用BurpSuite进行项目存储时HaE数据也会一并存储。
|
|
||||||
6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。
|
|
||||||
|
|
||||||
| 界面名称 | 界面展示 |
|
|
||||||
| ------------------------ | ---------------------------------------------------- |
|
| ------------------------ | ---------------------------------------------------- |
|
||||||
| Rules(规则管理) | <img src="images/rules.png" style="width: 80%" /> |
|
| Rules | <img src="images/rules.png" style="width: 80%" /> |
|
||||||
| Config(配置管理) | <img src="images/config.png" style="width: 80%" /> |
|
| Config | <img src="images/config.png" style="width: 80%" /> |
|
||||||
| 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%" /> |
|
||||||
|
|
||||||
## 支持项目
|
## Appreciation List
|
||||||
|
|
||||||
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
We appreciate everyone's support for the project. The following list is sorted based on the time of appreciation and is not in any particular order. If there are any omissions, please contact the project author for additions.
|
||||||
|
|
||||||
|
| ID | Amount |
|
||||||
|
| -------- | -------- |
|
||||||
|
| 毁三观大人 | 200.00¥ |
|
||||||
|
| ttt | 50.00¥ |
|
||||||
|
| C_soon5 | 66.66¥ |
|
||||||
|
| 1wtbb | 25.00¥ |
|
||||||
|
| Deep | 66.66¥ |
|
||||||
|
| NaTsUk0 | 50.00¥ |
|
||||||
|
| Kite | 48.00¥ |
|
||||||
|
| 红色键盘 | 99.99¥ |
|
||||||
|
| 曾哥 | 188.88¥ |
|
||||||
|
| NOP Team | 200.00¥ |
|
||||||
|
| vaycore | 188.88¥ |
|
||||||
|
| xccc | 168.00¥ |
|
||||||
|
| 柯林斯-民间新秀 | 1000.00¥ |
|
||||||
|
| Cuber | 100.00¥ |
|
||||||
|
| 时光难逆 | 50.00¥ |
|
||||||
|
| Celvin | 66.00¥ |
|
||||||
|
|
||||||
|
## Support the Project
|
||||||
|
|
||||||
|
If you find HaE useful, you can show your appreciation by donating to the author, giving them the motivation to continue updating and improving it!
|
||||||
|
|
||||||
<div align=center>
|
<div align=center>
|
||||||
<img src="images/reward.jpeg" style="width: 30%" />
|
<img src="images/reward.jpeg" style="width: 30%" />
|
||||||
@@ -83,6 +94,6 @@ HaE目前的规则一共有8个字段,详细的含义如下所示:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
`HaE` 是 404Team [星链计划2.0](https://github.com/knownsec/404StarLink2.0-Galaxy) 中的一环,如果对 `HaE` 有任何疑问又或是想要找小伙伴交流,可以参考星链计划的加群方式。
|
`HaE` is part of the 404Team's [Starlink Plan 2.0](https://github.com/knownsec/404StarLink2.0-Galaxy). If you have any questions about `HaE` or want to connect with other users, you can refer to the group joining methods provided by the Starlink Plan.
|
||||||
|
|
||||||
- [https://github.com/knownsec/404StarLink2.0-Galaxy#community](https://github.com/knownsec/404StarLink2.0-Galaxy#community)
|
- [https://github.com/knownsec/404StarLink2.0-Galaxy#community](https://github.com/knownsec/404StarLink2.0-Galaxy#community)
|
||||||
|
|||||||
112
README_CN.md
Normal file
112
README_CN.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<div align="center">
|
||||||
|
<img src="images/logo.png" style="width: 20%" />
|
||||||
|
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
README 版本: \[[English](README.md) | [简体中文](README_CN.md)\]
|
||||||
|
|
||||||
|
## 项目介绍
|
||||||
|
|
||||||
|
**HaE**是一款**网络安全(数据安全)领域**下的框架式项目,采用了**乐高积木式**模块化设计理念,实现对HTTP消息(包含WebSocket)精细化的标记和提取。
|
||||||
|
|
||||||
|
通过运用**多引擎**的自定义正则表达式,HaE能够准确匹配并处理HTTP请求与响应报文(包含WebSocket),对匹配成功的内容进行有效的标记和信息抽取,从而提升网络安全(数据安全)领域下的**漏洞和数据分析效率**。
|
||||||
|
|
||||||
|
> 随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
|
||||||
|
|
||||||
|
GitHub项目地址:https://github.com/gh0stkey/HaE
|
||||||
|
|
||||||
|
GitCode项目地址:https://gitcode.com/gh0stkey/HaE
|
||||||
|
|
||||||
|
**所获荣誉**:
|
||||||
|
|
||||||
|
1. [入选2022年KCon兵器谱](https://mp.weixin.qq.com/s/JohMsl1WD29LHCHuLf8mVQ)
|
||||||
|
2. [入选GitCode G-Star项目](https://gitcode.com/gh0stkey/HaE)
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
|
||||||
|
1. HaE 3.0版本开始采用`Montoya API`进行开发,使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。
|
||||||
|
2. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
插件装载: `Extender - Extensions - Add - Select File - Next`
|
||||||
|
|
||||||
|
初次装载`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/`
|
||||||
|
2. Windows用户的配置文件目录:`%USERPROFILE%/.config/HaE/`
|
||||||
|
|
||||||
|
除此之外,您也可以选择将配置文件存放在`HaE Jar包`的同级目录下的`/.config/HaE/`中,**以便于离线携带**。
|
||||||
|
|
||||||
|
### 规则释义
|
||||||
|
|
||||||
|
HaE目前的规则一共有8个字段,详细的含义如下所示:
|
||||||
|
|
||||||
|
| 字段 | 含义 |
|
||||||
|
|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| Name | 规则名称,主要用于简短概括当前规则的作用。 |
|
||||||
|
| F-Regex | 规则正则,主要用于填写正则表达式。在HaE中所需提取匹配的内容需要用`(`、`)`将正则表达式进行包裹。|
|
||||||
|
| S-Regex | 规则正则,作用及使用同F-Regex。S-Regex为二次正则,可以用于对F-Regex匹配的数据结果进行二次的匹配提取,如不需要的情况下可以留空。|
|
||||||
|
| Format | 格式化输出,在NFA引擎的正则表达式中,我们可以通过`{0}`、`{1}`、`{2}`…的方式进行取分组格式化输出。默认情况下使用`{0}`即可。 |
|
||||||
|
| Scope | 规则作用域,主要用于表示当前规则作用于HTTP报文的哪个部分。支持请求、响应的行、头、体,以及完整的报文。 |
|
||||||
|
| Engine | 正则引擎,主要用于表示当前规则的正则表达式所使用的引擎。**DFA引擎**:对于文本串里的每一个字符只需扫描一次,速度快、特性少;**NFA引擎**:要翻来覆去标注字符、取消标注字符,速度慢,但是特性(如:分组、替换、分割)丰富。 |
|
||||||
|
| Color | 规则匹配颜色,主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法,当出现相同颜色时会自动向上升级一个颜色进行标记。 |
|
||||||
|
| Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 |
|
||||||
|
|
||||||
|
## 优势特点
|
||||||
|
|
||||||
|
1. **功能**:通过对HTTP报文的颜色高亮、注释和提取,帮助使用者获取有意义的信息,**聚焦高价值报文**。
|
||||||
|
2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。
|
||||||
|
3. **查询**:将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。
|
||||||
|
4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。
|
||||||
|
5. **管理**:**融入BurpSuite的项目数据管理**,当使用BurpSuite进行项目存储时HaE数据也会一并存储。
|
||||||
|
6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。
|
||||||
|
|
||||||
|
| 界面名称 | 界面展示 |
|
||||||
|
| ------------------------ | ---------------------------------------------------- |
|
||||||
|
| Rules(规则管理) | <img src="images/rules.png" style="width: 80%" /> |
|
||||||
|
| Config(配置管理) | <img src="images/config.png" style="width: 80%" /> |
|
||||||
|
| Databoard(数据集合) | <img src="images/databoard.png" style="width: 80%" /> |
|
||||||
|
| MarkInfo(数据展示) | <img src="images/markinfo.png" style="width: 80%" /> |
|
||||||
|
|
||||||
|
## 赞赏榜单
|
||||||
|
|
||||||
|
感谢各位对项目的赞赏,以下名单基于赞赏时间进行排序,不分先后,如有遗留可联系项目作者进行补充。
|
||||||
|
|
||||||
|
| ID | 金额 |
|
||||||
|
| -------- | -------- |
|
||||||
|
| 毁三观大人 | 200.00元 |
|
||||||
|
| ttt | 50.00元 |
|
||||||
|
| C_soon5 | 66.66元 |
|
||||||
|
| 1wtbb | 25.00元 |
|
||||||
|
| Deep | 66.66元 |
|
||||||
|
| NaTsUk0 | 50.00元 |
|
||||||
|
| Kite | 48.00元 |
|
||||||
|
| 红色键盘 | 99.99元 |
|
||||||
|
| 曾哥 | 188.88元 |
|
||||||
|
| NOP Team | 200.00元 |
|
||||||
|
| vaycore | 188.88元 |
|
||||||
|
| xccc | 168.00元 |
|
||||||
|
| 柯林斯-民间新秀 | 1000.00元 |
|
||||||
|
| Cuber | 100.00元 |
|
||||||
|
| 时光难逆 | 50.00元 |
|
||||||
|
| Celvin | 66.00元 |
|
||||||
|
|
||||||
|
## 支持项目
|
||||||
|
|
||||||
|
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
||||||
|
|
||||||
|
<div align=center>
|
||||||
|
<img src="images/reward.jpeg" style="width: 30%" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## 404StarLink 2.0 - Galaxy
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
`HaE` 是 404Team [星链计划2.0](https://github.com/knownsec/404StarLink2.0-Galaxy) 中的一环,如果对 `HaE` 有任何疑问又或是想要找小伙伴交流,可以参考星链计划的加群方式。
|
||||||
|
|
||||||
|
- [https://github.com/knownsec/404StarLink2.0-Galaxy#community](https://github.com/knownsec/404StarLink2.0-Galaxy#community)
|
||||||
@@ -61,6 +61,8 @@ public class Config {
|
|||||||
"gray"
|
"gray"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static Boolean proVersionStatus = true;
|
||||||
|
|
||||||
public static Map<String, Object[][]> globalRules = new HashMap<>();
|
public static Map<String, Object[][]> globalRules = new HashMap<>();
|
||||||
|
|
||||||
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
|
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ 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.extension.ExtensionUnloadingHandler;
|
||||||
import burp.api.montoya.logging.Logging;
|
import burp.api.montoya.logging.Logging;
|
||||||
import hae.cache.CachePool;
|
import hae.cache.MessageCache;
|
||||||
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.HttpMessagePassiveHandler;
|
||||||
import hae.instances.websocket.WebSocketMessageHandler;
|
import hae.instances.websocket.WebSocketMessageHandler;
|
||||||
import hae.utils.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.DataManager;
|
import hae.utils.DataManager;
|
||||||
@@ -18,8 +19,8 @@ public class HaE implements BurpExtension {
|
|||||||
@Override
|
@Override
|
||||||
public void initialize(MontoyaApi api) {
|
public void initialize(MontoyaApi api) {
|
||||||
// 设置扩展名称
|
// 设置扩展名称
|
||||||
String version = "4.0.1";
|
|
||||||
api.extension().setName("HaE - Highlighter and Extractor");
|
api.extension().setName("HaE - Highlighter and Extractor");
|
||||||
|
String version = "4.1";
|
||||||
|
|
||||||
// 加载扩展后输出的项目信息
|
// 加载扩展后输出的项目信息
|
||||||
Logging logging = api.logging();
|
Logging logging = api.logging();
|
||||||
@@ -33,6 +34,9 @@ public class HaE implements BurpExtension {
|
|||||||
|
|
||||||
MessageTableModel messageTableModel = new MessageTableModel(api, configLoader);
|
MessageTableModel messageTableModel = new MessageTableModel(api, configLoader);
|
||||||
|
|
||||||
|
// 设置BurpSuite专业版状态
|
||||||
|
Config.proVersionStatus = getBurpSuiteProStatus(api, configLoader, messageTableModel);
|
||||||
|
|
||||||
// 注册Tab页(用于查询数据)
|
// 注册Tab页(用于查询数据)
|
||||||
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
|
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
|
||||||
|
|
||||||
@@ -48,13 +52,29 @@ public class HaE implements BurpExtension {
|
|||||||
DataManager dataManager = new DataManager(api);
|
DataManager dataManager = new DataManager(api);
|
||||||
dataManager.loadData(messageTableModel);
|
dataManager.loadData(messageTableModel);
|
||||||
|
|
||||||
|
|
||||||
api.extension().registerUnloadingHandler(new ExtensionUnloadingHandler() {
|
api.extension().registerUnloadingHandler(new ExtensionUnloadingHandler() {
|
||||||
@Override
|
@Override
|
||||||
public void extensionUnloaded() {
|
public void extensionUnloaded() {
|
||||||
// 卸载清空数据
|
// 卸载清空数据
|
||||||
Config.globalDataMap.clear();
|
Config.globalDataMap.clear();
|
||||||
CachePool.clear();
|
MessageCache.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean getBurpSuiteProStatus(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||||
|
boolean burpSuiteProStatus = false;
|
||||||
|
try {
|
||||||
|
burpSuiteProStatus = api.burpSuite().version().name().contains("Professional");
|
||||||
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
|
api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel)).deregister();
|
||||||
|
burpSuiteProStatus = true;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return burpSuiteProStatus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/main/java/hae/cache/DataQueryCache.java
vendored
Normal file
46
src/main/java/hae/cache/DataQueryCache.java
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package hae.cache;
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class DataQueryCache {
|
||||||
|
private static final int MAX_SIZE = 1000;
|
||||||
|
private static final int EXPIRE_DURATION = 30;
|
||||||
|
|
||||||
|
private static final Cache<String, Map<String, List<String>>> hostQueryCache =
|
||||||
|
Caffeine.newBuilder()
|
||||||
|
.maximumSize(MAX_SIZE)
|
||||||
|
.expireAfterWrite(EXPIRE_DURATION, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static final Cache<String, List<String>> hostFilterCache =
|
||||||
|
Caffeine.newBuilder()
|
||||||
|
.maximumSize(MAX_SIZE)
|
||||||
|
.expireAfterWrite(EXPIRE_DURATION, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static void putHostQueryResult(String host, Map<String, List<String>> result) {
|
||||||
|
hostQueryCache.put(host, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, List<String>> getHostQueryResult(String host) {
|
||||||
|
return hostQueryCache.getIfPresent(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void putHostFilterResult(String input, List<String> result) {
|
||||||
|
hostFilterCache.put(input, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getHostFilterResult(String input) {
|
||||||
|
return hostFilterCache.getIfPresent(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearCache() {
|
||||||
|
hostQueryCache.invalidateAll();
|
||||||
|
hostFilterCache.invalidateAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import com.github.benmanes.caffeine.cache.Caffeine;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class CachePool {
|
public class MessageCache {
|
||||||
private static final int MAX_SIZE = 100000;
|
private static final int MAX_SIZE = 100000;
|
||||||
private static final int EXPIRE_DURATION = 5;
|
private static final int EXPIRE_DURATION = 5;
|
||||||
|
|
||||||
@@ -135,6 +135,7 @@ public class Config extends JPanel {
|
|||||||
modePanel.setLayout(new BoxLayout(modePanel, BoxLayout.X_AXIS));
|
modePanel.setLayout(new BoxLayout(modePanel, BoxLayout.X_AXIS));
|
||||||
|
|
||||||
JCheckBox checkBox = new JCheckBox("Enable active http message handler");
|
JCheckBox checkBox = new JCheckBox("Enable active http message handler");
|
||||||
|
checkBox.setEnabled(hae.Config.proVersionStatus);
|
||||||
modePanel.add(checkBox);
|
modePanel.add(checkBox);
|
||||||
checkBox.addActionListener(e -> updateModeStatus(checkBox));
|
checkBox.addActionListener(e -> updateModeStatus(checkBox));
|
||||||
checkBox.setSelected(configLoader.getMode());
|
checkBox.setSelected(configLoader.getMode());
|
||||||
@@ -379,7 +380,7 @@ public class Config extends JPanel {
|
|||||||
configLoader.setMode(selected ? "true" : "false");
|
configLoader.setMode(selected ? "true" : "false");
|
||||||
|
|
||||||
if (checkBox.isSelected()) {
|
if (checkBox.isSelected()) {
|
||||||
if (passiveHandler.isRegistered()) {
|
if (hae.Config.proVersionStatus && passiveHandler.isRegistered()) {
|
||||||
passiveHandler.deregister();
|
passiveHandler.deregister();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +388,7 @@ public class Config extends JPanel {
|
|||||||
activeHandler = api.http().registerHttpHandler(new HttpMessageActiveHandler(api, configLoader, messageTableModel));
|
activeHandler = api.http().registerHttpHandler(new HttpMessageActiveHandler(api, configLoader, messageTableModel));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!passiveHandler.isRegistered()) {
|
if (hae.Config.proVersionStatus && !passiveHandler.isRegistered()) {
|
||||||
passiveHandler = api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel));
|
passiveHandler = api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package hae.component.board;
|
|||||||
|
|
||||||
import burp.api.montoya.MontoyaApi;
|
import burp.api.montoya.MontoyaApi;
|
||||||
import hae.Config;
|
import hae.Config;
|
||||||
|
import hae.cache.DataQueryCache;
|
||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.component.board.message.MessageTableModel.MessageTable;
|
import hae.component.board.message.MessageTableModel.MessageTable;
|
||||||
import hae.component.board.table.Datatable;
|
import hae.component.board.table.Datatable;
|
||||||
@@ -23,19 +24,17 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class Databoard extends JPanel {
|
public class Databoard extends JPanel {
|
||||||
|
private static Boolean isMatchHost = false;
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final ConfigLoader configLoader;
|
private final ConfigLoader configLoader;
|
||||||
private final MessageTableModel messageTableModel;
|
private final MessageTableModel messageTableModel;
|
||||||
|
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
|
||||||
|
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
|
||||||
private JTextField hostTextField;
|
private JTextField hostTextField;
|
||||||
private JTabbedPane dataTabbedPane;
|
private JTabbedPane dataTabbedPane;
|
||||||
private JSplitPane splitPane;
|
private JSplitPane splitPane;
|
||||||
private MessageTable messageTable;
|
private MessageTable messageTable;
|
||||||
|
private JProgressBar progressBar;
|
||||||
private static Boolean isMatchHost = false;
|
|
||||||
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
|
|
||||||
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
|
|
||||||
|
|
||||||
private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker;
|
private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker;
|
||||||
private SwingWorker<Void, Void> applyHostFilterWorker;
|
private SwingWorker<Void, Void> applyHostFilterWorker;
|
||||||
|
|
||||||
@@ -47,13 +46,25 @@ public class Databoard extends JPanel {
|
|||||||
initComponents();
|
initComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setProgressBar(boolean status, JProgressBar progressBar, String showString) {
|
||||||
|
progressBar.setIndeterminate(status);
|
||||||
|
if (!status) {
|
||||||
|
progressBar.setMaximum(100);
|
||||||
|
progressBar.setString("OK");
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
progressBar.setValue(progressBar.getMaximum());
|
||||||
|
} else {
|
||||||
|
progressBar.setString(showString);
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
setLayout(new GridBagLayout());
|
setLayout(new GridBagLayout());
|
||||||
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
|
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
|
||||||
((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 0};
|
((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 0, 0};
|
||||||
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||||
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 1.0E-4};
|
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||||
|
|
||||||
JLabel hostLabel = new JLabel("Host:");
|
JLabel hostLabel = new JLabel("Host:");
|
||||||
|
|
||||||
JButton clearButton = new JButton("Clear");
|
JButton clearButton = new JButton("Clear");
|
||||||
@@ -81,7 +92,7 @@ public class Databoard extends JPanel {
|
|||||||
|
|
||||||
clearButton.addActionListener(this::clearActionPerformed);
|
clearButton.addActionListener(this::clearActionPerformed);
|
||||||
|
|
||||||
|
progressBar = new JProgressBar();
|
||||||
splitPane.addComponentListener(new ComponentAdapter() {
|
splitPane.addComponentListener(new ComponentAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void componentResized(ComponentEvent e) {
|
public void componentResized(ComponentEvent e) {
|
||||||
@@ -90,6 +101,7 @@ public class Databoard extends JPanel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
splitPane.setVisible(false);
|
splitPane.setVisible(false);
|
||||||
|
progressBar.setVisible(false);
|
||||||
|
|
||||||
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
@@ -98,9 +110,12 @@ public class Databoard extends JPanel {
|
|||||||
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
|
|
||||||
add(splitPane, new GridBagConstraints(1, 1, 3, 2, 0.0, 1.0,
|
add(splitPane, new GridBagConstraints(1, 1, 3, 1, 0.0, 1.0,
|
||||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(0, 5, 0, 5), 0, 0));
|
new Insets(0, 5, 0, 5), 0, 0));
|
||||||
|
add(progressBar, new GridBagConstraints(1, 2, 3, 1, 1.0, 0.0,
|
||||||
|
GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
|
||||||
|
new Insets(0, 5, 0, 5), 0, 0));
|
||||||
hostComboBox.setMaximumRowCount(5);
|
hostComboBox.setMaximumRowCount(5);
|
||||||
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||||
new Insets(8, 0, 5, 5), 0, 0));
|
new Insets(8, 0, 5, 5), 0, 0));
|
||||||
@@ -120,6 +135,10 @@ public class Databoard extends JPanel {
|
|||||||
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setProgressBar(boolean status) {
|
||||||
|
setProgressBar(status, progressBar, "Loading ...");
|
||||||
|
}
|
||||||
|
|
||||||
private void setAutoMatch() {
|
private void setAutoMatch() {
|
||||||
hostComboBox.setSelectedItem(null);
|
hostComboBox.setSelectedItem(null);
|
||||||
hostComboBox.addActionListener(this::handleComboBoxAction);
|
hostComboBox.addActionListener(this::handleComboBoxAction);
|
||||||
@@ -155,6 +174,8 @@ public class Databoard extends JPanel {
|
|||||||
String selectedHost = hostComboBox.getSelectedItem().toString();
|
String selectedHost = hostComboBox.getSelectedItem().toString();
|
||||||
|
|
||||||
if (getHostByList().contains(selectedHost)) {
|
if (getHostByList().contains(selectedHost)) {
|
||||||
|
progressBar.setVisible(true);
|
||||||
|
setProgressBar(true);
|
||||||
hostTextField.setText(selectedHost);
|
hostTextField.setText(selectedHost);
|
||||||
|
|
||||||
if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
|
if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
|
||||||
@@ -193,6 +214,8 @@ public class Databoard extends JPanel {
|
|||||||
|
|
||||||
hostComboBox.setPopupVisible(false);
|
hostComboBox.setPopupVisible(false);
|
||||||
applyHostFilter(selectedHost);
|
applyHostFilter(selectedHost);
|
||||||
|
|
||||||
|
setProgressBar(false);
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
@@ -230,6 +253,12 @@ public class Databoard extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
|
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
|
||||||
|
// 先尝试从缓存获取结果
|
||||||
|
Map<String, List<String>> cachedResult = DataQueryCache.getHostQueryResult(selectedHost);
|
||||||
|
if (cachedResult != null) {
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||||
Map<String, List<String>> selectedDataMap;
|
Map<String, List<String>> selectedDataMap;
|
||||||
|
|
||||||
@@ -255,6 +284,11 @@ public class Databoard extends JPanel {
|
|||||||
selectedDataMap = dataMap.get(selectedHost);
|
selectedDataMap = dataMap.get(selectedHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将结果存入缓存
|
||||||
|
if (selectedDataMap != null) {
|
||||||
|
DataQueryCache.putHostQueryResult(selectedHost, selectedDataMap);
|
||||||
|
}
|
||||||
|
|
||||||
return selectedDataMap;
|
return selectedDataMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,19 +348,31 @@ public class Databoard extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getHostByList() {
|
private List<String> getHostByList() {
|
||||||
if (!Config.globalDataMap.keySet().isEmpty()) {
|
// 先尝试从缓存获取结果
|
||||||
return new ArrayList<>(Config.globalDataMap.keySet());
|
List<String> cachedResult = DataQueryCache.getHostFilterResult("all_hosts");
|
||||||
|
if (cachedResult != null) {
|
||||||
|
return cachedResult;
|
||||||
}
|
}
|
||||||
return new ArrayList<>();
|
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
if (!Config.globalDataMap.isEmpty()) {
|
||||||
|
result = new ArrayList<>(Config.globalDataMap.keySet());
|
||||||
|
// 将结果存入缓存
|
||||||
|
DataQueryCache.putHostFilterResult("all_hosts", result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearActionPerformed(ActionEvent e) {
|
private void clearActionPerformed(ActionEvent e) {
|
||||||
|
// 清除缓存
|
||||||
|
DataQueryCache.clearCache();
|
||||||
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
|
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
String host = hostTextField.getText();
|
String host = hostTextField.getText();
|
||||||
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
||||||
dataTabbedPane.removeAll();
|
dataTabbedPane.removeAll();
|
||||||
splitPane.setVisible(false);
|
splitPane.setVisible(false);
|
||||||
|
progressBar.setVisible(false);
|
||||||
|
|
||||||
Config.globalDataMap.keySet().parallelStream().forEach(key -> {
|
Config.globalDataMap.keySet().parallelStream().forEach(key -> {
|
||||||
if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) {
|
if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) {
|
||||||
@@ -353,8 +399,8 @@ public class Databoard extends JPanel {
|
|||||||
|
|
||||||
keysToRemove.forEach(Config.globalDataMap::remove);
|
keysToRemove.forEach(Config.globalDataMap::remove);
|
||||||
|
|
||||||
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
|
if (Config.globalDataMap.size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
|
||||||
Config.globalDataMap.keySet().remove("*");
|
Config.globalDataMap.remove("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
messageTableModel.deleteByHost(host);
|
messageTableModel.deleteByHost(host);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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.MessageCache;
|
||||||
import hae.utils.ConfigLoader;
|
import hae.utils.ConfigLoader;
|
||||||
import hae.utils.DataManager;
|
import hae.utils.DataManager;
|
||||||
import hae.utils.string.HashCalculator;
|
import hae.utils.string.HashCalculator;
|
||||||
@@ -90,7 +90,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||||
|
|
||||||
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||||
// 请求/相应文本框
|
// 请求/响应文本框
|
||||||
JScrollPane scrollPane = new JScrollPane(messageTable);
|
JScrollPane scrollPane = new JScrollPane(messageTable);
|
||||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
||||||
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||||
@@ -98,7 +98,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
splitPane.setRightComponent(messageTab);
|
splitPane.setRightComponent(messageTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
|
public synchronized void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
|
||||||
synchronized (log) {
|
synchronized (log) {
|
||||||
boolean isDuplicate = false;
|
boolean isDuplicate = false;
|
||||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
||||||
@@ -136,14 +136,18 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
|
|
||||||
if (!isDuplicate) {
|
if (!isDuplicate) {
|
||||||
if (flag) {
|
if (flag) {
|
||||||
DataManager dataManager = new DataManager(api);
|
try {
|
||||||
// 数据存储在BurpSuite空间内
|
DataManager dataManager = new DataManager(api);
|
||||||
PersistedObject persistedObject = PersistedObject.persistedObject();
|
// 数据存储在BurpSuite空间内
|
||||||
persistedObject.setHttpRequestResponse("messageInfo", messageInfo);
|
PersistedObject persistedObject = PersistedObject.persistedObject();
|
||||||
persistedObject.setString("comment", comment);
|
persistedObject.setHttpRequestResponse("messageInfo", messageInfo);
|
||||||
persistedObject.setString("color", color);
|
persistedObject.setString("comment", comment);
|
||||||
String uuidIndex = StringProcessor.getRandomUUID();
|
persistedObject.setString("color", color);
|
||||||
dataManager.putData("message", uuidIndex, persistedObject);
|
String uuidIndex = StringProcessor.getRandomUUID();
|
||||||
|
dataManager.putData("message", uuidIndex, persistedObject);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加进日志
|
// 添加进日志
|
||||||
@@ -153,6 +157,24 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void addBatch(List<Object[]> batchData) {
|
||||||
|
synchronized (log) {
|
||||||
|
for (Object[] data : batchData) {
|
||||||
|
HttpRequestResponse messageInfo = (HttpRequestResponse) data[0];
|
||||||
|
String url = (String) data[1];
|
||||||
|
String method = (String) data[2];
|
||||||
|
String status = (String) data[3];
|
||||||
|
String length = (String) data[4];
|
||||||
|
String comment = (String) data[5];
|
||||||
|
String color = (String) data[6];
|
||||||
|
|
||||||
|
// 复用现有的 add 方法逻辑,但跳过重复检查
|
||||||
|
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
||||||
|
log.add(logEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteByHost(String filterText) {
|
public void deleteByHost(String filterText) {
|
||||||
filteredLog.clear();
|
filteredLog.clear();
|
||||||
List<Integer> rowsToRemove = new ArrayList<>();
|
List<Integer> rowsToRemove = new ArrayList<>();
|
||||||
@@ -313,7 +335,7 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
|
|
||||||
private Map<String, Map<String, Object>> getCacheData(byte[] content) {
|
private Map<String, Map<String, Object>> getCacheData(byte[] content) {
|
||||||
String hashIndex = HashCalculator.calculateHash(content);
|
String hashIndex = HashCalculator.calculateHash(content);
|
||||||
return CachePool.get(hashIndex);
|
return MessageCache.get(hashIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
|
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
|
||||||
@@ -422,11 +444,11 @@ public class MessageTableModel extends AbstractTableModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class MessageTable extends JTable {
|
public class MessageTable extends JTable {
|
||||||
private MessageEntry messageEntry;
|
|
||||||
private final ExecutorService executorService;
|
private final ExecutorService executorService;
|
||||||
private int lastSelectedIndex = -1;
|
|
||||||
private final HttpRequestEditor requestEditor;
|
private final HttpRequestEditor requestEditor;
|
||||||
private final HttpResponseEditor responseEditor;
|
private final HttpResponseEditor responseEditor;
|
||||||
|
private MessageEntry messageEntry;
|
||||||
|
private int lastSelectedIndex = -1;
|
||||||
|
|
||||||
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
|
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
|
||||||
super(messageTableModel);
|
super(messageTableModel);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public class Datatable extends JPanel {
|
|||||||
private final JTextField secondSearchField;
|
private final JTextField secondSearchField;
|
||||||
private final TableRowSorter<DefaultTableModel> sorter;
|
private final TableRowSorter<DefaultTableModel> sorter;
|
||||||
private final JCheckBox searchMode = new JCheckBox("Reverse search");
|
private final JCheckBox searchMode = new JCheckBox("Reverse search");
|
||||||
|
private final JCheckBox regexMode = new JCheckBox("Regex mode");
|
||||||
private final String tabName;
|
private final String tabName;
|
||||||
private final JPanel footerPanel;
|
private final JPanel footerPanel;
|
||||||
|
|
||||||
@@ -51,6 +52,8 @@ public class Datatable extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initComponents(List<String> dataList) {
|
private void initComponents(List<String> dataList) {
|
||||||
|
dataTable.setRowSorter(sorter);
|
||||||
|
|
||||||
// 设置ID排序
|
// 设置ID排序
|
||||||
sorter.setComparator(0, new Comparator<Integer>() {
|
sorter.setComparator(0, new Comparator<Integer>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -107,7 +110,6 @@ public class Datatable extends JPanel {
|
|||||||
JScrollPane scrollPane = new JScrollPane(dataTable);
|
JScrollPane scrollPane = new JScrollPane(dataTable);
|
||||||
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||||
|
|
||||||
dataTable.setRowSorter(sorter);
|
|
||||||
TableColumn idColumn = dataTable.getColumnModel().getColumn(0);
|
TableColumn idColumn = dataTable.getColumnModel().getColumn(0);
|
||||||
idColumn.setPreferredWidth(50);
|
idColumn.setPreferredWidth(50);
|
||||||
idColumn.setMaxWidth(100);
|
idColumn.setMaxWidth(100);
|
||||||
@@ -118,10 +120,12 @@ public class Datatable extends JPanel {
|
|||||||
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.X_AXIS));
|
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.X_AXIS));
|
||||||
|
|
||||||
// Settings按钮
|
// Settings按钮
|
||||||
JPanel settingMenuPanel = new JPanel(new GridLayout(1, 1));
|
JPanel settingMenuPanel = new JPanel(new GridLayout(2, 1));
|
||||||
settingMenuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
settingMenuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||||
JPopupMenu settingMenu = new JPopupMenu();
|
JPopupMenu settingMenu = new JPopupMenu();
|
||||||
settingMenuPanel.add(searchMode);
|
settingMenuPanel.add(searchMode);
|
||||||
|
settingMenuPanel.add(regexMode);
|
||||||
|
regexMode.setSelected(true);
|
||||||
searchMode.addItemListener(e -> performSearch());
|
searchMode.addItemListener(e -> performSearch());
|
||||||
settingMenu.add(settingMenuPanel);
|
settingMenu.add(settingMenuPanel);
|
||||||
|
|
||||||
@@ -162,8 +166,8 @@ public class Datatable extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void performSearch() {
|
private void performSearch() {
|
||||||
RowFilter<Object, Object> firstRowFilter = applyFirstSearchFilter();
|
RowFilter<Object, Object> firstRowFilter = getObjectObjectRowFilter(searchField, true);
|
||||||
RowFilter<Object, Object> secondRowFilter = applySecondFilter();
|
RowFilter<Object, Object> secondRowFilter = getObjectObjectRowFilter(secondSearchField, false);
|
||||||
if (searchField.getForeground().equals(Color.BLACK)) {
|
if (searchField.getForeground().equals(Color.BLACK)) {
|
||||||
sorter.setRowFilter(firstRowFilter);
|
sorter.setRowFilter(firstRowFilter);
|
||||||
if (secondSearchField.getForeground().equals(Color.BLACK)) {
|
if (secondSearchField.getForeground().equals(Color.BLACK)) {
|
||||||
@@ -175,44 +179,29 @@ public class Datatable extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RowFilter<Object, Object> applyFirstSearchFilter() {
|
private RowFilter<Object, Object> getObjectObjectRowFilter(JTextField searchField, boolean firstFlag) {
|
||||||
return new RowFilter<Object, Object>() {
|
return new RowFilter<Object, Object>() {
|
||||||
public boolean include(Entry<?, ?> entry) {
|
public boolean include(Entry<?, ?> entry) {
|
||||||
String searchFieldTextText = searchField.getText();
|
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();
|
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();
|
String entryValue = ((String) entry.getValue(1)).toLowerCase();
|
||||||
searchFieldTextText = searchFieldTextText.toLowerCase();
|
boolean filterReturn = searchFieldTextText.isEmpty();
|
||||||
if (pattern != null) {
|
boolean firstFlagReturn = searchMode.isSelected() && firstFlag;
|
||||||
return searchFieldTextText.isEmpty() || pattern.matcher(entryValue).find();
|
if (regexMode.isSelected()) {
|
||||||
|
Pattern pattern = null;
|
||||||
|
try {
|
||||||
|
pattern = Pattern.compile(searchFieldTextText, Pattern.CASE_INSENSITIVE);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern != null) {
|
||||||
|
filterReturn = filterReturn || pattern.matcher(entryValue).find() != firstFlagReturn;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return searchFieldTextText.isEmpty() || entryValue.contains(searchFieldTextText);
|
filterReturn = filterReturn || entryValue.contains(searchFieldTextText) != firstFlagReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return filterReturn;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -248,22 +237,6 @@ public class Datatable extends JPanel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
public String getSelectedDataAtTable(JTable table) {
|
||||||
int[] selectRows = table.getSelectedRows();
|
int[] selectRows = table.getSelectedRows();
|
||||||
StringBuilder selectData = new StringBuilder();
|
StringBuilder selectData = new StringBuilder();
|
||||||
|
|||||||
@@ -11,12 +11,41 @@ import java.awt.event.*;
|
|||||||
|
|
||||||
public class Rules extends JTabbedPane {
|
public class Rules extends JTabbedPane {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private ConfigLoader configLoader;
|
|
||||||
private final RuleProcessor ruleProcessor;
|
private final RuleProcessor ruleProcessor;
|
||||||
private final JTextField ruleGroupNameTextField;
|
private final JTextField ruleGroupNameTextField;
|
||||||
|
private ConfigLoader configLoader;
|
||||||
private Component tabComponent;
|
private Component tabComponent;
|
||||||
private int selectedIndex;
|
private int selectedIndex;
|
||||||
|
private final Action cancelActionPerformed = new AbstractAction() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (selectedIndex >= 0) {
|
||||||
|
setTabComponentAt(selectedIndex, tabComponent);
|
||||||
|
|
||||||
|
ruleGroupNameTextField.setVisible(false);
|
||||||
|
ruleGroupNameTextField.setPreferredSize(null);
|
||||||
|
selectedIndex = -1;
|
||||||
|
tabComponent = null;
|
||||||
|
|
||||||
|
requestFocusInWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final Action renameTitleActionPerformed = new AbstractAction() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
String title = ruleGroupNameTextField.getText();
|
||||||
|
if (!title.isEmpty() && selectedIndex >= 0) {
|
||||||
|
String oldName = getTitleAt(selectedIndex);
|
||||||
|
setTitleAt(selectedIndex, title);
|
||||||
|
|
||||||
|
if (!oldName.equals(title)) {
|
||||||
|
ruleProcessor.renameRuleGroup(oldName, title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancelActionPerformed.actionPerformed(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public Rules(MontoyaApi api, ConfigLoader configLoader) {
|
public Rules(MontoyaApi api, ConfigLoader configLoader) {
|
||||||
this.api = api;
|
this.api = api;
|
||||||
@@ -49,43 +78,48 @@ public class Rules extends JTabbedPane {
|
|||||||
addMouseListener(new MouseAdapter() {
|
addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
int index = getSelectedIndex();
|
int index = indexAtLocation(e.getX(), e.getY());
|
||||||
Rectangle r = getBoundsAt(index);
|
if (index < 0) {
|
||||||
if (r.contains(e.getPoint()) && index >= 0) {
|
return;
|
||||||
switch (e.getButton()) {
|
}
|
||||||
case MouseEvent.BUTTON1:
|
|
||||||
if (e.getClickCount() == 2) {
|
|
||||||
selectedIndex = index;
|
|
||||||
tabComponent = getTabComponentAt(selectedIndex);
|
|
||||||
String ruleGroupName = getTitleAt(selectedIndex);
|
|
||||||
|
|
||||||
if (!"...".equals(ruleGroupName)) {
|
switch (e.getButton()) {
|
||||||
setTabComponentAt(selectedIndex, ruleGroupNameTextField);
|
case MouseEvent.BUTTON1:
|
||||||
ruleGroupNameTextField.setVisible(true);
|
if (e.getClickCount() == 2) {
|
||||||
ruleGroupNameTextField.setText(ruleGroupName);
|
selectedIndex = index;
|
||||||
ruleGroupNameTextField.selectAll();
|
tabComponent = getTabComponentAt(selectedIndex);
|
||||||
ruleGroupNameTextField.requestFocusInWindow();
|
String ruleGroupName = getTitleAt(selectedIndex);
|
||||||
ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize());
|
|
||||||
}
|
if (!"...".equals(ruleGroupName)) {
|
||||||
} else if (e.getClickCount() == 1) {
|
setTabComponentAt(selectedIndex, ruleGroupNameTextField);
|
||||||
if ("...".equals(getTitleAt(getSelectedIndex()))) {
|
ruleGroupNameTextField.setVisible(true);
|
||||||
String title = ruleProcessor.newRule();
|
ruleGroupNameTextField.setText(ruleGroupName);
|
||||||
Rule newRule = new Rule(api, configLoader, Config.ruleTemplate, tabbedPane);
|
ruleGroupNameTextField.selectAll();
|
||||||
insertTab(title, null, newRule, null, getTabCount() - 1);
|
ruleGroupNameTextField.requestFocusInWindow();
|
||||||
setSelectedIndex(getTabCount() - 2);
|
ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize());
|
||||||
} else {
|
|
||||||
renameTitleActionPerformed.actionPerformed(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
} else if (e.getClickCount() == 1) {
|
||||||
case MouseEvent.BUTTON3:
|
String title = getTitleAt(index);
|
||||||
if (!"...".equals(getTitleAt(getSelectedIndex()))) {
|
if ("...".equals(title)) {
|
||||||
popupMenu.show(e.getComponent(), e.getX(), e.getY());
|
// 阻止默认的选中行为
|
||||||
|
e.consume();
|
||||||
|
// 直接创建新标签
|
||||||
|
String newTitle = ruleProcessor.newRule();
|
||||||
|
Rule newRule = new Rule(api, configLoader, Config.ruleTemplate, Rules.this);
|
||||||
|
insertTab(newTitle, null, newRule, null, getTabCount() - 1);
|
||||||
|
setSelectedIndex(getTabCount() - 2);
|
||||||
|
} else {
|
||||||
|
renameTitleActionPerformed.actionPerformed(null);
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
default:
|
break;
|
||||||
break;
|
case MouseEvent.BUTTON3:
|
||||||
}
|
if (!"...".equals(getTitleAt(index))) {
|
||||||
|
popupMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -119,38 +153,6 @@ public class Rules extends JTabbedPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Action renameTitleActionPerformed = new AbstractAction() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
String title = ruleGroupNameTextField.getText();
|
|
||||||
if (!title.isEmpty() && selectedIndex >= 0) {
|
|
||||||
String oldName = getTitleAt(selectedIndex);
|
|
||||||
setTitleAt(selectedIndex, title);
|
|
||||||
|
|
||||||
if (!oldName.equals(title)) {
|
|
||||||
ruleProcessor.renameRuleGroup(oldName, title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cancelActionPerformed.actionPerformed(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action cancelActionPerformed = new AbstractAction() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
if (selectedIndex >= 0) {
|
|
||||||
setTabComponentAt(selectedIndex, tabComponent);
|
|
||||||
|
|
||||||
ruleGroupNameTextField.setVisible(false);
|
|
||||||
ruleGroupNameTextField.setPreferredSize(null);
|
|
||||||
selectedIndex = -1;
|
|
||||||
tabComponent = null;
|
|
||||||
|
|
||||||
requestFocusInWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,28 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
this.configLoader = configLoader;
|
this.configLoader = configLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isListHasData(List<Map<String, String>> dataList) {
|
||||||
|
if (dataList != null && !dataList.isEmpty()) {
|
||||||
|
Map<String, String> dataMap = dataList.get(0);
|
||||||
|
return dataMap != null && !dataMap.isEmpty();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void generateTabbedPaneFromResultMap(MontoyaApi api, ConfigLoader configLoader, JTabbedPane tabbedPane, List<Map<String, String>> result) {
|
||||||
|
tabbedPane.removeAll();
|
||||||
|
if (result != null && !result.isEmpty()) {
|
||||||
|
Map<String, String> dataMap = result.get(0);
|
||||||
|
if (dataMap != null && !dataMap.isEmpty()) {
|
||||||
|
dataMap.keySet().forEach(i -> {
|
||||||
|
String[] extractData = dataMap.get(i).split(Config.boundary);
|
||||||
|
Datatable dataPanel = new Datatable(api, configLoader, i, Arrays.asList(extractData));
|
||||||
|
tabbedPane.addTab(i, dataPanel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) {
|
public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) {
|
||||||
return new Editor(api, configLoader, editorCreationContext);
|
return new Editor(api, configLoader, editorCreationContext);
|
||||||
@@ -42,11 +64,10 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
private final HttpUtils httpUtils;
|
private final HttpUtils httpUtils;
|
||||||
private final EditorCreationContext creationContext;
|
private final EditorCreationContext creationContext;
|
||||||
private final MessageProcessor messageProcessor;
|
private final MessageProcessor messageProcessor;
|
||||||
|
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
||||||
private HttpRequestResponse requestResponse;
|
private HttpRequestResponse requestResponse;
|
||||||
private List<Map<String, String>> dataList;
|
private List<Map<String, String>> dataList;
|
||||||
|
|
||||||
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -118,26 +139,4 @@ public class RequestEditor implements HttpRequestEditorProvider {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isListHasData(List<Map<String, String>> dataList) {
|
|
||||||
if (dataList != null && !dataList.isEmpty()) {
|
|
||||||
Map<String, String> dataMap = dataList.get(0);
|
|
||||||
return dataMap != null && !dataMap.isEmpty();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void generateTabbedPaneFromResultMap(MontoyaApi api, ConfigLoader configLoader, JTabbedPane tabbedPane, List<Map<String, String>> result) {
|
|
||||||
tabbedPane.removeAll();
|
|
||||||
if (result != null && !result.isEmpty()) {
|
|
||||||
Map<String, String> dataMap = result.get(0);
|
|
||||||
if (dataMap != null && !dataMap.isEmpty()) {
|
|
||||||
dataMap.keySet().forEach(i -> {
|
|
||||||
String[] extractData = dataMap.get(i).split(Config.boundary);
|
|
||||||
Datatable dataPanel = new Datatable(api, configLoader, i, Arrays.asList(extractData));
|
|
||||||
tabbedPane.addTab(i, dataPanel);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,11 +41,10 @@ public class ResponseEditor implements HttpResponseEditorProvider {
|
|||||||
private final HttpUtils httpUtils;
|
private final HttpUtils httpUtils;
|
||||||
private final EditorCreationContext creationContext;
|
private final EditorCreationContext creationContext;
|
||||||
private final MessageProcessor messageProcessor;
|
private final MessageProcessor messageProcessor;
|
||||||
|
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
||||||
private HttpRequestResponse requestResponse;
|
private HttpRequestResponse requestResponse;
|
||||||
private List<Map<String, String>> dataList;
|
private List<Map<String, String>> dataList;
|
||||||
|
|
||||||
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -36,11 +36,10 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
|
|||||||
private final ConfigLoader configLoader;
|
private final ConfigLoader configLoader;
|
||||||
private final EditorCreationContext creationContext;
|
private final EditorCreationContext creationContext;
|
||||||
private final MessageProcessor messageProcessor;
|
private final MessageProcessor messageProcessor;
|
||||||
|
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
||||||
private ByteArray message;
|
private ByteArray message;
|
||||||
private List<Map<String, String>> dataList;
|
private List<Map<String, String>> dataList;
|
||||||
|
|
||||||
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
public class MessageProcessor {
|
public class MessageProcessor {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final RegularMatcher regularMatcher;
|
private final RegularMatcher regularMatcher;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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.MessageCache;
|
||||||
import hae.utils.DataManager;
|
import hae.utils.DataManager;
|
||||||
import hae.utils.string.HashCalculator;
|
import hae.utils.string.HashCalculator;
|
||||||
import hae.utils.string.StringProcessor;
|
import hae.utils.string.StringProcessor;
|
||||||
@@ -27,10 +27,57 @@ public class RegularMatcher {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
|
||||||
|
// 添加到全局变量中,便于Databoard检索
|
||||||
|
if (!Objects.equals(host, "") && host != null) {
|
||||||
|
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
||||||
|
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
|
||||||
|
|
||||||
|
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
|
||||||
|
Set<String> combinedSet = new LinkedHashSet<>(existingList);
|
||||||
|
combinedSet.addAll(newList);
|
||||||
|
return new ArrayList<>(combinedSet);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (flag) {
|
||||||
|
// 数据存储在BurpSuite空间内
|
||||||
|
try {
|
||||||
|
DataManager dataManager = new DataManager(api);
|
||||||
|
PersistedObject persistedObject = PersistedObject.persistedObject();
|
||||||
|
gRuleMap.forEach((kName, vList) -> {
|
||||||
|
PersistedList<String> persistedList = PersistedList.persistedStringList();
|
||||||
|
persistedList.addAll(vList);
|
||||||
|
persistedObject.setStringList(kName, persistedList);
|
||||||
|
});
|
||||||
|
dataManager.putData("data", host, persistedObject);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gRuleMap;
|
||||||
|
});
|
||||||
|
|
||||||
|
String[] splitHost = host.split("\\.");
|
||||||
|
String onlyHost = host.split(":")[0];
|
||||||
|
|
||||||
|
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
|
||||||
|
|
||||||
|
if (!Config.globalDataMap.containsKey(anyHost) && !anyHost.isEmpty()) {
|
||||||
|
// 添加通配符Host,实际数据从查询哪里将所有数据提取
|
||||||
|
Config.globalDataMap.put(anyHost, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.globalDataMap.containsKey("*")) {
|
||||||
|
// 添加通配符全匹配,同上
|
||||||
|
Config.globalDataMap.put("*", new HashMap<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
|
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
|
||||||
// 先从缓存池里判断是否有已经匹配好的结果
|
// 先从缓存池里判断是否有已经匹配好的结果
|
||||||
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
||||||
Map<String, Map<String, Object>> map = CachePool.get(messageIndex);
|
Map<String, Map<String, Object>> map = MessageCache.get(messageIndex);
|
||||||
if (map != null) {
|
if (map != null) {
|
||||||
return map;
|
return map;
|
||||||
} else {
|
} else {
|
||||||
@@ -106,55 +153,11 @@ public class RegularMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
CachePool.put(messageIndex, finalMap);
|
MessageCache.put(messageIndex, finalMap);
|
||||||
return finalMap;
|
return finalMap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
|
|
||||||
// 添加到全局变量中,便于Databoard检索
|
|
||||||
if (!Objects.equals(host, "") && host != null) {
|
|
||||||
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
|
||||||
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
|
|
||||||
|
|
||||||
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
|
|
||||||
Set<String> combinedSet = new LinkedHashSet<>(existingList);
|
|
||||||
combinedSet.addAll(newList);
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
String[] splitHost = host.split("\\.");
|
|
||||||
String onlyHost = host.split(":")[0];
|
|
||||||
|
|
||||||
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
|
|
||||||
|
|
||||||
if (!Config.globalDataMap.containsKey(anyHost) && !anyHost.isEmpty()) {
|
|
||||||
// 添加通配符Host,实际数据从查询哪里将所有数据提取
|
|
||||||
Config.globalDataMap.put(anyHost, new HashMap<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Config.globalDataMap.containsKey("*")) {
|
|
||||||
// 添加通配符全匹配,同上
|
|
||||||
Config.globalDataMap.put("*", new HashMap<>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
||||||
List<String> retList = new ArrayList<>();
|
List<String> retList = new ArrayList<>();
|
||||||
if ("nfa".equals(engine)) {
|
if ("nfa".equals(engine)) {
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ public class ConfigLoader {
|
|||||||
Config.globalRules = getRules();
|
Config.globalRules = getRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isValidConfigPath(String configPath) {
|
||||||
|
File configPathFile = new File(configPath);
|
||||||
|
return configPathFile.exists() && configPathFile.isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
private String determineConfigPath() {
|
private String determineConfigPath() {
|
||||||
// 优先级1:用户根目录
|
// 优先级1:用户根目录
|
||||||
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
|
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
|
||||||
@@ -67,11 +72,6 @@ public class ConfigLoader {
|
|||||||
return userConfigPath;
|
return userConfigPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isValidConfigPath(String configPath) {
|
|
||||||
File configPathFile = new File(configPath);
|
|
||||||
return configPathFile.exists() && configPathFile.isDirectory();
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
||||||
@@ -102,8 +102,6 @@ public class ConfigLoader {
|
|||||||
Representer representer = new Representer(dop);
|
Representer representer = new Representer(dop);
|
||||||
Map<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream);
|
Map<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream);
|
||||||
|
|
||||||
String[] fieldKeys = {"loaded", "name", "f_regex", "s_regex", "format", "color", "scope", "engine", "sensitive"};
|
|
||||||
|
|
||||||
Object rulesObj = rulesMap.get("rules");
|
Object rulesObj = rulesMap.get("rules");
|
||||||
if (rulesObj instanceof List) {
|
if (rulesObj instanceof List) {
|
||||||
List<Map<String, Object>> groupData = (List<Map<String, Object>>) rulesObj;
|
List<Map<String, Object>> groupData = (List<Map<String, Object>>) rulesObj;
|
||||||
@@ -114,9 +112,9 @@ public class ConfigLoader {
|
|||||||
if (ruleObj instanceof List) {
|
if (ruleObj instanceof List) {
|
||||||
List<Map<String, Object>> ruleData = (List<Map<String, Object>>) ruleObj;
|
List<Map<String, Object>> ruleData = (List<Map<String, Object>>) ruleObj;
|
||||||
for (Map<String, Object> ruleFields : ruleData) {
|
for (Map<String, Object> ruleFields : ruleData) {
|
||||||
Object[] valuesArray = new Object[fieldKeys.length];
|
Object[] valuesArray = new Object[Config.ruleFields.length];
|
||||||
for (int i = 0; i < fieldKeys.length; i++) {
|
for (int i = 0; i < Config.ruleFields.length; i++) {
|
||||||
valuesArray[i] = ruleFields.get(fieldKeys[i]);
|
valuesArray[i] = ruleFields.get(Config.ruleFields[i].toLowerCase().replace("-", "_"));
|
||||||
}
|
}
|
||||||
data.add(valuesArray);
|
data.add(valuesArray);
|
||||||
}
|
}
|
||||||
@@ -138,26 +136,50 @@ public class ConfigLoader {
|
|||||||
return getValueFromConfig("BlockHost", Config.host);
|
return getValueFromConfig("BlockHost", Config.host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBlockHost(String blockHost) {
|
||||||
|
setValueToConfig("BlockHost", blockHost);
|
||||||
|
}
|
||||||
|
|
||||||
public String getExcludeSuffix() {
|
public String getExcludeSuffix() {
|
||||||
return getValueFromConfig("ExcludeSuffix", Config.suffix);
|
return getValueFromConfig("ExcludeSuffix", Config.suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExcludeSuffix(String excludeSuffix) {
|
||||||
|
setValueToConfig("ExcludeSuffix", excludeSuffix);
|
||||||
|
}
|
||||||
|
|
||||||
public String getExcludeStatus() {
|
public String getExcludeStatus() {
|
||||||
return getValueFromConfig("ExcludeStatus", Config.status);
|
return getValueFromConfig("ExcludeStatus", Config.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExcludeStatus(String status) {
|
||||||
|
setValueToConfig("ExcludeStatus", status);
|
||||||
|
}
|
||||||
|
|
||||||
public String getLimitSize() {
|
public String getLimitSize() {
|
||||||
return getValueFromConfig("LimitSize", Config.size);
|
return getValueFromConfig("LimitSize", Config.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLimitSize(String size) {
|
||||||
|
setValueToConfig("LimitSize", size);
|
||||||
|
}
|
||||||
|
|
||||||
public String getScope() {
|
public String getScope() {
|
||||||
return getValueFromConfig("HaEScope", Config.scopeOptions);
|
return getValueFromConfig("HaEScope", Config.scopeOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setScope(String scope) {
|
||||||
|
setValueToConfig("HaEScope", scope);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getMode() {
|
public boolean getMode() {
|
||||||
return getValueFromConfig("HaEModeStatus", Config.modeStatus).equals("true");
|
return getValueFromConfig("HaEModeStatus", Config.modeStatus).equals("true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMode(String mode) {
|
||||||
|
setValueToConfig("HaEModeStatus", mode);
|
||||||
|
}
|
||||||
|
|
||||||
private String getValueFromConfig(String name, String defaultValue) {
|
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()) {
|
||||||
@@ -176,30 +198,6 @@ public class ConfigLoader {
|
|||||||
return defaultValue;
|
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) {
|
private void setValueToConfig(String name, String value) {
|
||||||
Map<String, Object> currentConfig = loadCurrentConfig();
|
Map<String, Object> currentConfig = loadCurrentConfig();
|
||||||
currentConfig.put(name, value);
|
currentConfig.put(name, value);
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ import burp.api.montoya.persistence.Persistence;
|
|||||||
import hae.component.board.message.MessageTableModel;
|
import hae.component.board.message.MessageTableModel;
|
||||||
import hae.instances.http.utils.RegularMatcher;
|
import hae.instances.http.utils.RegularMatcher;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
public class DataManager {
|
public class DataManager {
|
||||||
private final MontoyaApi api;
|
private final MontoyaApi api;
|
||||||
private final Persistence persistence;
|
private final Persistence persistence;
|
||||||
@@ -24,10 +30,11 @@ public class DataManager {
|
|||||||
persistence.extensionData().deleteChildObject(dataName);
|
persistence.extensionData().deleteChildObject(dataName);
|
||||||
}
|
}
|
||||||
persistence.extensionData().setChildObject(dataName, persistedObject);
|
persistence.extensionData().setChildObject(dataName, persistedObject);
|
||||||
|
|
||||||
saveIndex(dataType, dataName);
|
saveIndex(dataType, dataName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadData(MessageTableModel messageTableModel) {
|
public synchronized void loadData(MessageTableModel messageTableModel) {
|
||||||
// 1. 获取索引
|
// 1. 获取索引
|
||||||
PersistedList<String> dataIndex = persistence.extensionData().getStringList("data"); // 数据索引
|
PersistedList<String> dataIndex = persistence.extensionData().getStringList("data"); // 数据索引
|
||||||
PersistedList<String> messageIndex = persistence.extensionData().getStringList("message"); // 消息索引
|
PersistedList<String> messageIndex = persistence.extensionData().getStringList("message"); // 消息索引
|
||||||
@@ -42,7 +49,7 @@ public class DataManager {
|
|||||||
|
|
||||||
if (indexList != null && !indexList.isEmpty()) {
|
if (indexList != null && !indexList.isEmpty()) {
|
||||||
persistence.extensionData().deleteStringList(indexName);
|
persistence.extensionData().deleteStringList(indexName);
|
||||||
} else {
|
} else if (indexList == null) {
|
||||||
indexList = PersistedList.persistedStringList();
|
indexList = PersistedList.persistedStringList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,32 +62,94 @@ public class DataManager {
|
|||||||
|
|
||||||
private void loadHaEData(PersistedList<String> dataIndex) {
|
private void loadHaEData(PersistedList<String> dataIndex) {
|
||||||
if (dataIndex != null && !dataIndex.isEmpty()) {
|
if (dataIndex != null && !dataIndex.isEmpty()) {
|
||||||
dataIndex.parallelStream().forEach(index -> {
|
dataIndex.forEach(index -> {
|
||||||
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||||
dataObj.stringListKeys().forEach(dataKey -> {
|
try {
|
||||||
RegularMatcher.putDataToGlobalMap(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false);
|
dataObj.stringListKeys().forEach(dataKey -> {
|
||||||
});
|
RegularMatcher.putDataToGlobalMap(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false);
|
||||||
|
});
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMessageData(PersistedList<String> messageIndex, MessageTableModel messageTableModel) {
|
private void loadMessageData(PersistedList<String> messageIndex, MessageTableModel messageTableModel) {
|
||||||
if (messageIndex != null && !messageIndex.isEmpty()) {
|
if (messageIndex == null || messageIndex.isEmpty()) {
|
||||||
messageIndex.parallelStream().forEach(index -> {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> indexList = new ArrayList<>();
|
||||||
|
for (Object item : messageIndex) {
|
||||||
|
try {
|
||||||
|
if (item != null) {
|
||||||
|
indexList.add(item.toString());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("转换索引时出错: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final int batchSize = 2000; // 增加批处理大小
|
||||||
|
final int threadCount = Math.max(8, Runtime.getRuntime().availableProcessors() * 2); // 增加线程数
|
||||||
|
int totalSize = indexList.size();
|
||||||
|
|
||||||
|
// 使用更高效的线程池
|
||||||
|
ExecutorService executorService = Executors.newWorkStealingPool(threadCount);
|
||||||
|
List<Future<List<Object[]>>> futures = new ArrayList<>();
|
||||||
|
|
||||||
|
// 分批并行处理数据
|
||||||
|
for (int i = 0; i < totalSize; i += batchSize) {
|
||||||
|
int endIndex = Math.min(i + batchSize, totalSize);
|
||||||
|
List<String> batch = indexList.subList(i, endIndex);
|
||||||
|
|
||||||
|
Future<List<Object[]>> future = executorService.submit(() -> processBatchParallel(batch));
|
||||||
|
futures.add(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量添加数据到模型
|
||||||
|
try {
|
||||||
|
for (Future<List<Object[]>> future : futures) {
|
||||||
|
List<Object[]> batchData = future.get();
|
||||||
|
messageTableModel.addBatch(batchData);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("批量添加数据时出错: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Object[]> processBatchParallel(List<String> batch) {
|
||||||
|
List<Object[]> batchData = new ArrayList<>();
|
||||||
|
for (String index : batch) {
|
||||||
|
try {
|
||||||
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||||
if (dataObj != null) {
|
if (dataObj != null) {
|
||||||
HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo");
|
HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo");
|
||||||
String comment = dataObj.getString("comment");
|
if (messageInfo != null) {
|
||||||
String color = dataObj.getString("color");
|
batchData.add(prepareMessageData(messageInfo, dataObj));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
});
|
} catch (Exception e) {
|
||||||
|
api.logging().logToError("处理消息数据时出错: " + e.getMessage() + ", index: " + index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return batchData;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private Object[] prepareMessageData(HttpRequestResponse messageInfo, PersistedObject dataObj) {
|
||||||
|
HttpRequest request = messageInfo.request();
|
||||||
|
HttpResponse response = messageInfo.response();
|
||||||
|
return new Object[]{
|
||||||
|
messageInfo,
|
||||||
|
request.url(),
|
||||||
|
request.method(),
|
||||||
|
String.valueOf(response.statusCode()),
|
||||||
|
String.valueOf(response.toByteArray().length()),
|
||||||
|
dataObj.getString("comment"),
|
||||||
|
dataObj.getString("color"),
|
||||||
|
false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,15 @@ rules:
|
|||||||
scope: response body
|
scope: response body
|
||||||
engine: dfa
|
engine: dfa
|
||||||
sensitive: false
|
sensitive: false
|
||||||
|
- name: PDF.js Viewer
|
||||||
|
loaded: true
|
||||||
|
f_regex: (pdf.worker)
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: green
|
||||||
|
scope: response body
|
||||||
|
engine: dfa
|
||||||
|
sensitive: false
|
||||||
- group: Maybe Vulnerability
|
- group: Maybe Vulnerability
|
||||||
rule:
|
rule:
|
||||||
- name: Java Deserialization
|
- name: Java Deserialization
|
||||||
@@ -162,8 +171,9 @@ 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})(|\\)(|'|")(:|=|\)\.val\()(
|
f_regex: (((|\\)(|'|")(|[\.\w]{1,10})([p](ass|wd|asswd|assword))(|[\.\w]{1,10})(|\\)(|'|")(
|
||||||
|)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))
|
|)(:|[=]{1,3}|![=]{1,2}|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|
||||||
|
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,10})([p](ass|wd|asswd|assword))(|[\.\w]{1,10})(|\\)(|'|")))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
@@ -172,8 +182,9 @@ 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})(|\\)(|'|")(:|=|\)\.val\()(
|
f_regex: (((|\\)(|'|")(|[\.\w]{1,10})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\.\w]{1,10})(|\\)(|'|")(
|
||||||
|)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))
|
|)(:|=|!=|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|
||||||
|
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,10})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\.\w]{1,10})(|\\)(|'|")))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: green
|
color: green
|
||||||
@@ -209,8 +220,9 @@ 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)|(ticket))([\w]{0,10})('|")?(\])?(
|
f_regex: (((\[)?('|")?([\.\w]{0,10})(key|secret|token|config|auth|access|admin|ticket)([\.\w]{0,10})('|")?(\])?(
|
||||||
|)(:|=|\)\.val\()( |)('|")([^'"]+?)('|")(|,|\)))
|
|)(:|=|!=|[\)]{0,1}\.val\()( |)('|")([^'"]+?)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|
||||||
|
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,10})(key|secret|token|config|auth|access|admin|ticket)(|[\.\w]{1,10})(|\\)(|'|")))
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: yellow
|
color: yellow
|
||||||
@@ -219,8 +231,9 @@ rules:
|
|||||||
sensitive: false
|
sensitive: false
|
||||||
- name: Mobile Number Field
|
- name: Mobile Number Field
|
||||||
loaded: true
|
loaded: true
|
||||||
f_regex: ((|\\)(|'|")(|[\w]{1,10})(mobile|phone|sjh|shoujihao|concat)(|[\w]{1,10})(|\\)(|'|")(:|=|\)\.val\()(
|
f_regex: '(((|\\)(|''|")(|[\w]{1,10})(mobile|phone|sjh|shoujihao|concat)(|[\.\w]{1,10})(|\\)(|''|")(
|
||||||
|)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))
|
|)(:|=|!=|[\)]{0,1}\.val\()( |)(|\\)(''|")([^''"]+?)(|\\)(''|")(|,|\)))|((|\\)(''|")([^''"]+?)(|\\)(''|")(|\\)(|''|")(
|
||||||
|
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,10})(mobile|phone|sjh|shoujihao|concat)(|[\.\w]{1,10})(|\\)(|''|"))) '
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
color: green
|
color: green
|
||||||
@@ -284,7 +297,7 @@ rules:
|
|||||||
engine: nfa
|
engine: nfa
|
||||||
sensitive: true
|
sensitive: true
|
||||||
- name: Request URI
|
- name: Request URI
|
||||||
loaded: true
|
loaded: false
|
||||||
f_regex: ' ((?!.*\.js(\?.*)?$)(.*?[^.js$])) '
|
f_regex: ' ((?!.*\.js(\?.*)?$)(.*?[^.js$])) '
|
||||||
s_regex: ''
|
s_regex: ''
|
||||||
format: '{0}'
|
format: '{0}'
|
||||||
|
|||||||
Reference in New Issue
Block a user