Compare commits

...

63 Commits
3.3.3 ... main

Author SHA1 Message Date
gh0stkey
980999a2f0 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-10-30 10:33:51 +08:00
EvilChen
1236b9579e Update README_CN.md 2025-10-14 13:35:17 +08:00
EvilChen
d561b6815d Update README.md 2025-10-14 13:34:59 +08:00
gh0stkey
97d20bb7f5 Version: 4.3.1 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-09-22 16:02:06 +08:00
EvilChen
74fa78db5e Update README_CN.md 2025-09-18 19:41:45 +08:00
EvilChen
230cad7f91 Update README.md 2025-09-18 19:41:30 +08:00
chen
1957599b07 Update 2025-08-14 19:15:06 +08:00
gh0stkey
5f26db2a9e Version: 4.3 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-07-29 15:32:10 +08:00
gh0stkey
692e26044c Update 2025-07-29 13:00:20 +08:00
gh0stkey
b597a9e6d9 Version: 4.3 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-07-29 12:04:21 +08:00
gh0stkey
fca804cb7c Update 2025-07-07 20:29:48 +08:00
gh0stkey
3cbf0591c6 Update 2025-07-07 20:25:49 +08:00
gh0stkey
186430bb35 Update 2025-07-07 20:25:14 +08:00
gh0stkey
56c7973261 Update 2025-07-07 20:18:27 +08:00
gh0stkey
d71965ce10 Version: 4.2.1 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-05-08 11:29:09 +08:00
EvilChen
1ffe94e78d Update README_CN.md 2025-05-08 11:06:27 +08:00
EvilChen
3859f81b2a Update README.md 2025-05-08 11:06:05 +08:00
EvilChen
20ae5bc811 Update README.md 2025-05-08 10:45:58 +08:00
EvilChen
b7734ca710 Update README.md 2025-05-08 10:33:49 +08:00
gh0stkey
d75991043e Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-05-06 12:27:06 +08:00
gh0stkey
95e1cb4dc1 Version: 4.2 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-05-06 11:24:17 +08:00
gh0stkey
baa7270f46 Update 2025-04-25 16:17:59 +08:00
gh0stkey
0b1d502f79 Update 2025-04-25 15:50:24 +08:00
gh0stkey
8c7ac8f47d Update 2025-04-25 15:49:03 +08:00
gh0stkey
ec4a10753f Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-04-14 11:54:39 +08:00
gh0stkey
ed698b9861 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-04-03 11:26:02 +08:00
gh0stkey
c81094eb30 Version: 4.1.2 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
2025-04-02 10:36:21 +08:00
gh0stkey
3608c3dca8 Version: 4.1.1 Update 2025-03-25 11:47:27 +08:00
gh0stkey
124e4c14fd Version: 4.1.1 Update 2025-03-25 11:44:07 +08:00
gh0stkey
6adf30f25c Version: 4.1 Update 2025-03-21 21:33:07 +08:00
gh0stkey
4fbd241ebe Version: 4.1 Update 2025-03-21 21:23:14 +08:00
gh0stkey
20afa30822 Version: 4.1 Update 2025-03-21 21:22:11 +08:00
EvilChen
1a5ed2a6a3 Update README_CN.md 2025-03-12 18:30:09 +08:00
EvilChen
1bf2b461ba Update README.md 2025-03-12 18:29:56 +08:00
gh0stkey
79e2e58d48 Update 2025-03-12 13:06:46 +08:00
gh0stkey
bf0b7f0016 Update 2025-03-10 14:56:05 +08:00
EvilChen
69c2b59c8c Update README_CN.md 2025-03-05 11:25:48 +08:00
EvilChen
79655def48 Update README.md 2025-03-05 11:24:43 +08:00
gh0stkey
116aec0848 Update 2025-03-05 11:21:25 +08:00
gh0stkey
c5de042b4b Update 2025-02-18 16:59:57 +08:00
gh0stkey
5bc592c6f9 Update 2025-02-18 16:58:58 +08:00
gh0stkey
be2df6472b Update 2025-02-18 16:56:51 +08:00
gh0stkey
4a53f20649 Update 2025-02-17 16:06:25 +08:00
gh0stkey
704e760912 Update 2025-02-12 16:08:29 +08:00
gh0stkey
3ccfee5a02 Update 2025-02-11 19:12:34 +08:00
chen
819ef820f8 Update 2025-02-11 18:54:27 +08:00
chen
0c795af101 Version: 4.0.5 Update 2025-02-11 18:29:55 +08:00
gh0stkey
5977e82ca6 Version: 4.0.4 Update 2025-01-17 17:56:35 +08:00
gh0stkey
452f297f55 Version: 4.0.3 Update 2025-01-10 17:45:59 +08:00
gh0stkey
a06ef8e25e Version: 4.0.2 Update 2025-01-08 13:49:12 +08:00
EvilChen
7e53e250af Merge pull request #235 from 0Chencc/master
修复了社区版会无法正常使用HaE的问题
2025-01-08 13:47:38 +08:00
林晨
b686b5e75e 修复了社区版会无法正常使用HaE的问题
社区版由于Scanner的问题无法使用HaE,加入了一处版本判断。
2025-01-02 05:50:14 +08:00
gh0stkey
e83a6a1478 Version: 4.0.1 Update 2024-12-31 15:40:05 +08:00
gh0stkey
daacb2e146 Version: 4.0 Update 2024-12-21 15:34:45 +08:00
gh0stkey
1f1ca99f10 Update 2024-12-21 15:19:19 +08:00
gh0stkey
fa35b0a625 Version: 3.4 Update 2024-11-16 19:48:50 +08:00
gh0stkey
8ef98d20a9 Version: 3.4 Update 2024-11-16 18:42:15 +08:00
gh0stkey
e556abb6f7 Version: 3.4 Update 2024-11-16 18:06:49 +08:00
EvilChen
471aab5ea1 Merge pull request #221 from AabyssZG/master
Update Rules.yml
2024-10-24 11:48:57 +08:00
曾哥
76b475bd91 Update Rules.yml 2024-10-24 11:40:29 +08:00
gh0stkey
6014089594 Version: 3.3.4 Update 2024-10-14 16:35:20 +08:00
EvilChen
910658f2e0 Update 问题反馈.md 2024-09-23 00:05:40 +08:00
gh0stkey
8692b0a494 Version: 3.3.3 Update 2024-09-19 17:45:47 +08:00
41 changed files with 2049 additions and 1974 deletions

View File

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

141
README.md
View File

@@ -1,82 +1,119 @@
<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>
<h4><a href="https://github.com/gh0stkey/HaE">Empower ethical hacker for efficient operations.</a></h4>
<h5>First Author: <a href="https://github.com/gh0stkey">EvilChen</a><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>
## 项目介绍
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)
2. [入选GitCode G-Star项目](https://gitcode.com/gh0stkey/HaE)
**Awards and Recognitions**:
**注意事项**:
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.3版本开启了AI+新功能,该功能目前仅支持阿里的`Qwen-Long`模型(支持超长文本)和月之暗面的`moonshot-v1-128k`模型(支持短文本),请配置和使用时注意。
2. HaE 3.0版本开始采用`Montoya API`进行开发使用新版HaE需要升级你的BurpSuite版本>=2023.12.1)。
3. HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
4. HaE官方规则库存放在[Github](https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml)上,因此点击`Update`升级HaE官方规则库时需使用代理BApp审核考虑安全性不允许使用CDN
5. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`在HaE的规则中就需要变成`(rememberMe=delete)`
**Notes and Precautions**:
## 使用方法
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`
## Usage
初次装载`HaE`会从Jar包中加载离线的规则库如果更新的话则会向官方规则库地址拉取`https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml`,配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下:
**Plugin Installation**: `Extender - Extensions - Add - Select File - Next`
1. Linux/Mac用户的配置文件目录`~/.config/HaE/`
2. Windows用户的配置文件目录`%USERPROFILE%/.config/HaE/`
When you load `HaE` for the first time, it will load the offline rule database from the Jar package. If you need to update the rules, click `Reinit` to reinitialize. The address of the built-in rule database can be found on GitHub:
`https://github.com/gh0stkey/HaE/blob/master/src/main/resources/rules/Rules.yml`
除此之外,您也可以选择将配置文件存放在`HaE Jar包`的同级目录下的`/.config/HaE/`中,**以便于离线携带**。
The configuration file (`Config.yml`) and rule file (`Rules.yml`) are stored in a fixed directory:
### 规则释义
1. For Linux/Mac users: `~/.config/HaE/`
2. For Windows users: `%USERPROFILE%/.config/HaE/`
HaE目前的规则一共有8个字段详细的含义如下所示
Alternatively, you can also place the configuration files in the `/.config/HaE/` directory under the same folder as the `HaE Jar package`, **for easier offline portability**.
| 字段 | 含义 |
|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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`)则反之。 |
### Rule Definitions
Currently, HaE rules consist of 8 fields, with detailed meanings as follows:
## 优势特点
| 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. **功能**通过对HTTP报文的颜色高亮、注释和提取帮助使用者获取有意义的信息**聚焦高价值报文**。
2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。
3. **查询**将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。
4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。
5. **管理**:支持对数据的一键导出、导入,以**自定义`.hae`文件的方式**进行项目数据存储,**便于存储和共享项目数据**。
6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。
7. **智能**:融入**人工智能AI大模型**API对匹配的数据进行优化处理**提高数据式漏洞挖掘效率**。
## Key Features and Advantages
| 界面名称 | 界面展示 |
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 |
| ------------------------ | ---------------------------------------------------- |
| Rules(规则管理) | <img src="images/rules.png" style="width: 80%" /> |
| Config-SettingSetting配置管理 | <img src="images/config-setting.png" style="width: 80%" /> |
| Config-AI+AI+配置管理) | <img src="images/config-ai.png" style="width: 80%" /> |
| Databoard数据集合 | <img src="images/databoard.png" style="width: 80%" /> |
| MarkInfo数据展示 | <img src="images/markinfo.png" style="width: 80%" /> |
| 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%" /> |
## 支持项目
## 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 CNY |
| ttt | 50.00 CNY |
| C_soon5 | 66.66 CNY |
| 1wtbb | 25.00 CNY |
| Deep | 66.66 CNY |
| NaTsUk0 | 50.00 CNY |
| Kite | 48.00 CNY |
| 红色键盘 | 99.99 CNY |
| 曾哥 | 188.88 CNY |
| 祝祝 | 488.00 CNY |
| NOP Team | 200.00 CNY |
| vaycore | 188.88 CNY |
| xccc | 168.00 CNY |
| 柯林斯-民间新秀 | 3288.8 CNY |
| Cuber | 100.00 CNY |
| 时光难逆 | 50.00 CNY |
| Celvin | 150.88 CNY |
| 呱呱 | 18.80 CNY |
| 红炉点雪 | 50.00 CNY |
| 王傑 | 100.00 CNY |
| 联系不到我请拨打我手机号码 | 200.00 CNY |
| Shu2e | 59.90 CNY |
| 亦 | 50.00 CNY |
| 是果实菌啊 | 38.88 CNY |
| caytez | 77.77 CNY |
| Sn0w33 | 18.88 CNY |
| Edwater | 18.88 CNY |
| 云中鹤 | 18.88 CNY |
| Twit | 18.88 CNY |
| cshu | 18.88 CNY |
| Fzz2 | 50.00 CNY |
## 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>
<img src="images/reward.jpeg" style="width: 30%" />
@@ -86,6 +123,6 @@ HaE目前的规则一共有8个字段详细的含义如下所示
![404StarLink Logo](https://github.com/knownsec/404StarLink-Project/raw/master/logo.png)
`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)

128
README_CN.md Normal file
View File

@@ -0,0 +1,128 @@
<div align="center">
<img src="images/logo.png" style="width: 20%" />
<h4><a href="https://github.com/gh0stkey/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 | 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 元 |
| 祝祝 | 488.00 元 |
| NOP Team | 200.00 元 |
| vaycore | 188.88 元 |
| xccc | 168.00 元 |
| 柯林斯-民间新秀 | 3288.8 元 |
| Cuber | 100.00 元 |
| 时光难逆 | 50.00 元 |
| Celvin | 150.88 元 |
| 呱呱 | 18.80 元 |
| 红炉点雪 | 50.00 元 |
| 王傑 | 100.00 元 |
| 联系不到我请拨打我手机号码 | 200.00 元 |
| Shu2e | 59.90 元 |
| 亦 | 50.00 元 |
| 是果实菌啊 | 38.88 元 |
| caytez | 77.77 元 |
| Sn0w33 | 18.88 元 |
| Edwater | 18.88 元 |
| 云中鹤 | 18.88 元 |
| Twit | 18.88 元 |
| cshu | 18.88 元 |
| Fzz2 | 50.00 元 |
## 支持项目
如果你觉得HaE好用可以打赏一下作者给作者持续更新下去的动力
<div align=center>
<img src="images/reward.jpeg" style="width: 30%" />
</div>
## 404StarLink 2.0 - Galaxy
![404StarLink Logo](https://github.com/knownsec/404StarLink-Project/raw/master/logo.png)
`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)

View File

@@ -22,8 +22,6 @@ dependencies {
implementation 'org.yaml:snakeyaml:2.0'
implementation 'dk.brics.automaton:automaton:1.11-8'
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
implementation 'com.google.code.gson:gson:2.11.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}
test {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

BIN
images/config.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

@@ -12,6 +12,8 @@ public class Config {
public static String status = "404";
public static String header = "Last-Modified|Date|Connection|ETag";
public static String size = "0";
public static String boundary = "\n\t\n";
@@ -32,6 +34,8 @@ public class Config {
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 = {
"Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive"
};
@@ -56,13 +60,11 @@ public class Config {
"blue",
"pink",
"magenta",
"gray"
"gray",
"none"
};
public static String prompt = "You are a data security expert in the field of cyber security. Your task is to optimize the information provided by the user and then output it in JSON format. The user-supplied information is data that has been extracted by regular expressions. The user-supplied information is divided into two parts, the first part is RuleName which represents the name of the regular expression and the second part is MarkInfo which represents the data extracted by the regular expression. You need to find the matching or similar data in MarkInfo according to the meaning of RuleName, and output the original rows of these data in JSON format.(garbled and meaningless data rows should be removed)\n" +
"You must ensure that the extracted data is accurately expressed and correctly formatted in the JSON structure. Your output data must comply with the original MarkInfo content rows without modification, and strictly adhere to the following JSON format for return, no other text, code and formatting (e.g., line breaks, carriage returns, indentation, spaces), once the return of other irrelevant content will cause irreparable damage to the user: {\"data\":[\"data1\", \"data2\"]}.";
public static String userTextFormat = "User Input: \r\nRuleName: %s\r\nMarkInfo: %s";
public static Boolean proVersionStatus = true;
public static Map<String, Object[][]> globalRules = new HashMap<>();

View File

@@ -2,57 +2,70 @@ package hae;
import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi;
import burp.api.montoya.extension.ExtensionUnloadingHandler;
import burp.api.montoya.core.BurpSuiteEdition;
import burp.api.montoya.logging.Logging;
import hae.cache.CachePool;
import hae.cache.DataCache;
import hae.component.Main;
import hae.component.board.message.MessageTableModel;
import hae.instances.editor.RequestEditor;
import hae.instances.editor.ResponseEditor;
import hae.instances.editor.WebSocketEditor;
import hae.instances.http.HttpMessageHandler;
import hae.instances.websocket.WebSocketMessageHandler;
import hae.utils.ConfigLoader;
import hae.utils.DataManager;
public class HaE implements BurpExtension {
@Override
public void initialize(MontoyaApi api) {
// 设置扩展名称
String version = "3.3.3";
api.extension().setName(String.format("HaE (%s) - Highlighter and Extractor", version));
api.extension().setName("HaE - Highlighter and Extractor");
String version = "4.3.1";
// 加载扩展后输出的项目信息
Logging logging = api.logging();
logging.logToOutput("[ HACK THE WORLD - TO DO IT ]");
logging.logToOutput("[#] Author: EvilChen && 0chencc && vaycore");
logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE");
logging.logToOutput("[#] Version: " + version);
// 配置文件加载
ConfigLoader configLoader = new ConfigLoader(api);
MessageTableModel messageTableModel = new MessageTableModel(api, configLoader);
// 设置BurpSuite专业版状态
Config.proVersionStatus = getBurpSuiteProStatus(api);
// 注册Tab页用于查询数据
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
// 注册HTTP处理器
api.http().registerHttpHandler(new HttpMessageHandler(api, configLoader, messageTableModel));
// 注册WebSocket处理器
api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api)));
api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api, configLoader)));
// 注册消息编辑框(用于展示数据)
api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader));
api.userInterface().registerHttpResponseEditorProvider(new ResponseEditor(api, configLoader));
api.userInterface().registerWebSocketMessageEditorProvider(new WebSocketEditor(api, configLoader));
api.extension().registerUnloadingHandler(new ExtensionUnloadingHandler() {
@Override
public void extensionUnloaded() {
// 卸载清空数据
Config.globalDataMap.clear();
CachePool.clear();
}
// 从BurpSuite里加载数据
DataManager dataManager = new DataManager(api);
dataManager.loadData(messageTableModel);
api.extension().registerUnloadingHandler(() -> {
// 卸载清空数据
Config.globalDataMap.clear();
DataCache.clear();
});
}
private Boolean getBurpSuiteProStatus(MontoyaApi api) {
boolean burpSuiteProStatus = false;
try {
burpSuiteProStatus = api.burpSuite().version().edition() == BurpSuiteEdition.PROFESSIONAL;
} catch (Exception ignored) {
}
return burpSuiteProStatus;
}
}

View File

@@ -6,9 +6,9 @@ import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class CachePool {
public class DataCache {
private static final int MAX_SIZE = 100000;
private static final int EXPIRE_DURATION = 5;
private static final int EXPIRE_DURATION = 4;
private static final Cache<String, Map<String, Map<String, Object>>> cache =
Caffeine.newBuilder()
@@ -24,10 +24,6 @@ public class CachePool {
return cache.getIfPresent(key);
}
public static void remove(String key) {
cache.invalidate(key);
}
public static void clear() {
cache.invalidateAll();
}

View File

@@ -1,7 +1,11 @@
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;
@@ -10,30 +14,35 @@ 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.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
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 final String defaultText = "Enter a new item";
public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) {
private Registration activeHandler;
private Registration passiveHandler;
private boolean isLoadingData = false;
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();
}
@@ -52,33 +61,93 @@ public class Config extends JPanel {
pathTextField.setEditable(false);
pathTextField.setText(configLoader.getRulesFilePath());
JButton reloadButton = new JButton("Reload");
JButton updateButton = new JButton("Update");
JButton reinitButton = new JButton("Reinit");
ruleInfoPanel.add(ruleLabel);
ruleInfoPanel.add(pathTextField, constraints);
ruleInfoPanel.add(Box.createHorizontalStrut(5));
ruleInfoPanel.add(reloadButton);
ruleInfoPanel.add(reinitButton);
ruleInfoPanel.add(Box.createHorizontalStrut(5));
ruleInfoPanel.add(updateButton);
ruleInfoPanel.add(reloadButton);
reloadButton.addActionListener(this::reloadActionPerformed);
updateButton.addActionListener(this::onlineUpdateActionPerformed);
reinitButton.addActionListener(this::reinitActionPerformed);
constraints.gridx = 1;
JTabbedPane configTabbedPanel = new JTabbedPane();
String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status", "Limit size (MB)"};
JPanel settingPanel = createConfigTablePanel(settingMode, "Setting");
String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status", "Dynamic Header"};
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"));
settingPanel.add(scopeScrollPane, BorderLayout.NORTH);
configTabbedPanel.add("Setting", settingPanel);
String[] aiMode = new String[]{"Alibaba", "Moonshot"};
JPanel aiPanel = createConfigTablePanel(aiMode, "AI+");
JTextArea promptTextArea = new JTextArea();
promptTextArea.setLineWrap(true);
promptTextArea.getDocument().addDocumentListener(new DocumentListener() {
northPanel.add(scopeScrollPane, BorderLayout.SOUTH);
northPanel.add(northTopPanel, BorderLayout.NORTH);
settingPanel.add(northPanel, BorderLayout.NORTH);
configTabbedPanel.add("Setting", settingPanel);
add(ruleInfoPanel, BorderLayout.NORTH);
add(configTabbedPanel, BorderLayout.CENTER);
}
private JPanel getScopePanel() {
JPanel scopePanel = new JPanel();
scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.X_AXIS));
scopePanel.setBorder(new EmptyBorder(3, 0, 6, 0));
String[] scopeInit = hae.Config.scopeOptions.split("\\|");
String[] scopeMode = configLoader.getScope().split("\\|");
for (String scope : scopeInit) {
JCheckBox checkBox = new JCheckBox(scope);
scopePanel.add(checkBox);
checkBox.addActionListener(e -> updateScope(checkBox));
for (String mode : scopeMode) {
if (scope.equals(mode)) {
checkBox.setSelected(true);
}
}
updateScope(checkBox);
}
return scopePanel;
}
private JPanel getModePanel() {
JPanel modePanel = new JPanel();
modePanel.setLayout(new BoxLayout(modePanel, BoxLayout.X_AXIS));
JCheckBox checkBox = new JCheckBox("Enable active http message handler");
checkBox.setEnabled(hae.Config.proVersionStatus);
modePanel.add(checkBox);
checkBox.addActionListener(e -> updateModeStatus(checkBox));
checkBox.setSelected(configLoader.getMode());
updateModeStatus(checkBox);
return modePanel;
}
private JTextField getLimitPanel() {
JTextField limitSizeTextField = new JTextField();
limitSizeTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
onTextChange();
@@ -95,69 +164,48 @@ public class Config extends JPanel {
}
private void onTextChange() {
String promptText = promptTextArea.getText();
configLoader.setAIPrompt(promptText);
String limitSizeText = limitSizeTextField.getText();
configLoader.setLimitSize(limitSizeText);
}
});
promptTextArea.setText(configLoader.getAIPrompt());
JScrollPane promptScrollPane = new JScrollPane(promptTextArea);
promptScrollPane.setBorder(new TitledBorder("Prompt"));
promptScrollPane.setPreferredSize(new Dimension(0, 100));
aiPanel.add(promptScrollPane, BorderLayout.NORTH);
configTabbedPanel.add("AI+", aiPanel);
add(ruleInfoPanel, BorderLayout.NORTH);
add(configTabbedPanel, BorderLayout.CENTER);
}
private JPanel getScopePanel() {
JPanel scopePanel = new JPanel();
scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.X_AXIS));
limitSizeTextField.setText(configLoader.getLimitSize());
String[] scopeInit = hae.Config.scopeOptions.split("\\|");
String[] scopeMode = configLoader.getScope().split("\\|");
for (String scope : scopeInit) {
JCheckBox checkBox = new JCheckBox(scope);
scopePanel.add(checkBox);
for (String mode : scopeMode) {
if (scope.equals(mode)) {
checkBox.setSelected(true);
}
}
checkBox.addActionListener(e -> updateScope(checkBox));
}
return scopePanel;
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);
return e -> {
// 如果是程序正在加载数据,不处理事件
if (isLoadingData) {
return;
}
String selected = (String) setTypeComboBox.getSelectedItem();
String values = getFirstColumnDataAsString(model);
if (selected != null) {
if (selected.equals("Exclude suffix")) {
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
if (!values.equals(configLoader.getExcludeSuffix())) {
configLoader.setExcludeSuffix(values);
}
}
if (selected.equals("Block host")) {
if (!values.equals(configLoader.getBlockHost()) && !values.isEmpty()) {
if (!values.equals(configLoader.getBlockHost())) {
configLoader.setBlockHost(values);
}
}
if (selected.equals("Exclude status")) {
if (!values.equals(configLoader.getExcludeStatus()) && !values.isEmpty()) {
if (!values.equals(configLoader.getExcludeStatus())) {
configLoader.setExcludeStatus(values);
}
}
if (selected.contains("Limit size")) {
if (!values.equals(configLoader.getExcludeStatus()) && !values.isEmpty()) {
String[] limit = values.split("\\|");
configLoader.setLimitSize(limit[limit.length - 1]);
if (selected.equals("Dynamic Header")) {
if (!values.equals(configLoader.getExcludeStatus())) {
configLoader.setDynamicHeader(values);
}
}
}
@@ -165,12 +213,14 @@ public class Config extends JPanel {
}
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);
return e -> {
String selected = (String) setTypeComboBox.getSelectedItem();
// 设置标志,表示正在加载数据
isLoadingData = true;
model.setRowCount(0);
if (selected != null) {
if (selected.equals("Exclude suffix")) {
addDataToTable(configLoader.getExcludeSuffix().replaceAll("\\|", "\r\n"), model);
}
@@ -183,54 +233,17 @@ public class Config extends JPanel {
addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model);
}
if (selected.contains("Limit size")) {
addDataToTable(configLoader.getLimitSize(), model);
if (selected.equals("Dynamic Header")) {
addDataToTable(configLoader.getDynamicHeader().replaceAll("\\|", "\r\n"), model);
}
}
// 重置标志
isLoadingData = false;
};
}
private TableModelListener craeteAITableModelListener(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("Alibaba")) {
if (!values.equals(configLoader.getAlibabaAIAPIKey()) && !values.isEmpty()) {
configLoader.setAlibabaAIAPIKey(values);
}
}
if (selected.equals("Moonshot")) {
if (!values.equals(configLoader.getMoonshotAIAPIKey()) && !values.isEmpty()) {
configLoader.setMoonshotAIAPIKey(values);
}
}
}
};
}
private ActionListener createAIActionListener(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("Alibaba")) {
addDataToTable(configLoader.getAlibabaAIAPIKey().replaceAll("\\|", "\r\n"), model);
}
if (selected.equals("Moonshot")) {
addDataToTable(configLoader.getMoonshotAIAPIKey().replaceAll("\\|", "\r\n"), model);
}
}
};
}
private JPanel createConfigTablePanel(String[] mode, String type) {
private JPanel createConfigTablePanel(String[] mode) {
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 1.0;
constraints.fill = GridBagConstraints.HORIZONTAL;
@@ -261,12 +274,12 @@ public class Config extends JPanel {
JComboBox<String> setTypeComboBox = new JComboBox<>();
setTypeComboBox.setModel(new DefaultComboBoxModel<>(mode));
setTypeComboBox.addActionListener(type.equals("AI+") ? createAIActionListener(setTypeComboBox, model) : createSettingActionListener(setTypeComboBox, model));
model.addTableModelListener(craeteSettingTableModelListener(setTypeComboBox, model));
setTypeComboBox.addActionListener(createSettingActionListener(setTypeComboBox, model));
setTypeComboBox.setSelectedItem(mode[0]);
model.addTableModelListener(type.equals("AI+") ? craeteAITableModelListener(setTypeComboBox, model) : craeteSettingTableModelListener(setTypeComboBox, model));
constraints.insets = new Insets(0, 0, 3, 0);
constraints.gridy = 0;
buttonPanel.add(setTypeComboBox, constraints);
@@ -280,6 +293,7 @@ public class Config extends JPanel {
buttonPanel.add(clearButton, constraints);
JTextField addTextField = new JTextField();
String defaultText = "Enter a new item";
UIEnhancer.setTextFieldPlaceholder(addTextField, defaultText);
inputPanelB.add(addTextField, BorderLayout.CENTER);
@@ -290,13 +304,13 @@ public class Config extends JPanel {
settingPanel.add(inputPanel, BorderLayout.CENTER);
addButton.addActionListener(e -> addActionPerformed(e, model, addTextField, setTypeComboBox.getSelectedItem().toString()));
addButton.addActionListener(e -> addActionPerformed(e, model, addTextField));
addTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
addActionPerformed(null, model, addTextField, setTypeComboBox.getSelectedItem().toString());
addActionPerformed(null, model, addTextField);
}
}
});
@@ -305,9 +319,6 @@ public class Config extends JPanel {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
try {
String data = (String) clipboard.getData(DataFlavor.stringFlavor);
if (setTypeComboBox.getSelectedItem().toString().contains("Limit size")) {
model.setRowCount(0);
}
if (data != null && !data.isEmpty()) {
addDataToTable(data, model);
}
@@ -324,11 +335,10 @@ public class Config extends JPanel {
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(type.equals("AI+") ? "API Key" : "Setting"));
settingScroller.setBorder(new TitledBorder("Setting"));
settingMainPanel.add(settingScroller, BorderLayout.CENTER);
return settingMainPanel;
@@ -383,6 +393,29 @@ public class Config extends JPanel {
}
}
public void updateModeStatus(JCheckBox checkBox) {
boolean selected = checkBox.isSelected();
configLoader.setMode(selected ? "true" : "false");
if (checkBox.isSelected()) {
if (hae.Config.proVersionStatus && passiveHandler.isRegistered()) {
passiveHandler.deregister();
}
if (!activeHandler.isRegistered()) {
activeHandler = api.http().registerHttpHandler(new HttpMessageActiveHandler(api, configLoader, messageTableModel));
}
} else {
if (hae.Config.proVersionStatus && !passiveHandler.isRegistered()) {
passiveHandler = api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel));
}
if (activeHandler.isRegistered()) {
activeHandler.deregister();
}
}
}
public void updateScope(JCheckBox checkBox) {
String boxText = checkBox.getText();
boolean selected = checkBox.isSelected();
@@ -398,28 +431,26 @@ public class Config extends JPanel {
configLoader.setScope(String.join("|", HaEScope));
}
private void addActionPerformed(ActionEvent e, DefaultTableModel model, JTextField addTextField, String comboBoxSelected) {
private void addActionPerformed(ActionEvent e, DefaultTableModel model, JTextField addTextField) {
String addTextFieldText = addTextField.getText();
if (addTextField.getForeground().equals(Color.BLACK)) {
if (comboBoxSelected.contains("Limit size")) {
model.setRowCount(0);
}
addDataToTable(addTextFieldText, model);
addTextField.setText("");
addTextField.requestFocusInWindow();
}
}
private void onlineUpdateActionPerformed(ActionEvent e) {
// 添加提示框防止用户误触导致配置更新
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
if (retCode == JOptionPane.YES_OPTION) {
configLoader.initRulesByNet();
reloadActionPerformed(null);
}
}
private void reloadActionPerformed(ActionEvent e) {
rules.reloadRuleGroup();
}
private void reinitActionPerformed(ActionEvent e) {
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to reinitialize rules? This action will overwrite your existing rules.", "Info", JOptionPane.YES_NO_OPTION);
if (retCode == JOptionPane.YES_OPTION) {
boolean ret = configLoader.initRules();
if (ret) {
rules.reloadRuleGroup();
}
}
}
}

View File

@@ -5,6 +5,7 @@ import hae.component.board.Databoard;
import hae.component.board.message.MessageTableModel;
import hae.component.rule.Rules;
import hae.utils.ConfigLoader;
import hae.utils.UIEnhancer;
import javax.swing.*;
import java.awt.*;
@@ -36,7 +37,7 @@ public class Main extends JPanel {
// 新增Logo
JTabbedPane HaETabbedPane = new JTabbedPane();
boolean isDarkBg = isDarkBg(HaETabbedPane);
boolean isDarkBg = UIEnhancer.isDarkColor(HaETabbedPane.getBackground());
HaETabbedPane.addTab("", getImageIcon(isDarkBg), mainTabbedPane);
// 中文Slogan赋能白帽高效作战
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null);
@@ -44,7 +45,7 @@ public class Main extends JPanel {
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent e) {
boolean isDarkBg = isDarkBg(HaETabbedPane);
boolean isDarkBg = UIEnhancer.isDarkColor(HaETabbedPane.getBackground());
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
}
});
@@ -57,17 +58,7 @@ public class Main extends JPanel {
Rules rules = new Rules(api, configLoader);
mainTabbedPane.addTab("Rules", rules);
mainTabbedPane.addTab("Databoard", new Databoard(api, configLoader, messageTableModel));
mainTabbedPane.addTab("Config", new Config(api, configLoader, 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;
mainTabbedPane.addTab("Config", new Config(api, configLoader, messageTableModel, rules));
}
private ImageIcon getImageIcon(boolean isDark) {
@@ -81,7 +72,6 @@ public class Main extends JPanel {
ImageIcon originalIcon = new ImageIcon(imageURL);
Image originalImage = originalIcon.getImage();
Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST);
ImageIcon scaledIcon = new ImageIcon(scaledImage);
return scaledIcon;
return new ImageIcon(scaledImage);
}
}

View File

@@ -2,65 +2,46 @@ package hae.component.board;
import burp.api.montoya.MontoyaApi;
import hae.Config;
import hae.component.board.message.MessageEntry;
import hae.cache.DataCache;
import hae.component.board.message.MessageTableModel;
import hae.component.board.message.MessageTableModel.MessageTable;
import hae.component.board.table.Datatable;
import hae.instances.http.utils.RegularMatcher;
import hae.utils.ConfigLoader;
import hae.utils.UIEnhancer;
import hae.utils.project.ProjectProcessor;
import hae.utils.project.model.HaeFileContent;
import hae.utils.string.StringProcessor;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.List;
import java.text.Collator;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Databoard extends JPanel {
private static Boolean isMatchHost = false;
private final MontoyaApi api;
private final ConfigLoader configLoader;
private final ProjectProcessor projectProcessor;
private final MessageTableModel messageTableModel;
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
private JTextField hostTextField;
private JTabbedPane dataTabbedPane;
private JSplitPane splitPane;
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>>, Integer> handleComboBoxWorker;
private SwingWorker<Void, Void> applyHostFilterWorker;
private SwingWorker<List<Object[]>, Void> exportActionWorker;
private SwingWorker<List<Object[]>, Void> importActionWorker;
private final String defaultText = "Please enter the host";
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
this.api = api;
this.configLoader = configLoader;
this.projectProcessor = new ProjectProcessor(api);
this.messageTableModel = messageTableModel;
initComponents();
@@ -72,22 +53,20 @@ public class Databoard extends JPanel {
((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()).rowWeights = new double[]{0.0, 1.0, 0.0, 0.0, 1.0E-4};
JLabel hostLabel = new JLabel("Host:");
JButton clearButton = new JButton("Clear");
JButton exportButton = new JButton("Export");
JButton importButton = new JButton("Import");
JButton clearDataButton = new JButton("Clear data");
JButton clearCacheButton = new JButton("Clear cache");
JButton actionButton = new JButton("Action");
JPanel menuPanel = new JPanel(new GridLayout(3, 1, 0, 5));
JPanel menuPanel = new JPanel(new GridLayout(2, 1, 0, 5));
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
JPopupMenu menu = new JPopupMenu();
menuPanel.add(clearButton);
menuPanel.add(exportButton);
menuPanel.add(importButton);
menuPanel.add(clearDataButton);
menuPanel.add(clearCacheButton);
menu.add(menuPanel);
hostTextField = new JTextField();
String defaultText = "Please enter the host";
UIEnhancer.setTextFieldPlaceholder(hostTextField, defaultText);
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
@@ -101,12 +80,10 @@ public class Databoard extends JPanel {
menu.show(actionButton, x, y);
});
clearButton.addActionListener(this::clearActionPerformed);
exportButton.addActionListener(this::exportActionPerformed);
importButton.addActionListener(this::importActionPerformed);
clearDataButton.addActionListener(this::clearDataActionPerformed);
clearCacheButton.addActionListener(this::clearCacheActionPerformed);
progressBar = new JProgressBar();
splitPane.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
@@ -149,21 +126,16 @@ public class Databoard extends JPanel {
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
}
private void setProgressBar(boolean status) {
setProgressBar(status, progressBar, "Loading ...");
}
private void setProgressBar(boolean status, String message, int progress) {
progressBar.setIndeterminate(status && progress <= 0);
progressBar.setString(message);
progressBar.setStringPainted(true);
progressBar.setMaximum(100);
public static void setProgressBar(boolean status, JProgressBar progressBar, String showString) {
progressBar.setIndeterminate(status);
if (!status) {
progressBar.setMaximum(100);
progressBar.setString("OK");
progressBar.setStringPainted(true);
if (progress > 0) {
progressBar.setValue(progress);
} else if (!status) {
progressBar.setValue(progressBar.getMaximum());
} else {
progressBar.setString(showString);
progressBar.setStringPainted(true);
}
}
@@ -202,52 +174,15 @@ public class Databoard extends JPanel {
String selectedHost = hostComboBox.getSelectedItem().toString();
if (getHostByList().contains(selectedHost)) {
progressBar.setVisible(true);
setProgressBar(true);
hostTextField.setText(selectedHost);
hostComboBox.setPopupVisible(false);
if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
progressBar.setVisible(false);
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 = new DataLoadingWorker(selectedHost);
handleComboBoxWorker.execute();
}
@@ -278,33 +213,57 @@ public class Databoard extends JPanel {
isMatchHost = false;
}
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
private Map<String, List<String>> getSelectedMapByHost(String selectedHost, DataLoadingWorker worker) {
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
Map<String, List<String>> selectedDataMap;
if (selectedHost.contains("*")) {
selectedDataMap = new HashMap<>();
dataMap.keySet().forEach(key -> {
List<String> matchingKeys = new ArrayList<>();
// 第一步:找出所有匹配的键(预处理)
for (String key : dataMap.keySet()) {
if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) {
Map<String, List<String>> ruleMap = dataMap.get(key);
matchingKeys.add(key);
}
}
// 第二步:分批处理数据
int totalKeys = matchingKeys.size();
for (int i = 0; i < totalKeys; i++) {
String key = matchingKeys.get(i);
Map<String, List<String>> ruleMap = dataMap.get(key);
if (ruleMap != null) {
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去重
HashSet<String> uniqueSet = new HashSet<>(mergedList);
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
} else {
selectedDataMap.put(ruleKey, dataList);
selectedDataMap.put(ruleKey, new ArrayList<>(dataList));
}
}
}
});
// 报告进度
if (worker != null && i % 5 == 0) {
int progress = (int) ((i + 1) * 90.0 / totalKeys);
worker.publishProgress(progress);
}
}
} else {
selectedDataMap = dataMap.get(selectedHost);
// 对于非通配符匹配,直接返回结果
if (worker != null) {
worker.publishProgress(90);
}
}
return selectedDataMap;
return selectedDataMap != null ? selectedDataMap : new HashMap<>();
}
private void filterComboBoxList() {
@@ -338,10 +297,10 @@ public class Databoard extends JPanel {
applyHostFilterWorker.cancel(true);
}
applyHostFilterWorker = new SwingWorker<Void, Void>() {
applyHostFilterWorker = new SwingWorker<>() {
@Override
protected Void doInBackground() throws Exception {
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
protected Void doInBackground() {
RowFilter<Object, Object> rowFilter = new RowFilter<>() {
public boolean include(Entry<?, ?> entry) {
if (cleanedText.equals("*")) {
return true;
@@ -357,281 +316,29 @@ public class Databoard extends JPanel {
return null;
}
@Override
protected void done() {
setProgressBar(false);
}
};
applyHostFilterWorker.execute();
}
private List<String> getHostByList() {
if (!Config.globalDataMap.keySet().isEmpty()) {
return new ArrayList<>(Config.globalDataMap.keySet());
}
return new ArrayList<>();
}
private void exportActionPerformed(ActionEvent e) {
String selectedHost = hostTextField.getText().trim();
if (selectedHost.isEmpty()) {
return;
List<String> result = new ArrayList<>();
if (!Config.globalDataMap.isEmpty()) {
result = new ArrayList<>(Config.globalDataMap.keySet());
}
String exportDir = selectDirectory(true);
return result;
}
if (exportDir.isEmpty()) {
return;
private void clearCacheActionPerformed(ActionEvent e) {
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear cache?", "Info",
JOptionPane.YES_NO_OPTION);
if (retCode == JOptionPane.YES_OPTION) {
DataCache.clear();
}
if (exportActionWorker != null && !exportActionWorker.isDone()) {
exportActionWorker.cancel(true);
}
exportActionWorker = new SwingWorker<List<Object[]>, Void>() {
@Override
protected List<Object[]> doInBackground() {
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
return exportData(selectedHost, exportDir, dataMap);
}
@Override
protected void done() {
try {
List<Object[]> taskStatusList = get();
if (!taskStatusList.isEmpty()) {
JOptionPane.showMessageDialog(Databoard.this, generateTaskStatusPane(taskStatusList), "Info", JOptionPane.INFORMATION_MESSAGE);
}
} catch (Exception ignored) {
}
}
};
exportActionWorker.execute();
}
private JScrollPane generateTaskStatusPane(List<Object[]> dataList) {
String[] columnNames = {"#", "Filename", "Status"};
DefaultTableModel taskStatusTableModel = new DefaultTableModel(columnNames, 0);
JTable taskStatusTable = new JTable(taskStatusTableModel);
for (Object[] data : dataList) {
int rowCount = taskStatusTableModel.getRowCount();
int id = rowCount > 0 ? (Integer) taskStatusTableModel.getValueAt(rowCount - 1, 0) + 1 : 1;
Object[] rowData = new Object[data.length + 1];
rowData[0] = id;
System.arraycopy(data, 0, rowData, 1, data.length);
taskStatusTableModel.addRow(rowData);
}
TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(taskStatusTableModel);
taskStatusTable.setRowSorter(sorter);
JScrollPane scrollPane = new JScrollPane(taskStatusTable);
scrollPane.setBorder(new TitledBorder("Task status"));
scrollPane.setPreferredSize(new Dimension(500, 300));
int paneWidth = scrollPane.getPreferredSize().width;
taskStatusTable.getColumnModel().getColumn(0).setPreferredWidth((int) (paneWidth * 0.1));
taskStatusTable.getColumnModel().getColumn(1).setPreferredWidth((int) (paneWidth * 0.7));
taskStatusTable.getColumnModel().getColumn(2).setPreferredWidth((int) (paneWidth * 0.2));
return scrollPane;
}
private List<Object[]> 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 Object[] exportEntry(Map.Entry<String, Map<String, List<String>>> entry, String exportDir) {
String key = entry.getKey();
Map<String, List<String>> ruleMap = entry.getValue();
if (ruleMap == null || ruleMap.isEmpty()) {
return null;
}
List<MessageEntry> messageEntryList = messageTableModel.getLogs();
Map<MessageEntry, String> entryUUIDMap = messageEntryList.stream()
.collect(Collectors.toMap(
messageEntry -> messageEntry,
messageEntry -> StringProcessor.getRandomUUID(),
(existing, replacement) -> existing
));
Map<String, Map<String, Object>> httpMap = processEntries(
messageEntryList,
key,
entryUUIDMap,
this::createHttpItemMap
);
Map<String, Map<String, Object>> urlMap = processEntries(
messageEntryList,
key,
entryUUIDMap,
this::creteUrlItemMap
);
String hostName = key.replace(":", "_");
String filename = String.format("%s/%s-%s.hae", exportDir, StringProcessor.getCurrentTime(), hostName);
boolean createdStatus = projectProcessor.createHaeFile(filename, key, ruleMap, urlMap, httpMap);
return new Object[]{filename, createdStatus};
}
private Map<String, Map<String, Object>> processEntries(List<MessageEntry> messageEntryList, String key, Map<MessageEntry, String> entryUUIDMap, Function<MessageEntry, Map<String, Object>> mapFunction) {
return messageEntryList.stream()
.filter(messageEntry -> !StringProcessor.getHostByUrl(messageEntry.getUrl()).isEmpty())
.filter(messageEntry -> StringProcessor.getHostByUrl(messageEntry.getUrl()).equals(key))
.collect(Collectors.toMap(
entryUUIDMap::get,
mapFunction,
(existing, replacement) -> existing
));
}
private Map<String, Object> creteUrlItemMap(MessageEntry entry) {
Map<String, Object> urlItemMap = new LinkedHashMap<>();
urlItemMap.put("url", entry.getUrl());
urlItemMap.put("method", entry.getMethod());
urlItemMap.put("status", entry.getStatus());
urlItemMap.put("length", entry.getLength());
urlItemMap.put("comment", entry.getComment());
urlItemMap.put("color", entry.getColor());
urlItemMap.put("size", String.valueOf(entry.getRequestResponse().request().toByteArray().length()));
return urlItemMap;
}
private Map<String, Object> createHttpItemMap(MessageEntry entry) {
Map<String, Object> httpItemMap = new LinkedHashMap<>();
httpItemMap.put("request", entry.getRequestResponse().request().toByteArray().getBytes());
httpItemMap.put("response", entry.getRequestResponse().response().toByteArray().getBytes());
return httpItemMap;
}
private void importActionPerformed(ActionEvent e) {
String exportDir = selectDirectory(false);
if (exportDir.isEmpty()) {
return;
}
if (importActionWorker != null && !importActionWorker.isDone()) {
importActionWorker.cancel(true);
}
importActionWorker = new SwingWorker<List<Object[]>, Void>() {
@Override
protected List<Object[]> doInBackground() {
List<String> filesWithExtension = findFilesWithExtension(new File(exportDir), ".hae");
return filesWithExtension.stream()
.map(Databoard.this::importData)
.collect(Collectors.toList());
}
@Override
protected void done() {
try {
List<Object[]> taskStatusList = get();
if (!taskStatusList.isEmpty()) {
JOptionPane.showMessageDialog(Databoard.this, generateTaskStatusPane(taskStatusList), "Info", JOptionPane.INFORMATION_MESSAGE);
}
} catch (Exception ignored) {
}
}
};
importActionWorker.execute();
}
private Object[] importData(String filename) {
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
HaeFileContent haeFileContent = projectProcessor.readHaeFile(filename);
boolean readStatus = haeFileContent != null;
List<Callable<Void>> tasks = new ArrayList<>();
if (readStatus) {
try {
String host = haeFileContent.getHost();
haeFileContent.getDataMap().forEach((key, value) -> RegularMatcher.putDataToGlobalMap(host, key, value));
haeFileContent.getUrlMap().forEach((key, urlItemMap) -> {
tasks.add(() -> {
String url = urlItemMap.get("url");
String comment = urlItemMap.get("comment");
String color = urlItemMap.get("color");
String length = urlItemMap.get("length");
String method = urlItemMap.get("method");
String status = urlItemMap.get("status");
String path = haeFileContent.getHttpPath();
messageTableModel.add(null, url, method, status, length, comment, color, key, path);
return null;
});
});
executor.invokeAll(tasks);
} catch (Exception e) {
api.logging().logToError("importData: " + e.getMessage());
} finally {
executor.shutdown();
}
}
return new Object[]{filename, readStatus};
}
private List<String> findFilesWithExtension(File directory, String extension) {
List<String> filePaths = new ArrayList<>();
if (directory.isDirectory()) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
filePaths.addAll(findFilesWithExtension(file, extension));
} else if (file.isFile() && file.getName().toLowerCase().endsWith(extension)) {
filePaths.add(file.getAbsolutePath());
}
}
}
} else {
filePaths.add(directory.getAbsolutePath());
}
return filePaths;
}
private String selectDirectory(boolean forDirectories) {
JFileChooser chooser = new JFileChooser();
chooser.setCurrentDirectory(new java.io.File(configLoader.getRulesFilePath()));
chooser.setDialogTitle(String.format("Select a Directory%s", forDirectories ? "" : " or File"));
FileNameExtensionFilter filter = new FileNameExtensionFilter(".hae Files", "hae");
chooser.addChoosableFileFilter(filter);
chooser.setFileFilter(filter);
chooser.setFileSelectionMode(forDirectories ? JFileChooser.DIRECTORIES_ONLY : JFileChooser.FILES_AND_DIRECTORIES);
chooser.setAcceptAllFileFilterUsed(!forDirectories);
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
File selectedDirectory = chooser.getSelectedFile();
return selectedDirectory.getAbsolutePath();
}
return "";
}
private void clearActionPerformed(ActionEvent e) {
private void clearDataActionPerformed(ActionEvent e) {
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
JOptionPane.YES_NO_OPTION);
String host = hostTextField.getText();
@@ -665,8 +372,8 @@ public class Databoard extends JPanel {
keysToRemove.forEach(Config.globalDataMap::remove);
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
Config.globalDataMap.keySet().remove("*");
if (Config.globalDataMap.size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
Config.globalDataMap.remove("*");
}
messageTableModel.deleteByHost(host);
@@ -674,4 +381,87 @@ public class Databoard extends JPanel {
hostTextField.setText("");
}
}
// 定义为内部类
private class DataLoadingWorker extends SwingWorker<Map<String, List<String>>, Integer> {
private final String selectedHost;
public DataLoadingWorker(String selectedHost) {
this.selectedHost = selectedHost;
progressBar.setVisible(true);
}
@Override
protected Map<String, List<String>> doInBackground() throws Exception {
return getSelectedMapByHost(selectedHost, this);
}
@Override
protected void process(List<Integer> chunks) {
if (!chunks.isEmpty()) {
int progress = chunks.get(chunks.size() - 1);
setProgressBar(true, "Loading... " + progress + "%", progress);
}
}
@Override
protected void done() {
if (!isCancelled()) {
try {
Map<String, List<String>> selectedDataMap = get();
if (selectedDataMap != null && !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);
insertTabSorted(dataTabbedPane, tabTitle, datatablePanel);
}
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
splitPane.setLeftComponent(dataTabbedPane);
splitPane.setRightComponent(messageSplitPane);
messageTable = messageTableModel.getMessageTable();
resizePanel();
splitPane.setVisible(true);
applyHostFilter(selectedHost);
setProgressBar(false, "OK", 100);
} else {
setProgressBar(false, "Error", 0);
}
} catch (Exception e) {
api.logging().logToOutput("DataLoadingWorker: " + e.getMessage());
setProgressBar(false, "Error", 0);
}
}
}
public static void insertTabSorted(JTabbedPane tabbedPane, String title, Component component) {
int insertIndex = 0;
int tabCount = tabbedPane.getTabCount();
// 使用 Collator 实现更友好的语言排序(支持中文、特殊字符等)
Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.PRIMARY); // 忽略大小写和重音
for (int i = 0; i < tabCount; i++) {
String existingTitle = tabbedPane.getTitleAt(i);
if (collator.compare(existingTitle, title) > 0) {
insertIndex = i;
break;
}
insertIndex = i + 1;
}
tabbedPane.insertTab(title, null, component, null, insertIndex);
}
// 提供一个公共方法来发布进度
public void publishProgress(int progress) {
publish(progress);
}
}
}

View File

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

View File

@@ -25,6 +25,7 @@ public class MessageRenderer extends DefaultTableCellRenderer {
this.colorMap.put("pink", new Color(0xFF, 0xC8, 0xC8));
this.colorMap.put("magenta", new Color(0xFF, 0x64, 0xFF));
this.colorMap.put("gray", new Color(0xB4, 0xB4, 0xB4));
this.colorMap.put("none", new Color(0, 0, 0, 0));
this.table = table;
}
@@ -33,17 +34,29 @@ public class MessageRenderer extends DefaultTableCellRenderer {
boolean hasFocus, int row, int column) {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
MessageEntry messageEntry = log.get(table.convertRowIndexToModel(row)); // 使用convertRowIndexToModel方法转换行索引
// 添加边界检查以防止IndexOutOfBoundsException
int modelRow = table.convertRowIndexToModel(row);
if (modelRow < 0 || modelRow >= log.size()) {
// 如果索引无效,返回默认渲染组件(使用默认背景色)
component.setBackground(Color.WHITE);
component.setForeground(Color.BLACK);
return component;
}
MessageEntry messageEntry = log.get(modelRow);
// 设置颜色
String colorByLog = messageEntry.getColor();
Color color = colorMap.get(colorByLog);
// 如果颜色映射中没有找到对应颜色,使用默认白色
if (color == null) {
color = Color.WHITE;
}
if (isSelected) {
// 通过更改RGB颜色来达成阴影效果
component.setBackground(new Color(color.getRed() - 0x20, color.getGreen() - 0x20, color.getBlue() - 0x20));
component.setBackground(UIManager.getColor("Table.selectionBackground"));
} else {
// 否则使用原始颜色
component.setBackground(color);
}

View File

@@ -5,14 +5,13 @@ import burp.api.montoya.http.message.HttpHeader;
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.PersistedObject;
import burp.api.montoya.ui.UserInterface;
import burp.api.montoya.ui.editor.HttpRequestEditor;
import burp.api.montoya.ui.editor.HttpResponseEditor;
import hae.Config;
import hae.cache.CachePool;
import hae.utils.ConfigLoader;
import hae.utils.project.FileProcessor;
import hae.utils.string.HashCalculator;
import hae.utils.DataManager;
import hae.utils.string.StringProcessor;
import javax.swing.*;
@@ -56,15 +55,27 @@ public class MessageTableModel extends AbstractTableModel {
messageTable.setDefaultRenderer(Object.class, new MessageRenderer(filteredLog, messageTable));
messageTable.setAutoCreateRowSorter(true);
// Length字段根据大小进行排序
TableRowSorter<DefaultTableModel> sorter = getDefaultTableModelTableRowSorter();
messageTable.setRowSorter(sorter);
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
// 请求/响应文本框
JScrollPane scrollPane = new JScrollPane(messageTable);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
splitPane.setLeftComponent(scrollPane);
splitPane.setRightComponent(messageTab);
}
private TableRowSorter<DefaultTableModel> getDefaultTableModelTableRowSorter() {
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) messageTable.getRowSorter();
sorter.setComparator(4, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
Integer age1 = Integer.parseInt(s1);
Integer age2 = Integer.parseInt(s2);
return age1.compareTo(age2);
}
// Length字段根据大小进行排序
sorter.setComparator(4, (Comparator<String>) (s1, s2) -> {
Integer age1 = Integer.parseInt(s1);
Integer age2 = Integer.parseInt(s2);
return age1.compareTo(age2);
});
// Color字段根据颜色顺序进行排序
@@ -85,48 +96,39 @@ public class MessageTableModel extends AbstractTableModel {
return -1;
}
});
messageTable.setRowSorter(sorter);
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
// 请求/相应文本框
JScrollPane scrollPane = new JScrollPane(messageTable);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
splitPane.setLeftComponent(scrollPane);
splitPane.setRightComponent(messageTab);
return sorter;
}
public void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, String hash, String path) {
public synchronized void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
synchronized (log) {
boolean isDuplicate = false;
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status, hash, path);
byte[] reqByteA = new byte[0];
byte[] resByteA = new byte[0];
if (messageInfo != null) {
HttpRequest httpRequest = messageInfo.request();
HttpResponse httpResponse = messageInfo.response();
reqByteA = httpRequest.toByteArray().getBytes();
resByteA = httpResponse.toByteArray().getBytes();
if (messageInfo == null) {
return;
}
// 比较Hash如若存在重复的请求或响应则不放入消息内容里
if (comment == null || comment.trim().isEmpty()) {
return;
}
if (color == null || color.trim().isEmpty()) {
return;
}
boolean isDuplicate = false;
try {
if (!log.isEmpty()) {
if (!log.isEmpty() && flag) {
String host = StringProcessor.getHostByUrl(url);
for (MessageEntry entry : log) {
HttpRequestResponse reqResMessage = entry.getRequestResponse();
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
byte[] resByteB = reqResMessage.response().toByteArray().getBytes();
try {
// 通过URL、请求和响应报文、匹配数据内容多维度进行对比
if ((entry.getUrl().equals(url) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) {
if (host.equals(StringProcessor.getHostByUrl(entry.getUrl()))) {
if (isRequestDuplicate(
messageInfo, entry.getRequestResponse(),
url, entry.getUrl(),
comment, entry.getComment(),
color, entry.getColor()
)) {
isDuplicate = true;
break;
}
} catch (Exception ignored) {
}
}
}
@@ -134,10 +136,84 @@ public class MessageTableModel extends AbstractTableModel {
}
if (!isDuplicate) {
log.add(logEntry);
if (flag) {
persistData(messageInfo, comment, color);
}
log.add(new MessageEntry(messageInfo, method, url, comment, length, color, status));
}
}
}
private boolean isRequestDuplicate(
HttpRequestResponse newReq, HttpRequestResponse existingReq,
String newUrl, String existingUrl,
String newComment, String existingComment,
String newColor, String existingColor) {
try {
// 基础属性匹配
String normalizedNewUrl = normalizeUrl(newUrl);
String normalizedExistingUrl = normalizeUrl(existingUrl);
boolean basicMatch = normalizedNewUrl.equals(normalizedExistingUrl);
// 请求响应内容匹配
byte[] newReqBytes = newReq.request().toByteArray().getBytes();
byte[] newResBytes = newReq.response().toByteArray().getBytes();
byte[] existingReqBytes = existingReq.request().toByteArray().getBytes();
byte[] existingResBytes = existingReq.response().toByteArray().getBytes();
boolean contentMatch = Arrays.equals(newReqBytes, existingReqBytes) &&
Arrays.equals(newResBytes, existingResBytes);
// 注释和颜色匹配
boolean metadataMatch = areCommentsEqual(newComment, existingComment) &&
newColor.equals(existingColor);
return (basicMatch || contentMatch) && metadataMatch;
} catch (Exception e) {
return false;
}
}
private String normalizeUrl(String url) {
if (url == null) {
return "";
}
String normalized = url.trim().toLowerCase();
while (normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
return normalized.replaceAll("//", "/");
}
private boolean areCommentsEqual(String comment1, String comment2) {
if (comment1 == null || comment2 == null) {
return false;
}
try {
// 将注释按规则拆分并排序
Set<String> rules1 = new TreeSet<>(Arrays.asList(comment1.split(", ")));
Set<String> rules2 = new TreeSet<>(Arrays.asList(comment2.split(", ")));
return rules1.equals(rules2);
} catch (Exception e) {
return false;
}
}
private void persistData(HttpRequestResponse messageInfo, String comment, String color) {
try {
DataManager dataManager = new DataManager(api);
PersistedObject persistedObject = PersistedObject.persistedObject();
persistedObject.setHttpRequestResponse("messageInfo", messageInfo);
persistedObject.setString("comment", comment);
persistedObject.setString("color", color);
String uuidIndex = StringProcessor.getRandomUUID();
dataManager.putData("message", uuidIndex, persistedObject);
} catch (Exception e) {
api.logging().logToError("Data persistence error: " + e.getMessage());
}
}
public void deleteByHost(String filterText) {
@@ -148,7 +224,7 @@ public class MessageTableModel extends AbstractTableModel {
currentWorker.cancel(true);
}
currentWorker = new SwingWorker<Void, Void>() {
currentWorker = new SwingWorker<>() {
@Override
protected Void doInBackground() {
for (int i = 0; i < log.size(); i++) {
@@ -174,137 +250,163 @@ public class MessageTableModel extends AbstractTableModel {
}
public void applyHostFilter(String filterText) {
filteredLog.clear();
// 预分配合适的容量,避免频繁扩容
final List<MessageEntry> newFilteredLog = new ArrayList<>(log.size() / 2);
log.forEach(entry -> {
MessageEntry finalEntry = getEntryByFile(entry);
String host = StringProcessor.getHostByUrl(finalEntry.getUrl());
if (!host.isEmpty()) {
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
filteredLog.add(finalEntry);
}
}
});
// 预处理过滤条件,优化性能
final boolean isWildcardFilter = "*".equals(filterText) || filterText.contains("*");
final String normalizedFilter = filterText.toLowerCase().trim();
fireTableDataChanged();
}
private MessageEntry getEntryByFile(MessageEntry entry) {
HttpRequestResponse requestResponse = entry.getRequestResponse();
if (requestResponse == null) {
String url = entry.getUrl();
String method = entry.getMethod();
String status = entry.getStatus();
String comment = entry.getComment();
String color = entry.getColor();
String path = entry.getPath();
String hash = entry.getHash();
int length = Integer.parseInt(entry.getLength());
byte[] contents = FileProcessor.readFileContent(path, hash);
if (contents.length > length) {
byte[] response = Arrays.copyOf(contents, length);
byte[] request = Arrays.copyOfRange(contents, length, contents.length);
requestResponse = StringProcessor.createHttpRequestResponse(url, request, response);
int index = log.indexOf(entry);
entry = new MessageEntry(requestResponse, method, url, comment, String.valueOf(length), color, status, "", "");
log.set(index, entry);
}
// 创建log的安全副本
final List<MessageEntry> logSnapshot;
synchronized (log) {
logSnapshot = new ArrayList<>(log);
}
return entry;
// 使用并行流高效过滤,但保持有序
logSnapshot.parallelStream()
.filter(entry -> {
// 快速通配符检查
if (isWildcardFilter && "*".equals(filterText)) {
return true;
}
try {
String host = StringProcessor.getHostByUrl(entry.getUrl());
if (host.isEmpty()) {
return false;
}
// 优化后的匹配逻辑
return StringProcessor.matchesHostPattern(host, filterText) ||
(isWildcardFilter && host.toLowerCase().contains(normalizedFilter.replace("*", "")));
} catch (Exception e) {
return false;
}
})
.forEachOrdered(newFilteredLog::add);
// 一次性更新UI避免频繁刷新
SwingUtilities.invokeLater(() -> {
synchronized (filteredLog) {
filteredLog.clear();
filteredLog.addAll(newFilteredLog);
}
fireTableDataChanged();
});
}
public void applyMessageFilter(String tableName, String filterText) {
filteredLog.clear();
for (MessageEntry entry : log) {
List<MessageEntry> newFilteredLog = new ArrayList<>();
// 创建log的安全副本以避免ConcurrentModificationException
List<MessageEntry> logSnapshot;
synchronized (log) {
logSnapshot = new ArrayList<>(log);
}
for (MessageEntry entry : logSnapshot) {
// 标志变量,表示是否满足过滤条件
AtomicBoolean isMatched = new AtomicBoolean(false);
HttpRequestResponse requestResponse = entry.getRequestResponse();
HttpRequest httpRequest = requestResponse.request();
HttpResponse httpResponse = requestResponse.response();
try {
HttpRequestResponse requestResponse = entry.getRequestResponse();
HttpRequest httpRequest = requestResponse.request();
HttpResponse httpResponse = requestResponse.response();
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
String requestHeaders = httpRequest.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\n"));
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
String requestHeaders = httpRequest.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\r\n"));
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
String responseHeaders = httpResponse.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\n"));
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
String responseHeaders = httpResponse.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\r\n"));
Config.globalRules.keySet().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) {
String name = objects[1].toString();
String format = objects[4].toString();
String scope = objects[6].toString();
Config.globalRules.keySet().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) {
String name = objects[1].toString();
String format = objects[4].toString();
String scope = objects[6].toString();
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
if (entry.getComment().contains(name)) {
if (name.equals(tableName)) {
// 标志变量,表示当前规则是否匹配
boolean isMatch = false;
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
if (entry.getComment().contains(name)) {
if (name.equals(tableName)) {
// 标志变量,表示当前规则是否匹配
boolean isMatch = false;
switch (scope) {
case "any":
isMatch = matchingString(format, filterText, requestString) || matchingString(format, filterText, responseString);
break;
case "request":
isMatch = matchingString(format, filterText, requestString);
break;
case "response":
isMatch = matchingString(format, filterText, responseString);
break;
case "any header":
isMatch = matchingString(format, filterText, requestHeaders) || matchingString(format, filterText, responseHeaders);
break;
case "request header":
isMatch = matchingString(format, filterText, requestHeaders);
break;
case "response header":
isMatch = matchingString(format, filterText, responseHeaders);
break;
case "any body":
isMatch = matchingString(format, filterText, requestBody) || matchingString(format, filterText, responseBody);
break;
case "request body":
isMatch = matchingString(format, filterText, requestBody);
break;
case "response body":
isMatch = matchingString(format, filterText, responseBody);
break;
case "request line":
String requestLine = requestString.split("\\r?\\n", 2)[0];
isMatch = matchingString(format, filterText, requestLine);
break;
case "response line":
String responseLine = responseString.split("\\r?\\n", 2)[0];
isMatch = matchingString(format, filterText, responseLine);
break;
default:
break;
switch (scope) {
case "any":
isMatch = matchingString(format, filterText, requestString) || matchingString(format, filterText, responseString);
break;
case "request":
isMatch = matchingString(format, filterText, requestString);
break;
case "response":
isMatch = matchingString(format, filterText, responseString);
break;
case "any header":
isMatch = matchingString(format, filterText, requestHeaders) || matchingString(format, filterText, responseHeaders);
break;
case "request header":
isMatch = matchingString(format, filterText, requestHeaders);
break;
case "response header":
isMatch = matchingString(format, filterText, responseHeaders);
break;
case "any body":
isMatch = matchingString(format, filterText, requestBody) || matchingString(format, filterText, responseBody);
break;
case "request body":
isMatch = matchingString(format, filterText, requestBody);
break;
case "response body":
isMatch = matchingString(format, filterText, responseBody);
break;
case "request line":
String requestLine = requestString.split("\\r?\\n", 2)[0];
isMatch = matchingString(format, filterText, requestLine);
break;
case "response line":
String responseLine = responseString.split("\\r?\\n", 2)[0];
isMatch = matchingString(format, filterText, responseLine);
break;
default:
break;
}
isMatched.set(isMatch);
break;
}
isMatched.set(isMatch);
break;
}
}
}
});
});
// 由于每个用户规则不同,如果进行项目文件共享则需要考虑全部匹配一下
if (!isMatched.get()) {
isMatched.set(matchingString("{0}", filterText, requestString) || matchingString("{0}", filterText, responseString));
}
if (isMatched.get()) {
newFilteredLog.add(entry);
}
} catch (Exception ignored) {
if (isMatched.get()) {
filteredLog.add(entry);
}
}
fireTableDataChanged();
messageTable.lastSelectedIndex = -1;
// 在EDT线程中更新UI
SwingUtilities.invokeLater(() -> {
synchronized (filteredLog) {
filteredLog.clear();
filteredLog.addAll(newFilteredLog);
}
fireTableDataChanged();
messageTable.lastSelectedIndex = -1;
});
}
private boolean matchingString(String format, String filterText, String target) {
@@ -327,56 +429,6 @@ public class MessageTableModel extends AbstractTableModel {
return isMatch;
}
private Map<String, Map<String, Object>> getCacheData(byte[] content) {
String hashIndex = HashCalculator.calculateHash(content);
return CachePool.get(hashIndex);
}
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
if (map1 == null || map2 == null) {
return false;
}
if (map1.size() != map2.size()) {
return false;
}
for (String key : map1.keySet()) {
if (!map2.containsKey(key)) {
return false;
}
if (areInnerMapsEqual(map1.get(key), map2.get(key))) {
return false;
}
}
return true;
}
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
if (innerMap1.size() != innerMap2.size()) {
return true;
}
for (String key : innerMap1.keySet()) {
if (!innerMap2.containsKey(key)) {
return true;
}
Object value1 = innerMap1.get(key);
Object value2 = innerMap2.get(key);
// 如果值是Map则递归对比
if (value1 instanceof Map && value2 instanceof Map) {
if (areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
return true;
}
} else if (!value1.equals(value2)) {
return true;
}
}
return false;
}
public JSplitPane getSplitPane() {
return splitPane;
}
@@ -385,13 +437,11 @@ public class MessageTableModel extends AbstractTableModel {
return messageTable;
}
public LinkedList<MessageEntry> getLogs() {
return log;
}
@Override
public int getRowCount() {
return filteredLog.size();
synchronized (filteredLog) {
return filteredLog.size();
}
}
@Override
@@ -401,27 +451,31 @@ public class MessageTableModel extends AbstractTableModel {
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (!filteredLog.isEmpty()) {
synchronized (filteredLog) {
if (rowIndex < 0 || rowIndex >= filteredLog.size()) {
return "";
}
try {
MessageEntry messageEntry = filteredLog.get(rowIndex);
if (messageEntry != null) {
return switch (columnIndex) {
case 0 -> messageEntry.getMethod();
case 1 -> messageEntry.getUrl();
case 2 -> messageEntry.getComment();
case 3 -> messageEntry.getStatus();
case 4 -> messageEntry.getLength();
case 5 -> messageEntry.getColor();
default -> "";
};
if (messageEntry == null) {
return "";
}
return switch (columnIndex) {
case 0 -> messageEntry.getMethod();
case 1 -> messageEntry.getUrl();
case 2 -> messageEntry.getComment();
case 3 -> messageEntry.getStatus();
case 4 -> messageEntry.getLength();
case 5 -> messageEntry.getColor();
default -> "";
};
} catch (Exception e) {
api.logging().logToError("getValueAt: " + e.getMessage());
return "";
}
}
return "";
}
@Override
@@ -438,11 +492,10 @@ public class MessageTableModel extends AbstractTableModel {
}
public class MessageTable extends JTable {
private MessageEntry messageEntry;
private final ExecutorService executorService;
private int lastSelectedIndex = -1;
private final HttpRequestEditor requestEditor;
private final HttpResponseEditor responseEditor;
private int lastSelectedIndex = -1;
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
super(messageTableModel);
@@ -462,7 +515,7 @@ public class MessageTableModel extends AbstractTableModel {
}
private void getSelectedMessage() {
messageEntry = filteredLog.get(lastSelectedIndex);
MessageEntry messageEntry = filteredLog.get(lastSelectedIndex);
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();

View File

@@ -1,157 +0,0 @@
package hae.component.board.table;
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 com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import hae.Config;
import hae.utils.ConfigLoader;
import hae.utils.http.HttpUtils;
import okhttp3.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class AIPower {
private final MontoyaApi api;
private final HttpUtils httpUtils;
private final ConfigLoader configLoader;
private final String apiAuth;
private final String aiModel;
private final String aiBaseUrl;
public AIPower(MontoyaApi api, ConfigLoader configLoader, String aiModel, String aiBaseUrl, String[] apiKey) {
this.api = api;
this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader);
this.aiModel = aiModel;
this.aiBaseUrl = aiBaseUrl;
this.apiAuth = String.format("Bearer %s", apiKey[new Random().nextInt(apiKey.length)]);
}
// Stream Response
public String chatWithAPI(String ruleName, String data) {
OkHttpClient httpClient = new OkHttpClient();
String fileId = uploadFileToAIService(ruleName, data);
Gson gson = new Gson();
if (fileId != null) {
String chatUrl = String.format("%s/chat/completions", aiBaseUrl);
String chatMessage = generateJsonData(configLoader.getAIPrompt(), fileId);
Request request = new Request.Builder()
.url(chatUrl)
.header("Authorization", apiAuth)
.post(RequestBody.create(MediaType.parse("application/json"), chatMessage))
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
BufferedReader reader = new BufferedReader(new InputStreamReader(response.body().byteStream()));
StringBuilder chatReturn = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("data: ") && !line.contains("[DONE]")) {
String jsonData = line.substring(6);
Type type = new TypeToken<Map<String, Object>>() {
}.getType();
Map<String, Object> map = gson.fromJson(jsonData, type);
String content = getDeltaContent(map);
if (content != null) {
chatReturn.append(content);
}
}
}
deleteFileOnAIService(fileId);
return chatReturn.toString();
} catch (Exception e) {
return "";
}
}
return "";
}
private String getDeltaContent(Map<String, Object> map) {
List<Map<String, Map<String, String>>> choices = (List<Map<String, Map<String, String>>>) map.get("choices");
if (choices != null && !choices.isEmpty()) {
Map<String, String> delta = choices.get(0).get("delta");
return delta.get("content");
}
return null;
}
private String uploadFileToAIService(String ruleName, String data) {
String uploadUrl = String.format("%s/files", aiBaseUrl);
String uploadParam = "file";
String filename = "hae.txt";
String content = String.format(Config.userTextFormat, ruleName, data);
HttpRequest uploadFileRequest = httpUtils.generateRequestByMultipartUploadMethod(uploadUrl, uploadParam, filename, content).withAddedHeader("Authorization", apiAuth);
HttpRequestResponse uploadFileRequestResponse = api.http().sendRequest(uploadFileRequest, RequestOptions.requestOptions().withUpstreamTLSVerification());
String responseBody = uploadFileRequestResponse.response().bodyToString();
Pattern pattern = Pattern.compile("\"id\":\"(.*?)\",");
Matcher matcher = pattern.matcher(responseBody);
return matcher.find() ? matcher.group(1) : null;
}
private void deleteFileOnAIService(String fileId) {
String deleteFileUrl = String.format("%s/files/%s", aiBaseUrl, fileId);
HttpRequest deleteFileRequest = httpUtils.generateRequestByDeleteMethod(deleteFileUrl).withAddedHeader("Authorization", apiAuth);
api.http().sendRequest(deleteFileRequest, RequestOptions.requestOptions().withUpstreamTLSVerification());
}
private String getFileContentOnAiService(String fileId) {
String getFileContentUrl = String.format("%s/files/%s/content", aiBaseUrl, fileId);
HttpRequest getFileContentRequest = HttpRequest.httpRequestFromUrl(getFileContentUrl).withAddedHeader("Authorization", apiAuth);
HttpRequestResponse getFileRequestResponse = api.http().sendRequest(getFileContentRequest, RequestOptions.requestOptions().withUpstreamTLSVerification());
String responseBody = getFileRequestResponse.response().bodyToString();
Pattern pattern = Pattern.compile("\"content\":\"(.*?)\",\"file_type\"");
Matcher matcher = pattern.matcher(responseBody);
return matcher.find() ? matcher.group(1) : null;
}
private String generateJsonData(String prompt, String fileId) {
Map<String, Object> data = new HashMap<>();
data.put("model", aiModel);
data.put("stream", true);
data.put("messages", new Object[]{
new HashMap<String, Object>() {{
put("role", "system");
put("content", prompt);
}},
new HashMap<String, Object>() {{
put("role", "system");
put("content", aiModel.equals("qwen-long") ? String.format("fileid://%s", fileId) : getFileContentOnAiService(fileId));
}},
new HashMap<String, Object>() {{
put("role", "user");
put("content", "Start");
}}
});
Gson gson = new GsonBuilder().setPrettyPrinting().create();
return gson.toJson(data);
}
}

View File

@@ -1,9 +1,6 @@
package hae.component.board.table;
import burp.api.montoya.MontoyaApi;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import hae.component.board.Databoard;
import hae.component.board.message.MessageTableModel;
import hae.utils.ConfigLoader;
import hae.utils.UIEnhancer;
@@ -11,22 +8,17 @@ import hae.utils.UIEnhancer;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
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.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class Datatable extends JPanel {
@@ -38,16 +30,15 @@ public class Datatable extends JPanel {
private final JTextField secondSearchField;
private final TableRowSorter<DefaultTableModel> sorter;
private final JCheckBox searchMode = new JCheckBox("Reverse search");
private final JCheckBox regexMode = new JCheckBox("Regex mode");
private final String tabName;
private final JProgressBar progressBar;
private final JPopupMenu aiEmpoweredMenu;
private final JPanel footerPanel;
private SwingWorker<Void, Void> doubleClickWorker;
public Datatable(MontoyaApi api, ConfigLoader configLoader, String tabName, List<String> dataList) {
this.api = api;
this.configLoader = configLoader;
this.tabName = tabName;
this.progressBar = new JProgressBar();
String[] columnNames = {"#", "Information"};
this.dataTableModel = new DefaultTableModel(columnNames, 0);
@@ -56,26 +47,16 @@ public class Datatable extends JPanel {
this.sorter = new TableRowSorter<>(dataTableModel);
this.searchField = new JTextField(10);
this.secondSearchField = new JTextField(10);
this.aiEmpoweredMenu = new JPopupMenu();
this.footerPanel = new JPanel(new BorderLayout(0, 5));
initComponents(dataList);
}
private void initComponents(List<String> dataList) {
progressBar.setVisible(false);
dataTable.setRowSorter(sorter);
// 设置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);
sorter.setComparator(0, (Comparator<Integer>) Integer::compareTo);
for (String item : dataList) {
if (!item.isEmpty()) {
@@ -125,73 +106,39 @@ public class Datatable extends JPanel {
JScrollPane scrollPane = new JScrollPane(dataTable);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
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));
JPanel settingMenuPanel = new JPanel(new GridLayout(2, 1));
settingMenuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
JPopupMenu settingMenu = new JPopupMenu();
settingMenuPanel.add(searchMode);
settingMenuPanel.add(regexMode);
regexMode.setSelected(true);
searchMode.addItemListener(e -> performSearch());
settingMenu.add(settingMenuPanel);
JButton settingsButton = new JButton("Settings");
setMenuShow(settingMenu, settingsButton);
// AI Empowered按钮
JPanel aiEmpoweredPanel = new JPanel(new GridLayout(2, 1));
aiEmpoweredPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
JButton empoweredByAlibabaButton = new JButton("Alibaba - QwenLong");
empoweredByAlibabaButton.addActionListener(e -> {
aiEmpoweredByAlibabaActionPerformed(e, tabName, getTableData(dataTable));
});
JButton empoweredByMoonshotButton = new JButton("Moonshot - Kimi");
empoweredByMoonshotButton.addActionListener(e -> {
aiEmpoweredByMoonshotActionPerformed(e, tabName, getTableData(dataTable));
});
aiEmpoweredPanel.add(empoweredByAlibabaButton);
aiEmpoweredPanel.add(empoweredByMoonshotButton);
aiEmpoweredMenu.add(aiEmpoweredPanel);
JButton aiEmpoweredButton = new JButton("AI Empowered");
setMenuShow(aiEmpoweredMenu, aiEmpoweredButton);
aiEmpoweredMenu.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
empoweredByAlibabaButton.setEnabled(!configLoader.getAlibabaAIAPIKey().isEmpty());
empoweredByMoonshotButton.setEnabled(!configLoader.getMoonshotAIAPIKey().isEmpty());
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
}
});
optionsPanel.add(settingsButton);
optionsPanel.add(Box.createHorizontalStrut(5));
optionsPanel.add(searchField);
optionsPanel.add(Box.createHorizontalStrut(5));
optionsPanel.add(secondSearchField);
optionsPanel.add(Box.createHorizontalStrut(5));
optionsPanel.add(aiEmpoweredButton);
footerPanel.setBorder(BorderFactory.createEmptyBorder(2, 3, 5, 3));
footerPanel.add(optionsPanel, BorderLayout.CENTER);
footerPanel.add(progressBar, BorderLayout.SOUTH);
add(scrollPane, BorderLayout.CENTER);
add(footerPanel, BorderLayout.SOUTH);
setProgressBar(false);
}
private void setMenuShow(JPopupMenu menu, JButton button) {
@@ -204,9 +151,6 @@ public class Datatable extends JPanel {
});
}
private void setProgressBar(boolean status) {
Databoard.setProgressBar(status, progressBar, "AI+ ...");
}
private void addRowToTable(Object[] data) {
int rowCount = dataTableModel.getRowCount();
@@ -217,114 +161,65 @@ public class Datatable extends JPanel {
dataTableModel.addRow(rowData);
}
private void aiEmpoweredByAlibabaActionPerformed(ActionEvent e, String ruleName, String data) {
AIPower aiPower = new AIPower(api, configLoader, "qwen-long", "https://dashscope.aliyuncs.com/compatible-mode/v1", configLoader.getAlibabaAIAPIKey().split("\\|"));
aiEmpoweredButtonAction(ruleName, data, aiPower);
}
private void aiEmpoweredByMoonshotActionPerformed(ActionEvent e, String ruleName, String data) {
AIPower aiPower = new AIPower(api, configLoader, "moonshot-v1-128k", "https://api.moonshot.cn/v1", configLoader.getMoonshotAIAPIKey().split("\\|"));
aiEmpoweredButtonAction(ruleName, data, aiPower);
}
private void aiEmpoweredButtonAction(String ruleName, String data, AIPower aiPower) {
progressBar.setVisible(true);
aiEmpoweredMenu.setVisible(true);
setProgressBar(true);
SwingWorker<String, Void> worker = new SwingWorker<String, Void>() {
@Override
protected String doInBackground() throws Exception {
return aiPower.chatWithAPI(ruleName, data);
}
@Override
protected void done() {
setProgressBar(false);
try {
String chatReturn = get();
if (!chatReturn.isEmpty()) {
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Object>>() {
}.getType();
Map<String, List<String>> map = gson.fromJson(chatReturn, type);
dataTableModel.setRowCount(0);
for (String item : map.get("data")) {
if (!item.isEmpty()) {
addRowToTable(new Object[]{item});
}
}
JOptionPane.showMessageDialog(Datatable.this, "AI+ has completed the AI empowered work.", "AI+ Info", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(Datatable.this, "AI+ returns null, please check!", "AI+ Info", JOptionPane.WARNING_MESSAGE);
}
} catch (Exception ignored) {
JOptionPane.showMessageDialog(Datatable.this, "AI+ returns error, please check!", "AI+ Info", JOptionPane.ERROR_MESSAGE);
}
}
};
worker.execute();
aiEmpoweredMenu.setVisible(false);
}
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));
}
List<RowFilter<Object, Object>> filters = new ArrayList<>();
if (UIEnhancer.hasUserInput(searchField)) {
filters.add(getObjectObjectRowFilter(searchField, true));
}
if (UIEnhancer.hasUserInput(secondSearchField)) {
filters.add(getObjectObjectRowFilter(secondSearchField, false));
}
sorter.setRowFilter(filters.isEmpty() ? null : RowFilter.andFilter(filters));
}
private RowFilter<Object, Object> applyFirstSearchFilter() {
return new RowFilter<Object, Object>() {
private RowFilter<Object, Object> getObjectObjectRowFilter(JTextField searchField, boolean firstFlag) {
return new RowFilter<>() {
public boolean include(Entry<?, ?> entry) {
String searchFieldTextText = searchField.getText();
Pattern pattern = null;
try {
pattern = Pattern.compile(searchFieldTextText, Pattern.CASE_INSENSITIVE);
} catch (Exception ignored) {
searchFieldTextText = searchFieldTextText.toLowerCase();
String entryValue = ((String) entry.getValue(1)).toLowerCase();
boolean filterReturn = searchFieldTextText.isEmpty();
boolean firstFlagReturn = searchMode.isSelected() && firstFlag;
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 {
filterReturn = filterReturn || entryValue.contains(searchFieldTextText) != firstFlagReturn;
}
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();
}
return filterReturn;
}
};
}
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) {
}
private void handleDoubleClick(int selectedRow, MessageTableModel messagePanel) {
if (doubleClickWorker != null && !doubleClickWorker.isDone()) {
doubleClickWorker.cancel(true);
}
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);
}
doubleClickWorker = new SwingWorker<>() {
@Override
protected Void doInBackground() {
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
SwingUtilities.invokeLater(() -> {
if (!isCancelled()) {
messagePanel.applyMessageFilter(tabName, rowData);
}
});
return null;
}
};
doubleClickWorker.execute();
}
public void setTableListener(MessageTableModel messagePanel) {
@@ -350,30 +245,13 @@ public class Datatable extends JPanel {
if (e.getClickCount() == 2) {
int selectedRow = dataTable.getSelectedRow();
if (selectedRow != -1) {
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
messagePanel.applyMessageFilter(tabName, rowData);
handleDoubleClick(selectedRow, messagePanel);
}
}
}
});
}
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();

View File

@@ -7,9 +7,13 @@ import hae.utils.rule.RuleProcessor;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Vector;
import static javax.swing.JOptionPane.YES_OPTION;
@@ -19,6 +23,7 @@ public class Rule extends JPanel {
private final ConfigLoader configLoader;
private final RuleProcessor ruleProcessor;
private final JTabbedPane tabbedPane;
private JCheckBox headerCheckBox;
public Rule(MontoyaApi api, ConfigLoader configLoader, Object[][] data, JTabbedPane tabbedPane) {
this.api = api;
@@ -36,6 +41,7 @@ public class Rule extends JPanel {
((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 1.0, 1.0E-4};
((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 0.0, 0.0, 1.0, 1.0E-4};
JButton copyButton = new JButton("Copy");
JButton addButton = new JButton("Add");
JButton editButton = new JButton("Edit");
JButton removeButton = new JButton("Remove");
@@ -43,14 +49,13 @@ public class Rule extends JPanel {
JTable ruleTable = new JTable();
JScrollPane scrollPane = new JScrollPane();
ruleTable.setShowVerticalLines(false);
ruleTable.setShowHorizontalLines(false);
ruleTable.setVerifyInputWhenFocusTarget(false);
ruleTable.setUpdateSelectionOnSort(false);
ruleTable.setSurrendersFocusOnKeystroke(true);
scrollPane.setViewportView(ruleTable);
// 按钮监听事件
copyButton.addActionListener(e -> ruleCopyActionPerformed(e, ruleTable, tabbedPane));
addButton.addActionListener(e -> ruleAddActionPerformed(e, ruleTable, tabbedPane));
editButton.addActionListener(e -> ruleEditActionPerformed(e, ruleTable, tabbedPane));
removeButton.addActionListener(e -> ruleRemoveActionPerformed(e, ruleTable, tabbedPane));
@@ -76,88 +81,334 @@ public class Rule extends JPanel {
if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1) {
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
// 更新表头复选框状态并强制重新渲染
updateHeaderCheckBoxState(model);
ruleTable.getTableHeader().repaint();
}
});
add(addButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
// 设置表头复选框
setupHeaderCheckBox(ruleTable);
// 设置Loaded列的宽度第一列
setupColumnWidths(ruleTable);
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx = 1.0;
constraints.fill = GridBagConstraints.HORIZONTAL;
JPanel buttonPanel = new JPanel();
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);
constraints.insets = new Insets(0, 0, 3, 0);
constraints.gridy = 0;
buttonPanel.add(copyButton, constraints);
constraints.gridy = 1;
buttonPanel.add(addButton, constraints);
constraints.gridy = 2;
buttonPanel.add(editButton, constraints);
constraints.gridy = 3;
buttonPanel.add(removeButton, constraints);
add(buttonPanel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(15, 5, 3, 2), 0, 0));
add(editButton, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(0, 5, 3, 2), 0, 0));
add(removeButton, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(0, 5, 3, 2), 0, 0));
add(scrollPane, new GridBagConstraints(1, 0, 1, 4, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(15, 5, 5, 5), 0, 0));
}
/**
* 设置列宽度
*/
private void setupColumnWidths(JTable ruleTable) {
// 设置Loaded列第一列的宽度
ruleTable.getColumnModel().getColumn(0).setPreferredWidth(50);
ruleTable.getColumnModel().getColumn(0).setMaxWidth(50);
ruleTable.getColumnModel().getColumn(0).setMinWidth(50);
}
/**
* 设置表头复选框
*/
private void setupHeaderCheckBox(JTable ruleTable) {
// 创建表头复选框
headerCheckBox = new JCheckBox();
headerCheckBox.setHorizontalAlignment(SwingConstants.CENTER);
// 设置表头渲染器
ruleTable.getTableHeader().setDefaultRenderer(new HeaderCheckBoxRenderer(ruleTable.getTableHeader().getDefaultRenderer()));
// 添加表头鼠标点击事件
ruleTable.getTableHeader().addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
JTableHeader header = (JTableHeader) e.getSource();
JTable table = header.getTable();
int columnIndex = header.columnAtPoint(e.getPoint());
if (columnIndex == 0) { // 点击的是Loaded列表头
toggleAllRules(table);
}
}
}
});
}
/**
* 自定义表头渲染器在Loaded列显示复选框
*/
private class HeaderCheckBoxRenderer implements TableCellRenderer {
private final TableCellRenderer originalRenderer;
public HeaderCheckBoxRenderer(TableCellRenderer originalRenderer) {
this.originalRenderer = originalRenderer;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (column == 0) { // Loaded列
// 获取原始表头组件作为背景
Component originalComponent = originalRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
// 创建一个面板来包含复选框,保持原始样式
JPanel panel = new JPanel(new BorderLayout());
panel.setOpaque(true);
// 复制原始组件的样式
if (originalComponent instanceof JComponent origComp) {
panel.setBackground(origComp.getBackground());
panel.setBorder(origComp.getBorder());
}
// 更新复选框状态并添加到面板中心
updateHeaderCheckBoxState((DefaultTableModel) table.getModel());
headerCheckBox.setOpaque(false); // 让复选框透明,显示背景
panel.add(headerCheckBox, BorderLayout.CENTER);
return panel;
} else {
return originalRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}
}
/**
* 切换所有规则的开启/关闭状态
*/
private void toggleAllRules(JTable ruleTable) {
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
int rowCount = model.getRowCount();
if (rowCount == 0) {
return;
}
// 判断当前状态:如果所有规则都开启,则关闭所有;否则开启所有
boolean allEnabled = true;
for (int i = 0; i < rowCount; i++) {
if (!(Boolean) model.getValueAt(i, 0)) {
allEnabled = false;
break;
}
}
boolean newState = !allEnabled;
// 更新所有行的状态
for (int i = 0; i < rowCount; i++) {
model.setValueAt(newState, i, 0);
// 通知规则处理器更新规则状态
ruleProcessor.changeRule(model.getDataVector().get(i), i, getCurrentTabTitle());
}
// 更新表头复选框状态
updateHeaderCheckBoxState(model);
// 刷新表格和表头
ruleTable.repaint();
ruleTable.getTableHeader().repaint();
}
/**
* 更新表头复选框的状态
*/
private void updateHeaderCheckBoxState(DefaultTableModel model) {
int rowCount = model.getRowCount();
if (rowCount == 0) {
headerCheckBox.setSelected(false);
headerCheckBox.getModel().setArmed(false);
headerCheckBox.getModel().setPressed(false);
return;
}
int enabledCount = 0;
for (int i = 0; i < rowCount; i++) {
if ((Boolean) model.getValueAt(i, 0)) {
enabledCount++;
}
}
if (enabledCount == 0) {
// 全部未选中
headerCheckBox.setSelected(false);
headerCheckBox.getModel().setArmed(false);
headerCheckBox.getModel().setPressed(false);
} else if (enabledCount == rowCount) {
// 全部选中
headerCheckBox.setSelected(true);
headerCheckBox.getModel().setArmed(false);
headerCheckBox.getModel().setPressed(false);
} else {
// 部分选中 - 显示为按下但未选中的状态
headerCheckBox.setSelected(false);
headerCheckBox.getModel().setArmed(true);
headerCheckBox.getModel().setPressed(true);
}
}
/**
* 填充Display对象的字段值
*/
private void populateDisplayFromTable(Display ruleDisplay, JTable ruleTable, int selectedRow) {
ruleDisplay.ruleNameTextField.setText(ruleTable.getValueAt(selectedRow, 1).toString());
ruleDisplay.firstRegexTextField.setText(ruleTable.getValueAt(selectedRow, 2).toString());
ruleDisplay.secondRegexTextField.setText(ruleTable.getValueAt(selectedRow, 3).toString());
ruleDisplay.formatTextField.setText(ruleTable.getValueAt(selectedRow, 4).toString());
ruleDisplay.colorComboBox.setSelectedItem(ruleTable.getValueAt(selectedRow, 5).toString());
ruleDisplay.scopeComboBox.setSelectedItem(ruleTable.getValueAt(selectedRow, 6).toString());
ruleDisplay.engineComboBox.setSelectedItem(ruleTable.getValueAt(selectedRow, 7).toString());
ruleDisplay.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(selectedRow, 8));
}
/**
* 从Display对象创建规则数据Vector
*/
private Vector<Object> createRuleDataFromDisplay(Display ruleDisplay) {
Vector<Object> ruleData = new Vector<>();
ruleData.add(false);
ruleData.add(ruleDisplay.ruleNameTextField.getText());
ruleData.add(ruleDisplay.firstRegexTextField.getText());
ruleData.add(ruleDisplay.secondRegexTextField.getText());
ruleData.add(ruleDisplay.formatTextField.getText());
ruleData.add(ruleDisplay.colorComboBox.getSelectedItem().toString());
ruleData.add(ruleDisplay.scopeComboBox.getSelectedItem().toString());
ruleData.add(ruleDisplay.engineComboBox.getSelectedItem().toString());
ruleData.add(ruleDisplay.sensitiveComboBox.getSelectedItem());
return ruleData;
}
/**
* 显示规则编辑对话框
*/
private boolean showRuleDialog(Display ruleDisplay, String title) {
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, title, JOptionPane.YES_NO_OPTION);
return showState == YES_OPTION;
}
/**
* 检查是否有选中的行
*/
private boolean hasSelectedRow(JTable ruleTable) {
return ruleTable.getSelectedRowCount() >= 1;
}
/**
* 获取当前选中的Tab标题
*/
private String getCurrentTabTitle() {
return tabbedPane.getTitleAt(tabbedPane.getSelectedIndex());
}
private void ruleCopyActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
if (!hasSelectedRow(ruleTable)) {
return;
}
Display ruleDisplay = new Display();
int selectedRow = ruleTable.getSelectedRow();
populateDisplayFromTable(ruleDisplay, ruleTable, selectedRow);
// 为复制的规则名称添加前缀
ruleDisplay.ruleNameTextField.setText(String.format("Copy of %s", ruleDisplay.ruleNameTextField.getText()));
if (showRuleDialog(ruleDisplay, "Copy Rule")) {
Vector<Object> ruleData = createRuleDataFromDisplay(ruleDisplay);
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
model.insertRow(model.getRowCount(), ruleData);
ruleProcessor.addRule(ruleData, getCurrentTabTitle());
// 复制规则后更新表头复选框状态
updateHeaderCheckBoxState(model);
ruleTable.getTableHeader().repaint();
}
}
private void ruleAddActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
Display ruleDisplay = new Display();
ruleDisplay.formatTextField.setText("{0}");
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
if (showState == YES_OPTION) {
Vector<Object> ruleData = new Vector<>();
ruleData.add(false);
ruleData.add(ruleDisplay.ruleNameTextField.getText());
ruleData.add(ruleDisplay.firstRegexTextField.getText());
ruleData.add(ruleDisplay.secondRegexTextField.getText());
ruleData.add(ruleDisplay.formatTextField.getText());
ruleData.add(ruleDisplay.colorComboBox.getSelectedItem().toString());
ruleData.add(ruleDisplay.scopeComboBox.getSelectedItem().toString());
ruleData.add(ruleDisplay.engineComboBox.getSelectedItem().toString());
ruleData.add(ruleDisplay.sensitiveComboBox.getSelectedItem());
if (showRuleDialog(ruleDisplay, "Add Rule")) {
Vector<Object> ruleData = createRuleDataFromDisplay(ruleDisplay);
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
model.insertRow(model.getRowCount(), ruleData);
ruleProcessor.addRule(ruleData, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
ruleProcessor.addRule(ruleData, getCurrentTabTitle());
// 添加规则后更新表头复选框状态
updateHeaderCheckBoxState(model);
ruleTable.getTableHeader().repaint();
}
}
private void ruleEditActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
if (ruleTable.getSelectedRowCount() >= 1) {
if (!hasSelectedRow(ruleTable)) {
return;
}
Display ruleDisplay = new Display();
int selectedRow = ruleTable.getSelectedRow();
populateDisplayFromTable(ruleDisplay, ruleTable, selectedRow);
if (showRuleDialog(ruleDisplay, "Edit Rule")) {
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
Display ruleDisplay = new Display();
int modelIndex = ruleTable.convertRowIndexToModel(selectedRow);
ruleDisplay.ruleNameTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 1).toString());
ruleDisplay.firstRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 2).toString());
ruleDisplay.secondRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 3).toString());
ruleDisplay.formatTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 4).toString());
ruleDisplay.colorComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 5).toString());
ruleDisplay.scopeComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 6).toString());
ruleDisplay.engineComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 7).toString());
ruleDisplay.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 8));
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
if (showState == 0) {
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
model.setValueAt(ruleDisplay.firstRegexTextField.getText(), select, 2);
model.setValueAt(ruleDisplay.secondRegexTextField.getText(), select, 3);
model.setValueAt(ruleDisplay.formatTextField.getText(), select, 4);
model.setValueAt(ruleDisplay.colorComboBox.getSelectedItem().toString(), select, 5);
model.setValueAt(ruleDisplay.scopeComboBox.getSelectedItem().toString(), select, 6);
model.setValueAt(ruleDisplay.engineComboBox.getSelectedItem().toString(), select, 7);
model.setValueAt(ruleDisplay.sensitiveComboBox.getSelectedItem(), select, 8);
model = (DefaultTableModel) ruleTable.getModel();
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
// 更新表格数据
Vector<Object> ruleData = createRuleDataFromDisplay(ruleDisplay);
for (int i = 1; i < ruleData.size(); i++) {
model.setValueAt(ruleData.get(i), modelIndex, i);
}
ruleProcessor.changeRule(model.getDataVector().get(modelIndex), modelIndex, getCurrentTabTitle());
// 编辑规则后更新表头复选框状态(如果编辑影响了启用状态)
updateHeaderCheckBoxState(model);
ruleTable.getTableHeader().repaint();
}
}
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
if (ruleTable.getSelectedRowCount() >= 1) {
if (JOptionPane.showConfirmDialog(this, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
if (!hasSelectedRow(ruleTable)) {
return;
}
model.removeRow(select);
ruleProcessor.removeRule(select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
}
if (JOptionPane.showConfirmDialog(this, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
model.removeRow(select);
ruleProcessor.removeRule(select, getCurrentTabTitle());
// 删除规则后更新表头复选框状态
updateHeaderCheckBoxState(model);
ruleTable.getTableHeader().repaint();
}
}
}

View File

@@ -11,12 +11,41 @@ import java.awt.event.*;
public class Rules extends JTabbedPane {
private final MontoyaApi api;
private ConfigLoader configLoader;
private final RuleProcessor ruleProcessor;
private final JTextField ruleGroupNameTextField;
private ConfigLoader configLoader;
private Component tabComponent;
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) {
this.api = api;
@@ -30,8 +59,6 @@ public class Rules extends JTabbedPane {
private void initComponents() {
reloadRuleGroup();
JTabbedPane tabbedPane = this;
JMenuItem deleteMenuItem = new JMenuItem("Delete");
JPopupMenu popupMenu = new JPopupMenu();
popupMenu.add(deleteMenuItem);
@@ -49,43 +76,48 @@ public class Rules extends JTabbedPane {
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
int index = getSelectedIndex();
Rectangle r = getBoundsAt(index);
if (r.contains(e.getPoint()) && index >= 0) {
switch (e.getButton()) {
case MouseEvent.BUTTON1:
if (e.getClickCount() == 2) {
selectedIndex = index;
tabComponent = getTabComponentAt(selectedIndex);
String ruleGroupName = getTitleAt(selectedIndex);
int index = indexAtLocation(e.getX(), e.getY());
if (index < 0) {
return;
}
if (!"...".equals(ruleGroupName)) {
setTabComponentAt(selectedIndex, ruleGroupNameTextField);
ruleGroupNameTextField.setVisible(true);
ruleGroupNameTextField.setText(ruleGroupName);
ruleGroupNameTextField.selectAll();
ruleGroupNameTextField.requestFocusInWindow();
ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize());
}
} else if (e.getClickCount() == 1) {
if ("...".equals(getTitleAt(getSelectedIndex()))) {
String title = ruleProcessor.newRule();
Rule newRule = new Rule(api, configLoader, Config.ruleTemplate, tabbedPane);
insertTab(title, null, newRule, null, getTabCount() - 1);
setSelectedIndex(getTabCount() - 2);
} else {
renameTitleActionPerformed.actionPerformed(null);
}
switch (e.getButton()) {
case MouseEvent.BUTTON1:
if (e.getClickCount() == 2) {
selectedIndex = index;
tabComponent = getTabComponentAt(selectedIndex);
String ruleGroupName = getTitleAt(selectedIndex);
if (!"...".equals(ruleGroupName)) {
setTabComponentAt(selectedIndex, ruleGroupNameTextField);
ruleGroupNameTextField.setVisible(true);
ruleGroupNameTextField.setText(ruleGroupName);
ruleGroupNameTextField.selectAll();
ruleGroupNameTextField.requestFocusInWindow();
ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize());
}
break;
case MouseEvent.BUTTON3:
if (!"...".equals(getTitleAt(getSelectedIndex()))) {
popupMenu.show(e.getComponent(), e.getX(), e.getY());
} else if (e.getClickCount() == 1) {
String title = getTitleAt(index);
if ("...".equals(title)) {
// 阻止默认的选中行为
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 +151,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();
}
}
};
}

View File

@@ -31,6 +31,28 @@ public class RequestEditor implements HttpRequestEditorProvider {
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
public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) {
return new Editor(api, configLoader, editorCreationContext);
@@ -42,17 +64,16 @@ public class RequestEditor implements HttpRequestEditorProvider {
private final HttpUtils httpUtils;
private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor;
private final JTabbedPane jTabbedPane = new JTabbedPane();
private HttpRequestResponse requestResponse;
private List<Map<String, String>> dataList;
private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api;
this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader);
this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api);
this.messageProcessor = new MessageProcessor(api, configLoader);
}
@Override
@@ -118,26 +139,4 @@ public class RequestEditor implements HttpRequestEditorProvider {
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);
});
}
}
}
}

View File

@@ -41,17 +41,16 @@ public class ResponseEditor implements HttpResponseEditorProvider {
private final HttpUtils httpUtils;
private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor;
private final JTabbedPane jTabbedPane = new JTabbedPane();
private HttpRequestResponse requestResponse;
private List<Map<String, String>> dataList;
private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api;
this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader);
this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api);
this.messageProcessor = new MessageProcessor(api, configLoader);
}
@Override

View File

@@ -36,16 +36,15 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
private final ConfigLoader configLoader;
private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor;
private final JTabbedPane jTabbedPane = new JTabbedPane();
private ByteArray message;
private List<Map<String, String>> dataList;
private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api;
this.configLoader = configLoader;
this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api);
this.messageProcessor = new MessageProcessor(api, configLoader);
}
@Override

View File

@@ -17,7 +17,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpMessageHandler implements HttpHandler {
public class HttpMessageActiveHandler implements HttpHandler {
private final MontoyaApi api;
private final ConfigLoader configLoader;
private final HttpUtils httpUtils;
@@ -30,12 +30,12 @@ public class HttpMessageHandler implements HttpHandler {
private final ThreadLocal<List<String>> colorList = ThreadLocal.withInitial(ArrayList::new);
private final ThreadLocal<List<String>> commentList = ThreadLocal.withInitial(ArrayList::new);
public HttpMessageHandler(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
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);
this.messageProcessor = new MessageProcessor(api, configLoader);
}
@Override
@@ -68,25 +68,23 @@ public class HttpMessageHandler implements HttpHandler {
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);
HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(request, httpResponseReceived);
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, "", "");
messageTableModel.add(httpRequestResponse, url, method, status, length, comment, color, true);
return null;
}
}.execute();

View File

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

View File

@@ -5,28 +5,26 @@ import burp.api.montoya.http.message.HttpHeader;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
import hae.Config;
import hae.utils.ConfigLoader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
public class MessageProcessor {
private final MontoyaApi api;
private final RegularMatcher regularMatcher;
private String finalColor = "";
public MessageProcessor(MontoyaApi api) {
public MessageProcessor(MontoyaApi api, ConfigLoader configLoader) {
this.api = api;
this.regularMatcher = new RegularMatcher(api);
this.regularMatcher = new RegularMatcher(api, configLoader);
}
public List<Map<String, String>> processMessage(String host, String message, boolean flag) {
Map<String, Map<String, Object>> obj = null;
try {
obj = regularMatcher.match(host, "any", message, message, message);
obj = regularMatcher.performRegexMatching(host, "any", message, message, message);
} catch (Exception ignored) {
}
@@ -41,9 +39,9 @@ public class MessageProcessor {
String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
String header = httpResponse.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\n"));
.collect(Collectors.joining("\r\n"));
obj = regularMatcher.match(host, "response", response, header, body);
obj = regularMatcher.performRegexMatching(host, "response", response, header, body);
} catch (Exception ignored) {
}
@@ -58,9 +56,9 @@ public class MessageProcessor {
String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
String header = httpRequest.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\n"));
.collect(Collectors.joining("\r\n"));
obj = regularMatcher.match(host, "request", request, header, body);
obj = regularMatcher.performRegexMatching(host, "request", request, header, body);
} catch (Exception ignored) {
}
@@ -78,10 +76,10 @@ public class MessageProcessor {
List<String> commentList = resultList.get(1);
if (!colorList.isEmpty() && !commentList.isEmpty()) {
String color = retrieveFinalColor(retrieveColorIndices(colorList));
Map<String, String> colorMap = new HashMap<String, String>() {{
Map<String, String> colorMap = new HashMap<>() {{
put("color", color);
}};
Map<String, String> commentMap = new HashMap<String, String>() {{
Map<String, String> commentMap = new HashMap<>() {{
put("comment", String.join(", ", commentList));
}};
highlightList.add(colorMap);
@@ -138,41 +136,38 @@ public class MessageProcessor {
return indices;
}
private void upgradeColors(List<Integer> colorList) {
int colorSize = colorList.size();
String[] colorArray = Config.color;
colorList.sort(Comparator.comparingInt(Integer::intValue));
int i = 0;
List<Integer> stack = new ArrayList<>();
while (i < colorSize) {
if (stack.isEmpty()) {
stack.add(colorList.get(i));
} else {
if (!Objects.equals(colorList.get(i), stack.stream().reduce((first, second) -> second).orElse(99999999))) {
stack.add(colorList.get(i));
} else {
stack.set(stack.size() - 1, stack.get(stack.size() - 1) - 1);
}
}
i++;
private String upgradeColors(List<Integer> colorList) {
if (colorList == null || colorList.isEmpty()) {
return Config.color[0];
}
// 利用HashSet删除重复元素
HashSet tmpList = new HashSet(stack);
if (stack.size() == tmpList.size()) {
stack.sort(Comparator.comparingInt(Integer::intValue));
if (stack.get(0) < 0) {
finalColor = colorArray[0];
} else {
finalColor = colorArray[stack.get(0)];
// 创建副本避免修改原始数据
List<Integer> indices = new ArrayList<>(colorList);
indices.sort(Comparator.comparingInt(Integer::intValue));
// 处理颜色升级
for (int i = 1; i < indices.size(); i++) {
if (indices.get(i).equals(indices.get(i - 1))) {
// 如果发现重复的颜色索引,将当前索引降级
indices.set(i - 1, indices.get(i - 1) - 1);
}
} else {
upgradeColors(stack);
}
// 获取最终的颜色索引
int finalIndex = indices.stream()
.min(Integer::compareTo)
.orElse(0);
// 处理负数索引情况
if (finalIndex < 0) {
return Config.color[0];
}
return Config.color[finalIndex];
}
public String retrieveFinalColor(List<Integer> colorList) {
upgradeColors(colorList);
return finalColor;
return upgradeColors(colorList);
}
}

View File

@@ -1,12 +1,16 @@
package hae.instances.http.utils;
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.AutomatonMatcher;
import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton;
import hae.Config;
import hae.cache.CachePool;
import hae.cache.DataCache;
import hae.utils.ConfigLoader;
import hae.utils.DataManager;
import hae.utils.string.HashCalculator;
import hae.utils.string.StringProcessor;
@@ -17,98 +21,18 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegularMatcher {
private static final Map<String, Pattern> nfaPatternCache = new ConcurrentHashMap<>();
private static final Map<String, RunAutomaton> dfaAutomatonCache = new ConcurrentHashMap<>();
private static final Pattern formatIndexPattern = Pattern.compile("\\{(\\d+)}");
private final MontoyaApi api;
private final ConfigLoader configLoader;
public RegularMatcher(MontoyaApi api) {
public RegularMatcher(MontoyaApi api, ConfigLoader configLoader) {
this.api = api;
this.configLoader = configLoader;
}
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
// 先从缓存池里判断是否有已经匹配好的结果
String messageIndex = HashCalculator.calculateHash(message.getBytes());
Map<String, Map<String, Object>> map = CachePool.get(messageIndex);
if (map != null) {
return map;
} else {
// 最终返回的结果
Map<String, Map<String, Object>> finalMap = new HashMap<>();
Config.globalRules.keySet().parallelStream().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) {
// 多线程执行,一定程度上减少阻塞现象
String matchContent = "";
// 遍历获取规则
List<String> result = new ArrayList<>();
Map<String, Object> tmpMap = new HashMap<>();
boolean loaded = (Boolean) objects[0];
String name = objects[1].toString();
String f_regex = objects[2].toString();
String s_regex = objects[3].toString();
String format = objects[4].toString();
String color = objects[5].toString();
String scope = objects[6].toString();
String engine = objects[7].toString();
boolean sensitive = (Boolean) objects[8];
// 判断规则是否开启与作用域
if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) {
switch (scope) {
case "any":
case "request":
case "response":
matchContent = message;
break;
case "any header":
case "request header":
case "response header":
matchContent = header;
break;
case "any body":
case "request body":
case "response body":
matchContent = body;
break;
case "request line":
case "response line":
matchContent = message.split("\\r?\\n", 2)[0];
break;
default:
break;
}
try {
result.addAll(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive));
} catch (Exception e) {
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
api.logging().logToError(e.getMessage());
continue;
}
// 去除重复内容
HashSet tmpList = new HashSet(result);
result.clear();
result.addAll(tmpList);
if (!result.isEmpty()) {
tmpMap.put("color", color);
String dataStr = String.join(Config.boundary, result);
tmpMap.put("data", dataStr);
String nameAndSize = String.format("%s (%s)", name, result.size());
finalMap.put(nameAndSize, tmpMap);
putDataToGlobalMap(host, name, result);
}
}
}
});
CachePool.put(messageIndex, finalMap);
return finalMap;
}
}
public static void putDataToGlobalMap(String host, String name, List<String> dataList) {
public synchronized static void updateGlobalMatchCache(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
// 添加到全局变量中便于Databoard检索
if (!Objects.equals(host, "") && host != null) {
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
@@ -120,6 +44,21 @@ public class RegularMatcher {
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;
});
@@ -128,7 +67,7 @@ public class RegularMatcher {
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实际数据从查询哪里将所有数据提取
Config.globalDataMap.put(anyHost, new HashMap<>());
}
@@ -140,11 +79,123 @@ public class RegularMatcher {
}
}
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
public Map<String, Map<String, Object>> performRegexMatching(String host, String type, String message, String header, String body) {
// 删除动态响应头再进行存储
String originalMessage = message;
String dynamicHeader = configLoader.getDynamicHeader();
if (!dynamicHeader.isBlank()) {
String modifiedHeader = header.replaceAll(String.format("(%s):.*?\r\n", configLoader.getDynamicHeader()), "");
message = message.replace(header, modifiedHeader);
}
String messageIndex = HashCalculator.calculateHash(message.getBytes());
// 从数据缓存中读取
Map<String, Map<String, Object>> dataCacheMap = DataCache.get(messageIndex);
// 存在则返回
if (dataCacheMap != null) {
return dataCacheMap;
}
// 最终返回的结果
String firstLine = originalMessage.split("\\r?\\n", 2)[0];
Map<String, Map<String, Object>> finalMap = applyMatchingRules(host, type, originalMessage, firstLine, header, body);
// 数据缓存写入,有可能是空值,当作匹配过的索引不再匹配
DataCache.put(messageIndex, finalMap);
return finalMap;
}
private Map<String, Map<String, Object>> applyMatchingRules(String host, String type, String message, String firstLine, String header, String body) {
Map<String, Map<String, Object>> finalMap = new HashMap<>();
Config.globalRules.keySet().parallelStream().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) {
String matchContent = "";
// 遍历获取规则
List<String> result;
Map<String, Object> tmpMap = new HashMap<>();
boolean loaded = (Boolean) objects[0];
String name = objects[1].toString();
String f_regex = objects[2].toString();
String s_regex = objects[3].toString();
String format = objects[4].toString();
String color = objects[5].toString();
String scope = objects[6].toString();
String engine = objects[7].toString();
boolean sensitive = (Boolean) objects[8];
// 判断规则是否开启与作用域
if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) {
// 在此处检查内容是否缓存,缓存则返回为空
switch (scope) {
case "any":
case "request":
case "response":
matchContent = message;
break;
case "any header":
case "request header":
case "response header":
matchContent = header;
break;
case "any body":
case "request body":
case "response body":
matchContent = body;
break;
case "request line":
case "response line":
matchContent = firstLine;
break;
default:
break;
}
// 匹配内容为空则跳出
if (matchContent.isBlank()) {
break;
}
try {
result = new ArrayList<>(executeRegexEngine(f_regex, s_regex, matchContent, format, engine, sensitive));
} catch (Exception e) {
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
api.logging().logToError(e.getMessage());
continue;
}
// 去除重复内容
HashSet tmpList = new HashSet(result);
result.clear();
result.addAll(tmpList);
if (!result.isEmpty()) {
tmpMap.put("color", color);
String dataStr = String.join(Config.boundary, result);
tmpMap.put("data", dataStr);
String nameAndSize = String.format("%s (%s)", name, result.size());
finalMap.put(nameAndSize, tmpMap);
updateGlobalMatchCache(api, host, name, result, true);
}
}
}
});
return finalMap;
}
private List<String> executeRegexEngine(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
List<String> retList = new ArrayList<>();
if ("nfa".equals(engine)) {
Matcher matcher = createPatternMatcher(f_regex, content, sensitive);
retList.addAll(extractMatches(s_regex, format, sensitive, matcher));
retList.addAll(extractRegexMatchResults(s_regex, format, sensitive, matcher));
} else {
// DFA不支持格式化输出因此不关注format
String newContent = content;
@@ -154,47 +205,58 @@ public class RegularMatcher {
newFirstRegex = f_regex.toLowerCase();
}
AutomatonMatcher autoMatcher = createAutomatonMatcher(newFirstRegex, newContent);
retList.addAll(extractMatches(s_regex, autoMatcher, content));
retList.addAll(extractRegexMatchResults(s_regex, autoMatcher, content));
}
return retList;
}
private List<String> extractMatches(String s_regex, String format, boolean sensitive, Matcher matcher) {
private List<String> extractRegexMatchResults(String s_regex, String format, boolean sensitive, Matcher matcher) {
List<String> matches = new ArrayList<>();
if (s_regex.isEmpty()) {
matches.addAll(getFormatString(matcher, format));
matches.addAll(formatMatchResults(matcher, format));
} else {
while (matcher.find()) {
String matchContent = matcher.group(1);
if (!matchContent.isEmpty()) {
matcher = createPatternMatcher(s_regex, matchContent, sensitive);
matches.addAll(getFormatString(matcher, format));
Matcher secondMatcher = createPatternMatcher(s_regex, matchContent, sensitive);
matches.addAll(formatMatchResults(secondMatcher, format));
}
}
}
return matches;
}
private List<String> extractMatches(String s_regex, AutomatonMatcher autoMatcher, String content) {
private List<String> extractRegexMatchResults(String s_regex, AutomatonMatcher autoMatcher, String content) {
List<String> matches = new ArrayList<>();
if (s_regex.isEmpty()) {
matches.addAll(getFormatString(autoMatcher, content));
matches.addAll(formatMatchResults(autoMatcher, content));
} else {
while (autoMatcher.find()) {
String s = autoMatcher.group();
if (!s.isEmpty()) {
autoMatcher = createAutomatonMatcher(s_regex, getSubString(content, s));
matches.addAll(getFormatString(autoMatcher, content));
autoMatcher = createAutomatonMatcher(s_regex, extractMatchedContent(content, s));
matches.addAll(formatMatchResults(autoMatcher, content));
}
}
}
return matches;
}
private List<String> getFormatString(Matcher matcher, String format) {
List<Integer> indexList = parseIndexesFromString(format);
private List<String> formatMatchResults(Matcher matcher, String format) {
List<String> stringList = new ArrayList<>();
// 当format为{0}时,直接返回第一个捕获组,避免格式化开销
if ("{0}".equals(format)) {
while (matcher.find()) {
if (matcher.groupCount() > 0 && !matcher.group(1).isEmpty()) {
stringList.add(matcher.group(1));
}
}
return stringList;
}
// 需要复杂格式化的情况
List<Integer> indexList = parseIndexesFromString(format);
while (matcher.find()) {
if (!matcher.group(1).isEmpty()) {
Object[] params = indexList.stream().map(i -> {
@@ -204,20 +266,20 @@ public class RegularMatcher {
return "";
}).toArray();
stringList.add(MessageFormat.format(reorderIndex(format), params));
stringList.add(MessageFormat.format(normalizeFormatIndexes(format), params));
}
}
return stringList;
}
private List<String> getFormatString(AutomatonMatcher matcher, String content) {
private List<String> formatMatchResults(AutomatonMatcher matcher, String content) {
List<String> stringList = new ArrayList<>();
while (matcher.find()) {
String s = matcher.group(0);
if (!s.isEmpty()) {
stringList.add(getSubString(content, s));
stringList.add(extractMatchedContent(content, s));
}
}
@@ -225,21 +287,27 @@ public class RegularMatcher {
}
private Matcher createPatternMatcher(String regex, String content, boolean sensitive) {
Pattern pattern = sensitive ? Pattern.compile(regex) : Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Pattern pattern = nfaPatternCache.computeIfAbsent(regex, k -> {
int flags = sensitive ? 0 : Pattern.CASE_INSENSITIVE;
return Pattern.compile(regex, flags);
});
return pattern.matcher(content);
}
private AutomatonMatcher createAutomatonMatcher(String regex, String content) {
RegExp regexp = new RegExp(regex);
Automaton auto = regexp.toAutomaton();
RunAutomaton runAuto = new RunAutomaton(auto, true);
RunAutomaton runAuto = dfaAutomatonCache.computeIfAbsent(regex, k -> {
RegExp regexp = new RegExp(regex);
Automaton auto = regexp.toAutomaton();
return new RunAutomaton(auto, true);
});
return runAuto.newMatcher(content);
}
private LinkedList<Integer> parseIndexesFromString(String input) {
LinkedList<Integer> indexes = new LinkedList<>();
Pattern pattern = Pattern.compile("\\{(\\d+)}");
Matcher matcher = pattern.matcher(input);
Matcher matcher = formatIndexPattern.matcher(input);
while (matcher.find()) {
String index = matcher.group(1);
@@ -251,17 +319,17 @@ public class RegularMatcher {
return indexes;
}
private String getSubString(String content, String s) {
private String extractMatchedContent(String content, String s) {
byte[] contentByte = api.utilities().byteUtils().convertFromString(content);
byte[] sByte = api.utilities().byteUtils().convertFromString(s);
int startIndex = api.utilities().byteUtils().indexOf(contentByte, sByte, false, 1, contentByte.length);
int endIndex = startIndex + s.length();
return content.substring(startIndex, endIndex);
}
private String reorderIndex(String format) {
Pattern pattern = Pattern.compile("\\{(\\d+)}");
Matcher matcher = pattern.matcher(format);
private String normalizeFormatIndexes(String format) {
Matcher matcher = formatIndexPattern.matcher(format);
int count = 0;
while (matcher.find()) {
String newStr = String.format("{%s}", count);
@@ -269,6 +337,7 @@ public class RegularMatcher {
format = format.replace(matchStr, newStr);
count++;
}
return format;
}
}

View File

@@ -4,6 +4,7 @@ import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.HighlightColor;
import burp.api.montoya.proxy.websocket.*;
import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader;
import java.util.List;
import java.util.Map;
@@ -12,9 +13,9 @@ public class WebSocketMessageHandler implements ProxyMessageHandler {
private final MontoyaApi api;
private final MessageProcessor messageProcessor;
public WebSocketMessageHandler(MontoyaApi api) {
public WebSocketMessageHandler(MontoyaApi api, ConfigLoader configLoader) {
this.api = api;
this.messageProcessor = new MessageProcessor(api);
this.messageProcessor = new MessageProcessor(api, configLoader);
}
@Override
@@ -22,7 +23,7 @@ public class WebSocketMessageHandler implements ProxyMessageHandler {
String message = interceptedTextMessage.payload();
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().setNotes(result.get(1).get("comment"));
}

View File

@@ -1,12 +1,11 @@
package hae.utils;
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 org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.representer.Representer;
import java.io.*;
@@ -24,10 +23,7 @@ public class ConfigLoader {
public ConfigLoader(MontoyaApi api) {
this.api = api;
DumperOptions dop = new DumperOptions();
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Representer representer = new Representer(dop);
this.yaml = new Yaml(representer, dop);
this.yaml = createSecureYaml();
String configPath = determineConfigPath();
this.configFilePath = String.format("%s/%s", configPath, "Config.yml");
@@ -46,12 +42,36 @@ public class ConfigLoader {
File rulesFilePath = new File(this.rulesFilePath);
if (!(rulesFilePath.exists() && rulesFilePath.isFile())) {
initRulesByRes();
initRules();
}
Config.globalRules = getRules();
}
private static boolean isValidConfigPath(String configPath) {
File configPathFile = new File(configPath);
return configPathFile.exists() && configPathFile.isDirectory();
}
private Yaml createSecureYaml() {
// 配置 LoaderOptions 进行安全限制
LoaderOptions loaderOptions = new LoaderOptions();
// 禁用注释处理
loaderOptions.setProcessComments(false);
// 禁止递归键
loaderOptions.setAllowRecursiveKeys(false);
// 配置 DumperOptions 控制输出格式
DumperOptions dop = new DumperOptions();
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
// 创建 Representer
Representer representer = new Representer(dop);
// 使用 SafeConstructor创建安全的 YAML 实例
return new Yaml(new SafeConstructor(loaderOptions), representer, dop);
}
private String determineConfigPath() {
// 优先级1用户根目录
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
@@ -70,11 +90,6 @@ public class ConfigLoader {
return userConfigPath;
}
private static boolean isValidConfigPath(String configPath) {
File configPathFile = new File(configPath);
return configPathFile.exists() && configPathFile.isDirectory();
}
public void initConfig() {
Map<String, Object> r = new LinkedHashMap<>();
r.put("ExcludeSuffix", getExcludeSuffix());
@@ -82,6 +97,8 @@ public class ConfigLoader {
r.put("ExcludeStatus", getExcludeStatus());
r.put("LimitSize", getLimitSize());
r.put("HaEScope", getScope());
r.put("DynamicHeader", getDynamicHeader());
try {
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
yaml.dump(r, ws);
@@ -100,12 +117,7 @@ public class ConfigLoader {
try {
InputStream inputStream = Files.newInputStream(Paths.get(getRulesFilePath()));
DumperOptions dop = new DumperOptions();
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Representer representer = new Representer(dop);
Map<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream);
String[] fieldKeys = {"loaded", "name", "f_regex", "s_regex", "format", "color", "scope", "engine", "sensitive"};
Map<String, Object> rulesMap = yaml.load(inputStream);
Object rulesObj = rulesMap.get("rules");
if (rulesObj instanceof List) {
@@ -117,9 +129,9 @@ public class ConfigLoader {
if (ruleObj instanceof List) {
List<Map<String, Object>> ruleData = (List<Map<String, Object>>) ruleObj;
for (Map<String, Object> ruleFields : ruleData) {
Object[] valuesArray = new Object[fieldKeys.length];
for (int i = 0; i < fieldKeys.length; i++) {
valuesArray[i] = ruleFields.get(fieldKeys[i]);
Object[] valuesArray = new Object[Config.ruleFields.length];
for (int i = 0; i < Config.ruleFields.length; i++) {
valuesArray[i] = ruleFields.get(Config.ruleFields[i].toLowerCase().replace("-", "_"));
}
data.add(valuesArray);
}
@@ -137,38 +149,62 @@ public class ConfigLoader {
return rules;
}
public String getAlibabaAIAPIKey() {
return getValueFromConfig("AlibabaAIAPIKey", "");
}
public String getMoonshotAIAPIKey() {
return getValueFromConfig("MoonshotAIAPIKey", "");
}
public String getAIPrompt() {
return getValueFromConfig("AIPrompt", Config.prompt);
}
public String getBlockHost() {
return getValueFromConfig("BlockHost", Config.host);
}
public void setBlockHost(String blockHost) {
setValueToConfig("BlockHost", blockHost);
}
public String getExcludeSuffix() {
return getValueFromConfig("ExcludeSuffix", Config.suffix);
}
public void setExcludeSuffix(String excludeSuffix) {
setValueToConfig("ExcludeSuffix", excludeSuffix);
}
public String getExcludeStatus() {
return getValueFromConfig("ExcludeStatus", Config.status);
}
public void setExcludeStatus(String status) {
setValueToConfig("ExcludeStatus", status);
}
public String getDynamicHeader() {
return getValueFromConfig("DynamicHeader", Config.header);
}
public void setDynamicHeader(String header) {
setValueToConfig("DynamicHeader", header);
}
public String getLimitSize() {
return getValueFromConfig("LimitSize", Config.size);
}
public void setLimitSize(String size) {
setValueToConfig("LimitSize", size);
}
public String getScope() {
return getValueFromConfig("HaEScope", Config.scopeOptions);
}
public void setScope(String scope) {
setValueToConfig("HaEScope", scope);
}
public boolean getMode() {
return getValueFromConfig("HaEModeStatus", Config.modeStatus).equals("true");
}
public void setMode(String mode) {
setValueToConfig("HaEModeStatus", mode);
}
private String getValueFromConfig(String name, String defaultValue) {
File yamlSetting = new File(configFilePath);
if (!yamlSetting.exists() || !yamlSetting.isFile()) {
@@ -187,38 +223,6 @@ public class ConfigLoader {
return defaultValue;
}
public void setAlibabaAIAPIKey(String apiKey) {
setValueToConfig("AlibabaAIAPIKey", apiKey);
}
public void setMoonshotAIAPIKey(String apiKey) {
setValueToConfig("MoonshotAIAPIKey", apiKey);
}
public void setAIPrompt(String prompt) {
setValueToConfig("AIPrompt", prompt);
}
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);
}
private void setValueToConfig(String name, String value) {
Map<String, Object> currentConfig = loadCurrentConfig();
currentConfig.put(name, value);
@@ -242,11 +246,12 @@ public class ConfigLoader {
}
}
public void initRulesByRes() {
boolean isCopySuccess = copyRulesToFile(this.rulesFilePath);
if (!isCopySuccess) {
public boolean initRules() {
boolean ret = copyRulesToFile(this.rulesFilePath);
if (!ret) {
api.extension().unload();
}
return ret;
}
private boolean copyRulesToFile(String targetFilePath) {
@@ -269,33 +274,4 @@ public class ConfigLoader {
return false;
}
public void initRulesByNet() {
Thread t = new Thread() {
public void run() {
pullRules();
}
};
t.start();
try {
t.join(10000);
} catch (Exception ignored) {
}
}
private void pullRules() {
try {
String url = "https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml";
HttpRequest httpRequest = HttpRequest.httpRequestFromUrl(url);
HttpRequestResponse requestResponse = api.http().sendRequest(httpRequest, RequestOptions.requestOptions().withUpstreamTLSVerification());
String responseBody = requestResponse.response().bodyToString();
if (responseBody.contains("rules")) {
FileOutputStream fileOutputStream = new FileOutputStream(rulesFilePath);
fileOutputStream.write(responseBody.getBytes());
fileOutputStream.close();
}
} catch (Exception ignored) {
api.extension().unload();
}
}
}

View File

@@ -0,0 +1,137 @@
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;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DataManager {
private final MontoyaApi api;
private final Persistence persistence;
public DataManager(MontoyaApi api) {
this.api = api;
this.persistence = api.persistence();
}
public synchronized void putData(String dataType, String dataName, PersistedObject persistedObject) {
if (persistence.extensionData().getChildObject(dataName) != null) {
persistence.extensionData().deleteChildObject(dataName);
}
persistence.extensionData().setChildObject(dataName, persistedObject);
saveIndex(dataType, dataName);
}
public synchronized void loadData(MessageTableModel messageTableModel) {
// 1. 获取索引
PersistedList<String> dataIndex = persistence.extensionData().getStringList("data"); // 数据索引
PersistedList<String> messageIndex = persistence.extensionData().getStringList("message"); // 消息索引
// 2. 从索引获取数据
loadHaEData(dataIndex);
loadMessageData(messageIndex, messageTableModel);
}
private void saveIndex(String indexName, String indexValue) {
PersistedList<String> indexList = persistence.extensionData().getStringList(indexName);
if (indexList != null && !indexList.isEmpty()) {
persistence.extensionData().deleteStringList(indexName);
} else if (indexList == null) {
indexList = PersistedList.persistedStringList();
}
if (!indexList.contains(indexValue)) {
indexList.add(indexValue);
}
persistence.extensionData().setStringList(indexName, indexList);
}
private void loadHaEData(PersistedList<String> dataIndex) {
if (dataIndex != null && !dataIndex.isEmpty()) {
dataIndex.forEach(index -> {
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
try {
dataObj.stringListKeys().forEach(dataKey -> RegularMatcher.updateGlobalMatchCache(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false));
} catch (Exception ignored) {
}
});
}
}
private void loadMessageData(PersistedList<String> messageIndex, MessageTableModel messageTableModel) {
if (messageIndex == null || messageIndex.isEmpty()) {
return;
}
// 直接转换为List简化处理
List<String> indexList = messageIndex.stream()
.filter(Objects::nonNull)
.map(Object::toString)
.toList();
if (indexList.isEmpty()) {
return;
}
final int batchSize = 2000;
final int threadCount = Math.max(8, Runtime.getRuntime().availableProcessors() * 2);
ExecutorService executorService = Executors.newWorkStealingPool(threadCount);
try {
// 分批处理
for (int i = 0; i < indexList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, indexList.size());
List<String> batch = indexList.subList(i, endIndex);
processBatch(batch, messageTableModel);
}
} finally {
executorService.shutdown();
}
}
private void processBatch(List<String> batch, MessageTableModel messageTableModel) {
batch.forEach(index -> {
try {
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
if (dataObj != null) {
HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo");
if (messageInfo != null) {
addMessageToModel(messageInfo, dataObj, messageTableModel);
}
}
} catch (Exception e) {
api.logging().logToError("processBatch: " + e.getMessage());
}
});
}
private void addMessageToModel(HttpRequestResponse messageInfo, PersistedObject dataObj, MessageTableModel messageTableModel) {
HttpRequest request = messageInfo.request();
HttpResponse response = messageInfo.response();
messageTableModel.add(
messageInfo,
request.url(),
request.method(),
String.valueOf(response.statusCode()),
String.valueOf(response.toByteArray().length()),
dataObj.getString("comment"),
dataObj.getString("color"),
false
);
}
}

View File

@@ -7,24 +7,75 @@ import java.awt.event.FocusListener;
public class UIEnhancer {
public static void setTextFieldPlaceholder(JTextField textField, String placeholderText) {
textField.setForeground(Color.GRAY);
textField.setText(placeholderText);
// 存储占位符文本
textField.putClientProperty("placeholderText", placeholderText);
textField.putClientProperty("isPlaceholder", true);
updatePlaceholderText(textField);
textField.addPropertyChangeListener("background", evt -> {
updateForeground(textField);
});
textField.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
if (textField.getText().equals(placeholderText)) {
if (Boolean.TRUE.equals(textField.getClientProperty("isPlaceholder"))) {
textField.putClientProperty("isPlaceholder", false);
updateForeground(textField);
textField.setText("");
textField.setForeground(Color.BLACK);
}
}
@Override
public void focusLost(FocusEvent e) {
if (textField.getText().isEmpty()) {
textField.setForeground(Color.GRAY);
textField.setText(placeholderText);
updatePlaceholderText(textField);
}
}
});
textField.addPropertyChangeListener("text", evt -> {
if (Boolean.TRUE.equals(textField.getClientProperty("isPlaceholder"))) {
if (!textField.getText().isEmpty()) {
textField.putClientProperty("isPlaceholder", false);
updateForeground(textField);
}
} else {
if (textField.getText().isEmpty()) {
updatePlaceholderText(textField);
}
}
});
}
private static void updatePlaceholderText(JTextField textField) {
String placeholderText = (String) textField.getClientProperty("placeholderText");
textField.putClientProperty("isPlaceholder", true);
textField.setText(placeholderText);
textField.setForeground(Color.GRAY);
}
private static void updateForeground(JTextField textField) {
Color bg = textField.getBackground();
Color fg = isDarkColor(bg) ? Color.WHITE : Color.BLACK;
if (!Boolean.TRUE.equals(textField.getClientProperty("isPlaceholder"))) {
textField.setForeground(fg);
textField.putClientProperty("isPlaceholder", false);
}
}
public static boolean isDarkColor(Color color) {
double brightness = 0.299 * color.getRed()
+ 0.587 * color.getGreen()
+ 0.114 * color.getBlue();
return brightness < 128;
}
public static boolean hasUserInput(JTextField field) {
Object prop = field.getClientProperty("isPlaceholder");
return prop instanceof Boolean && !((Boolean) prop);
}
}

View File

@@ -3,9 +3,7 @@ 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.requests.HttpTransformation;
import burp.api.montoya.http.message.responses.HttpResponse;
import burp.api.montoya.utilities.RandomUtils;
import hae.utils.ConfigLoader;
import hae.utils.string.StringProcessor;
@@ -21,41 +19,35 @@ public class HttpUtils {
this.configLoader = configLoader;
}
public HttpRequest generateRequestByMultipartUploadMethod(String url, String name, String filename, String content) {
HttpRequest baseRequest = HttpRequest.httpRequestFromUrl(url).withTransformationApplied(HttpTransformation.TOGGLE_METHOD);
String boundary = api.utilities().randomUtils().randomString(32, RandomUtils.CharacterSet.ASCII_LETTERS);
String newBody = String.format("--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n\r\n%s\r\n", boundary, name, filename, content) +
String.format("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", boundary, "purpose", "file-extract") +
"--" + boundary + "--\r\n";
baseRequest = baseRequest.withUpdatedHeader("Content-Type", "multipart/form-data; boundary=" + boundary).withBody(newBody);
return baseRequest;
}
public HttpRequest generateRequestByDeleteMethod(String url) {
return HttpRequest.httpRequestFromUrl(url).withMethod("DELETE");
}
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 isBlockHost = false;
String blockHost = configLoader.getBlockHost();
if (!blockHost.isBlank()) {
String[] hostList = configLoader.getBlockHost().split("\\|");
isBlockHost = isBlockHost(hostList, host);
}
boolean isExcludeSuffix = false;
String suffix = configLoader.getExcludeSuffix();
if (!suffix.isBlank()) {
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
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()));
boolean isExcludeStatus = false;
String status = configLoader.getExcludeStatus();
if (!status.isBlank()) {
List<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|"));
isExcludeStatus = statusList.contains(String.valueOf(response.statusCode()));
}
retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus;
} catch (Exception ignored) {

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package hae.utils.rule;
import burp.api.montoya.MontoyaApi;
import hae.Config;
import hae.cache.DataCache;
import hae.utils.ConfigLoader;
import hae.utils.rule.model.Group;
import hae.utils.rule.model.Info;
@@ -27,6 +28,8 @@ public class RuleProcessor {
}
public void rulesFormatAndSave() {
DataCache.clear();
DumperOptions dop = new DumperOptions();
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Representer representer = new Representer(dop);

View File

@@ -1,14 +1,6 @@
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -64,19 +56,6 @@ public class StringProcessor {
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();

View File

@@ -46,6 +46,24 @@ rules:
scope: response body
engine: dfa
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
- name: Vite DevMode
loaded: true
f_regex: (/\@vite/client)
s_regex: ''
format: '{0}'
color: red
scope: response body
engine: dfa
sensitive: true
- group: Maybe Vulnerability
rule:
- name: Java Deserialization
@@ -93,12 +111,30 @@ rules:
scope: request
engine: dfa
sensitive: false
- name: Passwd File
loaded: true
f_regex: (/root:/bin/bash)
s_regex: ''
format: '{0}'
color: red
scope: response body
engine: dfa
sensitive: true
- name: Win.ini File
loaded: true
f_regex: (for 16-bit app)
s_regex: ''
format: '{0}'
color: red
scope: response body
engine: dfa
sensitive: true
- group: Basic Information
rule:
- name: Email
loaded: true
f_regex: (([a-z0-9]+[_|\.])*[a-z0-9]+@([a-z0-9]+[-|_|\.])*[a-z0-9]+\.((?!js|css|jpg|jpeg|png|ico)[a-z]{2,5}))
s_regex: ''
f_regex: (\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,5}\b)
s_regex: ^((?!.*\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff|ico?)$).*@.*\..*)$
format: '{0}'
color: yellow
scope: response
@@ -115,7 +151,7 @@ rules:
sensitive: true
- name: Chinese Mobile Number
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: ''
format: '{0}'
color: orange
@@ -153,7 +189,7 @@ rules:
sensitive: false
- name: Windows File/Dir Path
loaded: true
f_regex: '[^\w](([a-zA-Z]:\\(?:\w+\\?)*)|([a-zA-Z]:\\(?:\w+\\)*\w+\.\w+))'
f_regex: '[^\w]([a-zA-Z]:\\\\?(?:[^<>:/\\|?*]+\\\\?)*)([^<>:/\\|?*]+(?:\.[^<>:/\\|?*]+)?)'
s_regex: ''
format: '{0}'
color: green
@@ -162,8 +198,9 @@ rules:
sensitive: true
- name: Password Field
loaded: true
f_regex: ((|'|")(|[\w]{1,10})([p](ass|wd|asswd|assword))(|[\w]{1,10})(|'|")(:|=)(
|)('|")(.*?)('|")(|,))
f_regex: (((|\\)(|'|")(|[\.\w]{1,32})([p](ass|wd|asswd|assword))(|[\.\w]{1,32})(|\\)(|'|")(
|)(:|[=]{1,3}|![=]{1,2}|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,32})([p](ass|wd|asswd|assword))(|[\.\w]{1,32})(|\\)(|'|")))
s_regex: ''
format: '{0}'
color: yellow
@@ -172,8 +209,9 @@ rules:
sensitive: false
- name: Username Field
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,32})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\.\w]{1,32})(|\\)(|'|")(
|)(:|[=]{1,3}|![=]{1,2}|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,32})(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator))))(|[\.\w]{1,32})(|\\)(|'|")))
s_regex: ''
format: '{0}'
color: green
@@ -209,19 +247,40 @@ rules:
sensitive: false
- name: Sensitive Field
loaded: true
f_regex: ((\[)?('|")?([\w]{0,10})((key)|(secret)|(token)|(config)|(auth)|(access)|(admin)|(ticket))([\w]{0,10})('|")?(\])?(
|)(:|=)( |)('|")(.*?)('|")(|,))
f_regex: (((|\\)(|'|")(|[\.\w]{1,32})(key|secret|token|config|auth|access|admin|ticket)(|[\.\w]{1,32})(|\\)(|'|")(
|)(:|[=]{1,3}|![=]{1,2}|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,32})(key|secret|token|config|auth|access|admin|ticket)(|[\.\w]{1,32})(|\\)(|'|")))
s_regex: ''
format: '{0}'
color: yellow
scope: response
engine: nfa
sensitive: false
- name: Mobile Number Field
loaded: true
f_regex: (((|\\)(|'|")(|[\.\w]{1,32})(mobile|phone|sjh|shoujihao|concat)(|[\.\w]{1,32})(|\\)(|'|")(
|)(:|[=]{1,3}|![=]{1,2}|[\)]{0,1}\.val\()( |)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))|((|\\)('|")([^'"]+?)(|\\)('|")(|\\)(|'|")(
|)(:|[=]{1,3}|![=]{1,2})( |)(|[\.\w]{1,32})(mobile|phone|sjh|shoujihao|concat)(|[\.\w]{1,32})(|\\)(|'|")))
s_regex: ''
format: '{0}'
color: green
scope: response body
engine: nfa
sensitive: false
- name: Userinfo In Link
loaded: true
f_regex: (?:"|'|\`)(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|'|\`)
s_regex: ((([p](ass|wd|asswd|assword))|(([u](ser|name|sername))|(account)|((((create|update)((d|r)|(by|on|at)))|(creator)))))=[\.\w]{1,32})
format: '{0}'
color: green
scope: response body
engine: nfa
sensitive: false
- group: Other
rule:
- name: Linkfinder
loaded: true
f_regex: (?:"|')(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|')
f_regex: (?:"|'|\`)(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|'|\`)
s_regex: ''
format: '{0}'
color: gray
@@ -248,7 +307,7 @@ rules:
sensitive: false
- name: URL Schemes
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})://([-\w+&@#/%?=~_|!:,.;]*[-\w+&@#/%=~_|])?))
s_regex: ''
format: '{0}'
color: yellow
@@ -274,7 +333,7 @@ rules:
engine: nfa
sensitive: true
- name: Request URI
loaded: true
loaded: false
f_regex: ' ((?!.*\.js(\?.*)?$)(.*?[^.js$])) '
s_regex: ''
format: '{0}'
@@ -282,3 +341,21 @@ rules:
scope: request line
engine: nfa
sensitive: false
- name: 302 Location
loaded: true
f_regex: 'Location: (.*?)\r\n'
s_regex: ''
format: '{0}'
color: gray
scope: response header
engine: nfa
sensitive: false
- name: OSKeys
loaded: false
f_regex: <Key>(.*?)</Key>
s_regex: ''
format: '{0}'
color: gray
scope: response body
engine: nfa
sensitive: true