Compare commits

..

46 Commits
3.0 ... 4.0.1

Author SHA1 Message Date
gh0stkey
e83a6a1478 Version: 4.0.1 Update 2024-12-31 15:40:05 +08:00
gh0stkey
daacb2e146 Version: 4.0 Update 2024-12-21 15:34:45 +08:00
gh0stkey
1f1ca99f10 Update 2024-12-21 15:19:19 +08:00
gh0stkey
fa35b0a625 Version: 3.4 Update 2024-11-16 19:48:50 +08:00
gh0stkey
8ef98d20a9 Version: 3.4 Update 2024-11-16 18:42:15 +08:00
gh0stkey
e556abb6f7 Version: 3.4 Update 2024-11-16 18:06:49 +08:00
EvilChen
471aab5ea1 Merge pull request #221 from AabyssZG/master
Update Rules.yml
2024-10-24 11:48:57 +08:00
曾哥
76b475bd91 Update Rules.yml 2024-10-24 11:40:29 +08:00
gh0stkey
6014089594 Version: 3.3.4 Update 2024-10-14 16:35:20 +08:00
EvilChen
910658f2e0 Update 问题反馈.md 2024-09-23 00:05:40 +08:00
gh0stkey
8692b0a494 Version: 3.3.3 Update 2024-09-19 17:45:47 +08:00
gh0stkey
5419d4a679 Version: 3.3.3 Update 2024-09-19 17:11:55 +08:00
gh0stkey
ae8cb2fd25 Version: 3.3.3 Update 2024-09-19 17:08:46 +08:00
EvilChen
5b6bdbe5b6 Update README.md 2024-08-28 16:19:24 +08:00
EvilChen
ddb08e9a6e Update README.md 2024-08-28 16:18:25 +08:00
EvilChen
6a2f289d57 Update build.gradle 2024-08-26 10:04:57 +08:00
gh0stkey
84746a7089 Version: 3.3.2 Update 2024-08-23 22:03:31 +08:00
gh0stkey
68f0bce619 Version: 3.3.1 Update 2024-08-12 10:41:24 +08:00
gh0stkey
4f0401347c Version: 3.3.1 Update 2024-08-12 10:34:26 +08:00
gh0stkey
a7e0a2a6ce Update 2024-07-31 08:57:17 +08:00
gh0stkey
b7c5a8363d Update 2024-07-31 08:49:53 +08:00
gh0stkey
d7b4419d51 Version: 3.3 Update 2024-07-23 09:22:43 +08:00
gh0stkey
5f54d1f461 Version: 3.3 Update 2024-07-23 09:21:30 +08:00
EvilChen
e4b7f86a0c Update README.md 2024-07-19 00:24:04 +08:00
EvilChen
cc30f41bfa Add files via upload 2024-07-18 23:38:29 +08:00
EvilChen
386c562311 Delete images/config.png 2024-07-18 23:37:12 +08:00
EvilChen
a867039284 Update README.md 2024-07-18 23:36:52 +08:00
gh0stkey
3a8d9eae11 Version: 3.2.2 Update 2024-06-19 22:20:46 +08:00
gh0stkey
e5f55b6c4c Version: 3.2.2 Update 2024-06-19 22:16:57 +08:00
gh0stkey
54973d9f4f Version: 3.2.1 Update 2024-05-30 16:01:25 +08:00
gh0stkey
fb347a8dc6 Version: 3.2.1 Update 2024-05-30 15:56:49 +08:00
gh0stkey
04b6652b03 Version: 3.2.1 Update 2024-05-30 14:42:26 +08:00
gh0stkey
6d4abae898 Version: 3.2.1 Update 2024-05-30 14:37:01 +08:00
gh0stkey
97172fab45 Update 2024-05-26 15:09:12 +08:00
EvilChen
ba3b206acf Update build.gradle 2024-05-24 17:13:31 +08:00
gh0stkey
99ed2cb2fd Version: 3.2 Update 2024-05-24 15:31:07 +08:00
gh0stkey
8a47f61caa Version: 3.2 Update 2024-05-24 15:00:49 +08:00
gh0stkey
ad323ba7a5 Version: 3.1 Update 2024-05-23 12:12:33 +08:00
gh0stkey
332b119064 Version: 3.1 Update 2024-05-23 12:00:13 +08:00
gh0stkey
ead03d42b9 Version: 3.0.2 Update 2024-05-12 19:25:33 +08:00
gh0stkey
4da3d3f42d Version: 3.0.2 Update 2024-05-12 19:02:38 +08:00
EvilChen
3363ca25ed Update issue templates 2024-05-11 09:56:23 +08:00
gh0stkey
496d0d2174 Version: 3.0.1 Update 2024-05-11 09:44:19 +08:00
gh0stkey
f387834c4d Version: 3.0.1 Update 2024-05-09 13:34:38 +08:00
gh0stkey
ca773f368b Version: 3.0.1 Update 2024-05-09 13:32:22 +08:00
gh0stkey
a6cd01300b Version: 3.0 Update 2024-05-07 16:08:46 +08:00
39 changed files with 2258 additions and 903 deletions

View File

@@ -1,7 +1,7 @@
--- ---
name: 问题反馈 name: 问题反馈
about: 尽可能详细的描述问题并反馈 about: 尽可能详细的描述问题并反馈
title: "[BUG] " title: "[BUG] 问题标题"
labels: bug labels: bug
assignees: '' assignees: ''
@@ -10,11 +10,13 @@ assignees: ''
## 使用环境 ## 使用环境
``` ```
HaE版本 HaE 版本:
是否有自定义的HaE规则: 有无自定义规则:
BurpSuite版本 BurpSuite 版本:
JDK版本
操作系统版本: 操作系统版本:
是否阅读README
是否知晓注意事项:
是否查阅历史ISSUE
``` ```
## 问题详情 ## 问题详情

View File

@@ -1,25 +1,39 @@
<div align="center"> <div align="center">
<img src="images/logo.png" style="width: 20%" /> <img src="images/logo.png" style="width: 20%" />
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4> <h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
<h5>第一作者: <a href="https://github.com/gh0stkey">EvilChen</a>(中孚信息元亨实验室), 第二作者: <a href="https://github.com/0chencc">0chencc</a>(米斯特安全团队)</h5> <h5>第一作者: <a href="https://github.com/gh0stkey">EvilChen</a>(中孚信息元亨实验室)<br>第二作者: <a href="https://github.com/0chencc">0chencc</a>(米斯特安全团队)<br>第三作者: <a href="https://github.com/vaycore">vaycore</a>(独立安全研究员)</h5>
</div> </div>
## 项目介绍 ## 项目介绍
**HaE**是一款网络安全(数据安全)领域下的辅助型框架式项目,旨在实现对HTTP消息包含WebSocket高亮标记和信息提取。本项目通过自定义正则表达式匹配响应报文或请求报文,并对匹配成功的报文进行标记和提取。 **HaE**是一款**网络安全(数据安全)领域**下的框架式项目,采用了**乐高积木式**模块化设计理念,实现对HTTP消息包含WebSocket精细化的标记和提取。
通过运用**多引擎**的自定义正则表达式HaE能够准确匹配并处理HTTP请求与响应报文包含WebSocket对匹配成功的内容进行有效的标记和信息抽取从而提升网络安全数据安全领域下的**漏洞和数据分析效率**。
> 随着现代化Web应用采用前后端分离的开发模式日常漏洞挖掘的过程中捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**借助HaE您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。 > 随着现代化Web应用采用前后端分离的开发模式日常漏洞挖掘的过程中捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**借助HaE您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
**注意事项**: GitHub项目地址https://github.com/gh0stkey/HaE
1. 由于HaE 3.0版本开始采用`Montoya API`进行开发因此使用新版HaE需要升级你的BurpSuite版本>=2023.12.1)。 GitCode项目地址https://gitcode.com/gh0stkey/HaE
2. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`在HaE的规则中就需要变成`(rememberMe=delete)`
**所获荣誉**:
1. [入选2022年KCon兵器谱](https://mp.weixin.qq.com/s/JohMsl1WD29LHCHuLf8mVQ)
2. [入选GitCode G-Star项目](https://gitcode.com/gh0stkey/HaE)
**注意事项**:
1. HaE 3.0版本开始采用`Montoya API`进行开发使用新版HaE需要升级你的BurpSuite版本>=2023.12.1)。
2. HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
3. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`在HaE的规则中就需要变成`(rememberMe=delete)`
## 使用方法 ## 使用方法
插件装载: `Extender - Extensions - Add - Select File - Next` 插件装载: `Extender - Extensions - Add - Select File - Next`
初次装载`HaE`自动获取官方规则库`https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml`,配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下: 初次装载`HaE`从Jar包中加载离线的规则库如果更新可以点击`Reinit`进行重新初始化。内置规则库地址可以在Github上找到`https://github.com/gh0stkey/HaE/blob/master/src/main/resources/rules/Rules.yml`
配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下:
1. Linux/Mac用户的配置文件目录`~/.config/HaE/` 1. Linux/Mac用户的配置文件目录`~/.config/HaE/`
2. Windows用户的配置文件目录`%USERPROFILE%/.config/HaE/` 2. Windows用户的配置文件目录`%USERPROFILE%/.config/HaE/`
@@ -28,9 +42,7 @@
### 规则释义 ### 规则释义
HaE目前的规则一共有8个字段分别是规则名称、规则正则、规则作用域、正则引擎、规则匹配颜色、规则敏感性。 HaE目前的规则一共有8个字段详细的含义如下所示:
详细的含义如下所示:
| 字段 | 含义 | | 字段 | 含义 |
|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -38,22 +50,19 @@ HaE目前的规则一共有8个字段分别是规则名称、规则正则、
| F-Regex | 规则正则主要用于填写正则表达式。在HaE中所需提取匹配的内容需要用`(``)`将正则表达式进行包裹。| | F-Regex | 规则正则主要用于填写正则表达式。在HaE中所需提取匹配的内容需要用`(``)`将正则表达式进行包裹。|
| S-Regex | 规则正则作用及使用同F-Regex。S-Regex为二次正则可以用于对F-Regex匹配的数据结果进行二次的匹配提取如不需要的情况下可以留空。| | S-Regex | 规则正则作用及使用同F-Regex。S-Regex为二次正则可以用于对F-Regex匹配的数据结果进行二次的匹配提取如不需要的情况下可以留空。|
| Format | 格式化输出在NFA引擎的正则表达式中我们可以通过`{0}``{1}``{2}`…的方式进行取分组格式化输出。默认情况下使用`{0}`即可。 | | Format | 格式化输出在NFA引擎的正则表达式中我们可以通过`{0}``{1}``{2}`…的方式进行取分组格式化输出。默认情况下使用`{0}`即可。 |
| Scope | 规则作用域主要用于表示当前规则作用于HTTP报文的哪个部分。 | | Scope | 规则作用域主要用于表示当前规则作用于HTTP报文的哪个部分。支持请求、响应的行、头、体,以及完整的报文。 |
| Engine | 正则引擎,主要用于表示当前规则的正则表达式所使用的引擎。**DFA引擎**:对于文本串里的每一个字符只需扫描一次,速度快、特性少;**NFA引擎**:要翻来覆去标注字符、取消标注字符,速度慢,但是特性(如:分组、替换、分割)丰富。 | | Engine | 正则引擎,主要用于表示当前规则的正则表达式所使用的引擎。**DFA引擎**:对于文本串里的每一个字符只需扫描一次,速度快、特性少;**NFA引擎**:要翻来覆去标注字符、取消标注字符,速度慢,但是特性(如:分组、替换、分割)丰富。 |
| Color | 规则匹配颜色主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法当出现相同颜色时会自动向上升级一个颜色进行标记。 | | Color | 规则匹配颜色主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法当出现相同颜色时会自动向上升级一个颜色进行标记。 |
| Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 | | Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 |
## 优势特点 ## 优势特点
1. 精细配置:高度自由的配置选项,以满足各类精细化场景需求 1. **功能**通过对HTTP报文的颜色高亮、注释和提取帮助使用者获取有意义的信息**聚焦高价值报文**
2. 分类标签:使用标签对规则进行分类,便于管理和组织规则 2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**
3. 高亮标记在HTTP History页面通过颜色高亮注释判断请求的价值 3. **查询**将HTTP报文的高亮注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率
4. 易读配置使用易读的YAML格式存储配置文件方便阅读和修改 4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**
5. 数据集合:将匹配到的数据、请求和响应集中在数据面板中,提高测试和梳理效率 5. **管理****融入BurpSuite的项目数据管理**当使用BurpSuite进行项目存储时HaE数据也会一并存储
6. 简洁可视清晰可视的界面设计更轻松地了解和配置HaE操作简单、使用便捷 6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**
7. 颜色升级:内置颜色升级算法,避免“屠龙者终成恶龙”场景,突出最具价值的请求。
8. 实战规则:官方规则库是基于实战化场景总结输出,提升数据发现的有效性、精准性。
| 界面名称 | 界面展示 | | 界面名称 | 界面展示 |
| ------------------------ | ---------------------------------------------------- | | ------------------------ | ---------------------------------------------------- |
@@ -62,9 +71,7 @@ HaE目前的规则一共有8个字段分别是规则名称、规则正则、
| Databoard数据集合 | <img src="images/databoard.png" style="width: 80%" /> | | Databoard数据集合 | <img src="images/databoard.png" style="width: 80%" /> |
| MarkInfo数据展示 | <img src="images/markinfo.png" style="width: 80%" /> | | MarkInfo数据展示 | <img src="images/markinfo.png" style="width: 80%" /> |
## 文末随笔 ## 支持项目
正义感是一个不可丢失的东西。
如果你觉得HaE好用可以打赏一下作者给作者持续更新下去的动力 如果你觉得HaE好用可以打赏一下作者给作者持续更新下去的动力

View File

@@ -20,8 +20,8 @@ sourceSets {
dependencies { dependencies {
implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1' implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1'
implementation 'org.yaml:snakeyaml:2.0' implementation 'org.yaml:snakeyaml:2.0'
implementation 'net.sourceforge.jregex:jregex:1.2_01'
implementation 'dk.brics.automaton:automaton:1.11-8' implementation 'dk.brics.automaton:automaton:1.11-8'
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
} }
test { test {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

@@ -8,34 +8,48 @@ import java.util.concurrent.ConcurrentHashMap;
public class Config { public class Config {
public static String suffix = "3g2|3gp|7z|aac|abw|aif|aifc|aiff|apk|arc|au|avi|azw|bat|bin|bmp|bz|bz2|cmd|cmx|cod|com|csh|css|csv|dll|doc|docx|ear|eot|epub|exe|flac|flv|gif|gz|ico|ics|ief|jar|jfif|jpe|jpeg|jpg|less|m3u|mid|midi|mjs|mkv|mov|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpkg|mpp|mpv2|odp|ods|odt|oga|ogg|ogv|ogx|otf|pbm|pdf|pgm|png|pnm|ppm|ppt|pptx|ra|ram|rar|ras|rgb|rmi|rtf|scss|sh|snd|svg|swf|tar|tif|tiff|ttf|vsd|war|wav|weba|webm|webp|wmv|woff|woff2|xbm|xls|xlsx|xpm|xul|xwd|zip"; public static String suffix = "3g2|3gp|7z|aac|abw|aif|aifc|aiff|apk|arc|au|avi|azw|bat|bin|bmp|bz|bz2|cmd|cmx|cod|com|csh|css|csv|dll|doc|docx|ear|eot|epub|exe|flac|flv|gif|gz|ico|ics|ief|jar|jfif|jpe|jpeg|jpg|less|m3u|mid|midi|mjs|mkv|mov|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpkg|mpp|mpv2|odp|ods|odt|oga|ogg|ogv|ogx|otf|pbm|pdf|pgm|png|pnm|ppm|ppt|pptx|ra|ram|rar|ras|rgb|rmi|rtf|scss|sh|snd|svg|swf|tar|tif|tiff|ttf|vsd|war|wav|weba|webm|webp|wmv|woff|woff2|xbm|xls|xlsx|xpm|xul|xwd|zip";
public static String[] scope = new String[] { public static String host = "gh0st.cn";
public static String status = "404";
public static String size = "0";
public static String boundary = "\n\t\n";
public static String[] scope = new String[]{
"any", "any",
"any header", "any header",
"any body", "any body",
"response", "response",
"response line",
"response header", "response header",
"response body", "response body",
"request", "request",
"request line",
"request header", "request header",
"request body" "request body"
}; };
public static String scopeOptions = "Suite|Target|Proxy|Scanner|Intruder|Repeater|Logger|Sequencer|Decoder|Comparer|Extensions|Organizer|Recorded login replayer";
public static String modeStatus = "true";
public static String[] ruleFields = { public static String[] ruleFields = {
"Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive" "Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive"
}; };
public static Object[][] ruleTemplate = new Object[][] { public static Object[][] ruleTemplate = new Object[][]{
{ {
false, "New Name", "(First Regex)", "(Second Regex)", "{0}", "gray", "any", "nfa", false false, "New Name", "(First Regex)", "(Second Regex)", "{0}", "gray", "any", "nfa", false
} }
}; };
public static String[] engine = new String[] { public static String[] engine = new String[]{
"nfa", "nfa",
"dfa" "dfa"
}; };
public static String[] color = new String[] { public static String[] color = new String[]{
"red", "red",
"orange", "orange",
"yellow", "yellow",

View File

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

View File

@@ -1,19 +1,34 @@
package hae.cache; package hae.cache;
import java.util.*; import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class CachePool { public class CachePool {
private static final Map<String, Map<String, Map<String, Object>>> cache = new HashMap<>(); private static final int MAX_SIZE = 100000;
private static final int EXPIRE_DURATION = 5;
public static void addToCache(String key, Map<String, Map<String, Object>> value) { private static final Cache<String, Map<String, Map<String, Object>>> cache =
Caffeine.newBuilder()
.maximumSize(MAX_SIZE)
.expireAfterWrite(EXPIRE_DURATION, TimeUnit.HOURS)
.build();
public static void put(String key, Map<String, Map<String, Object>> value) {
cache.put(key, value); cache.put(key, value);
} }
public static Map<String, Map<String, Object>> getFromCache(String key) { public static Map<String, Map<String, Object>> get(String key) {
return cache.get(key); return cache.getIfPresent(key);
} }
public static void removeFromCache(String key) { public static void remove(String key) {
cache.remove(key); cache.invalidate(key);
}
public static void clear() {
cache.invalidateAll();
} }
} }

View File

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

View File

@@ -3,9 +3,8 @@ package hae.component;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.component.board.Databoard; import hae.component.board.Databoard;
import hae.component.board.message.MessageTableModel; import hae.component.board.message.MessageTableModel;
import hae.component.config.Config;
import hae.component.rule.Rules; import hae.component.rule.Rules;
import hae.utils.config.ConfigLoader; import hae.utils.ConfigLoader;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -28,35 +27,26 @@ public class Main extends JPanel {
private void initComponents() { private void initComponents() {
setLayout(new GridBagLayout()); setLayout(new GridBagLayout());
((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0}; ((GridBagLayout) getLayout()).columnWidths = new int[]{0, 0};
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0}; ((GridBagLayout) getLayout()).rowHeights = new int[]{0, 0};
((GridBagLayout)getLayout()).columnWeights = new double[] {1.0, 1.0E-4}; ((GridBagLayout) getLayout()).columnWeights = new double[]{1.0, 1.0E-4};
((GridBagLayout)getLayout()).rowWeights = new double[] {1.0, 1.0E-4}; ((GridBagLayout) getLayout()).rowWeights = new double[]{1.0, 1.0E-4};
JTabbedPane mainTabbedPane = new JTabbedPane(); JTabbedPane mainTabbedPane = new JTabbedPane();
// 新增Logo // 新增Logo
JTabbedPane HaETabbedPane = new JTabbedPane(); JTabbedPane HaETabbedPane = new JTabbedPane();
HaETabbedPane.addTab("", getImageIcon(false), mainTabbedPane); boolean isDarkBg = isDarkBg(HaETabbedPane);
HaETabbedPane.addTab("", getImageIcon(isDarkBg), mainTabbedPane);
// 中文Slogan赋能白帽高效作战 // 中文Slogan赋能白帽高效作战
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null); HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null);
HaETabbedPane.setEnabledAt(1, false); HaETabbedPane.setEnabledAt(1, false);
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() { HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
@Override @Override
public void propertyChange(PropertyChangeEvent e) { public void propertyChange(PropertyChangeEvent e) {
boolean isDarkBg = isDarkBg(); boolean isDarkBg = isDarkBg(HaETabbedPane);
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg)); HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
} }
private boolean isDarkBg() {
Color bg = HaETabbedPane.getBackground();
int r = bg.getRed();
int g = bg.getGreen();
int b = bg.getBlue();
int avg = (r + g + b) / 3;
return avg < 128;
}
}); });
add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
@@ -66,22 +56,31 @@ public class Main extends JPanel {
// 依次添加Rules、Config、Databoard // 依次添加Rules、Config、Databoard
Rules rules = new Rules(api, configLoader); Rules rules = new Rules(api, configLoader);
mainTabbedPane.addTab("Rules", rules); mainTabbedPane.addTab("Rules", rules);
mainTabbedPane.addTab("Config", new Config(api, configLoader, rules));
mainTabbedPane.addTab("Databoard", new Databoard(api, configLoader, messageTableModel)); mainTabbedPane.addTab("Databoard", new Databoard(api, configLoader, messageTableModel));
mainTabbedPane.addTab("Config", new Config(api, configLoader, messageTableModel, rules));
}
private boolean isDarkBg(JTabbedPane HaETabbedPane) {
Color bg = HaETabbedPane.getBackground();
int r = bg.getRed();
int g = bg.getGreen();
int b = bg.getBlue();
int avg = (r + g + b) / 3;
return avg < 128;
} }
private ImageIcon getImageIcon(boolean isDark) { private ImageIcon getImageIcon(boolean isDark) {
ClassLoader classLoader = getClass().getClassLoader(); ClassLoader classLoader = getClass().getClassLoader();
URL imageURL; URL imageURL;
if (isDark) { if (isDark) {
imageURL = classLoader.getResource("logo.png"); imageURL = classLoader.getResource("logo/logo.png");
} else { } else {
imageURL = classLoader.getResource("logo_black.png"); imageURL = classLoader.getResource("logo/logo_black.png");
} }
ImageIcon originalIcon = new ImageIcon(imageURL); ImageIcon originalIcon = new ImageIcon(imageURL);
Image originalImage = originalIcon.getImage(); Image originalImage = originalIcon.getImage();
Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST); Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST);
ImageIcon scaledIcon = new ImageIcon(scaledImage); return new ImageIcon(scaledImage);
return scaledIcon;
} }
} }

View File

@@ -3,33 +3,41 @@ package hae.component.board;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.Config; import hae.Config;
import hae.component.board.message.MessageTableModel; import hae.component.board.message.MessageTableModel;
import hae.utils.string.StringProcessor;
import hae.utils.config.ConfigLoader;
import hae.component.board.message.MessageTableModel.MessageTable; import hae.component.board.message.MessageTableModel.MessageTable;
import hae.component.board.table.Datatable;
import hae.utils.ConfigLoader;
import hae.utils.UIEnhancer;
import hae.utils.string.StringProcessor;
import java.util.*; import javax.swing.*;
import java.util.concurrent.ConcurrentHashMap; import javax.swing.event.DocumentEvent;
import javax.swing.event.*; import javax.swing.event.DocumentListener;
import javax.swing.table.TableColumnModel; import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter; import javax.swing.table.TableRowSorter;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.util.List; import java.util.List;
import javax.swing.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class Databoard extends JPanel { public class Databoard extends JPanel {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader; private final ConfigLoader configLoader;
private final MessageTableModel messageTableModel; private final MessageTableModel messageTableModel;
private JTextField hostTextField; private JTextField hostTextField;
private JTabbedPane dataTabbedPane; private JTabbedPane dataTabbedPane;
private JSplitPane splitPane; private JSplitPane splitPane;
private MessageTable messageTable; private MessageTable messageTable;
private static Boolean isMatchHost = false; private static Boolean isMatchHost = false;
private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(); private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
private JComboBox hostComboBox = new JComboBox(comboBoxModel); private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker;
private SwingWorker<Void, Void> applyHostFilterWorker;
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) { public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
this.api = api; this.api = api;
@@ -41,24 +49,29 @@ public class Databoard extends JPanel {
private void initComponents() { private void initComponents() {
setLayout(new GridBagLayout()); setLayout(new GridBagLayout());
((GridBagLayout)getLayout()).columnWidths = new int[] {25, 0, 0, 0,20, 0}; ((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 20, 0};
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 65, 20, 0}; ((GridBagLayout) getLayout()).rowHeights = new int[]{0, 65, 20, 0};
((GridBagLayout)getLayout()).columnWeights = new double[] {0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4}; ((GridBagLayout) getLayout()).columnWeights = new double[]{0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4};
((GridBagLayout)getLayout()).rowWeights = new double[] {0.0, 1.0, 0.0, 1.0E-4}; ((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 1.0, 0.0, 1.0E-4};
JLabel hostLabel = new JLabel("Host:"); JLabel hostLabel = new JLabel("Host:");
JButton clearButton = new JButton("Clear"); JButton clearButton = new JButton("Clear");
JButton actionButton = new JButton("Action"); JButton actionButton = new JButton("Action");
JPanel menuPanel = new JPanel(new GridLayout(1, 1)); JPanel menuPanel = new JPanel(new GridLayout(1, 1, 0, 5));
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
JPopupMenu menu = new JPopupMenu(); JPopupMenu menu = new JPopupMenu();
menuPanel.add(clearButton); menuPanel.add(clearButton);
menu.add(menuPanel); menu.add(menuPanel);
hostTextField = new JTextField(); hostTextField = new JTextField();
String defaultText = "Please enter the host";
UIEnhancer.setTextFieldPlaceholder(hostTextField, defaultText);
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP); dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
dataTabbedPane.setPreferredSize(new Dimension(500, 0));
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
actionButton.addActionListener(e -> { actionButton.addActionListener(e -> {
int x = 0; int x = 0;
@@ -68,6 +81,7 @@ public class Databoard extends JPanel {
clearButton.addActionListener(this::clearActionPerformed); clearButton.addActionListener(this::clearActionPerformed);
splitPane.addComponentListener(new ComponentAdapter() { splitPane.addComponentListener(new ComponentAdapter() {
@Override @Override
public void componentResized(ComponentEvent e) { public void componentResized(ComponentEvent e) {
@@ -81,11 +95,12 @@ public class Databoard extends JPanel {
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
add(hostTextField, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, add(hostTextField, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
add(splitPane, new GridBagConstraints(1, 1, 3, 3, 0.0, 0.0,
add(splitPane, new GridBagConstraints(1, 1, 3, 2, 0.0, 1.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(0, 5, 0, 5), 0, 0));
hostComboBox.setMaximumRowCount(5); hostComboBox.setMaximumRowCount(5);
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(8, 0, 5, 5), 0, 0)); new Insets(8, 0, 5, 5), 0, 0));
@@ -138,8 +153,55 @@ public class Databoard extends JPanel {
private void handleComboBoxAction(ActionEvent e) { private void handleComboBoxAction(ActionEvent e) {
if (!isMatchHost && hostComboBox.getSelectedItem() != null) { if (!isMatchHost && hostComboBox.getSelectedItem() != null) {
String selectedHost = hostComboBox.getSelectedItem().toString(); String selectedHost = hostComboBox.getSelectedItem().toString();
hostTextField.setText(selectedHost);
populateTabbedPaneByHost(selectedHost); if (getHostByList().contains(selectedHost)) {
hostTextField.setText(selectedHost);
if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
handleComboBoxWorker.cancel(true);
}
handleComboBoxWorker = new SwingWorker<Map<String, List<String>>, Void>() {
@Override
protected Map<String, List<String>> doInBackground() {
return getSelectedMapByHost(selectedHost);
}
@Override
protected void done() {
if (!isCancelled()) {
try {
Map<String, List<String>> selectedDataMap = get();
if (!selectedDataMap.isEmpty()) {
dataTabbedPane.removeAll();
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
Datatable datatablePanel = new Datatable(api, configLoader, entry.getKey(), entry.getValue());
datatablePanel.setTableListener(messageTableModel);
dataTabbedPane.addTab(tabTitle, datatablePanel);
}
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
splitPane.setLeftComponent(dataTabbedPane);
splitPane.setRightComponent(messageSplitPane);
messageTable = messageTableModel.getMessageTable();
resizePanel();
splitPane.setVisible(true);
hostTextField.setText(selectedHost);
hostComboBox.setPopupVisible(false);
applyHostFilter(selectedHost);
}
} catch (Exception ignored) {
}
}
}
};
handleComboBoxWorker.execute();
}
} }
} }
@@ -158,7 +220,6 @@ public class Databoard extends JPanel {
if (keyCode == KeyEvent.VK_ENTER) { if (keyCode == KeyEvent.VK_ENTER) {
isMatchHost = false; isMatchHost = false;
handleComboBoxAction(null); handleComboBoxAction(null);
hostComboBox.setPopupVisible(false);
} }
if (keyCode == KeyEvent.VK_ESCAPE) { if (keyCode == KeyEvent.VK_ESCAPE) {
@@ -168,10 +229,40 @@ public class Databoard extends JPanel {
isMatchHost = false; isMatchHost = false;
} }
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
Map<String, List<String>> selectedDataMap;
if (selectedHost.contains("*")) {
selectedDataMap = new HashMap<>();
dataMap.keySet().forEach(key -> {
if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) {
Map<String, List<String>> ruleMap = dataMap.get(key);
for (String ruleKey : ruleMap.keySet()) {
List<String> dataList = ruleMap.get(ruleKey);
if (selectedDataMap.containsKey(ruleKey)) {
List<String> mergedList = new ArrayList<>(selectedDataMap.get(ruleKey));
mergedList.addAll(dataList);
HashSet<String> uniqueSet = new HashSet<>(mergedList);
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
} else {
selectedDataMap.put(ruleKey, dataList);
}
}
}
});
} else {
selectedDataMap = dataMap.get(selectedHost);
}
return selectedDataMap;
}
private void filterComboBoxList() { private void filterComboBoxList() {
isMatchHost = true; isMatchHost = true;
comboBoxModel.removeAllElements(); comboBoxModel.removeAllElements();
String input = hostTextField.getText().toLowerCase(); String input = hostTextField.getText().toLowerCase();
if (!input.isEmpty()) { if (!input.isEmpty()) {
for (String host : getHostByList()) { for (String host : getHostByList()) {
String lowerCaseHost = host.toLowerCase(); String lowerCaseHost = host.toLowerCase();
@@ -190,96 +281,85 @@ public class Databoard extends JPanel {
isMatchHost = false; isMatchHost = false;
} }
private void populateTabbedPaneByHost(String selectedHost) {
if (!Objects.equals(selectedHost, "")) {
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
Map<String, List<String>> selectedDataMap;
dataTabbedPane.removeAll();
dataTabbedPane.setPreferredSize(new Dimension(500,0));
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
splitPane.setLeftComponent(dataTabbedPane);
if (selectedHost.contains("*")) {
// 通配符数据
selectedDataMap = new HashMap<>();
String hostPattern = StringProcessor.replaceFirstOccurrence(selectedHost, "*.", "");
for (String key : dataMap.keySet()) {
if (key.contains(hostPattern) || selectedHost.equals("*")) {
Map<String, List<String>> ruleMap = dataMap.get(key);
for (String ruleKey : ruleMap.keySet()) {
List<String> dataList = ruleMap.get(ruleKey);
if (selectedDataMap.containsKey(ruleKey)) {
List<String> mergedList = new ArrayList<>(selectedDataMap.get(ruleKey));
mergedList.addAll(dataList);
HashSet<String> uniqueSet = new HashSet<>(mergedList);
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
} else {
selectedDataMap.put(ruleKey, dataList);
}
}
}
}
} else {
selectedDataMap = dataMap.get(selectedHost);
}
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
Datatable datatablePanel = new Datatable(api, entry.getKey(), entry.getValue());
datatablePanel.setTableListener(messageTableModel);
dataTabbedPane.addTab(tabTitle, datatablePanel);
}
// 展示请求消息表单
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
this.splitPane.setRightComponent(messageSplitPane);
messageTable = messageTableModel.getMessageTable();
resizePanel();
splitPane.setVisible(true);
applyHostFilter(selectedHost);
hostTextField.setText(selectedHost);
}
}
private void applyHostFilter(String filterText) { private void applyHostFilter(String filterText) {
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter(); TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", ""); String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
if (cleanedText.contains("*")) { if (applyHostFilterWorker != null && !applyHostFilterWorker.isDone()) {
cleanedText = ""; applyHostFilterWorker.cancel(true);
} }
RowFilter<TableModel, Integer> filter = RowFilter.regexFilter(cleanedText, 1); applyHostFilterWorker = new SwingWorker<Void, Void>() {
sorter.setRowFilter(filter); @Override
protected Void doInBackground() throws Exception {
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
public boolean include(Entry<?, ?> entry) {
if (cleanedText.equals("*")) {
return true;
} else {
String host = StringProcessor.getHostByUrl((String) entry.getValue(1));
return StringProcessor.matchesHostPattern(host, filterText);
}
}
};
messageTableModel.applyHostFilter(filterText); sorter.setRowFilter(rowFilter);
messageTableModel.applyHostFilter(filterText);
return null;
}
};
applyHostFilterWorker.execute();
} }
private List<String> getHostByList() { private List<String> getHostByList() {
return new ArrayList<>(Config.globalDataMap.keySet()); if (!Config.globalDataMap.keySet().isEmpty()) {
return new ArrayList<>(Config.globalDataMap.keySet());
}
return new ArrayList<>();
} }
private void clearActionPerformed(ActionEvent e) { private void clearActionPerformed(ActionEvent e) {
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to clear data?", "Info", int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
JOptionPane.YES_NO_OPTION); JOptionPane.YES_NO_OPTION);
String host = hostTextField.getText(); String host = hostTextField.getText();
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) { if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
dataTabbedPane.removeAll(); dataTabbedPane.removeAll();
splitPane.setVisible(false); splitPane.setVisible(false);
String cleanedHost = StringProcessor.replaceFirstOccurrence(host, "*.", ""); Config.globalDataMap.keySet().parallelStream().forEach(key -> {
if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) {
Config.globalDataMap.remove(key);
}
});
if (host.contains("*")) { // 删除无用的数据
Config.globalDataMap.keySet().removeIf(i -> i.contains(cleanedHost) || cleanedHost.contains("*")); Set<String> wildcardKeys = Config.globalDataMap.keySet().stream()
} else { .filter(key -> key.startsWith("*."))
Config.globalDataMap.remove(host); .collect(Collectors.toSet());
Set<String> existingSuffixes = Config.globalDataMap.keySet().stream()
.filter(key -> !key.startsWith("*."))
.map(key -> {
int dotIndex = key.indexOf(".");
return dotIndex != -1 ? key.substring(dotIndex) : "";
})
.collect(Collectors.toSet());
Set<String> keysToRemove = wildcardKeys.stream()
.filter(key -> !existingSuffixes.contains(key.substring(1)))
.collect(Collectors.toSet());
keysToRemove.forEach(Config.globalDataMap::remove);
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
Config.globalDataMap.keySet().remove("*");
} }
messageTableModel.deleteByHost(cleanedHost); messageTableModel.deleteByHost(host);
hostTextField.setText("");
} }
} }
} }

View File

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

View File

@@ -1,20 +1,19 @@
package hae.component.board.message; package hae.component.board.message;
import java.awt.Color; import javax.swing.*;
import java.awt.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
public class MessageRenderer extends DefaultTableCellRenderer { public class MessageRenderer extends DefaultTableCellRenderer {
private List<MessageEntry> log; private final LinkedList<MessageEntry> log;
private Map<String, Color> colorMap = new HashMap<>(); private final Map<String, Color> colorMap = new HashMap<>();
private JTable table; // 保存对表格的引用 private final JTable table; // 保存对表格的引用
public MessageRenderer(List<MessageEntry> log, JTable table) { public MessageRenderer(LinkedList<MessageEntry> log, JTable table) {
this.log = log; this.log = log;
// 与BurpSuite的颜色保持一致 // 与BurpSuite的颜色保持一致
this.colorMap.put("red", new Color(0xFF, 0x64, 0x64)); this.colorMap.put("red", new Color(0xFF, 0x64, 0x64));
@@ -42,7 +41,7 @@ public class MessageRenderer extends DefaultTableCellRenderer {
if (isSelected) { if (isSelected) {
// 通过更改RGB颜色来达成阴影效果 // 通过更改RGB颜色来达成阴影效果
component.setBackground(new Color(color.getRed()-0x20, color.getGreen()-0x20, color.getBlue()-0x20)); component.setBackground(new Color(color.getRed() - 0x20, color.getGreen() - 0x20, color.getBlue() - 0x20));
} else { } else {
// 否则使用原始颜色 // 否则使用原始颜色
component.setBackground(color); component.setBackground(color);

View File

@@ -1,31 +1,31 @@
package hae.component.board.message; package hae.component.board.message;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.ByteArray;
import burp.api.montoya.http.message.HttpHeader; import burp.api.montoya.http.message.HttpHeader;
import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.http.message.responses.HttpResponse;
import burp.api.montoya.persistence.PersistedObject;
import burp.api.montoya.ui.UserInterface; import burp.api.montoya.ui.UserInterface;
import burp.api.montoya.ui.editor.HttpRequestEditor; import burp.api.montoya.ui.editor.HttpRequestEditor;
import burp.api.montoya.ui.editor.HttpResponseEditor; import burp.api.montoya.ui.editor.HttpResponseEditor;
import hae.Config; import hae.Config;
import hae.cache.CachePool; import hae.cache.CachePool;
import hae.utils.ConfigLoader;
import hae.utils.DataManager;
import hae.utils.string.HashCalculator; import hae.utils.string.HashCalculator;
import hae.utils.string.StringProcessor; import hae.utils.string.StringProcessor;
import java.nio.charset.StandardCharsets; import javax.swing.*;
import java.text.MessageFormat;
import java.util.*;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingWorker;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel; import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter; import javax.swing.table.TableRowSorter;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -33,17 +33,19 @@ import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY;
public class MessageTableModel extends AbstractTableModel { public class MessageTableModel extends AbstractTableModel {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
private final MessageTable messageTable; private final MessageTable messageTable;
private final JTabbedPane messageTab;
private final JSplitPane splitPane; private final JSplitPane splitPane;
private final List<MessageEntry> log = new ArrayList<MessageEntry>(); private final LinkedList<MessageEntry> log = new LinkedList<>();
private LinkedList<MessageEntry> filteredLog; private final LinkedList<MessageEntry> filteredLog;
private SwingWorker<Void, Void> currentWorker;
public MessageTableModel(MontoyaApi api) { public MessageTableModel(MontoyaApi api, ConfigLoader configLoader) {
this.filteredLog = new LinkedList<>(); this.filteredLog = new LinkedList<>();
this.api = api; this.api = api;
this.configLoader = configLoader;
messageTab = new JTabbedPane(); JTabbedPane messageTab = new JTabbedPane();
UserInterface userInterface = api.userInterface(); UserInterface userInterface = api.userInterface();
HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY); HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY);
HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY); HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY);
@@ -74,6 +76,7 @@ public class MessageTableModel extends AbstractTableModel {
int index2 = getIndex(s2); int index2 = getIndex(s2);
return Integer.compare(index1, index2); return Integer.compare(index1, index2);
} }
private int getIndex(String color) { private int getIndex(String color) {
for (int i = 0; i < Config.color.length; i++) { for (int i = 0; i < Config.color.length; i++) {
if (Config.color[i].equals(color)) { if (Config.color[i].equals(color)) {
@@ -95,32 +98,32 @@ public class MessageTableModel extends AbstractTableModel {
splitPane.setRightComponent(messageTab); splitPane.setRightComponent(messageTab);
} }
public void add(HttpRequestResponse messageInfo, String comment, String color) { public void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
synchronized(log) { synchronized (log) {
HttpRequest httpRequest = messageInfo.request(); boolean isDuplicate = false;
String url = httpRequest.url();
String method = httpRequest.method();
HttpResponse httpResponse = messageInfo.response();
String status = String.valueOf(httpResponse.statusCode());
String length = String.valueOf(httpResponse.body().length());
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status); MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
try { byte[] reqByteA = new byte[0];
// 比较Hash如若存在重复的请求或响应则不放入消息内容里 byte[] resByteA = new byte[0];
byte[] reqByteA = httpRequest.toByteArray().getBytes();
byte[] resByteA = httpResponse.toByteArray().getBytes();
boolean isDuplicate = false;
if (log.size() > 0) { if (messageInfo != null) {
HttpRequest httpRequest = messageInfo.request();
HttpResponse httpResponse = messageInfo.response();
reqByteA = httpRequest.toByteArray().getBytes();
resByteA = httpResponse.toByteArray().getBytes();
}
// 比较Hash如若存在重复的请求或响应则不放入消息内容里
try {
if (!log.isEmpty()) {
for (MessageEntry entry : log) { for (MessageEntry entry : log) {
HttpRequestResponse reqResMessage = entry.getRequestResponse(); HttpRequestResponse reqResMessage = entry.getRequestResponse();
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes(); byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
byte[] resByteB = reqResMessage.response().toByteArray().getBytes(); byte[] resByteB = reqResMessage.response().toByteArray().getBytes();
try { try {
// 通过URL、请求和响应报文、匹配数据内容多维度进行对比 // 通过URL、请求和响应报文、匹配数据内容多维度进行对比
if ((entry.getUrl().toString().equals(url.toString()) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) { if ((entry.getUrl().equals(url) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) {
isDuplicate = true; isDuplicate = true;
break; break;
} }
@@ -128,12 +131,24 @@ public class MessageTableModel extends AbstractTableModel {
} }
} }
} }
if (!isDuplicate) {
log.add(logEntry);
}
} catch (Exception ignored) { } catch (Exception ignored) {
} }
if (!isDuplicate) {
if (flag) {
DataManager dataManager = new DataManager(api);
// 数据存储在BurpSuite空间内
PersistedObject persistedObject = PersistedObject.persistedObject();
persistedObject.setHttpRequestResponse("messageInfo", messageInfo);
persistedObject.setString("comment", comment);
persistedObject.setString("color", color);
String uuidIndex = StringProcessor.getRandomUUID();
dataManager.putData("message", uuidIndex, persistedObject);
}
// 添加进日志
log.add(logEntry);
}
} }
} }
@@ -141,42 +156,47 @@ public class MessageTableModel extends AbstractTableModel {
public void deleteByHost(String filterText) { public void deleteByHost(String filterText) {
filteredLog.clear(); filteredLog.clear();
List<Integer> rowsToRemove = new ArrayList<>(); List<Integer> rowsToRemove = new ArrayList<>();
for (int i = 0; i < log.size(); i++) {
MessageEntry entry = log.get(i); if (currentWorker != null && !currentWorker.isDone()) {
String host = StringProcessor.getHostByUrl(entry.getUrl()); currentWorker.cancel(true);
if (!host.isEmpty()) { }
if (StringProcessor.matchFromEnd(host, filterText) || filterText.contains("*")) {
rowsToRemove.add(i); currentWorker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() {
for (int i = 0; i < log.size(); i++) {
MessageEntry entry = log.get(i);
String host = StringProcessor.getHostByUrl(entry.getUrl());
if (!host.isEmpty()) {
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.equals("*")) {
rowsToRemove.add(i);
}
}
} }
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
int row = rowsToRemove.get(i);
log.remove(row);
}
return null;
} }
} };
for (int i = rowsToRemove.size() - 1; i >= 0; i--) { currentWorker.execute();
int row = rowsToRemove.get(i);
log.remove(row);
}
if (!rowsToRemove.isEmpty()) {
int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray();
fireTableRowsDeleted(rows[0], rows[rows.length - 1]);
}
} }
public void applyHostFilter(String filterText) { public void applyHostFilter(String filterText) {
filteredLog.clear(); filteredLog.clear();
fireTableDataChanged();
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
for (MessageEntry entry : log) { log.forEach(entry -> {
String host = StringProcessor.getHostByUrl(entry.getUrl()); String host = StringProcessor.getHostByUrl(entry.getUrl());
if (!host.isEmpty()) { if (!host.isEmpty()) {
if (filterText.contains("*.") && StringProcessor.matchFromEnd(host, cleanedText)) { if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
filteredLog.add(entry);
} else if (host.equals(filterText) || filterText.contains("*")) {
filteredLog.add(entry); filteredLog.add(entry);
} }
} }
} });
fireTableDataChanged(); fireTableDataChanged();
} }
@@ -184,6 +204,9 @@ public class MessageTableModel extends AbstractTableModel {
public void applyMessageFilter(String tableName, String filterText) { public void applyMessageFilter(String tableName, String filterText) {
filteredLog.clear(); filteredLog.clear();
for (MessageEntry entry : log) { for (MessageEntry entry : log) {
// 标志变量,表示是否满足过滤条件
AtomicBoolean isMatched = new AtomicBoolean(false);
HttpRequestResponse requestResponse = entry.getRequestResponse(); HttpRequestResponse requestResponse = entry.getRequestResponse();
HttpRequest httpRequest = requestResponse.request(); HttpRequest httpRequest = requestResponse.request();
HttpResponse httpResponse = requestResponse.response(); HttpResponse httpResponse = requestResponse.response();
@@ -200,9 +223,6 @@ public class MessageTableModel extends AbstractTableModel {
.map(HttpHeader::toString) .map(HttpHeader::toString)
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
// 标志变量,表示是否满足过滤条件
AtomicBoolean isMatched = new AtomicBoolean(false);
Config.globalRules.keySet().forEach(i -> { Config.globalRules.keySet().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) { for (Object[] objects : Config.globalRules.get(i)) {
String name = objects[1].toString(); String name = objects[1].toString();
@@ -243,6 +263,14 @@ public class MessageTableModel extends AbstractTableModel {
case "response body": case "response body":
isMatch = matchingString(format, filterText, responseBody); isMatch = matchingString(format, filterText, responseBody);
break; 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: default:
break; break;
} }
@@ -285,7 +313,7 @@ public class MessageTableModel extends AbstractTableModel {
private Map<String, Map<String, Object>> getCacheData(byte[] content) { private Map<String, Map<String, Object>> getCacheData(byte[] content) {
String hashIndex = HashCalculator.calculateHash(content); String hashIndex = HashCalculator.calculateHash(content);
return CachePool.getFromCache(hashIndex); return CachePool.get(hashIndex);
} }
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) { private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
@@ -300,7 +328,7 @@ public class MessageTableModel extends AbstractTableModel {
if (!map2.containsKey(key)) { if (!map2.containsKey(key)) {
return false; return false;
} }
if (!areInnerMapsEqual(map1.get(key), map2.get(key))) { if (areInnerMapsEqual(map1.get(key), map2.get(key))) {
return false; return false;
} }
} }
@@ -310,45 +338,41 @@ public class MessageTableModel extends AbstractTableModel {
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) { private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
if (innerMap1.size() != innerMap2.size()) { if (innerMap1.size() != innerMap2.size()) {
return false; return true;
} }
for (String key : innerMap1.keySet()) { for (String key : innerMap1.keySet()) {
if (!innerMap2.containsKey(key)) { if (!innerMap2.containsKey(key)) {
return false; return true;
} }
Object value1 = innerMap1.get(key); Object value1 = innerMap1.get(key);
Object value2 = innerMap2.get(key); Object value2 = innerMap2.get(key);
// 如果值是Map则递归对比 // 如果值是Map则递归对比
if (value1 instanceof Map && value2 instanceof Map) { if (value1 instanceof Map && value2 instanceof Map) {
if (!areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) { if (areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
return false; return true;
} }
} else if (!value1.equals(value2)) { } else if (!value1.equals(value2)) {
return false; return true;
} }
} }
return true; return false;
} }
public JSplitPane getSplitPane() {
public JSplitPane getSplitPane()
{
return splitPane; return splitPane;
} }
public MessageTable getMessageTable() public MessageTable getMessageTable() {
{
return messageTable; return messageTable;
} }
public List<MessageEntry> getLogs() { public LinkedList<MessageEntry> getLogs() {
return log; return log;
} }
@Override @Override
public int getRowCount() { public int getRowCount() {
return filteredLog.size(); return filteredLog.size();
@@ -360,27 +384,32 @@ public class MessageTableModel extends AbstractTableModel {
} }
@Override @Override
public Object getValueAt(int rowIndex, int columnIndex) public Object getValueAt(int rowIndex, int columnIndex) {
{ if (!filteredLog.isEmpty()) {
if (filteredLog.isEmpty()) { try {
return ""; MessageEntry messageEntry = filteredLog.get(rowIndex);
}
MessageEntry messageEntry = filteredLog.get(rowIndex);
return switch (columnIndex) { if (messageEntry != null) {
case 0 -> messageEntry.getMethod(); return switch (columnIndex) {
case 1 -> messageEntry.getUrl(); case 0 -> messageEntry.getMethod();
case 2 -> messageEntry.getComment(); case 1 -> messageEntry.getUrl();
case 3 -> messageEntry.getStatus(); case 2 -> messageEntry.getComment();
case 4 -> messageEntry.getLength(); case 3 -> messageEntry.getStatus();
case 5 -> messageEntry.getColor(); case 4 -> messageEntry.getLength();
default -> ""; case 5 -> messageEntry.getColor();
}; default -> "";
};
}
} catch (Exception e) {
api.logging().logToError("getValueAt: " + e.getMessage());
}
}
return "";
} }
@Override @Override
public String getColumnName(int columnIndex) public String getColumnName(int columnIndex) {
{
return switch (columnIndex) { return switch (columnIndex) {
case 0 -> "Method"; case 0 -> "Method";
case 1 -> "URL"; case 1 -> "URL";
@@ -393,10 +422,8 @@ public class MessageTableModel extends AbstractTableModel {
} }
public class MessageTable extends JTable { public class MessageTable extends JTable {
private MessageEntry MessageEntry; private MessageEntry messageEntry;
private SwingWorker<Object, Void> currentWorker; private final ExecutorService executorService;
// 设置响应报文返回的最大长度为3MB
private final int MAX_LENGTH = 3145728;
private int lastSelectedIndex = -1; private int lastSelectedIndex = -1;
private final HttpRequestEditor requestEditor; private final HttpRequestEditor requestEditor;
private final HttpResponseEditor responseEditor; private final HttpResponseEditor responseEditor;
@@ -405,6 +432,7 @@ public class MessageTableModel extends AbstractTableModel {
super(messageTableModel); super(messageTableModel);
this.requestEditor = requestEditor; this.requestEditor = requestEditor;
this.responseEditor = responseEditor; this.responseEditor = responseEditor;
this.executorService = Executors.newSingleThreadExecutor();
} }
@Override @Override
@@ -413,43 +441,21 @@ public class MessageTableModel extends AbstractTableModel {
int selectedIndex = convertRowIndexToModel(row); int selectedIndex = convertRowIndexToModel(row);
if (lastSelectedIndex != selectedIndex) { if (lastSelectedIndex != selectedIndex) {
lastSelectedIndex = selectedIndex; lastSelectedIndex = selectedIndex;
MessageEntry = filteredLog.get(selectedIndex); executorService.execute(this::getSelectedMessage);
}
}
requestEditor.setRequest(HttpRequest.httpRequest("Loading...")); private void getSelectedMessage() {
responseEditor.setResponse(HttpResponse.httpResponse("Loading...")); messageEntry = filteredLog.get(lastSelectedIndex);
if (currentWorker != null && !currentWorker.isDone()) { HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
currentWorker.cancel(true);
}
currentWorker = new SwingWorker<>() { requestEditor.setRequest(HttpRequest.httpRequest(messageEntry.getRequestResponse().httpService(), httpRequestResponse.request().toByteArray()));
@Override int responseSizeWithMb = httpRequestResponse.response().toString().length() / 1024 / 1024;
protected ByteArray[] doInBackground() { if ((responseSizeWithMb < Integer.parseInt(configLoader.getLimitSize())) || configLoader.getLimitSize().equals("0")) {
ByteArray requestByte = MessageEntry.getRequestResponse().request().toByteArray(); responseEditor.setResponse(httpRequestResponse.response());
ByteArray responseByte = MessageEntry.getRequestResponse().response().toByteArray(); } else {
responseEditor.setResponse(HttpResponse.httpResponse("Exceeds length limit."));
if (responseByte.length() > MAX_LENGTH) {
String ellipsis = "\r\n......";
responseByte = responseByte.subArray(0, MAX_LENGTH).withAppended(ellipsis);
}
return new ByteArray[]{requestByte, responseByte};
}
@Override
protected void done() {
if (!isCancelled()) {
try {
ByteArray[] result = (ByteArray[]) get();
requestEditor.setRequest(HttpRequest.httpRequest(MessageEntry.getRequestResponse().httpService(), result[0]));
responseEditor.setResponse(HttpResponse.httpResponse(result[1]));
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
currentWorker.execute();
} }
} }
} }

View File

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

View File

@@ -1,89 +0,0 @@
package hae.component.config;
import burp.api.montoya.MontoyaApi;
import hae.component.rule.Rules;
import hae.utils.config.ConfigLoader;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class Config extends JPanel {
private final MontoyaApi api;
private final ConfigLoader configLoader;
private final Rules rules;
public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) {
this.api = api;
this.configLoader = configLoader;
this.rules = rules;
initComponents();
}
private void initComponents() {
setLayout(new GridBagLayout());
((GridBagLayout) getLayout()).columnWidths = new int[] {0, 0, 0, 0, 0};
((GridBagLayout) getLayout()).rowHeights = new int[] {0, 0, 0};
((GridBagLayout) getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 0.0, 1.0E-4};
((GridBagLayout) getLayout()).rowWeights = new double[] {0.0, 0.0, 1.0E-4};
JLabel rulesFilePathLabel = new JLabel("Rules Path:");
JTextField rulesFilePathTextField = new JTextField();
JButton onlineUpdateButton = new JButton("Update");
JLabel excludeSuffixLabel = new JLabel("Exclude Suffix:");
JTextField excludeSuffixTextField = new JTextField();
JButton excludeSuffixSaveButton = new JButton("Save");
JButton reloadButton = new JButton("Reload");
rulesFilePathTextField.setEditable(false);
onlineUpdateButton.addActionListener(this::onlineUpdateActionPerformed);
excludeSuffixSaveButton.addActionListener(e -> excludeSuffixSaveActionPerformed(e, excludeSuffixTextField.getText()));
reloadButton.addActionListener(this::reloadActionPerformed);
rulesFilePathTextField.setText(configLoader.getRulesFilePath());
excludeSuffixTextField.setText(configLoader.getExcludeSuffix());
add(rulesFilePathTextField, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(5, 0, 5, 5), 0, 0));
add(rulesFilePathLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.WEST, GridBagConstraints.VERTICAL,
new Insets(5, 5, 5, 5), 0, 0));
add(onlineUpdateButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(5, 0, 5, 5), 0, 0));
add(reloadButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(5, 0, 5, 5), 0, 0));
add(excludeSuffixLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE,
new Insets(0, 5, 5, 5), 0, 0));
add(excludeSuffixTextField, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0,
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
new Insets(0, 0, 0, 5), 0, 0));
add(excludeSuffixSaveButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0,
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
new Insets(0, 0, 0, 5), 0, 0));
}
private void onlineUpdateActionPerformed(ActionEvent e) {
// 添加提示框防止用户误触导致配置更新
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
if (retCode == JOptionPane.YES_OPTION) {
configLoader.initRules();
reloadActionPerformed(null);
}
}
private void excludeSuffixSaveActionPerformed(ActionEvent e, String suffix) {
if (!suffix.equals(configLoader.getExcludeSuffix()) && !suffix.isEmpty()) {
configLoader.setExcludeSuffix(suffix);
}
}
private void reloadActionPerformed(ActionEvent e) {
rules.reloadRuleGroup();
}
}

View File

@@ -1,18 +1,17 @@
package hae.component.rule; package hae.component.rule;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.Config;
import hae.utils.ConfigLoader;
import hae.utils.rule.RuleProcessor;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.DefaultTableModel; import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import javax.swing.table.TableRowSorter;
import java.util.Vector; import java.util.Vector;
import hae.Config;
import hae.utils.config.ConfigLoader;
import hae.utils.rule.RuleProcessor;
import static javax.swing.JOptionPane.YES_OPTION; import static javax.swing.JOptionPane.YES_OPTION;
public class Rule extends JPanel { public class Rule extends JPanel {
@@ -32,10 +31,10 @@ public class Rule extends JPanel {
private void initComponents(Object[][] data) { private void initComponents(Object[][] data) {
setLayout(new GridBagLayout()); setLayout(new GridBagLayout());
((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0, 0}; ((GridBagLayout) getLayout()).columnWidths = new int[]{0, 0, 0};
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0, 0, 0, 0}; ((GridBagLayout) getLayout()).rowHeights = new int[]{0, 0, 0, 0, 0};
((GridBagLayout)getLayout()).columnWeights = new double[] {0.0, 1.0, 1.0E-4}; ((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}; ((GridBagLayout) getLayout()).rowWeights = new double[]{0.0, 0.0, 0.0, 1.0, 1.0E-4};
JButton addButton = new JButton("Add"); JButton addButton = new JButton("Add");
JButton editButton = new JButton("Edit"); JButton editButton = new JButton("Edit");
@@ -74,7 +73,7 @@ public class Rule extends JPanel {
model.setDataVector(data, Config.ruleFields); model.setDataVector(data, Config.ruleFields);
model.addTableModelListener(e -> { model.addTableModelListener(e -> {
if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1){ if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1) {
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow()); int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex())); ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
} }
@@ -98,7 +97,7 @@ public class Rule extends JPanel {
Display ruleDisplay = new Display(); Display ruleDisplay = new Display();
ruleDisplay.formatTextField.setText("{0}"); ruleDisplay.formatTextField.setText("{0}");
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION); int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
if (showState == YES_OPTION) { if (showState == YES_OPTION) {
Vector<Object> ruleData = new Vector<>(); Vector<Object> ruleData = new Vector<>();
ruleData.add(false); ruleData.add(false);
@@ -117,8 +116,8 @@ public class Rule extends JPanel {
} }
} }
private void ruleEditActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane){ private void ruleEditActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
if (ruleTable.getSelectedRowCount() >= 1){ if (ruleTable.getSelectedRowCount() >= 1) {
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel(); DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
Display ruleDisplay = new Display(); Display ruleDisplay = new Display();
@@ -129,12 +128,12 @@ public class Rule extends JPanel {
ruleDisplay.colorComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 5).toString()); ruleDisplay.colorComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 5).toString());
ruleDisplay.scopeComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 6).toString()); ruleDisplay.scopeComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 6).toString());
ruleDisplay.engineComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 7).toString()); ruleDisplay.engineComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 7).toString());
ruleDisplay.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(),8)); ruleDisplay.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 8));
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa")); ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION); int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
if (showState == 0){ if (showState == 0) {
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow()); int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1); model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
model.setValueAt(ruleDisplay.firstRegexTextField.getText(), select, 2); model.setValueAt(ruleDisplay.firstRegexTextField.getText(), select, 2);
@@ -150,9 +149,9 @@ public class Rule extends JPanel {
} }
} }
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane){ private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
if (ruleTable.getSelectedRowCount() >= 1){ if (ruleTable.getSelectedRowCount() >= 1) {
if (JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this rule?", "Info", JOptionPane.OK_OPTION) == 0){ if (JOptionPane.showConfirmDialog(this, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel(); DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow()); int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());

View File

@@ -2,7 +2,7 @@ package hae.component.rule;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.Config; import hae.Config;
import hae.utils.config.ConfigLoader; import hae.utils.ConfigLoader;
import hae.utils.rule.RuleProcessor; import hae.utils.rule.RuleProcessor;
import javax.swing.*; import javax.swing.*;
@@ -11,7 +11,7 @@ import java.awt.event.*;
public class Rules extends JTabbedPane { public class Rules extends JTabbedPane {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader; private ConfigLoader configLoader;
private final RuleProcessor ruleProcessor; private final RuleProcessor ruleProcessor;
private final JTextField ruleGroupNameTextField; private final JTextField ruleGroupNameTextField;
@@ -101,13 +101,15 @@ public class Rules extends JTabbedPane {
public void reloadRuleGroup() { public void reloadRuleGroup() {
removeAll(); removeAll();
Config.globalRules.keySet().forEach(i-> addTab(i, new Rule(api, configLoader, hae.Config.globalRules.get(i), this)));
this.configLoader = new ConfigLoader(api);
Config.globalRules.keySet().forEach(i -> addTab(i, new Rule(api, configLoader, hae.Config.globalRules.get(i), this)));
addTab("...", null); addTab("...", null);
} }
private void deleteRuleGroupActionPerformed(ActionEvent e) { private void deleteRuleGroupActionPerformed(ActionEvent e) {
if (getTabCount() > 2) { if (getTabCount() > 2) {
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to delete this rule group?", "Info", int retCode = JOptionPane.showConfirmDialog(this, "Do you want to delete this rule group?", "Info",
JOptionPane.YES_NO_OPTION); JOptionPane.YES_NO_OPTION);
if (retCode == JOptionPane.YES_OPTION) { if (retCode == JOptionPane.YES_OPTION) {
String title = getTitleAt(getSelectedIndex()); String title = getTitleAt(getSelectedIndex());
@@ -118,7 +120,7 @@ public class Rules extends JTabbedPane {
} }
} }
private Action renameTitleActionPerformed = new AbstractAction() { private final Action renameTitleActionPerformed = new AbstractAction() {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
String title = ruleGroupNameTextField.getText(); String title = ruleGroupNameTextField.getText();
@@ -134,7 +136,7 @@ public class Rules extends JTabbedPane {
} }
}; };
private Action cancelActionPerformed = new AbstractAction() { private final Action cancelActionPerformed = new AbstractAction() {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
if (selectedIndex >= 0) { if (selectedIndex >= 0) {

View File

@@ -1,16 +1,20 @@
package hae.instances.editor; package hae.instances.editor;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor;
import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider;
import burp.api.montoya.core.ByteArray; import burp.api.montoya.core.ByteArray;
import burp.api.montoya.core.Range; import burp.api.montoya.core.Range;
import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.ui.Selection; import burp.api.montoya.ui.Selection;
import hae.component.board.Datatable; import burp.api.montoya.ui.editor.extension.EditorCreationContext;
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor;
import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider;
import hae.Config;
import hae.component.board.table.Datatable;
import hae.instances.http.utils.MessageProcessor; import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader;
import hae.utils.http.HttpUtils;
import hae.utils.string.StringProcessor;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -20,27 +24,33 @@ import java.util.Map;
public class RequestEditor implements HttpRequestEditorProvider { public class RequestEditor implements HttpRequestEditorProvider {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
public RequestEditor(MontoyaApi api) { public RequestEditor(MontoyaApi api, ConfigLoader configLoader) {
this.api = api; this.api = api;
this.configLoader = configLoader;
} }
@Override @Override
public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) { public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) {
return new Editor(api, editorCreationContext); return new Editor(api, configLoader, editorCreationContext);
} }
private static class Editor implements ExtensionProvidedHttpRequestEditor { private static class Editor implements ExtensionProvidedHttpRequestEditor {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
private final HttpUtils httpUtils;
private final EditorCreationContext creationContext; private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
private HttpRequestResponse requestResponse; private HttpRequestResponse requestResponse;
private List<Map<String, String>> dataList;
private JTabbedPane jTabbedPane = new JTabbedPane(); private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, EditorCreationContext creationContext) public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
{
this.api = api; this.api = api;
this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader);
this.creationContext = creationContext; this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api);
} }
@@ -53,15 +63,26 @@ public class RequestEditor implements HttpRequestEditorProvider {
@Override @Override
public void setRequestResponse(HttpRequestResponse requestResponse) { public void setRequestResponse(HttpRequestResponse requestResponse) {
this.requestResponse = requestResponse; this.requestResponse = requestResponse;
generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
} }
@Override @Override
public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) { public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) {
HttpRequest request = requestResponse.request(); HttpRequest request = requestResponse.request();
if (request != null && !request.bodyToString().equals("Loading...")) { if (request != null) {
List<Map<String, String>> result = messageProcessor.processRequest("", request, false); try {
jTabbedPane = generateTabbedPaneFromResultMap(api, result); String host = StringProcessor.getHostByUrl(request.url());
return jTabbedPane.getTabCount() > 0; if (!host.isEmpty()) {
String toolType = creationContext.toolSource().toolType().toolName();
boolean matches = httpUtils.verifyHttpRequestResponse(requestResponse, toolType);
if (!matches) {
this.dataList = messageProcessor.processRequest("", request, false);
return isListHasData(this.dataList);
}
}
} catch (Exception ignored) {
}
} }
return false; return false;
} }
@@ -81,7 +102,8 @@ public class RequestEditor implements HttpRequestEditorProvider {
return new Selection() { return new Selection() {
@Override @Override
public ByteArray contents() { public ByteArray contents() {
return ByteArray.byteArray(Datatable.getSelectedData(((Datatable) jTabbedPane.getSelectedComponent()).getDataTable())); Datatable dataTable = (Datatable) jTabbedPane.getSelectedComponent();
return ByteArray.byteArray(dataTable.getSelectedDataAtTable(dataTable.getDataTable()));
} }
@Override @Override
@@ -97,19 +119,25 @@ public class RequestEditor implements HttpRequestEditorProvider {
} }
} }
public static JTabbedPane generateTabbedPaneFromResultMap(MontoyaApi api, List<Map<String, String>> result) { public static boolean isListHasData(List<Map<String, String>> dataList) {
JTabbedPane tabbedPane = new JTabbedPane(); if (dataList != null && !dataList.isEmpty()) {
if (result != null && !result.isEmpty() && result.size() > 0) { 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); Map<String, String> dataMap = result.get(0);
if (dataMap != null && !dataMap.isEmpty() && dataMap.size() > 0) { if (dataMap != null && !dataMap.isEmpty()) {
dataMap.keySet().forEach(i->{ dataMap.keySet().forEach(i -> {
String[] extractData = dataMap.get(i).split("\n"); String[] extractData = dataMap.get(i).split(Config.boundary);
Datatable dataPanel = new Datatable(api, i, Arrays.asList(extractData)); Datatable dataPanel = new Datatable(api, configLoader, i, Arrays.asList(extractData));
tabbedPane.addTab(i, dataPanel); tabbedPane.addTab(i, dataPanel);
}); });
} }
} }
return tabbedPane;
} }
} }

View File

@@ -1,16 +1,20 @@
package hae.instances.editor; package hae.instances.editor;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.ByteArray;
import burp.api.montoya.core.Range;
import burp.api.montoya.http.message.HttpRequestResponse; import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.http.message.responses.HttpResponse;
import burp.api.montoya.ui.Selection;
import burp.api.montoya.ui.editor.extension.EditorCreationContext; import burp.api.montoya.ui.editor.extension.EditorCreationContext;
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor; import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor;
import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider; import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider;
import burp.api.montoya.core.ByteArray; import hae.component.board.table.Datatable;
import burp.api.montoya.core.Range;
import burp.api.montoya.ui.Selection;
import hae.component.board.Datatable;
import hae.instances.http.utils.MessageProcessor; import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader;
import hae.utils.http.HttpUtils;
import hae.utils.string.StringProcessor;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -19,27 +23,33 @@ import java.util.Map;
public class ResponseEditor implements HttpResponseEditorProvider { public class ResponseEditor implements HttpResponseEditorProvider {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
public ResponseEditor(MontoyaApi api) { public ResponseEditor(MontoyaApi api, ConfigLoader configLoader) {
this.api = api; this.api = api;
this.configLoader = configLoader;
} }
@Override @Override
public ExtensionProvidedHttpResponseEditor provideHttpResponseEditor(EditorCreationContext editorCreationContext) { public ExtensionProvidedHttpResponseEditor provideHttpResponseEditor(EditorCreationContext editorCreationContext) {
return new Editor(api, editorCreationContext); return new Editor(api, configLoader, editorCreationContext);
} }
private static class Editor implements ExtensionProvidedHttpResponseEditor { private static class Editor implements ExtensionProvidedHttpResponseEditor {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
private final HttpUtils httpUtils;
private final EditorCreationContext creationContext; private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
private HttpRequestResponse requestResponse; private HttpRequestResponse requestResponse;
private List<Map<String, String>> dataList;
private JTabbedPane jTabbedPane = new JTabbedPane(); private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, EditorCreationContext creationContext) public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
{
this.api = api; this.api = api;
this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader);
this.creationContext = creationContext; this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api);
} }
@@ -52,16 +62,34 @@ public class ResponseEditor implements HttpResponseEditorProvider {
@Override @Override
public void setRequestResponse(HttpRequestResponse requestResponse) { public void setRequestResponse(HttpRequestResponse requestResponse) {
this.requestResponse = requestResponse; this.requestResponse = requestResponse;
RequestEditor.generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
} }
@Override @Override
public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) { public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) {
HttpResponse request = requestResponse.response(); HttpResponse response = requestResponse.response();
if (request != null && !request.bodyToString().equals("Loading...")) {
List<Map<String, String>> result = messageProcessor.processResponse("", request, false); if (response != null) {
jTabbedPane = RequestEditor.generateTabbedPaneFromResultMap(api, result); HttpRequest request = requestResponse.request();
return jTabbedPane.getTabCount() > 0; boolean matches = false;
if (request != null) {
try {
String host = StringProcessor.getHostByUrl(request.url());
if (!host.isEmpty()) {
String toolType = creationContext.toolSource().toolType().toolName();
matches = httpUtils.verifyHttpRequestResponse(requestResponse, toolType);
}
} catch (Exception ignored) {
}
}
if (!matches) {
this.dataList = messageProcessor.processResponse("", response, false);
return RequestEditor.isListHasData(this.dataList);
}
} }
return false; return false;
} }
@@ -80,7 +108,8 @@ public class ResponseEditor implements HttpResponseEditorProvider {
return new Selection() { return new Selection() {
@Override @Override
public ByteArray contents() { public ByteArray contents() {
return ByteArray.byteArray(Datatable.getSelectedData(((Datatable) jTabbedPane.getSelectedComponent()).getDataTable())); Datatable dataTable = (Datatable) jTabbedPane.getSelectedComponent();
return ByteArray.byteArray(dataTable.getSelectedDataAtTable(dataTable.getDataTable()));
} }
@Override @Override

View File

@@ -5,9 +5,12 @@ import burp.api.montoya.core.ByteArray;
import burp.api.montoya.core.Range; import burp.api.montoya.core.Range;
import burp.api.montoya.ui.Selection; import burp.api.montoya.ui.Selection;
import burp.api.montoya.ui.contextmenu.WebSocketMessage; import burp.api.montoya.ui.contextmenu.WebSocketMessage;
import burp.api.montoya.ui.editor.extension.*; import burp.api.montoya.ui.editor.extension.EditorCreationContext;
import hae.component.board.Datatable; import burp.api.montoya.ui.editor.extension.ExtensionProvidedWebSocketMessageEditor;
import burp.api.montoya.ui.editor.extension.WebSocketMessageEditorProvider;
import hae.component.board.table.Datatable;
import hae.instances.http.utils.MessageProcessor; import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -16,26 +19,31 @@ import java.util.Map;
public class WebSocketEditor implements WebSocketMessageEditorProvider { public class WebSocketEditor implements WebSocketMessageEditorProvider {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
public WebSocketEditor(MontoyaApi api) { public WebSocketEditor(MontoyaApi api, ConfigLoader configLoader) {
this.api = api; this.api = api;
this.configLoader = configLoader;
} }
@Override @Override
public ExtensionProvidedWebSocketMessageEditor provideMessageEditor(EditorCreationContext editorCreationContext) { public ExtensionProvidedWebSocketMessageEditor provideMessageEditor(EditorCreationContext editorCreationContext) {
return new Editor(api, editorCreationContext); return new Editor(api, configLoader, editorCreationContext);
} }
private static class Editor implements ExtensionProvidedWebSocketMessageEditor { private static class Editor implements ExtensionProvidedWebSocketMessageEditor {
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
private final EditorCreationContext creationContext; private final EditorCreationContext creationContext;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
private ByteArray message; private ByteArray message;
private List<Map<String, String>> dataList;
private JTabbedPane jTabbedPane = new JTabbedPane(); private final JTabbedPane jTabbedPane = new JTabbedPane();
public Editor(MontoyaApi api, EditorCreationContext creationContext) { public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
this.api = api; this.api = api;
this.configLoader = configLoader;
this.creationContext = creationContext; this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api);
} }
@@ -48,15 +56,15 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
@Override @Override
public void setMessage(WebSocketMessage webSocketMessage) { public void setMessage(WebSocketMessage webSocketMessage) {
this.message = webSocketMessage.payload(); this.message = webSocketMessage.payload();
RequestEditor.generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
} }
@Override @Override
public boolean isEnabledFor(WebSocketMessage webSocketMessage) { public boolean isEnabledFor(WebSocketMessage webSocketMessage) {
String websocketMessage = webSocketMessage.payload().toString(); String websocketMessage = webSocketMessage.payload().toString();
if (!websocketMessage.isEmpty()) { if (!websocketMessage.isEmpty()) {
List<Map<String, String>> result = messageProcessor.processMessage("", websocketMessage, false); this.dataList = messageProcessor.processMessage("", websocketMessage, false);
jTabbedPane = RequestEditor.generateTabbedPaneFromResultMap(api, result); return RequestEditor.isListHasData(this.dataList);
return jTabbedPane.getTabCount() > 0;
} }
return false; return false;
} }
@@ -76,7 +84,8 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
return new Selection() { return new Selection() {
@Override @Override
public ByteArray contents() { public ByteArray contents() {
return ByteArray.byteArray(Datatable.getSelectedData(((Datatable) jTabbedPane.getSelectedComponent()).getDataTable())); Datatable dataTable = (Datatable) jTabbedPane.getSelectedComponent();
return ByteArray.byteArray(dataTable.getSelectedDataAtTable(dataTable.getDataTable()));
} }
@Override @Override

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,7 @@ public class MessageProcessor {
public List<Map<String, String>> processMessage(String host, String message, boolean flag) { public List<Map<String, String>> processMessage(String host, String message, boolean flag) {
Map<String, Map<String, Object>> obj = null; Map<String, Map<String, Object>> obj = null;
try { try {
obj = regularMatcher.match(host, "any", message, message, message); obj = regularMatcher.match(host, "any", message, message, message);
} catch (Exception ignored) { } catch (Exception ignored) {
@@ -34,6 +35,7 @@ public class MessageProcessor {
public List<Map<String, String>> processResponse(String host, HttpResponse httpResponse, boolean flag) { public List<Map<String, String>> processResponse(String host, HttpResponse httpResponse, boolean flag) {
Map<String, Map<String, Object>> obj = null; Map<String, Map<String, Object>> obj = null;
try { try {
String response = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8); String response = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8); String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
@@ -57,6 +59,7 @@ public class MessageProcessor {
String header = httpRequest.headers().stream() String header = httpRequest.headers().stream()
.map(HttpHeader::toString) .map(HttpHeader::toString)
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
obj = regularMatcher.match(host, "request", request, header, body); obj = regularMatcher.match(host, "request", request, header, body);
} catch (Exception ignored) { } catch (Exception ignored) {
} }
@@ -68,7 +71,7 @@ public class MessageProcessor {
List<Map<String, String>> highlightList = new ArrayList<>(); List<Map<String, String>> highlightList = new ArrayList<>();
List<Map<String, String>> extractList = new ArrayList<>(); List<Map<String, String>> extractList = new ArrayList<>();
if (obj != null && !obj.isEmpty() && obj.size() > 0) { if (obj != null && !obj.isEmpty()) {
if (actionFlag) { if (actionFlag) {
List<List<String>> resultList = extractColorsAndComments(obj); List<List<String>> resultList = extractColorsAndComments(obj);
List<String> colorList = resultList.get(0); List<String> colorList = resultList.get(0);
@@ -99,6 +102,7 @@ public class MessageProcessor {
String data = tempMap.get("data").toString(); String data = tempMap.get("data").toString();
extractedData.put(key, data); extractedData.put(key, data);
}); });
return extractedData; return extractedData;
} }
@@ -114,10 +118,11 @@ public class MessageProcessor {
List<List<String>> result = new ArrayList<>(); List<List<String>> result = new ArrayList<>();
result.add(colorList); result.add(colorList);
result.add(commentList); result.add(commentList);
return result; return result;
} }
public List<Integer> retrieveColorIndices(List<String> colors){ public List<Integer> retrieveColorIndices(List<String> colors) {
List<Integer> indices = new ArrayList<>(); List<Integer> indices = new ArrayList<>();
String[] colorArray = Config.color; String[] colorArray = Config.color;
int size = colorArray.length; int size = colorArray.length;
@@ -129,6 +134,7 @@ public class MessageProcessor {
} }
} }
} }
return indices; return indices;
} }
@@ -154,7 +160,7 @@ public class MessageProcessor {
HashSet tmpList = new HashSet(stack); HashSet tmpList = new HashSet(stack);
if (stack.size() == tmpList.size()) { if (stack.size() == tmpList.size()) {
stack.sort(Comparator.comparingInt(Integer::intValue)); stack.sort(Comparator.comparingInt(Integer::intValue));
if(stack.get(0) < 0) { if (stack.get(0) < 0) {
finalColor = colorArray[0]; finalColor = colorArray[0];
} else { } else {
finalColor = colorArray[stack.get(0)]; finalColor = colorArray[stack.get(0)];

View File

@@ -1,20 +1,23 @@
package hae.instances.http.utils; package hae.instances.http.utils;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.persistence.PersistedList;
import burp.api.montoya.persistence.PersistedObject;
import dk.brics.automaton.Automaton; import dk.brics.automaton.Automaton;
import dk.brics.automaton.AutomatonMatcher; import dk.brics.automaton.AutomatonMatcher;
import dk.brics.automaton.RegExp; import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton; import dk.brics.automaton.RunAutomaton;
import hae.Config; import hae.Config;
import hae.cache.CachePool; import hae.cache.CachePool;
import hae.utils.DataManager;
import hae.utils.string.HashCalculator; import hae.utils.string.HashCalculator;
import hae.utils.string.StringProcessor; import hae.utils.string.StringProcessor;
import jregex.Matcher;
import jregex.Pattern;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegularMatcher { public class RegularMatcher {
private final MontoyaApi api; private final MontoyaApi api;
@@ -27,7 +30,7 @@ public class RegularMatcher {
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) { public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
// 先从缓存池里判断是否有已经匹配好的结果 // 先从缓存池里判断是否有已经匹配好的结果
String messageIndex = HashCalculator.calculateHash(message.getBytes()); String messageIndex = HashCalculator.calculateHash(message.getBytes());
Map<String, Map<String, Object>> map = CachePool.getFromCache(messageIndex); Map<String, Map<String, Object>> map = CachePool.get(messageIndex);
if (map != null) { if (map != null) {
return map; return map;
} else { } else {
@@ -38,7 +41,7 @@ public class RegularMatcher {
// 多线程执行,一定程度上减少阻塞现象 // 多线程执行,一定程度上减少阻塞现象
String matchContent = ""; String matchContent = "";
// 遍历获取规则 // 遍历获取规则
List<String> result = new ArrayList<>(); List<String> result;
Map<String, Object> tmpMap = new HashMap<>(); Map<String, Object> tmpMap = new HashMap<>();
boolean loaded = (Boolean) objects[0]; boolean loaded = (Boolean) objects[0];
@@ -69,14 +72,19 @@ public class RegularMatcher {
case "response body": case "response body":
matchContent = body; matchContent = body;
break; break;
case "request line":
case "response line":
matchContent = message.split("\\r?\\n", 2)[0];
break;
default: default:
break; break;
} }
try { try {
result.addAll(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive)); result = new ArrayList<>(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive));
} catch (Exception e) { } catch (Exception e) {
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex)); api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
api.logging().logToError(e.getMessage());
continue; continue;
} }
@@ -85,61 +93,68 @@ public class RegularMatcher {
result.clear(); result.clear();
result.addAll(tmpList); result.addAll(tmpList);
String nameAndSize = String.format("%s (%s)", name, result.size());
if (!result.isEmpty()) { if (!result.isEmpty()) {
tmpMap.put("color", color); tmpMap.put("color", color);
String dataStr = String.join("\n", result); String dataStr = String.join(Config.boundary, result);
tmpMap.put("data", dataStr); tmpMap.put("data", dataStr);
String nameAndSize = String.format("%s (%s)", name, result.size());
finalMap.put(nameAndSize, tmpMap); finalMap.put(nameAndSize, tmpMap);
// 添加到全局变量中便于Databoard检索
if (!Objects.equals(host, "") && host != null) {
List<String> dataList = Arrays.asList(dataStr.split("\n"));
if (Config.globalDataMap.containsKey(host)) {
ConcurrentHashMap<String, List<String>> gRuleMap = new ConcurrentHashMap<>(Config.globalDataMap.get(host));
if (gRuleMap.containsKey(name)) {
// gDataList为不可变列表因此需要重新创建一个列表以便于使用addAll方法
List<String> gDataList = gRuleMap.get(name);
List<String> newDataList = new ArrayList<>(gDataList);
newDataList.addAll(dataList);
newDataList = new ArrayList<>(new HashSet<>(newDataList));
gRuleMap.remove(name);
gRuleMap.put(name, newDataList);
} else {
gRuleMap.put(name, dataList);
}
Config.globalDataMap.remove(host);
Config.globalDataMap.put(host, gRuleMap);
} else {
Map<String, List<String>> ruleMap = new HashMap<>();
ruleMap.put(name, dataList);
// 添加单一Host
Config.globalDataMap.put(host, ruleMap);
}
String[] splitHost = host.split("\\."); putDataToGlobalMap(api, host, name, result, true);
String onlyHost = host.split(":")[0];
String anyHost = (splitHost.length > 2 && !onlyHost.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b")) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
if (!Config.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) {
// 添加通配符Host实际数据从查询哪里将所有数据提取
Config.globalDataMap.put(anyHost, new HashMap<>());
}
if (!Config.globalDataMap.containsKey("*")) {
// 添加通配符全匹配,同上
Config.globalDataMap.put("*", new HashMap<>());
}
}
} }
} }
} }
}); });
CachePool.addToCache(messageIndex, finalMap); CachePool.put(messageIndex, finalMap);
return finalMap; return finalMap;
} }
} }
public synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
// 添加到全局变量中便于Databoard检索
if (!Objects.equals(host, "") && host != null) {
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
Set<String> combinedSet = new LinkedHashSet<>(existingList);
combinedSet.addAll(newList);
return new ArrayList<>(combinedSet);
});
if (flag) {
// 数据存储在BurpSuite空间内
DataManager dataManager = new DataManager(api);
PersistedObject persistedObject = PersistedObject.persistedObject();
gRuleMap.forEach((kName, vList) -> {
PersistedList<String> persistedList = PersistedList.persistedStringList();
persistedList.addAll(vList);
persistedObject.setStringList(kName, persistedList);
});
dataManager.putData("data", host, persistedObject);
}
return gRuleMap;
});
String[] splitHost = host.split("\\.");
String onlyHost = host.split(":")[0];
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
if (!Config.globalDataMap.containsKey(anyHost) && !anyHost.isEmpty()) {
// 添加通配符Host实际数据从查询哪里将所有数据提取
Config.globalDataMap.put(anyHost, new HashMap<>());
}
if (!Config.globalDataMap.containsKey("*")) {
// 添加通配符全匹配,同上
Config.globalDataMap.put("*", new HashMap<>());
}
}
}
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) { private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
List<String> retList = new ArrayList<>(); List<String> retList = new ArrayList<>();
if ("nfa".equals(engine)) { if ("nfa".equals(engine)) {
@@ -198,8 +213,8 @@ public class RegularMatcher {
while (matcher.find()) { while (matcher.find()) {
if (!matcher.group(1).isEmpty()) { if (!matcher.group(1).isEmpty()) {
Object[] params = indexList.stream().map(i -> { Object[] params = indexList.stream().map(i -> {
if (!matcher.group(i+1).isEmpty()) { if (!matcher.group(i + 1).isEmpty()) {
return matcher.group(i+1); return matcher.group(i + 1);
} }
return ""; return "";
}).toArray(); }).toArray();
@@ -225,7 +240,7 @@ public class RegularMatcher {
} }
private Matcher createPatternMatcher(String regex, String content, boolean sensitive) { private Matcher createPatternMatcher(String regex, String content, boolean sensitive) {
Pattern pattern = (sensitive) ? new Pattern(regex) : new Pattern(regex, Pattern.IGNORE_CASE); Pattern pattern = sensitive ? Pattern.compile(regex) : Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
return pattern.matcher(content); return pattern.matcher(content);
} }
@@ -238,7 +253,7 @@ public class RegularMatcher {
private LinkedList<Integer> parseIndexesFromString(String input) { private LinkedList<Integer> parseIndexesFromString(String input) {
LinkedList<Integer> indexes = new LinkedList<>(); LinkedList<Integer> indexes = new LinkedList<>();
Pattern pattern = new Pattern("\\{(\\d+)}"); Pattern pattern = Pattern.compile("\\{(\\d+)}");
Matcher matcher = pattern.matcher(input); Matcher matcher = pattern.matcher(input);
while (matcher.find()) { while (matcher.find()) {
@@ -260,7 +275,7 @@ public class RegularMatcher {
} }
private String reorderIndex(String format) { private String reorderIndex(String format) {
Pattern pattern = new Pattern("\\{(\\d+)}"); Pattern pattern = Pattern.compile("\\{(\\d+)}");
Matcher matcher = pattern.matcher(format); Matcher matcher = pattern.matcher(format);
int count = 0; int count = 0;
while (matcher.find()) { while (matcher.find()) {

View File

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

View File

@@ -1,23 +1,18 @@
package hae.utils.config; package hae.utils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import hae.Config; import hae.Config;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.representer.Representer; import org.yaml.snakeyaml.representer.Representer;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class ConfigLoader { public class ConfigLoader {
private final MontoyaApi api; private final MontoyaApi api;
private final Yaml yaml; private final Yaml yaml;
@@ -32,7 +27,7 @@ public class ConfigLoader {
this.yaml = new Yaml(representer, dop); this.yaml = new Yaml(representer, dop);
String configPath = determineConfigPath(); String configPath = determineConfigPath();
this.configFilePath = String.format("%s/%s", configPath, "Config.yml"); this.configFilePath = String.format("%s/%s", configPath, "Config.yml");
this.rulesFilePath = String.format("%s/%s", configPath, "Rules.yml"); this.rulesFilePath = String.format("%s/%s", configPath, "Rules.yml");
// 构造函数初始化配置 // 构造函数初始化配置
@@ -79,7 +74,11 @@ public class ConfigLoader {
public void initConfig() { public void initConfig() {
Map<String, Object> r = new LinkedHashMap<>(); Map<String, Object> r = new LinkedHashMap<>();
r.put("excludeSuffix", getExcludeSuffix()); r.put("ExcludeSuffix", getExcludeSuffix());
r.put("BlockHost", getBlockHost());
r.put("ExcludeStatus", getExcludeStatus());
r.put("LimitSize", getLimitSize());
r.put("HaEScope", getScope());
try { try {
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8); Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
yaml.dump(r, ws); yaml.dump(r, ws);
@@ -92,24 +91,6 @@ public class ConfigLoader {
return rulesFilePath; return rulesFilePath;
} }
public String getExcludeSuffix() {
File yamlSetting = new File(configFilePath);
if (!yamlSetting.exists() || !yamlSetting.isFile()) {
return Config.suffix;
}
try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) {
Map<String, Object> r = new Yaml().load(inorder);
if (r.containsKey("excludeSuffix")) {
return r.get("excludeSuffix").toString();
}
}catch (Exception ignored) {
}
return Config.suffix;
}
// 获取规则配置 // 获取规则配置
public Map<String, Object[][]> getRules() { public Map<String, Object[][]> getRules() {
Map<String, Object[][]> rules = new HashMap<>(); Map<String, Object[][]> rules = new HashMap<>();
@@ -147,49 +128,127 @@ public class ConfigLoader {
} }
return rules; return rules;
} catch (Exception ignored){ } catch (Exception ignored) {
} }
return rules; return rules;
} }
public String getBlockHost() {
return getValueFromConfig("BlockHost", Config.host);
}
public String getExcludeSuffix() {
return getValueFromConfig("ExcludeSuffix", Config.suffix);
}
public String getExcludeStatus() {
return getValueFromConfig("ExcludeStatus", Config.status);
}
public String getLimitSize() {
return getValueFromConfig("LimitSize", Config.size);
}
public String getScope() {
return getValueFromConfig("HaEScope", Config.scopeOptions);
}
public boolean getMode() {
return getValueFromConfig("HaEModeStatus", Config.modeStatus).equals("true");
}
private String getValueFromConfig(String name, String defaultValue) {
File yamlSetting = new File(configFilePath);
if (!yamlSetting.exists() || !yamlSetting.isFile()) {
return defaultValue;
}
try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) {
Map<String, Object> r = new Yaml().load(inorder);
if (r.containsKey(name)) {
return r.get(name).toString();
}
} catch (Exception ignored) {
}
return defaultValue;
}
public void setExcludeSuffix(String excludeSuffix) { public void setExcludeSuffix(String excludeSuffix) {
Map<String,Object> r = new LinkedHashMap<>(); setValueToConfig("ExcludeSuffix", excludeSuffix);
r.put("excludeSuffix", excludeSuffix); }
try{
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8); public void setBlockHost(String blockHost) {
yaml.dump(r, ws); setValueToConfig("BlockHost", blockHost);
ws.close(); }
public void setExcludeStatus(String status) {
setValueToConfig("ExcludeStatus", status);
}
public void setLimitSize(String size) {
setValueToConfig("LimitSize", size);
}
public void setScope(String scope) {
setValueToConfig("HaEScope", scope);
}
public void setMode(String mode) {
setValueToConfig("HaEModeStatus", mode);
}
private void setValueToConfig(String name, String value) {
Map<String, Object> currentConfig = loadCurrentConfig();
currentConfig.put(name, value);
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
yaml.dump(currentConfig, ws);
} catch (Exception ignored) { } catch (Exception ignored) {
} }
} }
public void initRules() { private Map<String, Object> loadCurrentConfig() {
Thread t = new Thread() { Path path = Paths.get(configFilePath);
public void run() { if (!Files.exists(path)) {
pullRules(); return new LinkedHashMap<>(); // 返回空的Map表示没有当前配置
} }
};
t.start(); try (InputStream in = Files.newInputStream(path)) {
try { return yaml.load(in);
t.join(10000); } catch (Exception e) {
} catch (Exception ignored) { return new LinkedHashMap<>(); // 读取失败时也返回空的Map
} }
} }
private void pullRules() { public boolean initRules() {
try { boolean ret = copyRulesToFile(this.rulesFilePath);
String url = "https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml"; if (!ret) {
HttpRequest httpRequest = HttpRequest.httpRequestFromUrl(url);
HttpRequestResponse requestResponse = api.http().sendRequest(httpRequest);
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(); api.extension().unload();
} }
return ret;
}
private boolean copyRulesToFile(String targetFilePath) {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream("rules/Rules.yml");
File targetFile = new File(targetFilePath);
try (inputStream; OutputStream outputStream = new FileOutputStream(targetFile)) {
if (inputStream != null) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
return true;
}
} catch (Exception ignored) {
}
return false;
} }
} }

View File

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

View File

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

View File

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

View File

@@ -2,11 +2,11 @@ package hae.utils.rule;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.Config; import hae.Config;
import hae.utils.ConfigLoader;
import hae.utils.rule.model.Group; import hae.utils.rule.model.Group;
import hae.utils.rule.model.Info; import hae.utils.rule.model.Info;
import hae.utils.config.ConfigLoader;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.representer.Representer; import org.yaml.snakeyaml.representer.Representer;
import java.io.File; import java.io.File;
@@ -72,13 +72,14 @@ public class RuleProcessor {
public void addRule(Vector data, String type) { public void addRule(Vector data, String type) {
ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type))); ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type)));
x.add(data.toArray()); x.add(data.toArray());
Config.globalRules.put(type,x.toArray(new Object[x.size()][])); Config.globalRules.put(type, x.toArray(new Object[x.size()][]));
this.rulesFormatAndSave(); this.rulesFormatAndSave();
} }
public void removeRule(int select,String type) {
public void removeRule(int select, String type) {
ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type))); ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type)));
x.remove(select); x.remove(select);
Config.globalRules.put(type,x.toArray(new Object[x.size()][])); Config.globalRules.put(type, x.toArray(new Object[x.size()][]));
this.rulesFormatAndSave(); this.rulesFormatAndSave();
} }

View File

@@ -3,7 +3,7 @@ package hae.utils.string;
import java.security.MessageDigest; import java.security.MessageDigest;
public class HashCalculator { public class HashCalculator {
public static String calculateHash(byte[] bytes){ public static String calculateHash(byte[] bytes) {
MessageDigest digest; MessageDigest digest;
try { try {
digest = MessageDigest.getInstance("MD5"); digest = MessageDigest.getInstance("MD5");

View File

@@ -1,8 +1,17 @@
package hae.utils.string; package hae.utils.string;
import burp.api.montoya.core.ByteArray;
import burp.api.montoya.http.HttpService;
import burp.api.montoya.http.message.HttpRequestResponse;
import burp.api.montoya.http.message.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse;
import java.net.URL; import java.net.URL;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID;
public class StringProcessor { public class StringProcessor {
public static String replaceFirstOccurrence(String original, String find, String replace) { public static String replaceFirstOccurrence(String original, String find, String replace) {
@@ -32,6 +41,47 @@ public class StringProcessor {
return patternIndex == -1; return patternIndex == -1;
} }
public static String extractHostname(String hostWithPort) {
if (hostWithPort == null || hostWithPort.isEmpty()) {
return "";
}
int colonIndex = hostWithPort.indexOf(":");
if (colonIndex != -1) {
return hostWithPort.substring(0, colonIndex);
} else {
return hostWithPort;
}
}
public static boolean matchesHostPattern(String host, String selectedHost) {
String hostname = StringProcessor.extractHostname(host);
String hostPattern = selectedHost.replace("*.", "");
boolean matchesDirectly = selectedHost.equals("*") || host.equals(selectedHost);
boolean matchesPattern = !host.contains("*") &&
(hostPattern.equals(selectedHost) ?
StringProcessor.matchFromEnd(host, hostPattern) :
StringProcessor.matchFromEnd(hostname, hostPattern));
return matchesDirectly || matchesPattern;
}
public static HttpRequestResponse createHttpRequestResponse(String url, byte[] request, byte[] response) {
HttpService httpService = HttpService.httpService(url);
HttpRequest httpRequest = HttpRequest.httpRequest(httpService, ByteArray.byteArray(request));
HttpResponse httpResponse = HttpResponse.httpResponse(ByteArray.byteArray(response));
return HttpRequestResponse.httpRequestResponse(httpRequest, httpResponse);
}
public static String getCurrentTime() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
LocalDateTime now = LocalDateTime.now();
return now.format(formatter);
}
public static String getRandomUUID() {
UUID uuid = UUID.randomUUID();
return uuid.toString();
}
public static String mergeComment(String comment) { public static String mergeComment(String comment) {
if (!comment.contains(",")) { if (!comment.contains(",")) {
return comment; return comment;
@@ -69,6 +119,10 @@ public class StringProcessor {
return host; return host;
} }
public static boolean matchHostIsIp(String host) {
return host.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b");
}
private static Map<String, Integer> getStringIntegerMap(String comment) { private static Map<String, Integer> getStringIntegerMap(String comment) {
Map<String, Integer> itemCounts = new HashMap<>(); Map<String, Integer> itemCounts = new HashMap<>();
String[] items = comment.split(", "); String[] items = comment.split(", ");

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,303 @@
rules:
- group: Fingerprint
rule:
- name: Shiro
loaded: true
f_regex: (=deleteMe|rememberMe=)
s_regex: ''
format: '{0}'
color: green
scope: any header
engine: dfa
sensitive: true
- name: JSON Web Token
loaded: true
f_regex: (eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]{10,}|eyJ[A-Za-z0-9_\/+-]{10,}\.[A-Za-z0-9._\/+-]{10,})
s_regex: ''
format: '{0}'
color: green
scope: any
engine: nfa
sensitive: true
- name: Swagger UI
loaded: true
f_regex: ((swagger-ui.html)|(\"swagger\":)|(Swagger UI)|(swaggerUi)|(swaggerVersion))
s_regex: ''
format: '{0}'
color: red
scope: response body
engine: dfa
sensitive: false
- name: Ueditor
loaded: true
f_regex: (ueditor\.(config|all)\.js)
s_regex: ''
format: '{0}'
color: green
scope: response body
engine: dfa
sensitive: false
- name: Druid
loaded: true
f_regex: (Druid Stat Index)
s_regex: ''
format: '{0}'
color: orange
scope: response body
engine: dfa
sensitive: false
- group: Maybe Vulnerability
rule:
- name: Java Deserialization
loaded: true
f_regex: (javax\.faces\.ViewState)
s_regex: ''
format: '{0}'
color: yellow
scope: response body
engine: dfa
sensitive: false
- name: Debug Logic Parameters
loaded: true
f_regex: ((access=)|(adm=)|(admin=)|(alter=)|(cfg=)|(clone=)|(config=)|(create=)|(dbg=)|(debug=)|(delete=)|(disable=)|(edit=)|(enable=)|(exec=)|(execute=)|(grant=)|(load=)|(make=)|(modify=)|(rename=)|(reset=)|(root=)|(shell=)|(test=)|(toggl=))
s_regex: ''
format: '{0}'
color: cyan
scope: request
engine: dfa
sensitive: false
- name: URL As A Value
loaded: true
f_regex: (=(https?)(://|%3a%2f%2f))
s_regex: ''
format: '{0}'
color: cyan
scope: any
engine: nfa
sensitive: false
- name: Upload Form
loaded: true
f_regex: (type\=\"file\")
s_regex: ''
format: '{0}'
color: yellow
scope: response body
engine: dfa
sensitive: false
- name: DoS Paramters
loaded: true
f_regex: ((size=)|(page=)|(num=)|(limit=)|(start=)|(end=)|(count=))
s_regex: ''
format: '{0}'
color: cyan
scope: request
engine: dfa
sensitive: false
- 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: ''
format: '{0}'
color: yellow
scope: response
engine: nfa
sensitive: false
- name: Chinese IDCard
loaded: true
f_regex: '[^0-9]((\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)))[^0-9]'
s_regex: ''
format: '{0}'
color: orange
scope: response body
engine: nfa
sensitive: true
- name: Chinese Mobile Number
loaded: true
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
scope: response body
engine: nfa
sensitive: false
- name: Internal IP Address
loaded: true
f_regex: '[^0-9]((127\.0\.0\.1)|(10\.\d{1,3}\.\d{1,3}\.\d{1,3})|(172\.((1[6-9])|(2\d)|(3[01]))\.\d{1,3}\.\d{1,3})|(192\.168\.\d{1,3}\.\d{1,3}))'
s_regex: ''
format: '{0}'
color: cyan
scope: response
engine: nfa
sensitive: true
- name: MAC Address
loaded: true
f_regex: (^([a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5})|[^a-zA-Z0-9]([a-fA-F0-9]{2}(:[a-fA-F0-9]{2}){5}))
s_regex: ''
format: '{0}'
color: green
scope: response
engine: nfa
sensitive: true
- group: Sensitive Information
rule:
- name: Cloud Key
loaded: true
f_regex: (((access)(|-|_)(key)(|-|_)(id|secret))|(LTAI[a-z0-9]{12,20}))
s_regex: ''
format: '{0}'
color: yellow
scope: any
engine: nfa
sensitive: false
- name: Windows File/Dir Path
loaded: true
f_regex: '[^\w]([a-zA-Z]:\\\\?(?:[^<>:/\\|?*]+\\\\?)*)([^<>:/\\|?*]+(?:\.[^<>:/\\|?*]+)?)'
s_regex: ''
format: '{0}'
color: green
scope: response
engine: nfa
sensitive: true
- name: Password Field
loaded: true
f_regex: ((|\\)(|'|")(|[\w]{1,10})([p](ass|wd|asswd|assword))(|[\w]{1,10})(|\\)(|'|")(:|=|\)\.val\()(
|)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))
s_regex: ''
format: '{0}'
color: yellow
scope: response body
engine: nfa
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})(|\\)(|'|")(:|=|\)\.val\()(
|)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))
s_regex: ''
format: '{0}'
color: green
scope: response body
engine: nfa
sensitive: false
- name: WeCom Key
loaded: true
f_regex: ((corp)(id|secret))
s_regex: ''
format: '{0}'
color: green
scope: response body
engine: dfa
sensitive: false
- name: JDBC Connection
loaded: true
f_regex: (jdbc:[a-z:]+://[a-z0-9\.\-_:;=/@?,&]+)
s_regex: ''
format: '{0}'
color: yellow
scope: any
engine: nfa
sensitive: false
- name: Authorization Header
loaded: true
f_regex: ((basic [a-z0-9=:_\+\/-]{5,100})|(bearer [a-z0-9_.=:_\+\/-]{5,100}))
s_regex: ''
format: '{0}'
color: yellow
scope: response body
engine: nfa
sensitive: false
- name: Sensitive Field
loaded: true
f_regex: ((\[)?('|")?([\w]{0,10})((key)|(secret)|(token)|(config)|(auth)|(access)|(admin)|(ticket))([\w]{0,10})('|")?(\])?(
|)(:|=|\)\.val\()( |)('|")([^'"]+?)('|")(|,|\)))
s_regex: ''
format: '{0}'
color: yellow
scope: response
engine: nfa
sensitive: false
- name: Mobile Number Field
loaded: true
f_regex: ((|\\)(|'|")(|[\w]{1,10})(mobile|phone|sjh|shoujihao|concat)(|[\w]{1,10})(|\\)(|'|")(:|=|\)\.val\()(
|)(|\\)('|")([^'"]+?)(|\\)('|")(|,|\)))
s_regex: ''
format: '{0}'
color: green
scope: response body
engine: nfa
sensitive: false
- group: Other
rule:
- name: Linkfinder
loaded: true
f_regex: (?:"|')((?:(?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|(?:(?:(?:/|\.\./|\./)?[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}\.[a-zA-Z]{1,4})|(?:(?:/|\.\./|\./)?[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}/[^"'><,;|()]{1,}(?:\.[a-zA-Z]{1,4}|action)?)))(?:[\?|#][^"|']{0,})?(?:"|')
s_regex: ''
format: '{0}'
color: gray
scope: response body
engine: nfa
sensitive: true
- name: Source Map
loaded: true
f_regex: (\.js\.map)
s_regex: ''
format: '{0}'
color: pink
scope: response body
engine: dfa
sensitive: false
- name: Create Script
loaded: true
f_regex: (\{[^{}]*\}\s*\[[^\s]*\]\s*\+\s*"[^\s]*\.js")
s_regex: '"?([\w].*?)"?:"(.*?)"'
format: '{0}.{1}'
color: green
scope: response body
engine: nfa
sensitive: false
- name: URL Schemes
loaded: true
f_regex: (\b(?![\w]{0,10}?https?://)(([-A-Za-z0-9]{1,20})://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]))
s_regex: ''
format: '{0}'
color: yellow
scope: response body
engine: nfa
sensitive: false
- name: Router Push
loaded: true
f_regex: (\$router\.push)
s_regex: ''
format: '{0}'
color: magenta
scope: response body
engine: dfa
sensitive: false
- name: All URL
loaded: true
f_regex: (https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;\u4E00-\u9FFF]+[-A-Za-z0-9+&@#/%=~_|])
s_regex: ''
format: '{0}'
color: gray
scope: response body
engine: nfa
sensitive: true
- name: Request URI
loaded: true
f_regex: ' ((?!.*\.js(\?.*)?$)(.*?[^.js$])) '
s_regex: ''
format: '{0}'
color: gray
scope: request line
engine: nfa
sensitive: false
- name: 302 Location
loaded: true
f_regex: 'Location: (.*?)\n'
s_regex: ''
format: '{0}'
color: gray
scope: response header
engine: nfa
sensitive: false