Compare commits
156 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daacb2e146 | ||
|
|
1f1ca99f10 | ||
|
|
fa35b0a625 | ||
|
|
8ef98d20a9 | ||
|
|
e556abb6f7 | ||
|
|
471aab5ea1 | ||
|
|
76b475bd91 | ||
|
|
6014089594 | ||
|
|
910658f2e0 | ||
|
|
8692b0a494 | ||
|
|
5419d4a679 | ||
|
|
ae8cb2fd25 | ||
|
|
5b6bdbe5b6 | ||
|
|
ddb08e9a6e | ||
|
|
6a2f289d57 | ||
|
|
84746a7089 | ||
|
|
68f0bce619 | ||
|
|
4f0401347c | ||
|
|
a7e0a2a6ce | ||
|
|
b7c5a8363d | ||
|
|
d7b4419d51 | ||
|
|
5f54d1f461 | ||
|
|
e4b7f86a0c | ||
|
|
cc30f41bfa | ||
|
|
386c562311 | ||
|
|
a867039284 | ||
|
|
3a8d9eae11 | ||
|
|
e5f55b6c4c | ||
|
|
54973d9f4f | ||
|
|
fb347a8dc6 | ||
|
|
04b6652b03 | ||
|
|
6d4abae898 | ||
|
|
97172fab45 | ||
|
|
ba3b206acf | ||
|
|
99ed2cb2fd | ||
|
|
8a47f61caa | ||
|
|
ad323ba7a5 | ||
|
|
332b119064 | ||
|
|
ead03d42b9 | ||
|
|
4da3d3f42d | ||
|
|
3363ca25ed | ||
|
|
496d0d2174 | ||
|
|
f387834c4d | ||
|
|
ca773f368b | ||
|
|
a6cd01300b | ||
|
|
ba079ab1d8 | ||
|
|
a96dab6615 | ||
|
|
ad1a14b27e | ||
|
|
3a536a52de | ||
|
|
ea87c53958 | ||
|
|
e08b930fb5 | ||
|
|
49647d68d0 | ||
|
|
1c63841140 | ||
|
|
105c506039 | ||
|
|
f1941bccd7 | ||
|
|
d38e70523a | ||
|
|
1f7651c114 | ||
|
|
fc9a253d2b | ||
|
|
4cbcc1bcc4 | ||
|
|
765807de6e | ||
|
|
548315e163 | ||
|
|
d3ab207825 | ||
|
|
44260dd4ff | ||
|
|
cf3ac4978f | ||
|
|
9c8dad8ac0 | ||
|
|
5cd216e45d | ||
|
|
87c5f713fa | ||
|
|
a0946bb723 | ||
|
|
bcb5177b54 | ||
|
|
0225c00f69 | ||
|
|
eafae602b8 | ||
|
|
e56d8eb5d5 | ||
|
|
681cce0644 | ||
|
|
d43809e25f | ||
|
|
567dea6c60 | ||
|
|
8c388510c5 | ||
|
|
e22596819b | ||
|
|
d2cd7a0d03 | ||
|
|
67afe1f650 | ||
|
|
0602346249 | ||
|
|
953b966961 | ||
|
|
4c23d62576 | ||
|
|
6e9b8c8f37 | ||
|
|
ed58d891d5 | ||
|
|
33f5cab037 | ||
|
|
8b79c71df9 | ||
|
|
9ea0e4be9c | ||
|
|
41f197bcb2 | ||
|
|
31e419aed2 | ||
|
|
cf90a9366a | ||
|
|
6546446e4f | ||
|
|
6c4073c8ee | ||
|
|
1e1d51921d | ||
|
|
9135b8cbd2 | ||
|
|
cc7956d8dc | ||
|
|
405efdd5da | ||
|
|
0bb425f00b | ||
|
|
0bdff6fe28 | ||
|
|
6bd153d16a | ||
|
|
b12f9355fa | ||
|
|
fa9dcfc3d2 | ||
|
|
2e23388925 | ||
|
|
06fd54c9ce | ||
|
|
0707a773c8 | ||
|
|
d0f49f8e6c | ||
|
|
5404c90c00 | ||
|
|
e68619d1c2 | ||
|
|
dd08ffaaa2 | ||
|
|
d9aeda4cc3 | ||
|
|
e1c05ba10d | ||
|
|
6a17064b3a | ||
|
|
e698bb1caa | ||
|
|
a69503ca3d | ||
|
|
d590d4a70e | ||
|
|
548339fa58 | ||
|
|
df4496d4fd | ||
|
|
48e355ac54 | ||
|
|
b784aa1425 | ||
|
|
440b3b1504 | ||
|
|
a8f1798c7b | ||
|
|
225ee471ec | ||
|
|
5097124867 | ||
|
|
7e0e3054be | ||
|
|
17a84fc19e | ||
|
|
1573d563eb | ||
|
|
515f7b33f0 | ||
|
|
1dc510d576 | ||
|
|
f401214524 | ||
|
|
7ebba02200 | ||
|
|
93f5c73aac | ||
|
|
60b261d6ef | ||
|
|
c84ebf3a9d | ||
|
|
15f84028bb | ||
|
|
1238e536d1 | ||
|
|
5d23a68c0e | ||
|
|
d7f04526b4 | ||
|
|
acff96ed7b | ||
|
|
350c093162 | ||
|
|
0d3d4f88e9 | ||
|
|
37ca315aba | ||
|
|
241247a4a0 | ||
|
|
08bfb69fce | ||
|
|
a6d5f3a204 | ||
|
|
c4d8743fe3 | ||
|
|
903077c830 | ||
|
|
daddf15af2 | ||
|
|
e747011ec0 | ||
|
|
f7b2e99eb2 | ||
|
|
59cd0a88b9 | ||
|
|
b16cbf5b60 | ||
|
|
4ef766dd82 | ||
|
|
5d9f590977 | ||
|
|
5c326d3ca6 | ||
|
|
62edae0ab4 | ||
|
|
562378873c | ||
|
|
5f62e9653f |
30
.github/ISSUE_TEMPLATE/问题反馈.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: 问题反馈
|
||||
about: 尽可能详细的描述问题并反馈
|
||||
title: "[BUG] 问题标题"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## 使用环境
|
||||
|
||||
```
|
||||
HaE 版本:
|
||||
有无自定义规则:
|
||||
BurpSuite 版本:
|
||||
操作系统版本:
|
||||
是否阅读README:
|
||||
是否知晓注意事项:
|
||||
是否查阅历史ISSUE:
|
||||
```
|
||||
|
||||
## 问题详情
|
||||
|
||||
问题描述:
|
||||
|
||||
出现的场景:
|
||||
|
||||
## 解决建议
|
||||
|
||||
无。
|
||||
138
.gitignore
vendored
@@ -1,3 +1,137 @@
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### JetBrains+all Patch ###
|
||||
# Ignore everything but code style settings and run configurations
|
||||
# that are supposed to be shared within teams.
|
||||
|
||||
.idea/*
|
||||
|
||||
!.idea/codeStyles
|
||||
!.idea/runConfigurations
|
||||
|
||||
### macOS ###
|
||||
# General
|
||||
.DS_Store
|
||||
.idea
|
||||
.gradle
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
### Gradle ###
|
||||
.gradle
|
||||
**/build/
|
||||
!src/**/build/
|
||||
|
||||
# Ignore Gradle GUI config
|
||||
gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Avoid ignore Gradle wrappper properties
|
||||
!gradle-wrapper.properties
|
||||
|
||||
# Cache of project
|
||||
.gradletasknamecache
|
||||
|
||||
# Eclipse Gradle plugin generated files
|
||||
# Eclipse Core
|
||||
.project
|
||||
# JDT-specific (Eclipse Java Development Tools)
|
||||
.classpath
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/macos,gradle,jetbrains+all
|
||||
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
115
README.md
@@ -1,90 +1,83 @@
|
||||
# HaE - Highlighter and Extractor
|
||||
<div align="center">
|
||||
<img src="images/logo.png" style="width: 20%" />
|
||||
<h4><a href="https://gh0st.cn/HaE/">赋能白帽,高效作战!</a></h4>
|
||||
<h5>第一作者: <a href="https://github.com/gh0stkey">EvilChen</a>(中孚信息元亨实验室)<br>第二作者: <a href="https://github.com/0chencc">0chencc</a>(米斯特安全团队)<br>第三作者: <a href="https://github.com/vaycore">vaycore</a>(独立安全研究员)</h5>
|
||||
</div>
|
||||
|
||||
HaE相关作者 (来自米斯特安全团队 www.acmesec.cn) :
|
||||
## 项目介绍
|
||||
|
||||
架构作者: [@0chencc](https://github.com/0Chencc)
|
||||
核心功能作者: [@EvilChen](https://github.com/gh0stkey)
|
||||
**HaE**是一款**网络安全(数据安全)领域**下的框架式项目,采用了**乐高积木式**模块化设计理念,实现对HTTP消息(包含WebSocket)精细化的标记和提取。
|
||||
|
||||
## 公共规则网站
|
||||
通过运用**多引擎**的自定义正则表达式,HaE能够准确匹配并处理HTTP请求与响应报文(包含WebSocket),对匹配成功的内容进行有效的标记和信息抽取,从而提升网络安全(数据安全)领域下的**漏洞和数据分析效率**。
|
||||
|
||||
https://gh0st.cn/HaE/
|
||||
> 随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
|
||||
|
||||
## 介绍
|
||||
GitHub项目地址:https://github.com/gh0stkey/HaE
|
||||
|
||||
**HaE**是基于 `BurpSuite` 插件 `JavaAPI` 开发的请求高亮标记与信息提取的辅助型插件。
|
||||
GitCode项目地址:https://gitcode.com/gh0stkey/HaE
|
||||
|
||||

|
||||
**所获荣誉**:
|
||||
|
||||
该插件可以通过自定义正则的方式匹配**响应报文或请求报文**,可以自行决定符合该自定义正则匹配的相应请求是否需要高亮标记、信息提取。
|
||||
1. [入选2022年KCon兵器谱](https://mp.weixin.qq.com/s/JohMsl1WD29LHCHuLf8mVQ)
|
||||
2. [入选GitCode G-Star项目](https://gitcode.com/gh0stkey/HaE)
|
||||
|
||||
**注**: `HaE`的使用,对测试人员来说需要基本的正则表达式基础,由于`Java`正则表达式的库并没有`Python`的优雅或方便,在使用正则的,HaE要求使用者必须使用`()`将所需提取的表达式内容包含;例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,如果你要提取这段内容的话就需要变成`(rememberMe=delete)`。
|
||||
**注意事项**:
|
||||
|
||||
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`
|
||||
|
||||
初次装载`HaE`会初始化配置文件,默认配置文件内置一个正则: `Email`,初始化的配置文件会放在与`BurpSuite Jar`包同级目录下。
|
||||
初次装载`HaE`会从Jar包中加载离线的规则库,如果更新可以点击`Reinit`进行重新初始化。内置规则库地址可以在Github上找到:`https://github.com/gh0stkey/HaE/blob/master/src/main/resources/rules/Rules.yml`。
|
||||
|
||||
除了初始化的配置文件外,还有`Setting.yml`,该文件用于存储配置文件路径;`HaE`支持自定义配置文件路径,你可以通过点击`Select File`按钮进行选择自定义配置文件。
|
||||
配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下:
|
||||
|
||||

|
||||
1. Linux/Mac用户的配置文件目录:`~/.config/HaE/`
|
||||
2. Windows用户的配置文件目录:`%USERPROFILE%/.config/HaE/`
|
||||
|
||||
## 插件优点
|
||||
除此之外,您也可以选择将配置文件存放在`HaE Jar包`的同级目录下的`/.config/HaE/`中,**以便于离线携带**。
|
||||
|
||||
1. 多选项自定义控制适配需求
|
||||
2. 多颜色高亮分类,将BurpSuite的所有高亮颜色集成: `red, orange, yellow, green, cyan, blue, pink, magenta, gray`
|
||||
3. **颜色升级算法**: 利用下标的方式进行优先级排序,当满足2个同颜色条件则以优先级顺序上升颜色(例如: **两个正则,颜色为橘黄色,该请求两个正则都匹配到了,那么将升级为红色**)
|
||||
4. 配置文件采用YAML格式存储,更加便于阅读和修改
|
||||
5. 内置简单缓存,在“多正则、大数据”的场景下减少卡顿现象
|
||||
6. **支持标签分页**,点击`...`即可添加新的标签页,对着标签页右键即可删除
|
||||
### 规则释义
|
||||
|
||||

|
||||
HaE目前的规则一共有8个字段,详细的含义如下所示:
|
||||
|
||||
## 实际使用
|
||||
| 字段 | 含义 |
|
||||
|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Name | 规则名称,主要用于简短概括当前规则的作用。 |
|
||||
| F-Regex | 规则正则,主要用于填写正则表达式。在HaE中所需提取匹配的内容需要用`(`、`)`将正则表达式进行包裹。|
|
||||
| S-Regex | 规则正则,作用及使用同F-Regex。S-Regex为二次正则,可以用于对F-Regex匹配的数据结果进行二次的匹配提取,如不需要的情况下可以留空。|
|
||||
| Format | 格式化输出,在NFA引擎的正则表达式中,我们可以通过`{0}`、`{1}`、`{2}`…的方式进行取分组格式化输出。默认情况下使用`{0}`即可。 |
|
||||
| Scope | 规则作用域,主要用于表示当前规则作用于HTTP报文的哪个部分。支持请求、响应的行、头、体,以及完整的报文。 |
|
||||
| Engine | 正则引擎,主要用于表示当前规则的正则表达式所使用的引擎。**DFA引擎**:对于文本串里的每一个字符只需扫描一次,速度快、特性少;**NFA引擎**:要翻来覆去标注字符、取消标注字符,速度慢,但是特性(如:分组、替换、分割)丰富。 |
|
||||
| Color | 规则匹配颜色,主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法,当出现相同颜色时会自动向上升级一个颜色进行标记。 |
|
||||
| Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 |
|
||||
|
||||
使用 RGPerson 生成测试数据,放入网站根目录文件中:
|
||||
## 优势特点
|
||||
|
||||

|
||||
1. **功能**:通过对HTTP报文的颜色高亮、注释和提取,帮助使用者获取有意义的信息,**聚焦高价值报文**。
|
||||
2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。
|
||||
3. **查询**:将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。
|
||||
4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。
|
||||
5. **管理**:**融入BurpSuite的项目数据管理**,当使用BurpSuite进行项目存储时HaE数据也会一并存储。
|
||||
6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。
|
||||
|
||||
访问该地址,在`Proxy - HTTP History`中可以看见高亮请求,响应标签页中含有`MarkINFO`标签,其中将匹配到的信息提取了出来。
|
||||
| 界面名称 | 界面展示 |
|
||||
| ------------------------ | ---------------------------------------------------- |
|
||||
| Rules(规则管理) | <img src="images/rules.png" style="width: 80%" /> |
|
||||
| Config(配置管理) | <img src="images/config.png" style="width: 80%" /> |
|
||||
| Databoard(数据集合) | <img src="images/databoard.png" style="width: 80%" /> |
|
||||
| MarkInfo(数据展示) | <img src="images/markinfo.png" style="width: 80%" /> |
|
||||
|
||||

|
||||
## 支持项目
|
||||
|
||||
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
||||
|
||||
## 正则优化
|
||||
|
||||
有些正则在实战应用场景中并不理想
|
||||
|
||||
在正则匹配手机号、身份证号码的时候(纯数字类)会存在一些误报(这里匹配身份证号码无法进行校验,误报率很高),但手机号处理这一块可以解决:
|
||||
|
||||
原正则:
|
||||
|
||||
```
|
||||
1[3-9]\d{9}
|
||||
```
|
||||
|
||||
误报场景: `12315188888888123`,这时候会匹配到`15188888888`,而实际上这一段并不是手机号,所以修改正则为:
|
||||
|
||||
```
|
||||
[^0-9]+(1[3-9]\d{9})[^0-9]+
|
||||
```
|
||||
|
||||
也就是要求匹配的手机号前后不能为0-9的数字。
|
||||
|
||||
## 实战用法
|
||||
|
||||
1. CMS指纹识别,Discuz正则: `(Powered by Discuz!)`
|
||||
2. OSS对象存储信息泄露,正则: `([A|a]ccess[K|k]ey[I|i]d|[A|a]ccess[K|k]ey[S|s]ecret)`
|
||||
3. 内网地址信息提取,正则: `(?: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})`
|
||||
4. 实战插件关联搭配,漏洞挖掘案例: https://mp.weixin.qq.com/s/5vNn7dMRZBtv0ojPBAHV7Q
|
||||
|
||||
...还有诸多使用方法等待大家去发掘。
|
||||
|
||||
## 文末
|
||||
|
||||
随笔: 正义感是一个不可丢失的东西。
|
||||
|
||||
Github项目地址(BUG、需求、正则欢迎提交): https://github.com/gh0stkey/HaE
|
||||
<div align=center>
|
||||
<img src="images/reward.jpeg" style="width: 30%" />
|
||||
</div>
|
||||
|
||||
## 404StarLink 2.0 - Galaxy
|
||||
|
||||
|
||||
68
build.gradle
@@ -1,33 +1,37 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir './src/main/java'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task fatJar(type: Jar) {
|
||||
baseName = project.name + '-all'
|
||||
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||
with jar
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'net.portswigger.burp.extender:burp-extender-api:1.7.13'
|
||||
compile 'org.jetbrains:annotations:16.0.2'
|
||||
compile group: 'org.yaml', name: 'snakeyaml', version: '1.28'
|
||||
compile 'net.sourceforge.jregex:jregex:1.2_01'
|
||||
compile 'dk.brics.automaton:automaton:1.11-8'
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir './src/main/java'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1'
|
||||
implementation 'org.yaml:snakeyaml:2.0'
|
||||
implementation 'dk.brics.automaton:automaton:1.11-8'
|
||||
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
jar {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 297 KiB |
BIN
images/config.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
BIN
images/databoard.png
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
images/logo.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
images/markinfo.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
images/reward.jpeg
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
images/rules.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
@@ -1,233 +0,0 @@
|
||||
package burp;
|
||||
|
||||
import burp.action.*;
|
||||
import burp.ui.MainUI;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEditorTabFactory, ITab {
|
||||
private MainUI main = new MainUI();
|
||||
private static PrintWriter stdout;
|
||||
private IBurpExtenderCallbacks callbacks;
|
||||
private static IExtensionHelpers helpers;
|
||||
private static IMessageEditorTab HaETab;
|
||||
MatchHTTP mh = new MatchHTTP();
|
||||
ExtractContent ec = new ExtractContent();
|
||||
DoAction da = new DoAction();
|
||||
GetColorKey gck = new GetColorKey();
|
||||
UpgradeColor uc = new UpgradeColor();
|
||||
|
||||
@Override
|
||||
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks)
|
||||
{
|
||||
this.callbacks = callbacks;
|
||||
BurpExtender.helpers = callbacks.getHelpers();
|
||||
|
||||
String version = "2.0.6";
|
||||
callbacks.setExtensionName(String.format("HaE (%s) - Highlighter and Extractor", version));
|
||||
// 定义输出
|
||||
stdout = new PrintWriter(callbacks.getStdout(), true);
|
||||
stdout.println("@UI Author: 0chencc");
|
||||
stdout.println("@Core Author: EvilChen");
|
||||
stdout.println("@Github: https://github.com/gh0stkey/HaE");
|
||||
// UI
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
initialize();
|
||||
}
|
||||
});
|
||||
|
||||
callbacks.registerHttpListener(BurpExtender.this);
|
||||
callbacks.registerMessageEditorTabFactory(BurpExtender.this);
|
||||
}
|
||||
private void initialize(){
|
||||
callbacks.customizeUiComponent(main);
|
||||
callbacks.addSuiteTab(BurpExtender.this);
|
||||
}
|
||||
@Override
|
||||
public String getTabCaption(){
|
||||
return "HaE";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getUiComponent() {
|
||||
return main;
|
||||
}
|
||||
|
||||
/*
|
||||
* 使用processHttpMessage用来做Highlighter
|
||||
*/
|
||||
@Override
|
||||
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
|
||||
// 判断是否是响应,且该代码作用域为:REPEATER、INTRUDER、PROXY(分别对应toolFlag 64、32、4)
|
||||
if (toolFlag == 64 || toolFlag == 32 || toolFlag == 4) {
|
||||
Map<String, Map<String, Object>> obj;
|
||||
// 流量清洗
|
||||
String urlString = helpers.analyzeRequest(messageInfo.getHttpService(), messageInfo.getRequest()).getUrl().toString();
|
||||
urlString = urlString.indexOf("?") > 0 ? urlString.substring(0, urlString.indexOf("?")) : urlString;
|
||||
|
||||
// 正则判断
|
||||
if (mh.matchSuffix(urlString)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageIsRequest) {
|
||||
byte[] byteRequest = messageInfo.getRequest();
|
||||
// 获取报文头
|
||||
List<String> requestTmpHeaders = helpers.analyzeRequest(messageInfo.getHttpService(), byteRequest).getHeaders();
|
||||
String requestHeaders = String.join("\n", requestTmpHeaders);
|
||||
|
||||
// 获取报文主体
|
||||
int requestBodyOffset = helpers.analyzeRequest(messageInfo.getHttpService(), byteRequest).getBodyOffset();
|
||||
byte[] requestBody = Arrays.copyOfRange(byteRequest, requestBodyOffset, byteRequest.length);
|
||||
|
||||
obj = ec.matchRegex(byteRequest, requestHeaders, requestBody, "request");
|
||||
} else {
|
||||
byte[] byteResponse = messageInfo.getResponse();
|
||||
|
||||
// 获取报文头
|
||||
List<String> responseTmpHeaders = helpers.analyzeRequest(messageInfo.getHttpService(), byteResponse).getHeaders();
|
||||
String responseHeaders = String.join("\n", responseTmpHeaders);
|
||||
|
||||
// 获取报文主体
|
||||
int responseBodyOffset = helpers.analyzeResponse(byteResponse).getBodyOffset();
|
||||
byte[] responseBody = Arrays.copyOfRange(byteResponse, responseBodyOffset, byteResponse.length);
|
||||
|
||||
obj = ec.matchRegex(byteResponse, responseHeaders, responseBody, "response");
|
||||
}
|
||||
|
||||
List<String> colorList = da.highlightList(obj);
|
||||
if (colorList.size() != 0) {
|
||||
String color = uc.getEndColor(gck.getColorKeys(colorList, Config.colorArray), Config.colorArray);
|
||||
messageInfo.setHighlight(color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MarkInfoTab implements IMessageEditorTab {
|
||||
private ITextEditor markInfoText;
|
||||
private byte[] currentMessage;
|
||||
private final IMessageEditorController controller;
|
||||
private byte[] extractRequestContent;
|
||||
private byte[] extractResponseContent;
|
||||
|
||||
public MarkInfoTab(IMessageEditorController controller, boolean editable) {
|
||||
this.controller = controller;
|
||||
markInfoText = callbacks.createTextEditor();
|
||||
markInfoText.setEditable(editable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabCaption() {
|
||||
return "MarkInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getUiComponent() {
|
||||
return markInfoText.getComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(byte[] content, boolean isRequest) {
|
||||
Map<String, Map<String, Object>> obj;
|
||||
|
||||
if (isRequest) {
|
||||
try {
|
||||
// 流量清洗
|
||||
String urlString = helpers.analyzeRequest(controller.getHttpService(), controller.getRequest()).getUrl().toString();
|
||||
urlString = urlString.indexOf("?") > 0 ? urlString.substring(0, urlString.indexOf("?")) : urlString;
|
||||
// 正则判断
|
||||
if (mh.matchSuffix(urlString)) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
IRequestInfo iRequestInfo = helpers.analyzeRequest(controller.getHttpService(), content);
|
||||
|
||||
// 获取报文头
|
||||
List<String> requestTmpHeaders = iRequestInfo.getHeaders();
|
||||
String requestHeaders = String.join("\n", requestTmpHeaders);
|
||||
// 获取报文主体
|
||||
int requestBodyOffset = iRequestInfo.getBodyOffset();
|
||||
byte[] requestBody = Arrays.copyOfRange(content, requestBodyOffset, content.length);
|
||||
|
||||
obj = ec.matchRegex(content, requestHeaders, requestBody, "request");
|
||||
if (obj.size() > 0) {
|
||||
String result = da.extractString(obj);
|
||||
extractRequestContent = result.getBytes();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
IResponseInfo iResponseInfo = helpers.analyzeResponse(content);
|
||||
// 获取报文头
|
||||
List<String> responseTmpHeaders = iResponseInfo.getHeaders();
|
||||
String responseHeaders = String.join("\n", responseTmpHeaders);
|
||||
// 获取报文主体
|
||||
int responseBodyOffset = iResponseInfo.getBodyOffset();
|
||||
byte[] responseBody = Arrays.copyOfRange(content, responseBodyOffset, content.length);
|
||||
|
||||
obj = ec.matchRegex(content, responseHeaders, responseBody, "response");
|
||||
if (obj.size() > 0) {
|
||||
String result = da.extractString(obj);
|
||||
extractResponseContent = result.getBytes();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getMessage() {
|
||||
return currentMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return markInfoText.isTextModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSelectedData() {
|
||||
return markInfoText.getSelectedText();
|
||||
}
|
||||
|
||||
/*
|
||||
* 使用setMessage用来做Extractor
|
||||
*/
|
||||
@Override
|
||||
public void setMessage(byte[] content, boolean isRequest) {
|
||||
try {
|
||||
String c = new String(content, "UTF-8").intern();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
stdout.println(e);
|
||||
}
|
||||
if (content.length > 0) {
|
||||
if (isRequest) {
|
||||
markInfoText.setText(extractRequestContent);
|
||||
} else {
|
||||
markInfoText.setText(extractResponseContent);
|
||||
}
|
||||
}
|
||||
currentMessage = content;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMessageEditorTab createNewInstance(IMessageEditorController controller, boolean editable) {
|
||||
HaETab = new MarkInfoTab(controller, editable);
|
||||
return HaETab;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package burp;
|
||||
|
||||
/*
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class Config {
|
||||
public static String excludeSuffix = "3g2|3gp|7z|aac|abw|aif|aifc|aiff|arc|au|avi|azw|bin|bmp|bz|bz2|cmx|cod|csh|css|csv|doc|docx|eot|epub|gif|gz|ico|ics|ief|jar|jfif|jpe|jpeg|jpg|m3u|mid|midi|mjs|mp2|mp3|mpa|mpe|mpeg|mpg|mpkg|mpp|mpv2|odp|ods|odt|oga|ogv|ogx|otf|pbm|pdf|pgm|png|pnm|ppm|ppt|pptx|ra|ram|rar|ras|rgb|rmi|rtf|snd|svg|swf|tar|tif|tiff|ttf|vsd|wav|weba|webm|webp|woff|woff2|xbm|xls|xlsx|xpm|xul|xwd|zip|zip";
|
||||
|
||||
public static String[] scopeArray = new String[] {
|
||||
"any",
|
||||
"response",
|
||||
"response header",
|
||||
"response body",
|
||||
"request",
|
||||
"request header",
|
||||
"request body"
|
||||
};
|
||||
|
||||
public static String[] engineArray = new String[] {
|
||||
"nfa",
|
||||
"dfa"
|
||||
};
|
||||
|
||||
public static String outputTplString = "[%s]\n%s\n\n";
|
||||
|
||||
public static String[] colorArray = new String[] {
|
||||
"red",
|
||||
"orange",
|
||||
"yellow",
|
||||
"green",
|
||||
"cyan",
|
||||
"blue",
|
||||
"pink",
|
||||
"magenta",
|
||||
"gray"
|
||||
};
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package burp.action;
|
||||
|
||||
import java.util.Map;
|
||||
import burp.Config;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class DoAction {
|
||||
public String extractString(Map<String, Map<String, Object>> obj) {
|
||||
String[] result = {""};
|
||||
obj.keySet().forEach(i->{
|
||||
Map<String, Object> tmpMap = obj.get(i);
|
||||
String data = tmpMap.get("data").toString();
|
||||
String tmpStr = String.format(Config.outputTplString, i, data).intern();
|
||||
result[0] += tmpStr;
|
||||
});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
public List<String> highlightList(Map<String, Map<String, Object>> obj) {
|
||||
List<String> colorList = new ArrayList<String>();
|
||||
obj.keySet().forEach(i->{
|
||||
Map<String, Object> tmpMap = obj.get(i);
|
||||
String color = tmpMap.get("color").toString();
|
||||
colorList.add(color);
|
||||
});
|
||||
return colorList;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package burp.action;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.*;
|
||||
|
||||
import dk.brics.automaton.Automaton;
|
||||
import dk.brics.automaton.AutomatonMatcher;
|
||||
import dk.brics.automaton.RegExp;
|
||||
import dk.brics.automaton.RunAutomaton;
|
||||
import jregex.Matcher;
|
||||
import jregex.Pattern;
|
||||
|
||||
import burp.yaml.LoadRule;
|
||||
import burp.yaml.LoadConfigFile;
|
||||
|
||||
/*
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class ExtractContent {
|
||||
private LoadConfigFile lcf = new LoadConfigFile();
|
||||
private LoadRule lr = new LoadRule(lcf.getConfigPath());
|
||||
|
||||
public Map<String, Map<String, Object>> matchRegex(byte[] content, String headers, byte[] body, String scopeString) {
|
||||
Map<String, Map<String, Object>> map = new HashMap<>(); // 最终返回的结果
|
||||
Map<String,Object[][]> rules = lr.getConfig();
|
||||
rules.keySet().forEach(i -> {
|
||||
String matchContent = "";
|
||||
for (Object[] objects : rules.get(i)) {
|
||||
// 遍历获取规则
|
||||
List<String> result = new ArrayList<>();
|
||||
Map<String, Object> tmpMap = new HashMap<>();
|
||||
|
||||
String name = objects[1].toString();
|
||||
boolean loaded = (Boolean) objects[0];
|
||||
String regex = objects[2].toString();
|
||||
String color = objects[3].toString();
|
||||
String scope = objects[4].toString();
|
||||
String engine = objects[5].toString();
|
||||
// 判断规则是否开启与作用域
|
||||
if (loaded && (scope.contains(scopeString) || scope.equals("any"))) {
|
||||
switch (scope) {
|
||||
case "any":
|
||||
case "request":
|
||||
case "response":
|
||||
try {
|
||||
matchContent = new String(content, "UTF-8").intern();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
case "request header":
|
||||
case "response header":
|
||||
matchContent = headers;
|
||||
break;
|
||||
case "request body":
|
||||
case "response body":
|
||||
try {
|
||||
matchContent = new String(body, "UTF-8").intern();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (engine.equals("nfa")) {
|
||||
Pattern pattern = new Pattern(regex);
|
||||
Matcher matcher = pattern.matcher(matchContent);
|
||||
while (matcher.find()) {
|
||||
// 添加匹配数据至list
|
||||
// 强制用户使用()包裹正则
|
||||
result.add(matcher.group(1));
|
||||
}
|
||||
} else {
|
||||
RegExp regexpr = new RegExp(regex);
|
||||
Automaton auto = regexpr.toAutomaton();
|
||||
RunAutomaton runAuto = new RunAutomaton(auto, true);
|
||||
AutomatonMatcher autoMatcher = runAuto.newMatcher(matchContent);
|
||||
while (autoMatcher.find()) {
|
||||
// 添加匹配数据至list
|
||||
// 强制用户使用()包裹正则
|
||||
result.add(autoMatcher.group());
|
||||
}
|
||||
}
|
||||
|
||||
// 去除重复内容
|
||||
HashSet tmpList = new HashSet(result);
|
||||
result.clear();
|
||||
result.addAll(tmpList);
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
tmpMap.put("color", color);
|
||||
tmpMap.put("data", String.join("\n", result));
|
||||
// 初始化格式
|
||||
map.put(name, tmpMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package burp.action;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class GetColorKey {
|
||||
/*
|
||||
* 颜色下标获取
|
||||
*/
|
||||
public List<Integer> getColorKeys(List<String> keys, String[] colorArray){
|
||||
List<Integer> result = new ArrayList<Integer>();
|
||||
int size = colorArray.length;
|
||||
// 根据颜色获取下标
|
||||
for (int x = 0; x < keys.size(); x++) {
|
||||
for (int v = 0; v < size; v++) {
|
||||
if (colorArray[v].equals(keys.get(x))) {
|
||||
result.add(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package burp.action;
|
||||
|
||||
import jregex.Matcher;
|
||||
import jregex.Pattern;
|
||||
import jregex.REFlags;
|
||||
import burp.yaml.LoadConfigFile;
|
||||
|
||||
/*
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class MatchHTTP {
|
||||
// 匹配后缀
|
||||
LoadConfigFile lc = new LoadConfigFile();
|
||||
public boolean matchSuffix(String str) {
|
||||
Pattern pattern = new Pattern(String.format("[\\w]+[\\.](%s)", lc.getExcludeSuffix()), REFlags.IGNORE_CASE);
|
||||
Matcher matcher = pattern.matcher(str);
|
||||
if(matcher.find()){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package burp.action;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class UpgradeColor {
|
||||
private String endColor = "";
|
||||
/*
|
||||
* 颜色升级递归算法
|
||||
*/
|
||||
private String colorUpgrade(List<Integer> colorList, String[] colorArray) {
|
||||
int colorSize = colorList.size();
|
||||
colorList.sort(Comparator.comparingInt(Integer::intValue));
|
||||
int i = 0;
|
||||
List<Integer> stack = new ArrayList<Integer>();
|
||||
while (i < colorSize) {
|
||||
if (stack.isEmpty()) {
|
||||
stack.add(colorList.get(i));
|
||||
i++;
|
||||
} else {
|
||||
if (colorList.get(i) != stack.stream().reduce((first, second) -> second).orElse(99999999)) {
|
||||
stack.add(colorList.get(i));
|
||||
i++;
|
||||
} else {
|
||||
stack.set(stack.size() - 1, stack.get(stack.size() - 1) - 1);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// 利用HashSet删除重复元素
|
||||
HashSet tmpList = new HashSet(stack);
|
||||
if (stack.size() == tmpList.size()) {
|
||||
stack.sort(Comparator.comparingInt(Integer::intValue));
|
||||
if(stack.get(0).equals(-1)) {
|
||||
this.endColor = colorArray[0];
|
||||
} else {
|
||||
this.endColor = colorArray[stack.get(0)];
|
||||
}
|
||||
} else {
|
||||
this.colorUpgrade(stack, colorArray);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getEndColor(List<Integer> colorList, String[] colorArray) {
|
||||
colorUpgrade(colorList, colorArray);
|
||||
return endColor;
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package burp.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.plaf.metal.MetalIconFactory;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
|
||||
/*
|
||||
* @author 6dc
|
||||
*
|
||||
* A class which creates a JTabbedPane and auto sets a close button when you add a tab
|
||||
*/
|
||||
|
||||
public class JTabbedPaneCloseButton extends JTabbedPane {
|
||||
|
||||
public JTabbedPaneCloseButton() {
|
||||
super();
|
||||
}
|
||||
|
||||
/* Override Addtab in order to add the close Button everytime */
|
||||
@Override
|
||||
public void addTab(String title, Icon icon, Component component, String tip) {
|
||||
super.addTab(title, icon, component, tip);
|
||||
int count = this.getTabCount() - 1;
|
||||
setTabComponentAt(count, new CloseButtonTab(component, title, icon));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTab(String title, Icon icon, Component component) {
|
||||
addTab(title, icon, component, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTab(String title, Component component) {
|
||||
addTab(title, null, component);
|
||||
}
|
||||
|
||||
|
||||
public void addTab(String title,Component component,Boolean closewith){
|
||||
if (closewith){
|
||||
addTab(title,component);
|
||||
}else{
|
||||
super.addTab(title,null,component,null);
|
||||
}
|
||||
}
|
||||
|
||||
/* addTabNoExit */
|
||||
public void addTabNoExit(String title, Icon icon, Component component, String tip) {
|
||||
super.addTab(title, icon, component, tip);
|
||||
}
|
||||
|
||||
public void addTabNoExit(String title, Icon icon, Component component) {
|
||||
addTabNoExit(title, icon, component, null);
|
||||
}
|
||||
|
||||
public void addTabNoExit(String title, Component component) {
|
||||
addTabNoExit(title, null, component);
|
||||
}
|
||||
|
||||
/* Button */
|
||||
public class CloseButtonTab extends JPanel {
|
||||
private Component tab;
|
||||
|
||||
public CloseButtonTab(final Component tab, String title, Icon icon) {
|
||||
this.tab = tab;
|
||||
setOpaque(false);
|
||||
FlowLayout flowLayout = new FlowLayout(FlowLayout.CENTER, 3, 3);
|
||||
setLayout(flowLayout);
|
||||
JLabel jLabel = new JLabel(title);
|
||||
jLabel.setIcon(icon);
|
||||
add(jLabel);
|
||||
JButton button = new JButton(MetalIconFactory.getInternalFrameCloseIcon(2));
|
||||
button.setMargin(new Insets(0, 0, 0, 0));
|
||||
button.addMouseListener(new CloseListener(tab));
|
||||
add(button);
|
||||
}
|
||||
}
|
||||
/* ClickListener */
|
||||
public class CloseListener implements MouseListener
|
||||
{
|
||||
private Component tab;
|
||||
|
||||
public CloseListener(Component tab){
|
||||
this.tab=tab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if(e.getSource() instanceof JButton){
|
||||
JButton clickedButton = (JButton) e.getSource();
|
||||
JTabbedPane tabbedPane = (JTabbedPane) clickedButton.getParent().getParent().getParent();
|
||||
tabbedPane.remove(tab);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
if(e.getSource() instanceof JButton){
|
||||
JButton clickedButton = (JButton) e.getSource();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
if(e.getSource() instanceof JButton){
|
||||
JButton clickedButton = (JButton) e.getSource();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,347 +0,0 @@
|
||||
package burp.ui;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import burp.yaml.LoadConfigFile;
|
||||
import burp.yaml.LoadRule;
|
||||
import burp.yaml.SetRuleConfig;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* @author LinChen
|
||||
*/
|
||||
|
||||
public class MainUI extends JPanel{
|
||||
public MainUI() {
|
||||
initComponents();
|
||||
}
|
||||
public void closeTabActionPerformed(ActionEvent e){
|
||||
if (tabbedPane1.getTabCount()>2){
|
||||
if (tabbedPane1.getSelectedIndex()!=0){
|
||||
SetRuleConfig setruleconfig = new SetRuleConfig();
|
||||
setruleconfig.deleteRules(tabbedPane1.getTitleAt(tabbedPane1.getSelectedIndex()));
|
||||
tabbedPane1.remove(tabbedPane1.getSelectedIndex());
|
||||
tabbedPane1.setSelectedIndex(tabbedPane1.getSelectedIndex()-1);
|
||||
}else{
|
||||
SetRuleConfig setruleconfig = new SetRuleConfig();
|
||||
setruleconfig.deleteRules(tabbedPane1.getTitleAt(tabbedPane1.getSelectedIndex()));
|
||||
tabbedPane1.remove(tabbedPane1.getSelectedIndex());
|
||||
tabbedPane1.setSelectedIndex(tabbedPane1.getSelectedIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectFileMouseClicked(MouseEvent e) {
|
||||
JFileChooser chooseconfig = new JFileChooser();
|
||||
chooseconfig.setFileSelectionMode(JFileChooser.FILES_ONLY);
|
||||
FileNameExtensionFilter filter = new FileNameExtensionFilter("Yaml File (.yml/.yaml)","yaml", "yml");
|
||||
chooseconfig.setFileFilter(filter);
|
||||
int selectframe = chooseconfig.showDialog(new JLabel(),"Select");
|
||||
if (selectframe == JFileChooser.APPROVE_OPTION){
|
||||
String configpath = chooseconfig.getSelectedFile().toString();
|
||||
reloadRule(configpath);
|
||||
loadfile.setConfigPath(configpath);
|
||||
}
|
||||
configfilepathtext.setText(loadfile.getConfigPath());
|
||||
}
|
||||
private void reloadRule(String configfile){
|
||||
tabbedPane1.removeAll();
|
||||
LoadRule loadrule = new LoadRule(configfile);
|
||||
Map<String,Object[][]> config = loadrule.getConfig();
|
||||
ruleSwitch.setListen(false);
|
||||
config.keySet().forEach(i->tabbedPane1.addTab(i,new RulePane(config.get(i),tabbedPane1)));
|
||||
tabbedPane1.addTab("...",new JLabel());
|
||||
ruleSwitch.setListen(true);
|
||||
}
|
||||
private void reloadRule(){
|
||||
tabbedPane1.removeAll();
|
||||
LoadRule loadrule = new LoadRule(loadfile.getConfigPath());
|
||||
Map<String,Object[][]> config = loadrule.getConfig();
|
||||
ruleSwitch.setListen(false);
|
||||
config.keySet().forEach(i->tabbedPane1.addTab(i,new RulePane(config.get(i),tabbedPane1))
|
||||
);
|
||||
tabbedPane1.addTab("...",new JLabel());
|
||||
ruleSwitch.setListen(true);
|
||||
}
|
||||
|
||||
private void reloadMouseClicked(MouseEvent e) {
|
||||
reloadRule();
|
||||
}
|
||||
private void ESSaveMouseClicked(MouseEvent e) {
|
||||
// TODO add your code here
|
||||
LoadConfigFile lcf = new LoadConfigFile();
|
||||
lcf.setExcludeSuffix(EStext.getText());
|
||||
}
|
||||
private void initComponents() {
|
||||
tabbedPane2 = new JTabbedPane();
|
||||
tabbedPane1 = new JTabbedPane();
|
||||
panel3 = new JPanel();
|
||||
configfilepathtext = new JTextField();
|
||||
label1 = new JLabel();
|
||||
SelectFile = new JButton();
|
||||
reload = new JButton();
|
||||
label2 = new JLabel();
|
||||
EStext = new JTextField();
|
||||
ESSave = new JButton();
|
||||
|
||||
//======== this ========
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0};
|
||||
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0};
|
||||
((GridBagLayout)getLayout()).columnWeights = new double[] {1.0, 1.0E-4};
|
||||
((GridBagLayout)getLayout()).rowWeights = new double[] {1.0, 1.0E-4};
|
||||
|
||||
//======== tabbedPane2 ========
|
||||
{
|
||||
tabbedPane2.addTab("Rules", tabbedPane1);
|
||||
|
||||
//======== panel3 ========
|
||||
{
|
||||
panel3.setLayout(new GridBagLayout());
|
||||
((GridBagLayout)panel3.getLayout()).columnWidths = new int[] {0, 0, 0, 0, 0};
|
||||
((GridBagLayout)panel3.getLayout()).rowHeights = new int[] {0, 0, 0};
|
||||
((GridBagLayout)panel3.getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||
((GridBagLayout)panel3.getLayout()).rowWeights = new double[] {0.0, 0.0, 1.0E-4};
|
||||
|
||||
//---- configfilepathtext ----
|
||||
configfilepathtext.setEditable(false);
|
||||
panel3.add(configfilepathtext, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
|
||||
//---- label1 ----
|
||||
label1.setText("Config File Path:");
|
||||
panel3.add(label1, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.WEST, GridBagConstraints.VERTICAL,
|
||||
new Insets(5, 5, 5, 5), 0, 0));
|
||||
|
||||
//---- SelectFile ----
|
||||
SelectFile.setText("Select File ...");
|
||||
SelectFile.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
SelectFileMouseClicked(e);
|
||||
}
|
||||
});
|
||||
panel3.add(SelectFile, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
|
||||
//---- reload ----
|
||||
reload.setText("Reload");
|
||||
reload.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
reloadMouseClicked(e);
|
||||
}
|
||||
});
|
||||
panel3.add(reload, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0,
|
||||
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
//---- label2 ----
|
||||
label2.setText("ExcludeSuffix:");
|
||||
panel3.add(label2, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE,
|
||||
new Insets(0, 5, 5, 5), 0, 0));
|
||||
panel3.add(EStext, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
|
||||
new Insets(0, 0, 0, 5), 0, 0));
|
||||
|
||||
//---- ESSave ----
|
||||
ESSave.setText("Save");
|
||||
ESSave.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
ESSaveMouseClicked(e);
|
||||
}
|
||||
});
|
||||
panel3.add(ESSave, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
|
||||
new Insets(0, 0, 0, 5), 0, 0));
|
||||
}
|
||||
tabbedPane2.addTab("Config", panel3);
|
||||
}
|
||||
add(tabbedPane2, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 0, 0, 0), 0, 0));
|
||||
// JFormDesigner - End of component initialization //GEN-END:initComponents
|
||||
|
||||
LoadRule loadRule = new LoadRule(loadfile.getConfigPath());
|
||||
Map<String,Object[][]> config = loadRule.getConfig();
|
||||
config.keySet().forEach(i->tabbedPane1.addTab(i,new RulePane(config.get(i),tabbedPane1)));
|
||||
|
||||
tabbedPane1.addTab("...",new JLabel());
|
||||
|
||||
//TabTitleEditListener ruleSwitch = new TabTitleEditListener(tabbedPane1);
|
||||
configfilepathtext.setText(loadfile.getConfigPath());
|
||||
LoadConfigFile lcf =new LoadConfigFile();
|
||||
EStext.setText(lcf.getExcludeSuffix());
|
||||
ruleSwitch = new TabTitleEditListener(tabbedPane1);
|
||||
tabbedPane1.addChangeListener(ruleSwitch);
|
||||
tabbedPane1.addMouseListener(ruleSwitch);
|
||||
closeTab.addActionListener(e -> closeTabActionPerformed(e));
|
||||
tabMenu.add(closeTab);
|
||||
}
|
||||
|
||||
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
|
||||
private JTabbedPane tabbedPane2;
|
||||
private JTabbedPane tabbedPane1;
|
||||
private JPanel panel3;
|
||||
private JTextField configfilepathtext;
|
||||
private JLabel label1;
|
||||
private JButton SelectFile;
|
||||
private JButton reload;
|
||||
private JLabel label2;
|
||||
private JTextField EStext;
|
||||
private JButton ESSave;
|
||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||
protected static JPopupMenu tabMenu = new JPopupMenu();
|
||||
private JMenuItem closeTab = new JMenuItem("Delete");
|
||||
private TabTitleEditListener ruleSwitch;
|
||||
private LoadConfigFile loadfile = new LoadConfigFile();
|
||||
}
|
||||
|
||||
class TabTitleEditListener extends MouseAdapter implements ChangeListener, DocumentListener {
|
||||
protected final JTextField editor = new JTextField();
|
||||
protected final JTabbedPane tabbedPane;
|
||||
protected int editingIdx = -1;
|
||||
protected int len = -1;
|
||||
protected Boolean listen = true;
|
||||
protected Dimension dim;
|
||||
protected Component tabComponent;
|
||||
protected Boolean isRenamesucc = false;
|
||||
protected LoadConfigFile loadfile = new LoadConfigFile();
|
||||
protected LoadRule lr = new LoadRule(loadfile.getConfigPath());
|
||||
protected SetRuleConfig setRuleConfig = new SetRuleConfig();
|
||||
protected final Action startEditing = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
editingIdx = tabbedPane.getSelectedIndex();
|
||||
tabComponent = tabbedPane.getTabComponentAt(editingIdx);
|
||||
tabbedPane.setTabComponentAt(editingIdx, editor);
|
||||
isRenamesucc = true;
|
||||
editor.setVisible(true);
|
||||
editor.setText(tabbedPane.getTitleAt(editingIdx));
|
||||
editor.selectAll();
|
||||
editor.requestFocusInWindow();
|
||||
len = editor.getText().length();
|
||||
dim = editor.getPreferredSize();
|
||||
editor.setMinimumSize(dim);
|
||||
}
|
||||
};
|
||||
protected final Action renameTabTitle = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
String title = editor.getText().trim();
|
||||
if (editingIdx >= 0 && !title.isEmpty()) {
|
||||
String oldname = tabbedPane.getTitleAt(editingIdx);
|
||||
tabbedPane.setTitleAt(editingIdx, title);
|
||||
setRuleConfig.rename(oldname,title);
|
||||
}
|
||||
cancelEditing.actionPerformed(null);
|
||||
}
|
||||
};
|
||||
protected final Action cancelEditing = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
if (editingIdx >= 0) {
|
||||
tabbedPane.setTabComponentAt(editingIdx, tabComponent);
|
||||
editor.setVisible(false);
|
||||
editingIdx = -1;
|
||||
len = -1;
|
||||
tabComponent = null;
|
||||
editor.setPreferredSize(null);
|
||||
tabbedPane.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected TabTitleEditListener(JTabbedPane tabbedPane) {
|
||||
super();
|
||||
this.tabbedPane = tabbedPane;
|
||||
editor.setBorder(BorderFactory.createEmptyBorder());
|
||||
editor.addFocusListener(new FocusAdapter() {
|
||||
@Override public void focusLost(FocusEvent e) {
|
||||
renameTabTitle.actionPerformed(null);
|
||||
}
|
||||
});
|
||||
InputMap im = editor.getInputMap(JComponent.WHEN_FOCUSED);
|
||||
ActionMap am = editor.getActionMap();
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel-editing");
|
||||
am.put("cancel-editing", cancelEditing);
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "rename-tab-title");
|
||||
am.put("rename-tab-title", renameTabTitle);
|
||||
editor.getDocument().addDocumentListener(this);
|
||||
tabbedPane.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "start-editing");
|
||||
tabbedPane.getActionMap().put("start-editing", startEditing);
|
||||
}
|
||||
@Override public void stateChanged(ChangeEvent e) {
|
||||
if (e.getSource() instanceof JTabbedPane && listen) {
|
||||
JTabbedPane pane = (JTabbedPane) e.getSource();
|
||||
if (!isRenamesucc){
|
||||
if (pane.getSelectedIndex() == pane.getComponentCount()-1){
|
||||
newTab();
|
||||
}
|
||||
}else{
|
||||
if (pane.getSelectedIndex() == pane.getComponentCount()-2){
|
||||
newTab();
|
||||
}
|
||||
}
|
||||
}
|
||||
renameTabTitle.actionPerformed(null);
|
||||
}
|
||||
public void newTab(){
|
||||
Object[][] data = new Object[][]{{false, "New Name", "(New Regex)", "gray", "any", "nfa"}};
|
||||
insertTab(tabbedPane,setRuleConfig.newRules(),data);
|
||||
}
|
||||
public void insertTab(@NotNull JTabbedPane pane,String title,Object[][] data){
|
||||
pane.addTab(title,new RulePane(data,pane));
|
||||
pane.remove(pane.getSelectedIndex());
|
||||
pane.addTab("...",new JLabel());
|
||||
}
|
||||
public void setListen(Boolean listen){
|
||||
this.listen = listen;
|
||||
}
|
||||
@Override public void insertUpdate(DocumentEvent e) {
|
||||
updateTabSize();
|
||||
}
|
||||
|
||||
@Override public void removeUpdate(DocumentEvent e) {
|
||||
updateTabSize();
|
||||
}
|
||||
|
||||
@Override public void changedUpdate(DocumentEvent e) {}
|
||||
|
||||
@Override public void mouseClicked(MouseEvent e) {
|
||||
switch (e.getButton()){
|
||||
case 1:
|
||||
{
|
||||
Rectangle r = tabbedPane.getBoundsAt(tabbedPane.getSelectedIndex());
|
||||
boolean isDoubleClick = e.getClickCount() >= 2;
|
||||
if (isDoubleClick && r.contains(e.getPoint())) {
|
||||
startEditing.actionPerformed(null);
|
||||
} else {
|
||||
renameTabTitle.actionPerformed(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:{
|
||||
MainUI.tabMenu.show(e.getComponent(),e.getX(),e.getY());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateTabSize() {
|
||||
editor.setPreferredSize(editor.getText().length() > len ? null : dim);
|
||||
tabbedPane.revalidate();
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
package burp.ui;
|
||||
|
||||
import burp.yaml.SetRuleConfig;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.event.TableModelListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Vector;
|
||||
|
||||
/*
|
||||
* @author LinChen
|
||||
*/
|
||||
|
||||
public class RulePane extends JPanel {
|
||||
public RulePane(Object[][] data,JTabbedPane pane) {
|
||||
initComponents(data,pane);
|
||||
}
|
||||
private SetRuleConfig setruleconfig = new SetRuleConfig();
|
||||
private Boolean isEdit = false;
|
||||
private void RuleAddMouseClicked(MouseEvent e, JTabbedPane pane) {
|
||||
// TODO add your code here
|
||||
RuleSetting add = new RuleSetting();
|
||||
int isOk = JOptionPane.showConfirmDialog(null,add,"RuleSetting - Add Rule",JOptionPane.OK_OPTION);
|
||||
if(isOk == 0){
|
||||
Vector data = new Vector();
|
||||
data.add(false);
|
||||
data.add(add.Name.getText());
|
||||
data.add(add.Regex.getText());
|
||||
data.add(add.ColorSelect.getSelectedItem().toString());
|
||||
data.add(add.ScopeSelect.getSelectedItem().toString());
|
||||
data.add(add.EngineSelect.getSelectedItem().toString());
|
||||
model.insertRow(model.getRowCount(),data);
|
||||
model = (DefaultTableModel) table.getModel();
|
||||
setruleconfig.add(data,pane.getTitleAt(pane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
private void RuleEditMouseClicked(MouseEvent e,JTabbedPane pane){
|
||||
if (table.getSelectedRowCount()>=1){
|
||||
RuleSetting edit = new RuleSetting();
|
||||
edit.Name.setText(table.getValueAt(table.getSelectedRow(),1).toString());
|
||||
edit.Regex.setText(table.getValueAt(table.getSelectedRow(),2).toString());
|
||||
edit.ColorSelect.setSelectedItem(table.getValueAt(table.getSelectedRow(),3).toString());
|
||||
edit.ScopeSelect.setSelectedItem(table.getValueAt(table.getSelectedRow(),4).toString());
|
||||
edit.EngineSelect.setSelectedItem(table.getValueAt(table.getSelectedRow(),5).toString());
|
||||
int isOk = JOptionPane.showConfirmDialog(null,edit,"RuleSetting - Edit Rule",JOptionPane.OK_OPTION);
|
||||
if (isOk ==0){
|
||||
int select = table.convertRowIndexToModel(table.getSelectedRow());
|
||||
model.setValueAt(edit.Name.getText(),select,1);
|
||||
model.setValueAt(edit.Regex.getText(),select,2);
|
||||
model.setValueAt(edit.ColorSelect.getSelectedItem().toString(),select,3);
|
||||
model.setValueAt(edit.ScopeSelect.getSelectedItem().toString(),select,4);
|
||||
model.setValueAt(edit.EngineSelect.getSelectedItem().toString(),select,5);
|
||||
model = (DefaultTableModel) table.getModel();
|
||||
setruleconfig.edit((Vector) model.getDataVector().get(select),select,pane.getTitleAt(pane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RuleRemoveMouseClicked(MouseEvent e,JTabbedPane pane){
|
||||
if (table.getSelectedRowCount()>=1){
|
||||
int isOk = JOptionPane.showConfirmDialog(null,"Are your sure?","RuleSetting - Delete Rule",JOptionPane.OK_OPTION);
|
||||
if (isOk==0){
|
||||
int select = table.convertRowIndexToModel(table.getSelectedRow());
|
||||
model.removeRow(select);
|
||||
model = (DefaultTableModel) table.getModel();
|
||||
setruleconfig.remove(select,pane.getTitleAt(pane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RuleTableChange(TableModelEvent e,JTabbedPane pane) {
|
||||
if (e.getColumn()==0&&table.getSelectedRow()!=-1&&!isEdit){
|
||||
model = (DefaultTableModel) table.getModel();
|
||||
int select = table.convertRowIndexToModel(table.getSelectedRow());
|
||||
setruleconfig.edit((Vector) model.getDataVector().get(select),select,pane.getTitleAt(pane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
private void initComponents(Object[][] data,JTabbedPane pane) {
|
||||
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
|
||||
RuleAdd = new JButton();
|
||||
RuleEdit = new JButton();
|
||||
scrollPane = new JScrollPane();
|
||||
table = new JTable();
|
||||
Remove = new JButton();
|
||||
|
||||
//======== this ========
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {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()).rowWeights = new double[] {0.0, 0.0, 0.0, 1.0, 1.0E-4};
|
||||
|
||||
//---- RuleAdd ----
|
||||
RuleAdd.setText("Add");
|
||||
RuleAdd.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
isEdit = true;
|
||||
RuleAddMouseClicked(e,pane);
|
||||
model = (DefaultTableModel) table.getModel();
|
||||
isEdit = false;
|
||||
}
|
||||
});
|
||||
add(RuleAdd, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 3, 2), 0, 0));
|
||||
|
||||
//---- RuleEdit ----
|
||||
RuleEdit.setText("Edit");
|
||||
RuleEdit.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
isEdit = true;
|
||||
RuleEditMouseClicked(e,pane);
|
||||
model = (DefaultTableModel) table.getModel();
|
||||
isEdit = false;
|
||||
}
|
||||
});
|
||||
add(RuleEdit, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
|
||||
//======== scrollPane ========
|
||||
{
|
||||
|
||||
//---- table ----
|
||||
table.setShowVerticalLines(false);
|
||||
table.setVerifyInputWhenFocusTarget(false);
|
||||
table.setUpdateSelectionOnSort(false);
|
||||
table.setShowHorizontalLines(false);
|
||||
table.setModel(new DefaultTableModel());
|
||||
table.setSurrendersFocusOnKeystroke(true);
|
||||
scrollPane.setViewportView(table);
|
||||
}
|
||||
add(scrollPane, new GridBagConstraints(1, 0, 1, 4, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 5, 5), 0, 0));
|
||||
|
||||
//---- Remove ----
|
||||
Remove.setText("Remove");
|
||||
Remove.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
isEdit = true;
|
||||
RuleRemoveMouseClicked(e,pane);
|
||||
model = (DefaultTableModel) table.getModel();
|
||||
isEdit = false;
|
||||
}
|
||||
});
|
||||
add(Remove, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
// JFormDesigner - End of component initialization //GEN-END:initComponents
|
||||
table.setModel(model);
|
||||
model.setDataVector(data,title);
|
||||
model.addTableModelListener(new TableModelListener() {
|
||||
@Override
|
||||
public void tableChanged(TableModelEvent e) {
|
||||
RuleTableChange(e,pane);
|
||||
}
|
||||
});
|
||||
table.setRowSorter(new TableRowSorter(model));
|
||||
}
|
||||
|
||||
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
|
||||
public JButton RuleAdd;
|
||||
public JButton RuleEdit;
|
||||
public JScrollPane scrollPane;
|
||||
public JTable table;
|
||||
public JButton Remove;
|
||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||
private final String[] title = new String[]{"Loaded", "Name", "Regex", "Color", "Scope", "Engine"};
|
||||
private DefaultTableModel model = new DefaultTableModel() {
|
||||
public Class<?> getColumnClass ( int column){
|
||||
if (column == 0) {
|
||||
return Boolean.class;
|
||||
}else{
|
||||
return String.class;
|
||||
}
|
||||
}
|
||||
public boolean isCellEditable(int row,int column){
|
||||
if (column ==0){
|
||||
return true;
|
||||
}else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
package burp.ui;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import burp.Config;
|
||||
|
||||
/*
|
||||
* @author LinChen
|
||||
*/
|
||||
|
||||
public class RuleSetting extends JPanel {
|
||||
public RuleSetting() {
|
||||
initComponents();
|
||||
}
|
||||
|
||||
public void initComponents() {
|
||||
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
|
||||
label5 = new JLabel();
|
||||
label4 = new JLabel();
|
||||
Regex = new JTextField();
|
||||
label3 = new JLabel();
|
||||
label2 = new JLabel();
|
||||
Name = new JTextField();
|
||||
ScopeSelect = new JComboBox<>();
|
||||
EngineSelect = new JComboBox<>();
|
||||
label6 = new JLabel();
|
||||
ColorSelect = new JComboBox<>();
|
||||
|
||||
//======== this ========
|
||||
setLayout(null);
|
||||
|
||||
//---- label5 ----
|
||||
label5.setText("Engine:");
|
||||
add(label5);
|
||||
label5.setBounds(new Rectangle(new Point(10, 175), label5.getPreferredSize()));
|
||||
|
||||
//---- label4 ----
|
||||
label4.setText("Scope:");
|
||||
add(label4);
|
||||
label4.setBounds(new Rectangle(new Point(10, 135), label4.getPreferredSize()));
|
||||
add(Regex);
|
||||
Regex.setBounds(70, 50, 265, 30);
|
||||
|
||||
//---- label3 ----
|
||||
label3.setText("Regex:");
|
||||
add(label3);
|
||||
label3.setBounds(new Rectangle(new Point(10, 55), label3.getPreferredSize()));
|
||||
|
||||
//---- label2 ----
|
||||
label2.setText("Name:");
|
||||
add(label2);
|
||||
label2.setBounds(new Rectangle(new Point(10, 15), label2.getPreferredSize()));
|
||||
add(Name);
|
||||
Name.setBounds(70, 10, 265, 30);
|
||||
|
||||
//---- ScopeSelect ----
|
||||
ScopeSelect.setModel(new DefaultComboBoxModel<>(Config.scopeArray));
|
||||
add(ScopeSelect);
|
||||
ScopeSelect.setBounds(70, 130, 265, ScopeSelect.getPreferredSize().height);
|
||||
|
||||
//---- EngineSelect ----
|
||||
EngineSelect.setModel(new DefaultComboBoxModel<>(Config.engineArray));
|
||||
add(EngineSelect);
|
||||
EngineSelect.setBounds(70, 170, 265, EngineSelect.getPreferredSize().height);
|
||||
|
||||
//---- label7 ----
|
||||
label6.setText("Color:");
|
||||
add(label6);
|
||||
label6.setBounds(new Rectangle(new Point(10, 95), label6.getPreferredSize()));
|
||||
|
||||
//---- ColorSelect ----
|
||||
ColorSelect.setModel(new DefaultComboBoxModel<>(Config.colorArray));
|
||||
add(ColorSelect);
|
||||
ColorSelect.setBounds(70, 90, 265, ColorSelect.getPreferredSize().height);
|
||||
|
||||
{
|
||||
// compute preferred size
|
||||
Dimension preferredSize = new Dimension();
|
||||
for(int i = 0; i < getComponentCount(); i++) {
|
||||
Rectangle bounds = getComponent(i).getBounds();
|
||||
preferredSize.width = Math.max(bounds.x + bounds.width, preferredSize.width);
|
||||
preferredSize.height = Math.max(bounds.y + bounds.height, preferredSize.height);
|
||||
}
|
||||
Insets insets = getInsets();
|
||||
preferredSize.width += insets.right;
|
||||
preferredSize.height += insets.bottom;
|
||||
setMinimumSize(preferredSize);
|
||||
setPreferredSize(preferredSize);
|
||||
}
|
||||
// JFormDesigner - End of component initialization //GEN-END:initComponents
|
||||
}
|
||||
|
||||
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
|
||||
private JLabel label5;
|
||||
private JLabel label4;
|
||||
public JTextField Regex;
|
||||
private JLabel label3;
|
||||
private JLabel label2;
|
||||
public JTextField Name;
|
||||
public JComboBox<String> ScopeSelect;
|
||||
public JComboBox<String> EngineSelect;
|
||||
private JLabel label6;
|
||||
public JComboBox<String> ColorSelect;
|
||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package burp.yaml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* @author LinChen
|
||||
*/
|
||||
|
||||
public class Config {
|
||||
public List<Rules> rules;
|
||||
|
||||
public List<Rules> getRules() {
|
||||
return rules;
|
||||
}
|
||||
|
||||
public void setRules(List<Rules> rules) {
|
||||
this.rules = rules;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package burp.yaml;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* @author LinChen
|
||||
*/
|
||||
|
||||
public class LoadConfigFile {
|
||||
private static Yaml yaml = new Yaml();
|
||||
private static final String SettingPath = "Setting.yml";
|
||||
private static final String ConfigPath = "Config.yml";
|
||||
|
||||
public LoadConfigFile(){
|
||||
init();
|
||||
}
|
||||
|
||||
// 初始化配置
|
||||
public void init(){
|
||||
File yamlSetting = new File(SettingPath);
|
||||
if (!(yamlSetting.exists() && yamlSetting.isFile())) {
|
||||
Map<String,Object> r = new HashMap<>();
|
||||
r.put("configPath", ConfigPath);
|
||||
r.put("excludeSuffix", getExcludeSuffix());
|
||||
try{
|
||||
Writer ws = new OutputStreamWriter(new FileOutputStream(SettingPath),"UTF-8");
|
||||
yaml.dump(r, ws);
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getExcludeSuffix(){
|
||||
try {
|
||||
InputStream inorder = new FileInputStream(SettingPath);
|
||||
Map<String,Object> r;
|
||||
r = yaml.load(inorder);
|
||||
return r.get("excludeSuffix").toString();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return "css|jpeg|gif|jpg|png|pdf|rar|zip|docx|doc|svg|jpeg|ico|woff|woff2|ttf|otf";
|
||||
}
|
||||
}
|
||||
|
||||
public String getConfigPath(){
|
||||
try {
|
||||
InputStream inorder = new FileInputStream(SettingPath);
|
||||
Map<String,Object> r;
|
||||
r = yaml.load(inorder);
|
||||
return r.get("configPath").toString();
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
return ConfigPath;
|
||||
}
|
||||
}
|
||||
|
||||
public void setExcludeSuffix(@NotNull String excludeSuffix){
|
||||
Map<String,Object> r = new HashMap<>();
|
||||
r.put("excludeSuffix", excludeSuffix);
|
||||
r.put("configPath", getConfigPath());
|
||||
try{
|
||||
Writer ws = new OutputStreamWriter(new FileOutputStream(SettingPath),"UTF-8");
|
||||
yaml.dump(r, ws);
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void setConfigPath(@NotNull String filePath){
|
||||
Map<String,Object> r = new HashMap<>();
|
||||
r.put("configPath", filePath);
|
||||
r.put("excludeSuffix", getExcludeSuffix());
|
||||
try{
|
||||
Writer ws = new OutputStreamWriter(new FileOutputStream(SettingPath),"UTF-8");
|
||||
yaml.dump(r, ws);
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package burp.yaml;
|
||||
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
import org.yaml.snakeyaml.nodes.Tag;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* @author LinChen
|
||||
*/
|
||||
|
||||
public class LoadRule {
|
||||
private static String filePath = "Config.yml";
|
||||
public LoadRule(String configfile){
|
||||
filePath = configfile;
|
||||
init();
|
||||
}
|
||||
|
||||
// 初始化配置
|
||||
public void init(){
|
||||
File settingyaml = new File(filePath);
|
||||
if (!(settingyaml.exists() && settingyaml.isFile())){
|
||||
Map<String,Object[][]> r = new HashMap<>();
|
||||
Rule rule = new Rule();
|
||||
rule.setLoaded(true);
|
||||
rule.setName("Email");
|
||||
rule.setColor("yellow");
|
||||
rule.setEngine("nfa");
|
||||
rule.setScope("response");
|
||||
rule.setRegex("(([a-zA-Z0-9][_|\\.])*[a-zA-Z0-9]+@([a-zA-Z0-9][-|_|\\.])*[a-zA-Z0-9]+\\.((?!js|css|jpg|jpeg|png|ico)[a-zA-Z]{2,}))");
|
||||
Rules rules = new Rules();
|
||||
rules.setType("Basic Information");
|
||||
ArrayList<Rule> rl = new ArrayList<>();
|
||||
rl.add(rule);
|
||||
rules.setRule(rl);
|
||||
ArrayList<Rules> rls = new ArrayList<>();
|
||||
rls.add(rules);
|
||||
Config config = new Config();
|
||||
config.setRules(rls);
|
||||
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer();
|
||||
representer.addClassTag(Config.class, Tag.MAP);
|
||||
|
||||
Yaml yaml = new Yaml(new Constructor(),representer,dop);
|
||||
LoadConfigFile loadfile = new LoadConfigFile();
|
||||
File f = new File(loadfile.getConfigPath());
|
||||
try{
|
||||
Writer ws = new OutputStreamWriter(new FileOutputStream(f),"UTF-8");
|
||||
yaml.dump(config,ws);
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String,Object[][]> getConfig(){
|
||||
InputStream inorder = null;
|
||||
{
|
||||
try {
|
||||
inorder = new FileInputStream(new File(filePath));
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
Yaml yaml = new Yaml(new Constructor(Config.class));
|
||||
Config plugin = yaml.loadAs(inorder, Config.class);
|
||||
Map<String,Object[][]> config = new HashMap<>();
|
||||
plugin.rules.forEach(i->{
|
||||
ArrayList<Object[]> data = new ArrayList<>();
|
||||
i.rule.forEach(j->{
|
||||
try {
|
||||
data.add(j.getRuleObject());
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
config.put(i.getType(), data.toArray(new Object[data.size()][]));
|
||||
});
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package burp.yaml;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/*
|
||||
* @author LinChen
|
||||
*/
|
||||
|
||||
public class Rule {
|
||||
private String Name;
|
||||
private Boolean Loaded;
|
||||
private String Regex;
|
||||
private String Color;
|
||||
private String Engine;
|
||||
private String Scope;
|
||||
|
||||
public Boolean getLoaded() {
|
||||
return Loaded;
|
||||
}
|
||||
public String getColor() {
|
||||
return Color;
|
||||
}
|
||||
|
||||
public String getEngine() {
|
||||
return Engine;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return Name;
|
||||
}
|
||||
|
||||
public String getRegex() {
|
||||
return Regex;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return Scope;
|
||||
}
|
||||
|
||||
public void setLoaded(Boolean loaded) {
|
||||
this.Loaded = loaded;
|
||||
}
|
||||
|
||||
|
||||
public void setColor(String color) {
|
||||
this.Color = color;
|
||||
}
|
||||
|
||||
public void setEngine(String engine) {
|
||||
this.Engine = engine;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.Name = name;
|
||||
}
|
||||
|
||||
public void setRegex(String regex) {
|
||||
this.Regex = regex;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.Scope = scope;
|
||||
}
|
||||
public Object[] getRuleObject(){
|
||||
return new Object[]{Loaded, Name, Regex, Color, Scope, Engine};
|
||||
}
|
||||
public Map<String,Object> getRuleObjMap(){
|
||||
Map<String,Object> r = new HashMap<>();
|
||||
r.put("Loaded",Loaded);
|
||||
r.put("Name",Name);
|
||||
r.put("Regex",Regex);
|
||||
r.put("Color",Color);
|
||||
r.put("Scope",Scope);
|
||||
r.put("Engine",Engine);
|
||||
return r;
|
||||
}
|
||||
public String toString(){
|
||||
return "{ \nLoaded: "+Loaded+"\nName: "+Name+"\nRegex: "+Regex+"\nColor: "+Color+"\nScope: "+Scope+"\nEngine: "+Engine+"\n}";
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package burp.yaml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* @author LinChen
|
||||
*/
|
||||
|
||||
public class Rules {
|
||||
private String type;
|
||||
public List<Rule> rule;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<Rule> getRule() {
|
||||
return rule;
|
||||
}
|
||||
|
||||
public void setRule(List<Rule> rule) {
|
||||
this.rule = rule;
|
||||
}
|
||||
|
||||
public void setRuleObj(){
|
||||
|
||||
}
|
||||
public String toString(){
|
||||
return "{ type: "+type+"\n config: "+ rule +"}\n";
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package burp.yaml;
|
||||
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.nodes.Tag;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
|
||||
public class SetRuleConfig {
|
||||
private static Yaml yaml;
|
||||
private static LoadConfigFile loadfile;
|
||||
private static LoadRule lr;
|
||||
private Map<String,Object[][]> config = lr.getConfig();
|
||||
public void format(){
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer();
|
||||
representer.addClassTag(Config.class, Tag.MAP);
|
||||
yaml = new Yaml(new Constructor(),representer,dop);
|
||||
Config con = new Config();
|
||||
List<Rules> rls = new ArrayList<>();
|
||||
|
||||
config.keySet().forEach(i->
|
||||
{
|
||||
Rules rlstmp = new Rules();
|
||||
rlstmp.setType(i);
|
||||
List<Rule> rl = new ArrayList<>();
|
||||
for (Object[] objects : config.get(i)) {
|
||||
Rule rltmp = new Rule();
|
||||
rltmp.setName((String) objects[1]);
|
||||
rltmp.setLoaded((Boolean) objects[0]);
|
||||
rltmp.setRegex((String) objects[2]);
|
||||
rltmp.setColor((String) objects[3]);
|
||||
rltmp.setScope((String) objects[4]);
|
||||
rltmp.setEngine((String) objects[5]);
|
||||
rl.add(rltmp);
|
||||
}
|
||||
rlstmp.setRule(rl);
|
||||
rls.add(rlstmp);
|
||||
});
|
||||
con.setRules(rls);
|
||||
File f = new File(loadfile.getConfigPath());
|
||||
try{
|
||||
Writer ws = new OutputStreamWriter(new FileOutputStream(f),"UTF-8");
|
||||
yaml.dump(con,ws);
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
public void edit(Vector data,int select, String type){
|
||||
loadfile = new LoadConfigFile();
|
||||
lr = new LoadRule(loadfile.getConfigPath());
|
||||
config = lr.getConfig();
|
||||
config.get(type)[select] = data.toArray();
|
||||
this.format();
|
||||
}
|
||||
public void add(Vector data,String type){
|
||||
loadfile = new LoadConfigFile();
|
||||
lr = new LoadRule(loadfile.getConfigPath());
|
||||
config = lr.getConfig();
|
||||
ArrayList<Object[]> x = new ArrayList<Object[]>(Arrays.asList(config.get(type)));
|
||||
x.add(data.toArray());
|
||||
config.put(type,x.toArray(new Object[x.size()][]));
|
||||
this.format();
|
||||
}
|
||||
public void remove(int select,String type){
|
||||
loadfile = new LoadConfigFile();
|
||||
lr = new LoadRule(loadfile.getConfigPath());
|
||||
config = lr.getConfig();
|
||||
ArrayList<Object[]> x = new ArrayList<Object[]>(Arrays.asList(config.get(type)));
|
||||
x.remove(select);
|
||||
config.put(type,x.toArray(new Object[x.size()][]));
|
||||
this.format();
|
||||
}
|
||||
public void rename(String oldname,String newname){
|
||||
loadfile = new LoadConfigFile();
|
||||
lr = new LoadRule(loadfile.getConfigPath());
|
||||
config = lr.getConfig();
|
||||
config.put(newname,config.remove(oldname));
|
||||
this.format();
|
||||
}
|
||||
public void deleteRules(String Rules){
|
||||
loadfile = new LoadConfigFile();
|
||||
lr = new LoadRule(loadfile.getConfigPath());
|
||||
config = lr.getConfig();
|
||||
config.remove(Rules);
|
||||
this.format();
|
||||
}
|
||||
public String newRules(){
|
||||
int i = 0;
|
||||
loadfile = new LoadConfigFile();
|
||||
lr = new LoadRule(loadfile.getConfigPath());
|
||||
config = lr.getConfig();
|
||||
String name = "New ";
|
||||
Object[][] data = new Object[][]{{false, "New Name", "(New Regex)", "gray", "any", "nfa"}};
|
||||
while (config.containsKey(name+i)){
|
||||
i++;
|
||||
}
|
||||
config.put(name+i,data);
|
||||
this.format();
|
||||
return name+i;
|
||||
}
|
||||
}
|
||||
67
src/main/java/hae/Config.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package hae;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
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 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 header",
|
||||
"any body",
|
||||
"response",
|
||||
"response line",
|
||||
"response header",
|
||||
"response body",
|
||||
"request",
|
||||
"request line",
|
||||
"request header",
|
||||
"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 = {
|
||||
"Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive"
|
||||
};
|
||||
|
||||
public static Object[][] ruleTemplate = new Object[][]{
|
||||
{
|
||||
false, "New Name", "(First Regex)", "(Second Regex)", "{0}", "gray", "any", "nfa", false
|
||||
}
|
||||
};
|
||||
|
||||
public static String[] engine = new String[]{
|
||||
"nfa",
|
||||
"dfa"
|
||||
};
|
||||
|
||||
public static String[] color = new String[]{
|
||||
"red",
|
||||
"orange",
|
||||
"yellow",
|
||||
"green",
|
||||
"cyan",
|
||||
"blue",
|
||||
"pink",
|
||||
"magenta",
|
||||
"gray"
|
||||
};
|
||||
|
||||
public static Map<String, Object[][]> globalRules = new HashMap<>();
|
||||
|
||||
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
|
||||
}
|
||||
60
src/main/java/hae/HaE.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package hae;
|
||||
|
||||
import burp.api.montoya.BurpExtension;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.extension.ExtensionUnloadingHandler;
|
||||
import burp.api.montoya.logging.Logging;
|
||||
import hae.cache.CachePool;
|
||||
import hae.component.Main;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.instances.editor.RequestEditor;
|
||||
import hae.instances.editor.ResponseEditor;
|
||||
import hae.instances.editor.WebSocketEditor;
|
||||
import hae.instances.websocket.WebSocketMessageHandler;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.DataManager;
|
||||
|
||||
public class HaE implements BurpExtension {
|
||||
@Override
|
||||
public void initialize(MontoyaApi api) {
|
||||
// 设置扩展名称
|
||||
String version = "4.0";
|
||||
api.extension().setName("HaE - Highlighter and Extractor");
|
||||
|
||||
// 加载扩展后输出的项目信息
|
||||
Logging logging = api.logging();
|
||||
logging.logToOutput("[ HACK THE WORLD - TO DO IT ]");
|
||||
logging.logToOutput("[#] Author: EvilChen && 0chencc && vaycore");
|
||||
logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE");
|
||||
logging.logToOutput("[#] Version: " + version);
|
||||
|
||||
// 配置文件加载
|
||||
ConfigLoader configLoader = new ConfigLoader(api);
|
||||
|
||||
MessageTableModel messageTableModel = new MessageTableModel(api, configLoader);
|
||||
|
||||
// 注册Tab页(用于查询数据)
|
||||
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
|
||||
|
||||
// 注册WebSocket处理器
|
||||
api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api)));
|
||||
|
||||
// 注册消息编辑框(用于展示数据)
|
||||
api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader));
|
||||
api.userInterface().registerHttpResponseEditorProvider(new ResponseEditor(api, configLoader));
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
34
src/main/java/hae/cache/CachePool.java
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package hae.cache;
|
||||
|
||||
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 {
|
||||
private static final int MAX_SIZE = 100000;
|
||||
private static final int EXPIRE_DURATION = 5;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static Map<String, Map<String, Object>> get(String key) {
|
||||
return cache.getIfPresent(key);
|
||||
}
|
||||
|
||||
public static void remove(String key) {
|
||||
cache.invalidate(key);
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
cache.invalidateAll();
|
||||
}
|
||||
}
|
||||
437
src/main/java/hae/component/Config.java
Normal file
@@ -0,0 +1,437 @@
|
||||
package hae.component;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.Registration;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.component.rule.Rules;
|
||||
import hae.instances.http.HttpMessageActiveHandler;
|
||||
import hae.instances.http.HttpMessagePassiveHandler;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.UIEnhancer;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.TitledBorder;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.event.TableModelListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.event.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
public class Config extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final MessageTableModel messageTableModel;
|
||||
private final Rules rules;
|
||||
|
||||
private Registration activeHandler;
|
||||
private Registration passiveHandler;
|
||||
|
||||
public Config(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel, Rules rules) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.messageTableModel = messageTableModel;
|
||||
this.rules = rules;
|
||||
|
||||
this.activeHandler = api.http().registerHttpHandler(new HttpMessageActiveHandler(api, configLoader, messageTableModel));
|
||||
this.passiveHandler = api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel));
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
GridBagConstraints constraints = new GridBagConstraints();
|
||||
constraints.weightx = 1.0;
|
||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
JPanel ruleInfoPanel = new JPanel(new GridBagLayout());
|
||||
ruleInfoPanel.setBorder(new EmptyBorder(10, 15, 5, 15));
|
||||
|
||||
JLabel ruleLabel = new JLabel("Path:");
|
||||
JTextField pathTextField = new JTextField();
|
||||
pathTextField.setEditable(false);
|
||||
pathTextField.setText(configLoader.getRulesFilePath());
|
||||
JButton reloadButton = new JButton("Reload");
|
||||
JButton reinitButton = new JButton("Reinit");
|
||||
ruleInfoPanel.add(ruleLabel);
|
||||
ruleInfoPanel.add(pathTextField, constraints);
|
||||
ruleInfoPanel.add(Box.createHorizontalStrut(5));
|
||||
ruleInfoPanel.add(reinitButton);
|
||||
ruleInfoPanel.add(Box.createHorizontalStrut(5));
|
||||
ruleInfoPanel.add(reloadButton);
|
||||
|
||||
reloadButton.addActionListener(this::reloadActionPerformed);
|
||||
reinitButton.addActionListener(this::reinitActionPerformed);
|
||||
|
||||
constraints.gridx = 1;
|
||||
JTabbedPane configTabbedPanel = new JTabbedPane();
|
||||
|
||||
String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status"};
|
||||
JPanel settingPanel = createConfigTablePanel(settingMode);
|
||||
|
||||
JPanel northPanel = new JPanel(new BorderLayout());
|
||||
|
||||
JPanel modePanel = getModePanel();
|
||||
JScrollPane modeScrollPane = new JScrollPane(modePanel);
|
||||
modeScrollPane.setBorder(new TitledBorder("Mode"));
|
||||
|
||||
JTextField limitPanel = getLimitPanel();
|
||||
JScrollPane limitScrollPane = new JScrollPane(limitPanel);
|
||||
limitScrollPane.setBorder(new TitledBorder("Limit Size (MB)"));
|
||||
|
||||
JSplitPane northTopPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, modeScrollPane, limitScrollPane);
|
||||
northTopPanel.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
northTopPanel.setDividerLocation(0.5);
|
||||
}
|
||||
});
|
||||
|
||||
JPanel scopePanel = getScopePanel();
|
||||
JScrollPane scopeScrollPane = new JScrollPane(scopePanel);
|
||||
scopeScrollPane.setBorder(new TitledBorder("Scope"));
|
||||
|
||||
northPanel.add(scopeScrollPane, BorderLayout.SOUTH);
|
||||
northPanel.add(northTopPanel, BorderLayout.NORTH);
|
||||
settingPanel.add(northPanel, BorderLayout.NORTH);
|
||||
|
||||
configTabbedPanel.add("Setting", settingPanel);
|
||||
add(ruleInfoPanel, BorderLayout.NORTH);
|
||||
add(configTabbedPanel, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
private JPanel getScopePanel() {
|
||||
JPanel scopePanel = new JPanel();
|
||||
scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.X_AXIS));
|
||||
scopePanel.setBorder(new EmptyBorder(3, 0, 6, 0));
|
||||
|
||||
String[] scopeInit = hae.Config.scopeOptions.split("\\|");
|
||||
String[] scopeMode = configLoader.getScope().split("\\|");
|
||||
for (String scope : scopeInit) {
|
||||
JCheckBox checkBox = new JCheckBox(scope);
|
||||
scopePanel.add(checkBox);
|
||||
checkBox.addActionListener(e -> updateScope(checkBox));
|
||||
for (String mode : scopeMode) {
|
||||
if (scope.equals(mode)) {
|
||||
checkBox.setSelected(true);
|
||||
}
|
||||
}
|
||||
updateScope(checkBox);
|
||||
}
|
||||
|
||||
return scopePanel;
|
||||
}
|
||||
|
||||
private JPanel getModePanel() {
|
||||
JPanel modePanel = new JPanel();
|
||||
modePanel.setLayout(new BoxLayout(modePanel, BoxLayout.X_AXIS));
|
||||
|
||||
JCheckBox checkBox = new JCheckBox("Enable active http message handler");
|
||||
modePanel.add(checkBox);
|
||||
checkBox.addActionListener(e -> updateModeStatus(checkBox));
|
||||
checkBox.setSelected(configLoader.getMode());
|
||||
updateModeStatus(checkBox);
|
||||
|
||||
return modePanel;
|
||||
}
|
||||
|
||||
private JTextField getLimitPanel() {
|
||||
JTextField limitSizeTextField = new JTextField();
|
||||
limitSizeTextField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
onTextChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
onTextChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
onTextChange();
|
||||
}
|
||||
|
||||
private void onTextChange() {
|
||||
String limitSizeText = limitSizeTextField.getText();
|
||||
configLoader.setLimitSize(limitSizeText);
|
||||
}
|
||||
});
|
||||
|
||||
limitSizeTextField.setText(configLoader.getLimitSize());
|
||||
|
||||
return limitSizeTextField;
|
||||
}
|
||||
|
||||
private TableModelListener craeteSettingTableModelListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||
return new TableModelListener() {
|
||||
@Override
|
||||
public void tableChanged(TableModelEvent e) {
|
||||
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||
String values = getFirstColumnDataAsString(model);
|
||||
|
||||
if (selected.equals("Exclude suffix")) {
|
||||
if (!values.equals(configLoader.getExcludeSuffix()) && !values.isEmpty()) {
|
||||
configLoader.setExcludeSuffix(values);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.equals("Block host")) {
|
||||
if (!values.equals(configLoader.getBlockHost()) && !values.isEmpty()) {
|
||||
configLoader.setBlockHost(values);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.equals("Exclude status")) {
|
||||
if (!values.equals(configLoader.getExcludeStatus()) && !values.isEmpty()) {
|
||||
configLoader.setExcludeStatus(values);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ActionListener createSettingActionListener(JComboBox<String> setTypeComboBox, DefaultTableModel model) {
|
||||
return new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String selected = (String) setTypeComboBox.getSelectedItem();
|
||||
model.setRowCount(0);
|
||||
|
||||
if (selected.equals("Exclude suffix")) {
|
||||
addDataToTable(configLoader.getExcludeSuffix().replaceAll("\\|", "\r\n"), model);
|
||||
}
|
||||
|
||||
if (selected.equals("Block host")) {
|
||||
addDataToTable(configLoader.getBlockHost().replaceAll("\\|", "\r\n"), model);
|
||||
}
|
||||
|
||||
if (selected.equals("Exclude status")) {
|
||||
addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private JPanel createConfigTablePanel(String[] mode) {
|
||||
GridBagConstraints constraints = new GridBagConstraints();
|
||||
constraints.weightx = 1.0;
|
||||
constraints.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
JPanel settingPanel = new JPanel(new BorderLayout());
|
||||
DefaultTableModel model = new DefaultTableModel();
|
||||
|
||||
JTable table = new JTable(model);
|
||||
model.addColumn("Value");
|
||||
JScrollPane scrollPane = new JScrollPane(table);
|
||||
|
||||
JPanel buttonPanel = new JPanel();
|
||||
buttonPanel.setBorder(new EmptyBorder(0, 3, 0, 0));
|
||||
GridBagLayout layout = new GridBagLayout();
|
||||
layout.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0};
|
||||
layout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
|
||||
buttonPanel.setLayout(layout);
|
||||
|
||||
JPanel inputPanel = new JPanel(new BorderLayout());
|
||||
JPanel inputPanelB = new JPanel(new BorderLayout());
|
||||
inputPanelB.setBorder(new EmptyBorder(0, 0, 3, 0));
|
||||
|
||||
JButton addButton = new JButton("Add");
|
||||
JButton removeButton = new JButton("Remove");
|
||||
JButton pasteButton = new JButton("Paste");
|
||||
JButton clearButton = new JButton("Clear");
|
||||
|
||||
JComboBox<String> setTypeComboBox = new JComboBox<>();
|
||||
setTypeComboBox.setModel(new DefaultComboBoxModel<>(mode));
|
||||
|
||||
model.addTableModelListener(craeteSettingTableModelListener(setTypeComboBox, model));
|
||||
|
||||
setTypeComboBox.addActionListener(createSettingActionListener(setTypeComboBox, model));
|
||||
|
||||
setTypeComboBox.setSelectedItem(mode[0]);
|
||||
|
||||
constraints.insets = new Insets(0, 0, 3, 0);
|
||||
constraints.gridy = 0;
|
||||
buttonPanel.add(setTypeComboBox, constraints);
|
||||
constraints.gridy = 1;
|
||||
buttonPanel.add(addButton, constraints);
|
||||
constraints.gridy = 2;
|
||||
buttonPanel.add(removeButton, constraints);
|
||||
constraints.gridy = 3;
|
||||
buttonPanel.add(pasteButton, constraints);
|
||||
constraints.gridy = 4;
|
||||
buttonPanel.add(clearButton, constraints);
|
||||
|
||||
JTextField addTextField = new JTextField();
|
||||
String defaultText = "Enter a new item";
|
||||
UIEnhancer.setTextFieldPlaceholder(addTextField, defaultText);
|
||||
|
||||
inputPanelB.add(addTextField, BorderLayout.CENTER);
|
||||
inputPanel.add(scrollPane, BorderLayout.CENTER);
|
||||
inputPanel.add(inputPanelB, BorderLayout.NORTH);
|
||||
|
||||
settingPanel.add(buttonPanel, BorderLayout.EAST);
|
||||
settingPanel.add(inputPanel, BorderLayout.CENTER);
|
||||
|
||||
|
||||
addButton.addActionListener(e -> addActionPerformed(e, model, addTextField, setTypeComboBox.getSelectedItem().toString()));
|
||||
|
||||
addTextField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
addActionPerformed(null, model, addTextField, setTypeComboBox.getSelectedItem().toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pasteButton.addActionListener(e -> {
|
||||
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
try {
|
||||
String data = (String) clipboard.getData(DataFlavor.stringFlavor);
|
||||
if (data != null && !data.isEmpty()) {
|
||||
addDataToTable(data, model);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
});
|
||||
|
||||
removeButton.addActionListener(e -> {
|
||||
int selectedRow = table.getSelectedRow();
|
||||
if (selectedRow != -1) {
|
||||
model.removeRow(selectedRow);
|
||||
}
|
||||
});
|
||||
|
||||
clearButton.addActionListener(e -> model.setRowCount(0));
|
||||
|
||||
JPanel settingMainPanel = new JPanel(new BorderLayout());
|
||||
settingMainPanel.setBorder(new EmptyBorder(5, 15, 10, 15));
|
||||
JScrollPane settingScroller = new JScrollPane(settingPanel);
|
||||
settingScroller.setBorder(new TitledBorder("Setting"));
|
||||
settingMainPanel.add(settingScroller, BorderLayout.CENTER);
|
||||
|
||||
return settingMainPanel;
|
||||
}
|
||||
|
||||
|
||||
private String getFirstColumnDataAsString(DefaultTableModel model) {
|
||||
StringBuilder firstColumnData = new StringBuilder();
|
||||
int numRows = model.getRowCount();
|
||||
|
||||
for (int row = 0; row < numRows; row++) {
|
||||
firstColumnData.append(model.getValueAt(row, 0));
|
||||
if (row < numRows - 1) {
|
||||
firstColumnData.append("|");
|
||||
}
|
||||
}
|
||||
|
||||
return firstColumnData.toString();
|
||||
}
|
||||
|
||||
private void addDataToTable(String data, DefaultTableModel model) {
|
||||
if (!data.isBlank()) {
|
||||
String[] rows = data.split("\\r?\\n");
|
||||
for (String row : rows) {
|
||||
model.addRow(new String[]{row});
|
||||
}
|
||||
deduplicateTableData(model);
|
||||
}
|
||||
}
|
||||
|
||||
private void deduplicateTableData(DefaultTableModel model) {
|
||||
// 使用 Map 存储每一行的数据,用于去重
|
||||
Set<List<Object>> rowData = new LinkedHashSet<>();
|
||||
|
||||
int columnCount = model.getColumnCount();
|
||||
|
||||
// 将每一行数据作为一个列表,添加到 Set 中
|
||||
for (int i = 0; i < model.getRowCount(); i++) {
|
||||
List<Object> row = new ArrayList<>();
|
||||
for (int j = 0; j < columnCount; j++) {
|
||||
row.add(model.getValueAt(i, j));
|
||||
}
|
||||
rowData.add(row);
|
||||
}
|
||||
|
||||
// 清除原始数据
|
||||
model.setRowCount(0);
|
||||
|
||||
// 将去重后的数据添加回去
|
||||
for (List<Object> uniqueRow : rowData) {
|
||||
model.addRow(uniqueRow.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
public void updateModeStatus(JCheckBox checkBox) {
|
||||
boolean selected = checkBox.isSelected();
|
||||
configLoader.setMode(selected ? "true" : "false");
|
||||
|
||||
if (checkBox.isSelected()) {
|
||||
if (passiveHandler.isRegistered()) {
|
||||
passiveHandler.deregister();
|
||||
}
|
||||
|
||||
if (!activeHandler.isRegistered()) {
|
||||
activeHandler = api.http().registerHttpHandler(new HttpMessageActiveHandler(api, configLoader, messageTableModel));
|
||||
}
|
||||
} else {
|
||||
if (!passiveHandler.isRegistered()) {
|
||||
passiveHandler = api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel));
|
||||
}
|
||||
|
||||
if (activeHandler.isRegistered()) {
|
||||
activeHandler.deregister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateScope(JCheckBox checkBox) {
|
||||
String boxText = checkBox.getText();
|
||||
boolean selected = checkBox.isSelected();
|
||||
|
||||
Set<String> HaEScope = new HashSet<>(Arrays.asList(configLoader.getScope().split("\\|")));
|
||||
|
||||
if (selected) {
|
||||
HaEScope.add(boxText);
|
||||
} else {
|
||||
HaEScope.remove(boxText);
|
||||
}
|
||||
|
||||
configLoader.setScope(String.join("|", HaEScope));
|
||||
}
|
||||
|
||||
private void addActionPerformed(ActionEvent e, DefaultTableModel model, JTextField addTextField, String comboBoxSelected) {
|
||||
String addTextFieldText = addTextField.getText();
|
||||
if (addTextField.getForeground().equals(Color.BLACK)) {
|
||||
addDataToTable(addTextFieldText, model);
|
||||
addTextField.setText("");
|
||||
addTextField.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadActionPerformed(ActionEvent e) {
|
||||
rules.reloadRuleGroup();
|
||||
}
|
||||
|
||||
private void reinitActionPerformed(ActionEvent e) {
|
||||
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to reinitialize rules? This action will overwrite your existing rules.", "Info", JOptionPane.YES_NO_OPTION);
|
||||
if (retCode == JOptionPane.YES_OPTION) {
|
||||
boolean ret = configLoader.initRules();
|
||||
if (ret) {
|
||||
rules.reloadRuleGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/main/java/hae/component/Main.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package hae.component;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.component.board.Databoard;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.component.rule.Rules;
|
||||
import hae.utils.ConfigLoader;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.net.URL;
|
||||
|
||||
public class Main extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final MessageTableModel messageTableModel;
|
||||
|
||||
public Main(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.messageTableModel = messageTableModel;
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout) getLayout()).columnWidths = new int[]{0, 0};
|
||||
((GridBagLayout) getLayout()).rowHeights = new int[]{0, 0};
|
||||
((GridBagLayout) getLayout()).columnWeights = new double[]{1.0, 1.0E-4};
|
||||
((GridBagLayout) getLayout()).rowWeights = new double[]{1.0, 1.0E-4};
|
||||
|
||||
JTabbedPane mainTabbedPane = new JTabbedPane();
|
||||
|
||||
// 新增Logo
|
||||
JTabbedPane HaETabbedPane = new JTabbedPane();
|
||||
boolean isDarkBg = isDarkBg(HaETabbedPane);
|
||||
HaETabbedPane.addTab("", getImageIcon(isDarkBg), mainTabbedPane);
|
||||
// 中文Slogan:赋能白帽,高效作战
|
||||
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null);
|
||||
HaETabbedPane.setEnabledAt(1, false);
|
||||
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
boolean isDarkBg = isDarkBg(HaETabbedPane);
|
||||
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
|
||||
}
|
||||
});
|
||||
|
||||
add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 0, 0, 0), 0, 0));
|
||||
|
||||
// 依次添加Rules、Config、Databoard
|
||||
Rules rules = new Rules(api, configLoader);
|
||||
mainTabbedPane.addTab("Rules", rules);
|
||||
mainTabbedPane.addTab("Databoard", new Databoard(api, configLoader, messageTableModel));
|
||||
mainTabbedPane.addTab("Config", new Config(api, configLoader, 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) {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
URL imageURL;
|
||||
if (isDark) {
|
||||
imageURL = classLoader.getResource("logo/logo.png");
|
||||
} else {
|
||||
imageURL = classLoader.getResource("logo/logo_black.png");
|
||||
}
|
||||
ImageIcon originalIcon = new ImageIcon(imageURL);
|
||||
Image originalImage = originalIcon.getImage();
|
||||
Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST);
|
||||
return new ImageIcon(scaledImage);
|
||||
}
|
||||
}
|
||||
365
src/main/java/hae/component/board/Databoard.java
Normal file
@@ -0,0 +1,365 @@
|
||||
package hae.component.board;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
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 javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Databoard extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final MessageTableModel messageTableModel;
|
||||
|
||||
private JTextField hostTextField;
|
||||
private JTabbedPane dataTabbedPane;
|
||||
private JSplitPane splitPane;
|
||||
private MessageTable messageTable;
|
||||
|
||||
private static Boolean isMatchHost = false;
|
||||
private final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
|
||||
private final JComboBox hostComboBox = new JComboBox(comboBoxModel);
|
||||
|
||||
private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker;
|
||||
private SwingWorker<Void, Void> applyHostFilterWorker;
|
||||
|
||||
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.messageTableModel = messageTableModel;
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout) getLayout()).columnWidths = new int[]{25, 0, 0, 0, 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()).rowWeights = new double[]{0.0, 1.0, 0.0, 1.0E-4};
|
||||
|
||||
JLabel hostLabel = new JLabel("Host:");
|
||||
|
||||
JButton clearButton = new JButton("Clear");
|
||||
JButton actionButton = new JButton("Action");
|
||||
JPanel menuPanel = new JPanel(new GridLayout(1, 1, 0, 5));
|
||||
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menuPanel.add(clearButton);
|
||||
menu.add(menuPanel);
|
||||
|
||||
hostTextField = new JTextField();
|
||||
String defaultText = "Please enter the host";
|
||||
UIEnhancer.setTextFieldPlaceholder(hostTextField, defaultText);
|
||||
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
|
||||
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
|
||||
dataTabbedPane.setPreferredSize(new Dimension(500, 0));
|
||||
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||
|
||||
actionButton.addActionListener(e -> {
|
||||
int x = 0;
|
||||
int y = actionButton.getHeight();
|
||||
menu.show(actionButton, x, y);
|
||||
});
|
||||
|
||||
clearButton.addActionListener(this::clearActionPerformed);
|
||||
|
||||
|
||||
splitPane.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
resizePanel();
|
||||
}
|
||||
});
|
||||
|
||||
splitPane.setVisible(false);
|
||||
|
||||
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
add(hostTextField, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
|
||||
add(splitPane, new GridBagConstraints(1, 1, 3, 2, 0.0, 1.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 0, 5), 0, 0));
|
||||
hostComboBox.setMaximumRowCount(5);
|
||||
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
|
||||
setAutoMatch();
|
||||
}
|
||||
|
||||
private void resizePanel() {
|
||||
splitPane.setDividerLocation(0.4);
|
||||
TableColumnModel columnModel = messageTable.getColumnModel();
|
||||
int totalWidth = (int) (getWidth() * 0.6);
|
||||
columnModel.getColumn(0).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
columnModel.getColumn(1).setPreferredWidth((int) (totalWidth * 0.3));
|
||||
columnModel.getColumn(2).setPreferredWidth((int) (totalWidth * 0.3));
|
||||
columnModel.getColumn(3).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
columnModel.getColumn(4).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
}
|
||||
|
||||
private void setAutoMatch() {
|
||||
hostComboBox.setSelectedItem(null);
|
||||
hostComboBox.addActionListener(this::handleComboBoxAction);
|
||||
|
||||
hostTextField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
handleKeyEvents(e);
|
||||
}
|
||||
});
|
||||
|
||||
hostTextField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
filterComboBoxList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
filterComboBoxList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
filterComboBoxList();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void handleComboBoxAction(ActionEvent e) {
|
||||
if (!isMatchHost && hostComboBox.getSelectedItem() != null) {
|
||||
String selectedHost = hostComboBox.getSelectedItem().toString();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleKeyEvents(KeyEvent e) {
|
||||
isMatchHost = true;
|
||||
int keyCode = e.getKeyCode();
|
||||
|
||||
if (keyCode == KeyEvent.VK_SPACE && hostComboBox.isPopupVisible()) {
|
||||
e.setKeyCode(KeyEvent.VK_ENTER);
|
||||
}
|
||||
|
||||
if (Arrays.asList(KeyEvent.VK_DOWN, KeyEvent.VK_UP).contains(keyCode)) {
|
||||
hostComboBox.dispatchEvent(e);
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.VK_ENTER) {
|
||||
isMatchHost = false;
|
||||
handleComboBoxAction(null);
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.VK_ESCAPE) {
|
||||
hostComboBox.setPopupVisible(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() {
|
||||
isMatchHost = true;
|
||||
comboBoxModel.removeAllElements();
|
||||
String input = hostTextField.getText().toLowerCase();
|
||||
|
||||
if (!input.isEmpty()) {
|
||||
for (String host : getHostByList()) {
|
||||
String lowerCaseHost = host.toLowerCase();
|
||||
if (lowerCaseHost.contains(input)) {
|
||||
if (lowerCaseHost.equals(input)) {
|
||||
comboBoxModel.insertElementAt(lowerCaseHost, 0);
|
||||
comboBoxModel.setSelectedItem(lowerCaseHost);
|
||||
} else {
|
||||
comboBoxModel.addElement(host);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hostComboBox.setPopupVisible(comboBoxModel.getSize() > 0);
|
||||
isMatchHost = false;
|
||||
}
|
||||
|
||||
private void applyHostFilter(String filterText) {
|
||||
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
|
||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
||||
|
||||
if (applyHostFilterWorker != null && !applyHostFilterWorker.isDone()) {
|
||||
applyHostFilterWorker.cancel(true);
|
||||
}
|
||||
|
||||
applyHostFilterWorker = new SwingWorker<Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground() throws Exception {
|
||||
RowFilter<Object, Object> rowFilter = new RowFilter<Object, Object>() {
|
||||
public boolean include(Entry<?, ?> entry) {
|
||||
if (cleanedText.equals("*")) {
|
||||
return true;
|
||||
} else {
|
||||
String host = StringProcessor.getHostByUrl((String) entry.getValue(1));
|
||||
return StringProcessor.matchesHostPattern(host, filterText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sorter.setRowFilter(rowFilter);
|
||||
messageTableModel.applyHostFilter(filterText);
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
applyHostFilterWorker.execute();
|
||||
}
|
||||
|
||||
private List<String> getHostByList() {
|
||||
if (!Config.globalDataMap.keySet().isEmpty()) {
|
||||
return new ArrayList<>(Config.globalDataMap.keySet());
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private void clearActionPerformed(ActionEvent e) {
|
||||
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear data?", "Info",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
String host = hostTextField.getText();
|
||||
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
||||
dataTabbedPane.removeAll();
|
||||
splitPane.setVisible(false);
|
||||
|
||||
Config.globalDataMap.keySet().parallelStream().forEach(key -> {
|
||||
if (StringProcessor.matchesHostPattern(key, host) || host.equals("*")) {
|
||||
Config.globalDataMap.remove(key);
|
||||
}
|
||||
});
|
||||
|
||||
// 删除无用的数据
|
||||
Set<String> wildcardKeys = Config.globalDataMap.keySet().stream()
|
||||
.filter(key -> key.startsWith("*."))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> existingSuffixes = Config.globalDataMap.keySet().stream()
|
||||
.filter(key -> !key.startsWith("*."))
|
||||
.map(key -> {
|
||||
int dotIndex = key.indexOf(".");
|
||||
return dotIndex != -1 ? key.substring(dotIndex) : "";
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Set<String> keysToRemove = wildcardKeys.stream()
|
||||
.filter(key -> !existingSuffixes.contains(key.substring(1)))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
keysToRemove.forEach(Config.globalDataMap::remove);
|
||||
|
||||
if (Config.globalDataMap.keySet().size() == 1 && Config.globalDataMap.keySet().stream().anyMatch(key -> key.equals("*"))) {
|
||||
Config.globalDataMap.keySet().remove("*");
|
||||
}
|
||||
|
||||
messageTableModel.deleteByHost(host);
|
||||
|
||||
hostTextField.setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/main/java/hae/component/board/message/MessageEntry.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package hae.component.board.message;
|
||||
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
|
||||
public class MessageEntry {
|
||||
|
||||
private final String comment;
|
||||
private final HttpRequestResponse requestResponse;
|
||||
private final String url;
|
||||
private final String length;
|
||||
private final String status;
|
||||
private final String color;
|
||||
private final String method;
|
||||
|
||||
MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status) {
|
||||
this.requestResponse = requestResponse;
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
this.comment = comment;
|
||||
this.length = length;
|
||||
this.color = color;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
public String getLength() {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return this.comment;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public HttpRequestResponse getRequestResponse() {
|
||||
return this.requestResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package hae.component.board.message;
|
||||
|
||||
import javax.swing.*;
|
||||
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 {
|
||||
|
||||
private final LinkedList<MessageEntry> log;
|
||||
private final Map<String, Color> colorMap = new HashMap<>();
|
||||
private final JTable table; // 保存对表格的引用
|
||||
|
||||
public MessageRenderer(LinkedList<MessageEntry> log, JTable table) {
|
||||
this.log = log;
|
||||
// 与BurpSuite的颜色保持一致
|
||||
this.colorMap.put("red", new Color(0xFF, 0x64, 0x64));
|
||||
this.colorMap.put("orange", new Color(0xFF, 0xC8, 0x64));
|
||||
this.colorMap.put("yellow", new Color(0xFF, 0xFF, 0x64));
|
||||
this.colorMap.put("green", new Color(0x64, 0xFF, 0x64));
|
||||
this.colorMap.put("cyan", new Color(0x64, 0xFF, 0xFF));
|
||||
this.colorMap.put("blue", new Color(0x64, 0x64, 0xFF));
|
||||
this.colorMap.put("pink", new Color(0xFF, 0xC8, 0xC8));
|
||||
this.colorMap.put("magenta", new Color(0xFF, 0x64, 0xFF));
|
||||
this.colorMap.put("gray", new Color(0xB4, 0xB4, 0xB4));
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
|
||||
boolean hasFocus, int row, int column) {
|
||||
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
MessageEntry messageEntry = log.get(table.convertRowIndexToModel(row)); // 使用convertRowIndexToModel方法转换行索引
|
||||
|
||||
// 设置颜色
|
||||
String colorByLog = messageEntry.getColor();
|
||||
Color color = colorMap.get(colorByLog);
|
||||
|
||||
if (isSelected) {
|
||||
// 通过更改RGB颜色来达成阴影效果
|
||||
component.setBackground(new Color(color.getRed() - 0x20, color.getGreen() - 0x20, color.getBlue() - 0x20));
|
||||
} else {
|
||||
// 否则使用原始颜色
|
||||
component.setBackground(color);
|
||||
}
|
||||
|
||||
component.setForeground(Color.BLACK);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
|
||||
super.firePropertyChange(propertyName, oldValue, newValue);
|
||||
// 监听表格排序的属性变化
|
||||
if ("tableCellRenderer".equals(propertyName)) {
|
||||
// 更新每一行数据的颜色
|
||||
for (int i = 0; i < table.getRowCount(); i++) {
|
||||
table.repaint(table.getCellRect(i, 0, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
462
src/main/java/hae/component/board/message/MessageTableModel.java
Normal file
@@ -0,0 +1,462 @@
|
||||
package hae.component.board.message;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.http.message.HttpHeader;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||
import burp.api.montoya.persistence.PersistedObject;
|
||||
import burp.api.montoya.ui.UserInterface;
|
||||
import burp.api.montoya.ui.editor.HttpRequestEditor;
|
||||
import burp.api.montoya.ui.editor.HttpResponseEditor;
|
||||
import hae.Config;
|
||||
import hae.cache.CachePool;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.DataManager;
|
||||
import hae.utils.string.HashCalculator;
|
||||
import hae.utils.string.StringProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableModel;
|
||||
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.stream.Collectors;
|
||||
|
||||
import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY;
|
||||
|
||||
public class MessageTableModel extends AbstractTableModel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final MessageTable messageTable;
|
||||
private final JSplitPane splitPane;
|
||||
private final LinkedList<MessageEntry> log = new LinkedList<>();
|
||||
private final LinkedList<MessageEntry> filteredLog;
|
||||
private SwingWorker<Void, Void> currentWorker;
|
||||
|
||||
public MessageTableModel(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.filteredLog = new LinkedList<>();
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
|
||||
JTabbedPane messageTab = new JTabbedPane();
|
||||
UserInterface userInterface = api.userInterface();
|
||||
HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY);
|
||||
HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY);
|
||||
messageTab.addTab("Request", requestViewer.uiComponent());
|
||||
messageTab.addTab("Response", responseViewer.uiComponent());
|
||||
|
||||
// 请求条目表格
|
||||
messageTable = new MessageTable(MessageTableModel.this, requestViewer, responseViewer);
|
||||
messageTable.setDefaultRenderer(Object.class, new MessageRenderer(filteredLog, messageTable));
|
||||
messageTable.setAutoCreateRowSorter(true);
|
||||
|
||||
// Length字段根据大小进行排序
|
||||
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) messageTable.getRowSorter();
|
||||
sorter.setComparator(4, new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
Integer age1 = Integer.parseInt(s1);
|
||||
Integer age2 = Integer.parseInt(s2);
|
||||
return age1.compareTo(age2);
|
||||
}
|
||||
});
|
||||
|
||||
// Color字段根据颜色顺序进行排序
|
||||
sorter.setComparator(5, new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
int index1 = getIndex(s1);
|
||||
int index2 = getIndex(s2);
|
||||
return Integer.compare(index1, index2);
|
||||
}
|
||||
|
||||
private int getIndex(String color) {
|
||||
for (int i = 0; i < Config.color.length; i++) {
|
||||
if (Config.color[i].equals(color)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
messageTable.setRowSorter(sorter);
|
||||
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||
|
||||
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
// 请求/相应文本框
|
||||
JScrollPane scrollPane = new JScrollPane(messageTable);
|
||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
||||
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
splitPane.setLeftComponent(scrollPane);
|
||||
splitPane.setRightComponent(messageTab);
|
||||
}
|
||||
|
||||
public void add(HttpRequestResponse messageInfo, String url, String method, String status, String length, String comment, String color, boolean flag) {
|
||||
synchronized (log) {
|
||||
boolean isDuplicate = false;
|
||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
||||
|
||||
byte[] reqByteA = new byte[0];
|
||||
byte[] resByteA = new byte[0];
|
||||
|
||||
if (messageInfo != null) {
|
||||
HttpRequest httpRequest = messageInfo.request();
|
||||
HttpResponse httpResponse = messageInfo.response();
|
||||
|
||||
reqByteA = httpRequest.toByteArray().getBytes();
|
||||
resByteA = httpResponse.toByteArray().getBytes();
|
||||
}
|
||||
|
||||
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
||||
try {
|
||||
if (!log.isEmpty()) {
|
||||
for (MessageEntry entry : log) {
|
||||
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
||||
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
||||
byte[] resByteB = reqResMessage.response().toByteArray().getBytes();
|
||||
try {
|
||||
// 通过URL、请求和响应报文、匹配数据内容,多维度进行对比
|
||||
if ((entry.getUrl().equals(url) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) {
|
||||
isDuplicate = true;
|
||||
break;
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void deleteByHost(String filterText) {
|
||||
filteredLog.clear();
|
||||
List<Integer> rowsToRemove = new ArrayList<>();
|
||||
|
||||
if (currentWorker != null && !currentWorker.isDone()) {
|
||||
currentWorker.cancel(true);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
currentWorker.execute();
|
||||
}
|
||||
|
||||
public void applyHostFilter(String filterText) {
|
||||
filteredLog.clear();
|
||||
|
||||
log.forEach(entry -> {
|
||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||
if (!host.isEmpty()) {
|
||||
if (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*")) {
|
||||
filteredLog.add(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public void applyMessageFilter(String tableName, String filterText) {
|
||||
filteredLog.clear();
|
||||
for (MessageEntry entry : log) {
|
||||
// 标志变量,表示是否满足过滤条件
|
||||
AtomicBoolean isMatched = new AtomicBoolean(false);
|
||||
|
||||
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||
HttpRequest httpRequest = requestResponse.request();
|
||||
HttpResponse httpResponse = requestResponse.response();
|
||||
|
||||
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestHeaders = httpRequest.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String responseHeaders = httpResponse.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
Config.globalRules.keySet().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
String name = objects[1].toString();
|
||||
String format = objects[4].toString();
|
||||
String scope = objects[6].toString();
|
||||
|
||||
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
|
||||
if (entry.getComment().contains(name)) {
|
||||
if (name.equals(tableName)) {
|
||||
// 标志变量,表示当前规则是否匹配
|
||||
boolean isMatch = false;
|
||||
|
||||
switch (scope) {
|
||||
case "any":
|
||||
isMatch = matchingString(format, filterText, requestString) || matchingString(format, filterText, responseString);
|
||||
break;
|
||||
case "request":
|
||||
isMatch = matchingString(format, filterText, requestString);
|
||||
break;
|
||||
case "response":
|
||||
isMatch = matchingString(format, filterText, responseString);
|
||||
break;
|
||||
case "any header":
|
||||
isMatch = matchingString(format, filterText, requestHeaders) || matchingString(format, filterText, responseHeaders);
|
||||
break;
|
||||
case "request header":
|
||||
isMatch = matchingString(format, filterText, requestHeaders);
|
||||
break;
|
||||
case "response header":
|
||||
isMatch = matchingString(format, filterText, responseHeaders);
|
||||
break;
|
||||
case "any body":
|
||||
isMatch = matchingString(format, filterText, requestBody) || matchingString(format, filterText, responseBody);
|
||||
break;
|
||||
case "request body":
|
||||
isMatch = matchingString(format, filterText, requestBody);
|
||||
break;
|
||||
case "response body":
|
||||
isMatch = matchingString(format, filterText, responseBody);
|
||||
break;
|
||||
case "request line":
|
||||
String requestLine = requestString.split("\\r?\\n", 2)[0];
|
||||
isMatch = matchingString(format, filterText, requestLine);
|
||||
break;
|
||||
case "response line":
|
||||
String responseLine = responseString.split("\\r?\\n", 2)[0];
|
||||
isMatch = matchingString(format, filterText, responseLine);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
isMatched.set(isMatch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isMatched.get()) {
|
||||
filteredLog.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
fireTableDataChanged();
|
||||
messageTable.lastSelectedIndex = -1;
|
||||
}
|
||||
|
||||
private boolean matchingString(String format, String filterText, String target) {
|
||||
boolean isMatch = true;
|
||||
|
||||
try {
|
||||
MessageFormat mf = new MessageFormat(format);
|
||||
Object[] parsedObjects = mf.parse(filterText);
|
||||
|
||||
for (Object parsedObject : parsedObjects) {
|
||||
if (!target.contains(parsedObject.toString())) {
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
isMatch = false;
|
||||
}
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
private Map<String, Map<String, Object>> getCacheData(byte[] content) {
|
||||
String hashIndex = HashCalculator.calculateHash(content);
|
||||
return CachePool.get(hashIndex);
|
||||
}
|
||||
|
||||
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
|
||||
if (map1 == null || map2 == null) {
|
||||
return false;
|
||||
}
|
||||
if (map1.size() != map2.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String key : map1.keySet()) {
|
||||
if (!map2.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
if (areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
||||
if (innerMap1.size() != innerMap2.size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String key : innerMap1.keySet()) {
|
||||
if (!innerMap2.containsKey(key)) {
|
||||
return true;
|
||||
}
|
||||
Object value1 = innerMap1.get(key);
|
||||
Object value2 = innerMap2.get(key);
|
||||
|
||||
// 如果值是Map,则递归对比
|
||||
if (value1 instanceof Map && value2 instanceof Map) {
|
||||
if (areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
||||
return true;
|
||||
}
|
||||
} else if (!value1.equals(value2)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public JSplitPane getSplitPane() {
|
||||
return splitPane;
|
||||
}
|
||||
|
||||
public MessageTable getMessageTable() {
|
||||
return messageTable;
|
||||
}
|
||||
|
||||
public LinkedList<MessageEntry> getLogs() {
|
||||
return log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return filteredLog.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
if (!filteredLog.isEmpty()) {
|
||||
try {
|
||||
MessageEntry messageEntry = filteredLog.get(rowIndex);
|
||||
|
||||
if (messageEntry != null) {
|
||||
return switch (columnIndex) {
|
||||
case 0 -> messageEntry.getMethod();
|
||||
case 1 -> messageEntry.getUrl();
|
||||
case 2 -> messageEntry.getComment();
|
||||
case 3 -> messageEntry.getStatus();
|
||||
case 4 -> messageEntry.getLength();
|
||||
case 5 -> messageEntry.getColor();
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
} catch (Exception e) {
|
||||
api.logging().logToError("getValueAt: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int columnIndex) {
|
||||
return switch (columnIndex) {
|
||||
case 0 -> "Method";
|
||||
case 1 -> "URL";
|
||||
case 2 -> "Comment";
|
||||
case 3 -> "Status";
|
||||
case 4 -> "Length";
|
||||
case 5 -> "Color";
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
public class MessageTable extends JTable {
|
||||
private MessageEntry messageEntry;
|
||||
private final ExecutorService executorService;
|
||||
private int lastSelectedIndex = -1;
|
||||
private final HttpRequestEditor requestEditor;
|
||||
private final HttpResponseEditor responseEditor;
|
||||
|
||||
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
|
||||
super(messageTableModel);
|
||||
this.requestEditor = requestEditor;
|
||||
this.responseEditor = responseEditor;
|
||||
this.executorService = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeSelection(int row, int col, boolean toggle, boolean extend) {
|
||||
super.changeSelection(row, col, toggle, extend);
|
||||
int selectedIndex = convertRowIndexToModel(row);
|
||||
if (lastSelectedIndex != selectedIndex) {
|
||||
lastSelectedIndex = selectedIndex;
|
||||
executorService.execute(this::getSelectedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void getSelectedMessage() {
|
||||
messageEntry = filteredLog.get(lastSelectedIndex);
|
||||
|
||||
HttpRequestResponse httpRequestResponse = messageEntry.getRequestResponse();
|
||||
|
||||
requestEditor.setRequest(HttpRequest.httpRequest(messageEntry.getRequestResponse().httpService(), httpRequestResponse.request().toByteArray()));
|
||||
int responseSizeWithMb = httpRequestResponse.response().toString().length() / 1024 / 1024;
|
||||
if ((responseSizeWithMb < Integer.parseInt(configLoader.getLimitSize())) || configLoader.getLimitSize().equals("0")) {
|
||||
responseEditor.setResponse(httpRequestResponse.response());
|
||||
} else {
|
||||
responseEditor.setResponse(HttpResponse.httpResponse("Exceeds length limit."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
289
src/main/java/hae/component/board/table/Datatable.java
Normal file
@@ -0,0 +1,289 @@
|
||||
package hae.component.board.table;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.UIEnhancer;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Datatable extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final JTable dataTable;
|
||||
private final DefaultTableModel dataTableModel;
|
||||
private final JTextField searchField;
|
||||
private final JTextField secondSearchField;
|
||||
private final TableRowSorter<DefaultTableModel> sorter;
|
||||
private final JCheckBox searchMode = new JCheckBox("Reverse search");
|
||||
private final String tabName;
|
||||
private final JPanel footerPanel;
|
||||
|
||||
public Datatable(MontoyaApi api, ConfigLoader configLoader, String tabName, List<String> dataList) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.tabName = tabName;
|
||||
|
||||
String[] columnNames = {"#", "Information"};
|
||||
this.dataTableModel = new DefaultTableModel(columnNames, 0);
|
||||
|
||||
this.dataTable = new JTable(dataTableModel);
|
||||
this.sorter = new TableRowSorter<>(dataTableModel);
|
||||
this.searchField = new JTextField(10);
|
||||
this.secondSearchField = new JTextField(10);
|
||||
this.footerPanel = new JPanel(new BorderLayout(0, 5));
|
||||
|
||||
initComponents(dataList);
|
||||
}
|
||||
|
||||
private void initComponents(List<String> dataList) {
|
||||
// 设置ID排序
|
||||
sorter.setComparator(0, new Comparator<Integer>() {
|
||||
@Override
|
||||
public int compare(Integer s1, Integer s2) {
|
||||
return s1.compareTo(s2);
|
||||
}
|
||||
});
|
||||
|
||||
for (String item : dataList) {
|
||||
if (!item.isEmpty()) {
|
||||
addRowToTable(new Object[]{item});
|
||||
}
|
||||
}
|
||||
|
||||
UIEnhancer.setTextFieldPlaceholder(searchField, "Search");
|
||||
searchField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
UIEnhancer.setTextFieldPlaceholder(secondSearchField, "Second search");
|
||||
secondSearchField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// 设置布局
|
||||
JScrollPane scrollPane = new JScrollPane(dataTable);
|
||||
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
|
||||
dataTable.setRowSorter(sorter);
|
||||
TableColumn idColumn = dataTable.getColumnModel().getColumn(0);
|
||||
idColumn.setPreferredWidth(50);
|
||||
idColumn.setMaxWidth(100);
|
||||
|
||||
setLayout(new BorderLayout(0, 5));
|
||||
|
||||
JPanel optionsPanel = new JPanel();
|
||||
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.X_AXIS));
|
||||
|
||||
// Settings按钮
|
||||
JPanel settingMenuPanel = new JPanel(new GridLayout(1, 1));
|
||||
settingMenuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||
JPopupMenu settingMenu = new JPopupMenu();
|
||||
settingMenuPanel.add(searchMode);
|
||||
searchMode.addItemListener(e -> performSearch());
|
||||
settingMenu.add(settingMenuPanel);
|
||||
|
||||
JButton settingsButton = new JButton("Settings");
|
||||
setMenuShow(settingMenu, settingsButton);
|
||||
|
||||
optionsPanel.add(settingsButton);
|
||||
optionsPanel.add(Box.createHorizontalStrut(5));
|
||||
optionsPanel.add(searchField);
|
||||
optionsPanel.add(Box.createHorizontalStrut(5));
|
||||
optionsPanel.add(secondSearchField);
|
||||
|
||||
footerPanel.setBorder(BorderFactory.createEmptyBorder(2, 3, 5, 3));
|
||||
footerPanel.add(optionsPanel, BorderLayout.CENTER);
|
||||
|
||||
add(scrollPane, BorderLayout.CENTER);
|
||||
add(footerPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
private void setMenuShow(JPopupMenu menu, JButton button) {
|
||||
button.addActionListener(e -> {
|
||||
Point buttonLocation = button.getLocationOnScreen();
|
||||
Dimension menuSize = menu.getPreferredSize();
|
||||
int x = buttonLocation.x + (button.getWidth() - menuSize.width) / 2;
|
||||
int y = buttonLocation.y - menuSize.height;
|
||||
menu.show(button, x - buttonLocation.x, y - buttonLocation.y);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void addRowToTable(Object[] data) {
|
||||
int rowCount = dataTableModel.getRowCount();
|
||||
int id = rowCount > 0 ? (Integer) dataTableModel.getValueAt(rowCount - 1, 0) + 1 : 1;
|
||||
Object[] rowData = new Object[data.length + 1];
|
||||
rowData[0] = id;
|
||||
System.arraycopy(data, 0, rowData, 1, data.length);
|
||||
dataTableModel.addRow(rowData);
|
||||
}
|
||||
|
||||
private void performSearch() {
|
||||
RowFilter<Object, Object> firstRowFilter = applyFirstSearchFilter();
|
||||
RowFilter<Object, Object> secondRowFilter = applySecondFilter();
|
||||
if (searchField.getForeground().equals(Color.BLACK)) {
|
||||
sorter.setRowFilter(firstRowFilter);
|
||||
if (secondSearchField.getForeground().equals(Color.BLACK)) {
|
||||
List<RowFilter<Object, Object>> filters = new ArrayList<>();
|
||||
filters.add(firstRowFilter);
|
||||
filters.add(secondRowFilter);
|
||||
sorter.setRowFilter(RowFilter.andFilter(filters));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RowFilter<Object, Object> applyFirstSearchFilter() {
|
||||
return new RowFilter<Object, Object>() {
|
||||
public boolean include(Entry<?, ?> entry) {
|
||||
String searchFieldTextText = searchField.getText();
|
||||
Pattern pattern = null;
|
||||
try {
|
||||
pattern = Pattern.compile(searchFieldTextText, Pattern.CASE_INSENSITIVE);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
String entryValue = ((String) entry.getValue(1)).toLowerCase();
|
||||
searchFieldTextText = searchFieldTextText.toLowerCase();
|
||||
if (pattern != null) {
|
||||
return searchFieldTextText.isEmpty() || pattern.matcher(entryValue).find() != searchMode.isSelected();
|
||||
} else {
|
||||
return searchFieldTextText.isEmpty() || entryValue.contains(searchFieldTextText) != searchMode.isSelected();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private RowFilter<Object, Object> applySecondFilter() {
|
||||
return new RowFilter<Object, Object>() {
|
||||
public boolean include(Entry<?, ?> entry) {
|
||||
String searchFieldTextText = secondSearchField.getText();
|
||||
Pattern pattern = null;
|
||||
try {
|
||||
pattern = Pattern.compile(searchFieldTextText, Pattern.CASE_INSENSITIVE);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
String entryValue = ((String) entry.getValue(1)).toLowerCase();
|
||||
searchFieldTextText = searchFieldTextText.toLowerCase();
|
||||
if (pattern != null) {
|
||||
return searchFieldTextText.isEmpty() || pattern.matcher(entryValue).find();
|
||||
} else {
|
||||
return searchFieldTextText.isEmpty() || entryValue.contains(searchFieldTextText);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void setTableListener(MessageTableModel messagePanel) {
|
||||
// 表格复制功能
|
||||
dataTable.setTransferHandler(new TransferHandler() {
|
||||
@Override
|
||||
public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException {
|
||||
if (comp instanceof JTable) {
|
||||
StringSelection stringSelection = new StringSelection(getSelectedDataAtTable((JTable) comp).replace("\0", "").replaceAll("[\\p{Cntrl}&&[^\r\n\t]]", ""));
|
||||
clip.setContents(stringSelection, null);
|
||||
} else {
|
||||
super.exportToClipboard(comp, clip, action);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dataTable.setDefaultEditor(Object.class, null);
|
||||
|
||||
// 表格内容双击事件
|
||||
dataTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
int selectedRow = dataTable.getSelectedRow();
|
||||
if (selectedRow != -1) {
|
||||
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
|
||||
messagePanel.applyMessageFilter(tabName, rowData);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getTableData(JTable table) {
|
||||
StringBuilder selectData = new StringBuilder();
|
||||
int rowCount = table.getRowCount();
|
||||
for (int i = 0; i < rowCount; i++) {
|
||||
selectData.append(table.getValueAt(i, 1).toString()).append("\r\n");
|
||||
}
|
||||
|
||||
if (!selectData.isEmpty()) {
|
||||
selectData.delete(selectData.length() - 2, selectData.length());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
return selectData.toString();
|
||||
}
|
||||
|
||||
public String getSelectedDataAtTable(JTable table) {
|
||||
int[] selectRows = table.getSelectedRows();
|
||||
StringBuilder selectData = new StringBuilder();
|
||||
|
||||
for (int row : selectRows) {
|
||||
selectData.append(table.getValueAt(row, 1).toString()).append("\r\n");
|
||||
}
|
||||
|
||||
if (!selectData.isEmpty()) {
|
||||
selectData.delete(selectData.length() - 2, selectData.length());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
return selectData.toString();
|
||||
}
|
||||
|
||||
|
||||
public JTable getDataTable() {
|
||||
return this.dataTable;
|
||||
}
|
||||
}
|
||||
|
||||
79
src/main/java/hae/component/rule/Display.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package hae.component.rule;
|
||||
|
||||
import hae.Config;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class Display extends JPanel {
|
||||
public JTextField firstRegexTextField;
|
||||
public JTextField secondRegexTextField;
|
||||
public JTextField formatTextField;
|
||||
public JTextField ruleNameTextField;
|
||||
public JComboBox<String> scopeComboBox;
|
||||
public JComboBox<String> engineComboBox;
|
||||
public JComboBox<String> colorComboBox;
|
||||
public JComboBox<Boolean> sensitiveComboBox;
|
||||
|
||||
public Display() {
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
|
||||
addLabel("Name:", 0, c);
|
||||
ruleNameTextField = addTextField(0, c);
|
||||
|
||||
addLabel("F-Regex:", 1, c);
|
||||
firstRegexTextField = addTextField(1, c);
|
||||
|
||||
addLabel("S-Regex:", 2, c);
|
||||
secondRegexTextField = addTextField(2, c);
|
||||
|
||||
addLabel("Format:", 3, c);
|
||||
formatTextField = addTextField(3, c);
|
||||
|
||||
addLabel("Scope:", 4, c);
|
||||
scopeComboBox = addComboBox(Config.scope, 4, c);
|
||||
|
||||
addLabel("Engine:", 5, c);
|
||||
engineComboBox = addComboBox(Config.engine, 5, c);
|
||||
engineComboBox.addActionListener(e -> {
|
||||
boolean isNfa = "nfa".equals(engineComboBox.getSelectedItem().toString());
|
||||
formatTextField.setEnabled(isNfa);
|
||||
formatTextField.setText(isNfa ? formatTextField.getText() : "{0}");
|
||||
});
|
||||
|
||||
addLabel("Color:", 6, c);
|
||||
colorComboBox = addComboBox(Config.color, 6, c);
|
||||
|
||||
addLabel("Sensitive:", 7, c);
|
||||
sensitiveComboBox = addComboBox(new Boolean[]{true, false}, 7, c);
|
||||
}
|
||||
|
||||
private void addLabel(String text, int y, GridBagConstraints c) {
|
||||
JLabel label = new JLabel(text);
|
||||
c.gridx = 0;
|
||||
c.gridy = y;
|
||||
add(label, c);
|
||||
}
|
||||
|
||||
private JTextField addTextField(int y, GridBagConstraints c) {
|
||||
JTextField textField = new JTextField(35);
|
||||
c.gridx = 1;
|
||||
c.gridy = y;
|
||||
add(textField, c);
|
||||
return textField;
|
||||
}
|
||||
|
||||
private <T> JComboBox<T> addComboBox(T[] items, int y, GridBagConstraints c) {
|
||||
JComboBox<T> comboBox = new JComboBox<>(items);
|
||||
c.gridx = 1;
|
||||
c.gridy = y;
|
||||
add(comboBox, c);
|
||||
return comboBox;
|
||||
}
|
||||
}
|
||||
163
src/main/java/hae/component/rule/Rule.java
Normal file
@@ -0,0 +1,163 @@
|
||||
package hae.component.rule;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.rule.RuleProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.Vector;
|
||||
|
||||
import static javax.swing.JOptionPane.YES_OPTION;
|
||||
|
||||
public class Rule extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final RuleProcessor ruleProcessor;
|
||||
private final JTabbedPane tabbedPane;
|
||||
|
||||
public Rule(MontoyaApi api, ConfigLoader configLoader, Object[][] data, JTabbedPane tabbedPane) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.ruleProcessor = new RuleProcessor(api, configLoader);
|
||||
this.tabbedPane = tabbedPane;
|
||||
|
||||
initComponents(data);
|
||||
}
|
||||
|
||||
private void initComponents(Object[][] data) {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout) getLayout()).columnWidths = new int[]{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()).rowWeights = new double[]{0.0, 0.0, 0.0, 1.0, 1.0E-4};
|
||||
|
||||
JButton addButton = new JButton("Add");
|
||||
JButton editButton = new JButton("Edit");
|
||||
JButton removeButton = new JButton("Remove");
|
||||
|
||||
JTable ruleTable = new JTable();
|
||||
JScrollPane scrollPane = new JScrollPane();
|
||||
|
||||
ruleTable.setShowVerticalLines(false);
|
||||
ruleTable.setShowHorizontalLines(false);
|
||||
ruleTable.setVerifyInputWhenFocusTarget(false);
|
||||
ruleTable.setUpdateSelectionOnSort(false);
|
||||
ruleTable.setSurrendersFocusOnKeystroke(true);
|
||||
scrollPane.setViewportView(ruleTable);
|
||||
|
||||
// 按钮监听事件
|
||||
addButton.addActionListener(e -> ruleAddActionPerformed(e, ruleTable, tabbedPane));
|
||||
editButton.addActionListener(e -> ruleEditActionPerformed(e, ruleTable, tabbedPane));
|
||||
removeButton.addActionListener(e -> ruleRemoveActionPerformed(e, ruleTable, tabbedPane));
|
||||
|
||||
// 表格
|
||||
DefaultTableModel model = new DefaultTableModel() {
|
||||
@Override
|
||||
public Class<?> getColumnClass(int column) {
|
||||
return (column == 0) ? Boolean.class : String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return column == 0;
|
||||
}
|
||||
};
|
||||
|
||||
ruleTable.setModel(model);
|
||||
ruleTable.setRowSorter(new TableRowSorter<>(model));
|
||||
|
||||
model.setDataVector(data, Config.ruleFields);
|
||||
model.addTableModelListener(e -> {
|
||||
if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1) {
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
});
|
||||
|
||||
add(addButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 3, 2), 0, 0));
|
||||
add(editButton, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
add(removeButton, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
add(scrollPane, new GridBagConstraints(1, 0, 1, 4, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 5, 5), 0, 0));
|
||||
}
|
||||
|
||||
private void ruleAddActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||
Display ruleDisplay = new Display();
|
||||
ruleDisplay.formatTextField.setText("{0}");
|
||||
|
||||
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
|
||||
if (showState == YES_OPTION) {
|
||||
Vector<Object> ruleData = new Vector<>();
|
||||
ruleData.add(false);
|
||||
ruleData.add(ruleDisplay.ruleNameTextField.getText());
|
||||
ruleData.add(ruleDisplay.firstRegexTextField.getText());
|
||||
ruleData.add(ruleDisplay.secondRegexTextField.getText());
|
||||
ruleData.add(ruleDisplay.formatTextField.getText());
|
||||
ruleData.add(ruleDisplay.colorComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.scopeComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.engineComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.sensitiveComboBox.getSelectedItem());
|
||||
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
model.insertRow(model.getRowCount(), ruleData);
|
||||
ruleProcessor.addRule(ruleData, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleEditActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||
if (ruleTable.getSelectedRowCount() >= 1) {
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
Display ruleDisplay = new Display();
|
||||
|
||||
ruleDisplay.ruleNameTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 1).toString());
|
||||
ruleDisplay.firstRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 2).toString());
|
||||
ruleDisplay.secondRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 3).toString());
|
||||
ruleDisplay.formatTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 4).toString());
|
||||
ruleDisplay.colorComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 5).toString());
|
||||
ruleDisplay.scopeComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 6).toString());
|
||||
ruleDisplay.engineComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 7).toString());
|
||||
ruleDisplay.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 8));
|
||||
|
||||
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
||||
|
||||
int showState = JOptionPane.showConfirmDialog(this, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
|
||||
if (showState == 0) {
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
||||
model.setValueAt(ruleDisplay.firstRegexTextField.getText(), select, 2);
|
||||
model.setValueAt(ruleDisplay.secondRegexTextField.getText(), select, 3);
|
||||
model.setValueAt(ruleDisplay.formatTextField.getText(), select, 4);
|
||||
model.setValueAt(ruleDisplay.colorComboBox.getSelectedItem().toString(), select, 5);
|
||||
model.setValueAt(ruleDisplay.scopeComboBox.getSelectedItem().toString(), select, 6);
|
||||
model.setValueAt(ruleDisplay.engineComboBox.getSelectedItem().toString(), select, 7);
|
||||
model.setValueAt(ruleDisplay.sensitiveComboBox.getSelectedItem(), select, 8);
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||
if (ruleTable.getSelectedRowCount() >= 1) {
|
||||
if (JOptionPane.showConfirmDialog(this, "Are you sure you want to remove this rule?", "Info", JOptionPane.YES_NO_OPTION) == 0) {
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
|
||||
model.removeRow(select);
|
||||
ruleProcessor.removeRule(select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
158
src/main/java/hae/component/rule/Rules.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package hae.component.rule;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.rule.RuleProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
public class Rules extends JTabbedPane {
|
||||
private final MontoyaApi api;
|
||||
private ConfigLoader configLoader;
|
||||
private final RuleProcessor ruleProcessor;
|
||||
private final JTextField ruleGroupNameTextField;
|
||||
|
||||
private Component tabComponent;
|
||||
private int selectedIndex;
|
||||
|
||||
public Rules(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.ruleProcessor = new RuleProcessor(api, configLoader);
|
||||
this.ruleGroupNameTextField = new JTextField();
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
reloadRuleGroup();
|
||||
|
||||
JTabbedPane tabbedPane = this;
|
||||
|
||||
JMenuItem deleteMenuItem = new JMenuItem("Delete");
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
popupMenu.add(deleteMenuItem);
|
||||
|
||||
deleteMenuItem.addActionListener(this::deleteRuleGroupActionPerformed);
|
||||
|
||||
ruleGroupNameTextField.setBorder(BorderFactory.createEmptyBorder());
|
||||
ruleGroupNameTextField.addFocusListener(new FocusAdapter() {
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
renameTitleActionPerformed.actionPerformed(null);
|
||||
}
|
||||
});
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
int index = getSelectedIndex();
|
||||
Rectangle r = getBoundsAt(index);
|
||||
if (r.contains(e.getPoint()) && index >= 0) {
|
||||
switch (e.getButton()) {
|
||||
case MouseEvent.BUTTON1:
|
||||
if (e.getClickCount() == 2) {
|
||||
selectedIndex = index;
|
||||
tabComponent = getTabComponentAt(selectedIndex);
|
||||
String ruleGroupName = getTitleAt(selectedIndex);
|
||||
|
||||
if (!"...".equals(ruleGroupName)) {
|
||||
setTabComponentAt(selectedIndex, ruleGroupNameTextField);
|
||||
ruleGroupNameTextField.setVisible(true);
|
||||
ruleGroupNameTextField.setText(ruleGroupName);
|
||||
ruleGroupNameTextField.selectAll();
|
||||
ruleGroupNameTextField.requestFocusInWindow();
|
||||
ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize());
|
||||
}
|
||||
} else if (e.getClickCount() == 1) {
|
||||
if ("...".equals(getTitleAt(getSelectedIndex()))) {
|
||||
String title = ruleProcessor.newRule();
|
||||
Rule newRule = new Rule(api, configLoader, Config.ruleTemplate, tabbedPane);
|
||||
insertTab(title, null, newRule, null, getTabCount() - 1);
|
||||
setSelectedIndex(getTabCount() - 2);
|
||||
} else {
|
||||
renameTitleActionPerformed.actionPerformed(null);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MouseEvent.BUTTON3:
|
||||
if (!"...".equals(getTitleAt(getSelectedIndex()))) {
|
||||
popupMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
InputMap im = ruleGroupNameTextField.getInputMap(JComponent.WHEN_FOCUSED);
|
||||
ActionMap am = ruleGroupNameTextField.getActionMap();
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
|
||||
am.put("cancel", cancelActionPerformed);
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "rename");
|
||||
am.put("rename", renameTitleActionPerformed);
|
||||
}
|
||||
|
||||
public void reloadRuleGroup() {
|
||||
removeAll();
|
||||
|
||||
this.configLoader = new ConfigLoader(api);
|
||||
Config.globalRules.keySet().forEach(i -> addTab(i, new Rule(api, configLoader, hae.Config.globalRules.get(i), this)));
|
||||
addTab("...", null);
|
||||
}
|
||||
|
||||
private void deleteRuleGroupActionPerformed(ActionEvent e) {
|
||||
if (getTabCount() > 2) {
|
||||
int retCode = JOptionPane.showConfirmDialog(this, "Do you want to delete this rule group?", "Info",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
if (retCode == JOptionPane.YES_OPTION) {
|
||||
String title = getTitleAt(getSelectedIndex());
|
||||
ruleProcessor.deleteRuleGroup(title);
|
||||
remove(getSelectedIndex());
|
||||
setSelectedIndex(getSelectedIndex() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Action renameTitleActionPerformed = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String title = ruleGroupNameTextField.getText();
|
||||
if (!title.isEmpty() && selectedIndex >= 0) {
|
||||
String oldName = getTitleAt(selectedIndex);
|
||||
setTitleAt(selectedIndex, title);
|
||||
|
||||
if (!oldName.equals(title)) {
|
||||
ruleProcessor.renameRuleGroup(oldName, title);
|
||||
}
|
||||
}
|
||||
cancelActionPerformed.actionPerformed(null);
|
||||
}
|
||||
};
|
||||
|
||||
private final Action cancelActionPerformed = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (selectedIndex >= 0) {
|
||||
setTabComponentAt(selectedIndex, tabComponent);
|
||||
|
||||
ruleGroupNameTextField.setVisible(false);
|
||||
ruleGroupNameTextField.setPreferredSize(null);
|
||||
selectedIndex = -1;
|
||||
tabComponent = null;
|
||||
|
||||
requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
143
src/main/java/hae/instances/editor/RequestEditor.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package hae.instances.editor;
|
||||
|
||||
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.requests.HttpRequest;
|
||||
import burp.api.montoya.ui.Selection;
|
||||
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.utils.ConfigLoader;
|
||||
import hae.utils.http.HttpUtils;
|
||||
import hae.utils.string.StringProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class RequestEditor implements HttpRequestEditorProvider {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
|
||||
public RequestEditor(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) {
|
||||
return new Editor(api, configLoader, editorCreationContext);
|
||||
}
|
||||
|
||||
private static class Editor implements ExtensionProvidedHttpRequestEditor {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final HttpUtils httpUtils;
|
||||
private final EditorCreationContext creationContext;
|
||||
private final MessageProcessor messageProcessor;
|
||||
private HttpRequestResponse requestResponse;
|
||||
private List<Map<String, String>> dataList;
|
||||
|
||||
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
||||
|
||||
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.httpUtils = new HttpUtils(api, configLoader);
|
||||
this.creationContext = creationContext;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest getRequest() {
|
||||
return requestResponse.request();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestResponse(HttpRequestResponse requestResponse) {
|
||||
this.requestResponse = requestResponse;
|
||||
generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) {
|
||||
HttpRequest request = requestResponse.request();
|
||||
if (request != null) {
|
||||
try {
|
||||
String host = StringProcessor.getHostByUrl(request.url());
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String caption() {
|
||||
return "MarkInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component uiComponent() {
|
||||
return jTabbedPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Selection selectedData() {
|
||||
return new Selection() {
|
||||
@Override
|
||||
public ByteArray contents() {
|
||||
Datatable dataTable = (Datatable) jTabbedPane.getSelectedComponent();
|
||||
return ByteArray.byteArray(dataTable.getSelectedDataAtTable(dataTable.getDataTable()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range offsets() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isListHasData(List<Map<String, String>> dataList) {
|
||||
if (dataList != null && !dataList.isEmpty()) {
|
||||
Map<String, String> dataMap = dataList.get(0);
|
||||
return dataMap != null && !dataMap.isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void generateTabbedPaneFromResultMap(MontoyaApi api, ConfigLoader configLoader, JTabbedPane tabbedPane, List<Map<String, String>> result) {
|
||||
tabbedPane.removeAll();
|
||||
if (result != null && !result.isEmpty()) {
|
||||
Map<String, String> dataMap = result.get(0);
|
||||
if (dataMap != null && !dataMap.isEmpty()) {
|
||||
dataMap.keySet().forEach(i -> {
|
||||
String[] extractData = dataMap.get(i).split(Config.boundary);
|
||||
Datatable dataPanel = new Datatable(api, configLoader, i, Arrays.asList(extractData));
|
||||
tabbedPane.addTab(i, dataPanel);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/main/java/hae/instances/editor/ResponseEditor.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package hae.instances.editor;
|
||||
|
||||
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.requests.HttpRequest;
|
||||
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.ExtensionProvidedHttpResponseEditor;
|
||||
import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider;
|
||||
import hae.component.board.table.Datatable;
|
||||
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.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ResponseEditor implements HttpResponseEditorProvider {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
|
||||
public ResponseEditor(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionProvidedHttpResponseEditor provideHttpResponseEditor(EditorCreationContext editorCreationContext) {
|
||||
return new Editor(api, configLoader, editorCreationContext);
|
||||
}
|
||||
|
||||
private static class Editor implements ExtensionProvidedHttpResponseEditor {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final HttpUtils httpUtils;
|
||||
private final EditorCreationContext creationContext;
|
||||
private final MessageProcessor messageProcessor;
|
||||
private HttpRequestResponse requestResponse;
|
||||
private List<Map<String, String>> dataList;
|
||||
|
||||
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
||||
|
||||
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.httpUtils = new HttpUtils(api, configLoader);
|
||||
this.creationContext = creationContext;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse getResponse() {
|
||||
return requestResponse.response();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestResponse(HttpRequestResponse requestResponse) {
|
||||
this.requestResponse = requestResponse;
|
||||
RequestEditor.generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) {
|
||||
HttpResponse response = requestResponse.response();
|
||||
|
||||
if (response != null) {
|
||||
HttpRequest request = requestResponse.request();
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String caption() {
|
||||
return "MarkInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component uiComponent() {
|
||||
return jTabbedPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Selection selectedData() {
|
||||
return new Selection() {
|
||||
@Override
|
||||
public ByteArray contents() {
|
||||
Datatable dataTable = (Datatable) jTabbedPane.getSelectedComponent();
|
||||
return ByteArray.byteArray(dataTable.getSelectedDataAtTable(dataTable.getDataTable()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range offsets() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/main/java/hae/instances/editor/WebSocketEditor.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package hae.instances.editor;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.ByteArray;
|
||||
import burp.api.montoya.core.Range;
|
||||
import burp.api.montoya.ui.Selection;
|
||||
import burp.api.montoya.ui.contextmenu.WebSocketMessage;
|
||||
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
||||
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.utils.ConfigLoader;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebSocketEditor implements WebSocketMessageEditorProvider {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
|
||||
public WebSocketEditor(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionProvidedWebSocketMessageEditor provideMessageEditor(EditorCreationContext editorCreationContext) {
|
||||
return new Editor(api, configLoader, editorCreationContext);
|
||||
}
|
||||
|
||||
private static class Editor implements ExtensionProvidedWebSocketMessageEditor {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final EditorCreationContext creationContext;
|
||||
private final MessageProcessor messageProcessor;
|
||||
private ByteArray message;
|
||||
private List<Map<String, String>> dataList;
|
||||
|
||||
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
||||
|
||||
public Editor(MontoyaApi api, ConfigLoader configLoader, EditorCreationContext creationContext) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.creationContext = creationContext;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteArray getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(WebSocketMessage webSocketMessage) {
|
||||
this.message = webSocketMessage.payload();
|
||||
RequestEditor.generateTabbedPaneFromResultMap(api, configLoader, jTabbedPane, this.dataList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledFor(WebSocketMessage webSocketMessage) {
|
||||
String websocketMessage = webSocketMessage.payload().toString();
|
||||
if (!websocketMessage.isEmpty()) {
|
||||
this.dataList = messageProcessor.processMessage("", websocketMessage, false);
|
||||
return RequestEditor.isListHasData(this.dataList);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String caption() {
|
||||
return "MarkInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component uiComponent() {
|
||||
return jTabbedPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Selection selectedData() {
|
||||
return new Selection() {
|
||||
@Override
|
||||
public ByteArray contents() {
|
||||
Datatable dataTable = (Datatable) jTabbedPane.getSelectedComponent();
|
||||
return ByteArray.byteArray(dataTable.getSelectedDataAtTable(dataTable.getDataTable()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range offsets() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
106
src/main/java/hae/instances/http/HttpMessageActiveHandler.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package hae.instances.http;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.Annotations;
|
||||
import burp.api.montoya.core.HighlightColor;
|
||||
import burp.api.montoya.http.handler.*;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.instances.http.utils.MessageProcessor;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.http.HttpUtils;
|
||||
import hae.utils.string.StringProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpMessageActiveHandler implements HttpHandler {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final HttpUtils httpUtils;
|
||||
private final MessageTableModel messageTableModel;
|
||||
private final MessageProcessor messageProcessor;
|
||||
|
||||
// Montoya API对HTTP消息的处理分为了请求和响应,因此此处设置高亮和标记需要使用全局变量的方式,以此兼顾请求和响应
|
||||
// 同时采用 ThreadLocal 来保证多线程并发的情况下全局变量的安全性
|
||||
private final ThreadLocal<String> host = ThreadLocal.withInitial(() -> "");
|
||||
private final ThreadLocal<List<String>> colorList = ThreadLocal.withInitial(ArrayList::new);
|
||||
private final ThreadLocal<List<String>> commentList = ThreadLocal.withInitial(ArrayList::new);
|
||||
|
||||
public HttpMessageActiveHandler(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.httpUtils = new HttpUtils(api, configLoader);
|
||||
this.messageTableModel = messageTableModel;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent httpRequestToBeSent) {
|
||||
colorList.get().clear();
|
||||
commentList.get().clear();
|
||||
|
||||
Annotations annotations = httpRequestToBeSent.annotations();
|
||||
|
||||
try {
|
||||
host.set(StringProcessor.getHostByUrl(httpRequestToBeSent.url()));
|
||||
} catch (Exception e) {
|
||||
api.logging().logToError("handleHttpRequestToBeSent: " + e.getMessage());
|
||||
}
|
||||
|
||||
return RequestToBeSentAction.continueWith(httpRequestToBeSent, annotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived httpResponseReceived) {
|
||||
Annotations annotations = httpResponseReceived.annotations();
|
||||
HttpRequest request = httpResponseReceived.initiatingRequest();
|
||||
HttpRequestResponse requestResponse = HttpRequestResponse.httpRequestResponse(request, httpResponseReceived);
|
||||
String toolType = httpResponseReceived.toolSource().toolType().toolName();
|
||||
|
||||
boolean matches = httpUtils.verifyHttpRequestResponse(requestResponse, toolType);
|
||||
|
||||
if (!matches) {
|
||||
try {
|
||||
setColorAndCommentList(messageProcessor.processRequest(host.get(), request, true));
|
||||
setColorAndCommentList(messageProcessor.processResponse(host.get(), httpResponseReceived, true));
|
||||
|
||||
if (!colorList.get().isEmpty() && !commentList.get().isEmpty()) {
|
||||
HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(request, httpResponseReceived);
|
||||
|
||||
String color = messageProcessor.retrieveFinalColor(messageProcessor.retrieveColorIndices(colorList.get()));
|
||||
annotations.setHighlightColor(HighlightColor.highlightColor(color));
|
||||
String comment = StringProcessor.mergeComment(String.join(", ", commentList.get()));
|
||||
annotations.setNotes(comment);
|
||||
|
||||
String method = request.method();
|
||||
String url = request.url();
|
||||
String status = String.valueOf(httpResponseReceived.statusCode());
|
||||
String length = String.valueOf(httpResponseReceived.toByteArray().length());
|
||||
|
||||
new SwingWorker<Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground() {
|
||||
messageTableModel.add(httpRequestResponse, url, method, status, length, comment, color, true);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
api.logging().logToError("handleHttpResponseReceived: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseReceivedAction.continueWith(httpResponseReceived, annotations);
|
||||
}
|
||||
|
||||
private void setColorAndCommentList(List<Map<String, String>> result) {
|
||||
if (result != null && !result.isEmpty()) {
|
||||
colorList.get().add(result.get(0).get("color"));
|
||||
commentList.get().add(result.get(1).get("comment"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
178
src/main/java/hae/instances/http/utils/MessageProcessor.java
Normal file
@@ -0,0 +1,178 @@
|
||||
package hae.instances.http.utils;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.http.message.HttpHeader;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||
import hae.Config;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
public class MessageProcessor {
|
||||
private final MontoyaApi api;
|
||||
private final RegularMatcher regularMatcher;
|
||||
|
||||
private String finalColor = "";
|
||||
|
||||
public MessageProcessor(MontoyaApi api) {
|
||||
this.api = api;
|
||||
this.regularMatcher = new RegularMatcher(api);
|
||||
}
|
||||
|
||||
public List<Map<String, String>> processMessage(String host, String message, boolean flag) {
|
||||
Map<String, Map<String, Object>> obj = null;
|
||||
|
||||
try {
|
||||
obj = regularMatcher.match(host, "any", message, message, message);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return getDataList(obj, flag);
|
||||
}
|
||||
|
||||
public List<Map<String, String>> processResponse(String host, HttpResponse httpResponse, boolean flag) {
|
||||
Map<String, Map<String, Object>> obj = null;
|
||||
|
||||
try {
|
||||
String response = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String header = httpResponse.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
obj = regularMatcher.match(host, "response", response, header, body);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return getDataList(obj, flag);
|
||||
}
|
||||
|
||||
public List<Map<String, String>> processRequest(String host, HttpRequest httpRequest, boolean flag) {
|
||||
Map<String, Map<String, Object>> obj = null;
|
||||
|
||||
try {
|
||||
String request = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String header = httpRequest.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
obj = regularMatcher.match(host, "request", request, header, body);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return getDataList(obj, flag);
|
||||
}
|
||||
|
||||
private List<Map<String, String>> getDataList(Map<String, Map<String, Object>> obj, boolean actionFlag) {
|
||||
List<Map<String, String>> highlightList = new ArrayList<>();
|
||||
List<Map<String, String>> extractList = new ArrayList<>();
|
||||
|
||||
if (obj != null && !obj.isEmpty()) {
|
||||
if (actionFlag) {
|
||||
List<List<String>> resultList = extractColorsAndComments(obj);
|
||||
List<String> colorList = resultList.get(0);
|
||||
List<String> commentList = resultList.get(1);
|
||||
if (!colorList.isEmpty() && !commentList.isEmpty()) {
|
||||
String color = retrieveFinalColor(retrieveColorIndices(colorList));
|
||||
Map<String, String> colorMap = new HashMap<String, String>() {{
|
||||
put("color", color);
|
||||
}};
|
||||
Map<String, String> commentMap = new HashMap<String, String>() {{
|
||||
put("comment", String.join(", ", commentList));
|
||||
}};
|
||||
highlightList.add(colorMap);
|
||||
highlightList.add(commentMap);
|
||||
}
|
||||
} else {
|
||||
extractList.add(extractDataFromMap(obj));
|
||||
}
|
||||
}
|
||||
|
||||
return actionFlag ? highlightList : extractList;
|
||||
}
|
||||
|
||||
private Map<String, String> extractDataFromMap(Map<String, Map<String, Object>> inputData) {
|
||||
Map<String, String> extractedData = new HashMap<>();
|
||||
inputData.keySet().forEach(key -> {
|
||||
Map<String, Object> tempMap = inputData.get(key);
|
||||
String data = tempMap.get("data").toString();
|
||||
extractedData.put(key, data);
|
||||
});
|
||||
|
||||
return extractedData;
|
||||
}
|
||||
|
||||
private List<List<String>> extractColorsAndComments(Map<String, Map<String, Object>> inputData) {
|
||||
List<String> colorList = new ArrayList<>();
|
||||
List<String> commentList = new ArrayList<>();
|
||||
inputData.keySet().forEach(key -> {
|
||||
Map<String, Object> tempMap = inputData.get(key);
|
||||
String color = tempMap.get("color").toString();
|
||||
colorList.add(color);
|
||||
commentList.add(key);
|
||||
});
|
||||
List<List<String>> result = new ArrayList<>();
|
||||
result.add(colorList);
|
||||
result.add(commentList);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Integer> retrieveColorIndices(List<String> colors) {
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
String[] colorArray = Config.color;
|
||||
int size = colorArray.length;
|
||||
|
||||
for (String color : colors) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (colorArray[i].equals(color)) {
|
||||
indices.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
private void upgradeColors(List<Integer> colorList) {
|
||||
int colorSize = colorList.size();
|
||||
String[] colorArray = Config.color;
|
||||
colorList.sort(Comparator.comparingInt(Integer::intValue));
|
||||
int i = 0;
|
||||
List<Integer> stack = new ArrayList<>();
|
||||
while (i < colorSize) {
|
||||
if (stack.isEmpty()) {
|
||||
stack.add(colorList.get(i));
|
||||
} else {
|
||||
if (!Objects.equals(colorList.get(i), stack.stream().reduce((first, second) -> second).orElse(99999999))) {
|
||||
stack.add(colorList.get(i));
|
||||
} else {
|
||||
stack.set(stack.size() - 1, stack.get(stack.size() - 1) - 1);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// 利用HashSet删除重复元素
|
||||
HashSet tmpList = new HashSet(stack);
|
||||
if (stack.size() == tmpList.size()) {
|
||||
stack.sort(Comparator.comparingInt(Integer::intValue));
|
||||
if (stack.get(0) < 0) {
|
||||
finalColor = colorArray[0];
|
||||
} else {
|
||||
finalColor = colorArray[stack.get(0)];
|
||||
}
|
||||
} else {
|
||||
upgradeColors(stack);
|
||||
}
|
||||
}
|
||||
|
||||
public String retrieveFinalColor(List<Integer> colorList) {
|
||||
upgradeColors(colorList);
|
||||
return finalColor;
|
||||
}
|
||||
|
||||
}
|
||||
289
src/main/java/hae/instances/http/utils/RegularMatcher.java
Normal file
@@ -0,0 +1,289 @@
|
||||
package hae.instances.http.utils;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.persistence.PersistedList;
|
||||
import burp.api.montoya.persistence.PersistedObject;
|
||||
import dk.brics.automaton.Automaton;
|
||||
import dk.brics.automaton.AutomatonMatcher;
|
||||
import dk.brics.automaton.RegExp;
|
||||
import dk.brics.automaton.RunAutomaton;
|
||||
import hae.Config;
|
||||
import hae.cache.CachePool;
|
||||
import hae.utils.DataManager;
|
||||
import hae.utils.string.HashCalculator;
|
||||
import hae.utils.string.StringProcessor;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class RegularMatcher {
|
||||
private final MontoyaApi api;
|
||||
|
||||
public RegularMatcher(MontoyaApi api) {
|
||||
this.api = api;
|
||||
|
||||
}
|
||||
|
||||
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
|
||||
// 先从缓存池里判断是否有已经匹配好的结果
|
||||
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
||||
Map<String, Map<String, Object>> map = CachePool.get(messageIndex);
|
||||
if (map != null) {
|
||||
return map;
|
||||
} else {
|
||||
// 最终返回的结果
|
||||
Map<String, Map<String, Object>> finalMap = new HashMap<>();
|
||||
Config.globalRules.keySet().parallelStream().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
// 多线程执行,一定程度上减少阻塞现象
|
||||
String matchContent = "";
|
||||
// 遍历获取规则
|
||||
List<String> result;
|
||||
Map<String, Object> tmpMap = new HashMap<>();
|
||||
|
||||
boolean loaded = (Boolean) objects[0];
|
||||
String name = objects[1].toString();
|
||||
String f_regex = objects[2].toString();
|
||||
String s_regex = objects[3].toString();
|
||||
String format = objects[4].toString();
|
||||
String color = objects[5].toString();
|
||||
String scope = objects[6].toString();
|
||||
String engine = objects[7].toString();
|
||||
boolean sensitive = (Boolean) objects[8];
|
||||
|
||||
// 判断规则是否开启与作用域
|
||||
if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) {
|
||||
switch (scope) {
|
||||
case "any":
|
||||
case "request":
|
||||
case "response":
|
||||
matchContent = message;
|
||||
break;
|
||||
case "any header":
|
||||
case "request header":
|
||||
case "response header":
|
||||
matchContent = header;
|
||||
break;
|
||||
case "any body":
|
||||
case "request body":
|
||||
case "response body":
|
||||
matchContent = body;
|
||||
break;
|
||||
case "request line":
|
||||
case "response line":
|
||||
matchContent = message.split("\\r?\\n", 2)[0];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
result = new ArrayList<>(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive));
|
||||
} catch (Exception e) {
|
||||
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
|
||||
api.logging().logToError(e.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 去除重复内容
|
||||
HashSet tmpList = new HashSet(result);
|
||||
result.clear();
|
||||
result.addAll(tmpList);
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
tmpMap.put("color", color);
|
||||
String dataStr = String.join(Config.boundary, result);
|
||||
tmpMap.put("data", dataStr);
|
||||
|
||||
String nameAndSize = String.format("%s (%s)", name, result.size());
|
||||
finalMap.put(nameAndSize, tmpMap);
|
||||
|
||||
putDataToGlobalMap(api, host, name, result, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
CachePool.put(messageIndex, finalMap);
|
||||
return finalMap;
|
||||
}
|
||||
}
|
||||
|
||||
public 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) {
|
||||
List<String> retList = new ArrayList<>();
|
||||
if ("nfa".equals(engine)) {
|
||||
Matcher matcher = createPatternMatcher(f_regex, content, sensitive);
|
||||
retList.addAll(extractMatches(s_regex, format, sensitive, matcher));
|
||||
} else {
|
||||
// DFA不支持格式化输出,因此不关注format
|
||||
String newContent = content;
|
||||
String newFirstRegex = f_regex;
|
||||
if (!sensitive) {
|
||||
newContent = content.toLowerCase();
|
||||
newFirstRegex = f_regex.toLowerCase();
|
||||
}
|
||||
AutomatonMatcher autoMatcher = createAutomatonMatcher(newFirstRegex, newContent);
|
||||
retList.addAll(extractMatches(s_regex, autoMatcher, content));
|
||||
}
|
||||
return retList;
|
||||
}
|
||||
|
||||
private List<String> extractMatches(String s_regex, String format, boolean sensitive, Matcher matcher) {
|
||||
List<String> matches = new ArrayList<>();
|
||||
if (s_regex.isEmpty()) {
|
||||
matches.addAll(getFormatString(matcher, format));
|
||||
} else {
|
||||
while (matcher.find()) {
|
||||
String matchContent = matcher.group(1);
|
||||
if (!matchContent.isEmpty()) {
|
||||
matcher = createPatternMatcher(s_regex, matchContent, sensitive);
|
||||
matches.addAll(getFormatString(matcher, format));
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private List<String> extractMatches(String s_regex, AutomatonMatcher autoMatcher, String content) {
|
||||
List<String> matches = new ArrayList<>();
|
||||
if (s_regex.isEmpty()) {
|
||||
matches.addAll(getFormatString(autoMatcher, content));
|
||||
} else {
|
||||
while (autoMatcher.find()) {
|
||||
String s = autoMatcher.group();
|
||||
if (!s.isEmpty()) {
|
||||
autoMatcher = createAutomatonMatcher(s_regex, getSubString(content, s));
|
||||
matches.addAll(getFormatString(autoMatcher, content));
|
||||
}
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private List<String> getFormatString(Matcher matcher, String format) {
|
||||
List<Integer> indexList = parseIndexesFromString(format);
|
||||
List<String> stringList = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
if (!matcher.group(1).isEmpty()) {
|
||||
Object[] params = indexList.stream().map(i -> {
|
||||
if (!matcher.group(i + 1).isEmpty()) {
|
||||
return matcher.group(i + 1);
|
||||
}
|
||||
return "";
|
||||
}).toArray();
|
||||
|
||||
stringList.add(MessageFormat.format(reorderIndex(format), params));
|
||||
}
|
||||
}
|
||||
|
||||
return stringList;
|
||||
}
|
||||
|
||||
private List<String> getFormatString(AutomatonMatcher matcher, String content) {
|
||||
List<String> stringList = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
String s = matcher.group(0);
|
||||
if (!s.isEmpty()) {
|
||||
stringList.add(getSubString(content, s));
|
||||
}
|
||||
}
|
||||
|
||||
return stringList;
|
||||
}
|
||||
|
||||
private Matcher createPatternMatcher(String regex, String content, boolean sensitive) {
|
||||
Pattern pattern = sensitive ? Pattern.compile(regex) : Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||
return pattern.matcher(content);
|
||||
}
|
||||
|
||||
private AutomatonMatcher createAutomatonMatcher(String regex, String content) {
|
||||
RegExp regexp = new RegExp(regex);
|
||||
Automaton auto = regexp.toAutomaton();
|
||||
RunAutomaton runAuto = new RunAutomaton(auto, true);
|
||||
return runAuto.newMatcher(content);
|
||||
}
|
||||
|
||||
private LinkedList<Integer> parseIndexesFromString(String input) {
|
||||
LinkedList<Integer> indexes = new LinkedList<>();
|
||||
Pattern pattern = Pattern.compile("\\{(\\d+)}");
|
||||
Matcher matcher = pattern.matcher(input);
|
||||
|
||||
while (matcher.find()) {
|
||||
String index = matcher.group(1);
|
||||
if (!index.isEmpty()) {
|
||||
indexes.add(Integer.valueOf(index));
|
||||
}
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
private String getSubString(String content, String s) {
|
||||
byte[] contentByte = api.utilities().byteUtils().convertFromString(content);
|
||||
byte[] sByte = api.utilities().byteUtils().convertFromString(s);
|
||||
int startIndex = api.utilities().byteUtils().indexOf(contentByte, sByte, false, 1, contentByte.length);
|
||||
int endIndex = startIndex + s.length();
|
||||
return content.substring(startIndex, endIndex);
|
||||
}
|
||||
|
||||
private String reorderIndex(String format) {
|
||||
Pattern pattern = Pattern.compile("\\{(\\d+)}");
|
||||
Matcher matcher = pattern.matcher(format);
|
||||
int count = 0;
|
||||
while (matcher.find()) {
|
||||
String newStr = String.format("{%s}", count);
|
||||
String matchStr = matcher.group(0);
|
||||
format = format.replace(matchStr, newStr);
|
||||
count++;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package hae.instances.websocket;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.HighlightColor;
|
||||
import burp.api.montoya.proxy.websocket.*;
|
||||
import hae.instances.http.utils.MessageProcessor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebSocketMessageHandler implements ProxyMessageHandler {
|
||||
private final MontoyaApi api;
|
||||
private final MessageProcessor messageProcessor;
|
||||
|
||||
public WebSocketMessageHandler(MontoyaApi api) {
|
||||
this.api = api;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextMessageReceivedAction handleTextMessageReceived(InterceptedTextMessage interceptedTextMessage) {
|
||||
String message = interceptedTextMessage.payload();
|
||||
List<Map<String, String>> result = messageProcessor.processMessage("", message, true);
|
||||
|
||||
if (result != null && !result.isEmpty()) {
|
||||
interceptedTextMessage.annotations().setHighlightColor(HighlightColor.highlightColor(result.get(0).get("color")));
|
||||
interceptedTextMessage.annotations().setNotes(result.get(1).get("comment"));
|
||||
}
|
||||
|
||||
return TextMessageReceivedAction.continueWith(interceptedTextMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextMessageToBeSentAction handleTextMessageToBeSent(InterceptedTextMessage interceptedTextMessage) {
|
||||
return TextMessageToBeSentAction.continueWith(interceptedTextMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryMessageReceivedAction handleBinaryMessageReceived(InterceptedBinaryMessage interceptedBinaryMessage) {
|
||||
return BinaryMessageReceivedAction.continueWith(interceptedBinaryMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryMessageToBeSentAction handleBinaryMessageToBeSent(InterceptedBinaryMessage interceptedBinaryMessage) {
|
||||
return BinaryMessageToBeSentAction.continueWith(interceptedBinaryMessage);
|
||||
}
|
||||
}
|
||||
254
src/main/java/hae/utils/ConfigLoader.java
Normal file
@@ -0,0 +1,254 @@
|
||||
package hae.utils;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
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 {
|
||||
private final MontoyaApi api;
|
||||
private final Yaml yaml;
|
||||
private final String configFilePath;
|
||||
private final String rulesFilePath;
|
||||
|
||||
public ConfigLoader(MontoyaApi api) {
|
||||
this.api = api;
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer(dop);
|
||||
this.yaml = new Yaml(representer, dop);
|
||||
|
||||
String configPath = determineConfigPath();
|
||||
this.configFilePath = String.format("%s/%s", configPath, "Config.yml");
|
||||
this.rulesFilePath = String.format("%s/%s", configPath, "Rules.yml");
|
||||
|
||||
// 构造函数,初始化配置
|
||||
File HaEConfigPathFile = new File(configPath);
|
||||
if (!(HaEConfigPathFile.exists() && HaEConfigPathFile.isDirectory())) {
|
||||
HaEConfigPathFile.mkdirs();
|
||||
}
|
||||
|
||||
File configFilePath = new File(this.configFilePath);
|
||||
if (!(configFilePath.exists() && configFilePath.isFile())) {
|
||||
initConfig();
|
||||
}
|
||||
|
||||
File rulesFilePath = new File(this.rulesFilePath);
|
||||
if (!(rulesFilePath.exists() && rulesFilePath.isFile())) {
|
||||
initRules();
|
||||
}
|
||||
|
||||
Config.globalRules = getRules();
|
||||
}
|
||||
|
||||
private String determineConfigPath() {
|
||||
// 优先级1:用户根目录
|
||||
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
|
||||
if (isValidConfigPath(userConfigPath)) {
|
||||
return userConfigPath;
|
||||
}
|
||||
|
||||
// 优先级2:Jar包所在目录
|
||||
String jarPath = api.extension().filename();
|
||||
String jarDirectory = new File(jarPath).getParent();
|
||||
String jarConfigPath = String.format("%s/.config/HaE", jarDirectory);
|
||||
if (isValidConfigPath(jarConfigPath)) {
|
||||
return jarConfigPath;
|
||||
}
|
||||
|
||||
return userConfigPath;
|
||||
}
|
||||
|
||||
private static boolean isValidConfigPath(String configPath) {
|
||||
File configPathFile = new File(configPath);
|
||||
return configPathFile.exists() && configPathFile.isDirectory();
|
||||
}
|
||||
|
||||
public void initConfig() {
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("ExcludeSuffix", getExcludeSuffix());
|
||||
r.put("BlockHost", getBlockHost());
|
||||
r.put("ExcludeStatus", getExcludeStatus());
|
||||
r.put("LimitSize", getLimitSize());
|
||||
r.put("HaEScope", getScope());
|
||||
try {
|
||||
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
|
||||
yaml.dump(r, ws);
|
||||
ws.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public String getRulesFilePath() {
|
||||
return rulesFilePath;
|
||||
}
|
||||
|
||||
// 获取规则配置
|
||||
public Map<String, Object[][]> getRules() {
|
||||
Map<String, Object[][]> rules = new HashMap<>();
|
||||
|
||||
try {
|
||||
InputStream inputStream = Files.newInputStream(Paths.get(getRulesFilePath()));
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer(dop);
|
||||
Map<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream);
|
||||
|
||||
String[] fieldKeys = {"loaded", "name", "f_regex", "s_regex", "format", "color", "scope", "engine", "sensitive"};
|
||||
|
||||
Object rulesObj = rulesMap.get("rules");
|
||||
if (rulesObj instanceof List) {
|
||||
List<Map<String, Object>> groupData = (List<Map<String, Object>>) rulesObj;
|
||||
for (Map<String, Object> groupFields : groupData) {
|
||||
ArrayList<Object[]> data = new ArrayList<>();
|
||||
|
||||
Object ruleObj = groupFields.get("rule");
|
||||
if (ruleObj instanceof List) {
|
||||
List<Map<String, Object>> ruleData = (List<Map<String, Object>>) ruleObj;
|
||||
for (Map<String, Object> ruleFields : ruleData) {
|
||||
Object[] valuesArray = new Object[fieldKeys.length];
|
||||
for (int i = 0; i < fieldKeys.length; i++) {
|
||||
valuesArray[i] = ruleFields.get(fieldKeys[i]);
|
||||
}
|
||||
data.add(valuesArray);
|
||||
}
|
||||
}
|
||||
|
||||
Object[][] dataArray = data.toArray(new Object[data.size()][]);
|
||||
rules.put(groupFields.get("group").toString(), dataArray);
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
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) {
|
||||
setValueToConfig("ExcludeSuffix", excludeSuffix);
|
||||
}
|
||||
|
||||
public void setBlockHost(String blockHost) {
|
||||
setValueToConfig("BlockHost", blockHost);
|
||||
}
|
||||
|
||||
public void setExcludeStatus(String status) {
|
||||
setValueToConfig("ExcludeStatus", status);
|
||||
}
|
||||
|
||||
public void setLimitSize(String size) {
|
||||
setValueToConfig("LimitSize", size);
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
setValueToConfig("HaEScope", scope);
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
setValueToConfig("HaEModeStatus", mode);
|
||||
}
|
||||
|
||||
private void setValueToConfig(String name, String value) {
|
||||
Map<String, Object> currentConfig = loadCurrentConfig();
|
||||
currentConfig.put(name, value);
|
||||
|
||||
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8)) {
|
||||
yaml.dump(currentConfig, ws);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> loadCurrentConfig() {
|
||||
Path path = Paths.get(configFilePath);
|
||||
if (!Files.exists(path)) {
|
||||
return new LinkedHashMap<>(); // 返回空的Map,表示没有当前配置
|
||||
}
|
||||
|
||||
try (InputStream in = Files.newInputStream(path)) {
|
||||
return yaml.load(in);
|
||||
} catch (Exception e) {
|
||||
return new LinkedHashMap<>(); // 读取失败时也返回空的Map
|
||||
}
|
||||
}
|
||||
|
||||
public boolean initRules() {
|
||||
boolean ret = copyRulesToFile(this.rulesFilePath);
|
||||
if (!ret) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
85
src/main/java/hae/utils/DataManager.java
Normal file
@@ -0,0 +1,85 @@
|
||||
package hae.utils;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||
import burp.api.montoya.persistence.PersistedList;
|
||||
import burp.api.montoya.persistence.PersistedObject;
|
||||
import burp.api.montoya.persistence.Persistence;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.instances.http.utils.RegularMatcher;
|
||||
|
||||
public class DataManager {
|
||||
private final MontoyaApi api;
|
||||
private final Persistence persistence;
|
||||
|
||||
public DataManager(MontoyaApi api) {
|
||||
this.api = api;
|
||||
this.persistence = api.persistence();
|
||||
}
|
||||
|
||||
private void saveIndex(String indexName, String indexValue) {
|
||||
PersistedList<String> indexList = persistence.extensionData().getStringList(indexName);
|
||||
|
||||
if (indexList != null && !indexList.isEmpty()) {
|
||||
persistence.extensionData().deleteStringList(indexName);
|
||||
} else {
|
||||
indexList = PersistedList.persistedStringList();
|
||||
}
|
||||
|
||||
if (!indexList.contains(indexValue)) {
|
||||
indexList.add(indexValue);
|
||||
}
|
||||
|
||||
persistence.extensionData().setStringList(indexName, indexList);
|
||||
}
|
||||
|
||||
public void putData(String dataType, String dataName, PersistedObject persistedObject) {
|
||||
if (persistence.extensionData().getChildObject(dataName) != null) {
|
||||
persistence.extensionData().deleteChildObject(dataName);
|
||||
}
|
||||
persistence.extensionData().setChildObject(dataName, persistedObject);
|
||||
saveIndex(dataType, dataName);
|
||||
}
|
||||
|
||||
public void loadData(MessageTableModel messageTableModel) {
|
||||
// 1. 获取索引
|
||||
PersistedList<String> dataIndex = persistence.extensionData().getStringList("data"); // 数据索引
|
||||
PersistedList<String> messageIndex = persistence.extensionData().getStringList("message"); // 消息索引
|
||||
|
||||
// 2. 从索引获取数据
|
||||
loadHaEData(dataIndex);
|
||||
loadMessageData(messageIndex, messageTableModel);
|
||||
|
||||
}
|
||||
|
||||
private void loadHaEData(PersistedList<String> dataIndex) {
|
||||
if (dataIndex != null && !dataIndex.isEmpty()) {
|
||||
dataIndex.parallelStream().forEach(index -> {
|
||||
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||
dataObj.stringListKeys().forEach(dataKey -> {
|
||||
RegularMatcher.putDataToGlobalMap(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void loadMessageData(PersistedList<String> messageIndex, MessageTableModel messageTableModel) {
|
||||
if (messageIndex != null && !messageIndex.isEmpty()) {
|
||||
messageIndex.parallelStream().forEach(index -> {
|
||||
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||
HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo");
|
||||
String comment = dataObj.getString("comment");
|
||||
String color = dataObj.getString("color");
|
||||
HttpRequest request = messageInfo.request();
|
||||
HttpResponse response = messageInfo.response();
|
||||
String method = request.method();
|
||||
String url = request.url();
|
||||
String status = String.valueOf(response.statusCode());
|
||||
String length = String.valueOf(response.toByteArray().length());
|
||||
messageTableModel.add(messageInfo, url, method, status, length, comment, color, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/main/java/hae/utils/UIEnhancer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
58
src/main/java/hae/utils/http/HttpUtils.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package hae.utils.http;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.string.StringProcessor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class HttpUtils {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
|
||||
public HttpUtils(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
}
|
||||
|
||||
public boolean verifyHttpRequestResponse(HttpRequestResponse requestResponse, String toolType) {
|
||||
HttpRequest request = requestResponse.request();
|
||||
HttpResponse response = requestResponse.response();
|
||||
boolean retStatus = false;
|
||||
try {
|
||||
String host = StringProcessor.getHostByUrl(request.url());
|
||||
String[] hostList = configLoader.getBlockHost().split("\\|");
|
||||
boolean isBlockHost = isBlockHost(hostList, host);
|
||||
|
||||
List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
|
||||
boolean isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase());
|
||||
|
||||
boolean isToolScope = !configLoader.getScope().contains(toolType);
|
||||
|
||||
List<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|"));
|
||||
boolean isExcludeStatus = statusList.contains(String.valueOf(response.statusCode()));
|
||||
|
||||
retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return retStatus;
|
||||
}
|
||||
|
||||
private boolean isBlockHost(String[] hostList, String host) {
|
||||
boolean isBlockHost = false;
|
||||
for (String hostName : hostList) {
|
||||
String cleanedHost = StringProcessor.replaceFirstOccurrence(hostName, "*.", "");
|
||||
if (hostName.contains("*.") && StringProcessor.matchFromEnd(host, cleanedHost)) {
|
||||
isBlockHost = true;
|
||||
} else if (host.equals(hostName) || hostName.equals("*")) {
|
||||
isBlockHost = true;
|
||||
}
|
||||
}
|
||||
return isBlockHost;
|
||||
}
|
||||
}
|
||||
110
src/main/java/hae/utils/rule/RuleProcessor.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package hae.utils.rule;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.utils.ConfigLoader;
|
||||
import hae.utils.rule.model.Group;
|
||||
import hae.utils.rule.model.Info;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RuleProcessor {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
|
||||
public RuleProcessor(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
}
|
||||
|
||||
public void rulesFormatAndSave() {
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer(dop);
|
||||
Yaml yaml = new Yaml(representer, dop);
|
||||
|
||||
List<Group> ruleGroupList = new ArrayList<>();
|
||||
|
||||
Config.globalRules.forEach((k, v) -> {
|
||||
List<Info> ruleList = Arrays.stream(v)
|
||||
.map(objects -> new Info(
|
||||
(boolean) objects[0],
|
||||
(String) objects[1],
|
||||
(String) objects[2],
|
||||
(String) objects[3],
|
||||
(String) objects[4],
|
||||
(String) objects[5],
|
||||
(String) objects[6],
|
||||
(String) objects[7],
|
||||
(boolean) objects[8]))
|
||||
.collect(Collectors.toList());
|
||||
ruleGroupList.add(new Group(k, ruleList));
|
||||
});
|
||||
|
||||
List<Map<String, Object>> outputGroupsMap = ruleGroupList.stream()
|
||||
.map(Group::getFields)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<String, Object> outputMap = new LinkedHashMap<>();
|
||||
outputMap.put("rules", outputGroupsMap);
|
||||
|
||||
File f = new File(configLoader.getRulesFilePath());
|
||||
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(f.toPath()), StandardCharsets.UTF_8)) {
|
||||
yaml.dump(outputMap, ws);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void changeRule(Vector data, int select, String type) {
|
||||
Config.globalRules.get(type)[select] = data.toArray();
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void addRule(Vector data, String type) {
|
||||
ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type)));
|
||||
x.add(data.toArray());
|
||||
Config.globalRules.put(type, x.toArray(new Object[x.size()][]));
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void removeRule(int select, String type) {
|
||||
ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type)));
|
||||
x.remove(select);
|
||||
Config.globalRules.put(type, x.toArray(new Object[x.size()][]));
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void renameRuleGroup(String oldName, String newName) {
|
||||
Config.globalRules.put(newName, Config.globalRules.remove(oldName));
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void deleteRuleGroup(String Rules) {
|
||||
Config.globalRules.remove(Rules);
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public String newRule() {
|
||||
int i = 0;
|
||||
String name = "New ";
|
||||
|
||||
while (Config.globalRules.containsKey(name + i)) {
|
||||
i++;
|
||||
}
|
||||
|
||||
Config.globalRules.put(name + i, Config.ruleTemplate);
|
||||
this.rulesFormatAndSave();
|
||||
return name + i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
29
src/main/java/hae/utils/rule/model/Group.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package hae.utils.rule.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Group {
|
||||
private Map<String, Object> fields;
|
||||
|
||||
public Group(String groupName, List<Info> rules) {
|
||||
List<Map<String, Object>> ruleList = new ArrayList<>();
|
||||
for (Info rule : rules) {
|
||||
ruleList.add(rule.getFields());
|
||||
}
|
||||
|
||||
fields = new LinkedHashMap<>();
|
||||
fields.put("group", groupName);
|
||||
fields.put("rule", ruleList);
|
||||
}
|
||||
|
||||
public Map<String, Object> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void loadFields(Map<String, Object> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
}
|
||||
29
src/main/java/hae/utils/rule/model/Info.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package hae.utils.rule.model;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Info {
|
||||
private Map<String, Object> fields;
|
||||
|
||||
public Info(boolean loaded, String name, String f_regex, String s_regex, String format, String color, String scope, String engine, boolean sensitive) {
|
||||
fields = new LinkedHashMap<>();
|
||||
fields.put("name", name);
|
||||
fields.put("loaded", loaded);
|
||||
fields.put("f_regex", f_regex);
|
||||
fields.put("s_regex", s_regex);
|
||||
fields.put("format", format);
|
||||
fields.put("color", color);
|
||||
fields.put("scope", scope);
|
||||
fields.put("engine", engine);
|
||||
fields.put("sensitive", sensitive);
|
||||
}
|
||||
|
||||
public Map<String, Object> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void loadFields(Map<String, Object> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
}
|
||||
28
src/main/java/hae/utils/string/HashCalculator.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package hae.utils.string;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class HashCalculator {
|
||||
public static String calculateHash(byte[] bytes) {
|
||||
MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("MD5");
|
||||
byte[] hashBytes = digest.digest(bytes);
|
||||
return bytesToHex(hashBytes);
|
||||
} catch (Exception ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
}
|
||||
145
src/main/java/hae/utils/string/StringProcessor.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package hae.utils.string;
|
||||
|
||||
import burp.api.montoya.core.ByteArray;
|
||||
import burp.api.montoya.http.HttpService;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class StringProcessor {
|
||||
public static String replaceFirstOccurrence(String original, String find, String replace) {
|
||||
int index = original.indexOf(find);
|
||||
if (index != -1) {
|
||||
return original.substring(0, index) + replace + original.substring(index + find.length());
|
||||
}
|
||||
return original;
|
||||
}
|
||||
|
||||
public static boolean matchFromEnd(String input, String pattern) {
|
||||
int inputLength = input.length();
|
||||
int patternLength = pattern.length();
|
||||
|
||||
int inputIndex = inputLength - 1;
|
||||
int patternIndex = patternLength - 1;
|
||||
|
||||
while (inputIndex >= 0 && patternIndex >= 0) {
|
||||
if (input.charAt(inputIndex) != pattern.charAt(patternIndex)) {
|
||||
return false;
|
||||
}
|
||||
inputIndex--;
|
||||
patternIndex--;
|
||||
}
|
||||
|
||||
// 如果patternIndex为-1,表示pattern字符串已经完全匹配
|
||||
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) {
|
||||
if (!comment.contains(",")) {
|
||||
return comment;
|
||||
}
|
||||
|
||||
Map<String, Integer> itemCounts = getStringIntegerMap(comment);
|
||||
|
||||
StringBuilder mergedItems = new StringBuilder();
|
||||
|
||||
for (Map.Entry<String, Integer> entry : itemCounts.entrySet()) {
|
||||
String itemName = entry.getKey();
|
||||
int count = entry.getValue();
|
||||
if (count != 0) {
|
||||
mergedItems.append(itemName).append(" (").append(count).append("), ");
|
||||
}
|
||||
}
|
||||
|
||||
return mergedItems.substring(0, mergedItems.length() - 2);
|
||||
}
|
||||
|
||||
public static String getHostByUrl(String url) {
|
||||
String host = "";
|
||||
|
||||
try {
|
||||
URL u = new URL(url);
|
||||
int port = u.getPort();
|
||||
if (port == -1) {
|
||||
host = u.getHost();
|
||||
} else {
|
||||
host = String.format("%s:%s", u.getHost(), port);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
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) {
|
||||
Map<String, Integer> itemCounts = new HashMap<>();
|
||||
String[] items = comment.split(", ");
|
||||
|
||||
for (String item : items) {
|
||||
if (item.contains("(") && item.contains(")")) {
|
||||
int openParenIndex = item.lastIndexOf("(");
|
||||
int closeParenIndex = item.lastIndexOf(")");
|
||||
String itemName = item.substring(0, openParenIndex).trim();
|
||||
int count = Integer.parseInt(item.substring(openParenIndex + 1, closeParenIndex).trim());
|
||||
itemCounts.put(itemName, itemCounts.getOrDefault(itemName, 0) + count);
|
||||
} else {
|
||||
itemCounts.put(item, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return itemCounts;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/main/resources/logo/logo.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/main/resources/logo/logo_black.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
303
src/main/resources/rules/Rules.yml
Normal 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-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|')
|
||||
s_regex: ''
|
||||
format: '{0}'
|
||||
color: gray
|
||||
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
|
||||