Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e83a6a1478 | ||
|
|
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 | ||
|
|
3fc1869a7b | ||
|
|
43fbc46b65 | ||
|
|
00a4a835b2 | ||
|
|
f872dadf46 | ||
|
|
15bbb9f1a0 | ||
|
|
89f3f6cf09 | ||
|
|
cf9f434ff8 | ||
|
|
37a907d6df | ||
|
|
83e5da2f7e | ||
|
|
e43a96b8ad | ||
|
|
a7112ad297 | ||
|
|
efc7a2d7e3 | ||
|
|
0cfebb8464 | ||
|
|
2b4e56ea8c | ||
|
|
4bdb576448 | ||
|
|
e7a7f823df | ||
|
|
a0d33f8c58 | ||
|
|
72511d9f14 | ||
|
|
2336a49023 | ||
|
|
d89bec65e3 | ||
|
|
2239ca1e90 | ||
|
|
27fbf465b7 | ||
|
|
2879a1494c | ||
|
|
d9e0bf4714 | ||
|
|
170ed412b3 | ||
|
|
91009337c1 | ||
|
|
a0127d75a9 | ||
|
|
2ce57f8ee1 | ||
|
|
7b5027a528 | ||
|
|
df0f4bd41b | ||
|
|
d0a61ba9ac | ||
|
|
c632782bc6 | ||
|
|
503fea6f55 | ||
|
|
db1f8b9cc9 | ||
|
|
1e22b48001 | ||
|
|
9cb5c93fd7 | ||
|
|
8f18079ea6 | ||
|
|
ad8ebefb63 | ||
|
|
570fc1ed54 | ||
|
|
edeb263712 | ||
|
|
78628b03d1 | ||
|
|
1df27ea121 | ||
|
|
94b3cee2ec | ||
|
|
6e5c60a738 | ||
|
|
91dc5f22f6 |
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<classpath>
|
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
|
||||||
<classpathentry kind="src" path="src"/>
|
|
||||||
<classpathentry kind="lib" path="lib/json.jar"/>
|
|
||||||
<classpathentry kind="output" path="bin"/>
|
|
||||||
</classpath>
|
|
||||||
30
.github/ISSUE_TEMPLATE/问题反馈.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
name: 问题反馈
|
||||||
|
about: 尽可能详细的描述问题并反馈
|
||||||
|
title: "[BUG] 问题标题"
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用环境
|
||||||
|
|
||||||
|
```
|
||||||
|
HaE 版本:
|
||||||
|
有无自定义规则:
|
||||||
|
BurpSuite 版本:
|
||||||
|
操作系统版本:
|
||||||
|
是否阅读README:
|
||||||
|
是否知晓注意事项:
|
||||||
|
是否查阅历史ISSUE:
|
||||||
|
```
|
||||||
|
|
||||||
|
## 问题详情
|
||||||
|
|
||||||
|
问题描述:
|
||||||
|
|
||||||
|
出现的场景:
|
||||||
|
|
||||||
|
## 解决建议
|
||||||
|
|
||||||
|
无。
|
||||||
137
.gitignore
vendored
Normal file
@@ -0,0 +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
|
||||||
|
.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
|
||||||
17
.project
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<projectDescription>
|
|
||||||
<name>HaE</name>
|
|
||||||
<comment></comment>
|
|
||||||
<projects>
|
|
||||||
</projects>
|
|
||||||
<buildSpec>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
|
||||||
<natures>
|
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
</natures>
|
|
||||||
</projectDescription>
|
|
||||||
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.
|
||||||
141
README.md
@@ -1,103 +1,88 @@
|
|||||||
# 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**是基于 `BurpSuite` 插件 `JavaAPI` 开发的请求高亮标记与信息提取的辅助型插件。
|
**HaE**是一款**网络安全(数据安全)领域**下的框架式项目,采用了**乐高积木式**模块化设计理念,实现对HTTP消息(包含WebSocket)精细化的标记和提取。
|
||||||
|
|
||||||

|
通过运用**多引擎**的自定义正则表达式,HaE能够准确匹配并处理HTTP请求与响应报文(包含WebSocket),对匹配成功的内容进行有效的标记和信息抽取,从而提升网络安全(数据安全)领域下的**漏洞和数据分析效率**。
|
||||||
|
|
||||||
该插件可以通过自定义正则的方式匹配**响应报文**,可以自行决定符合该自定义正则匹配的相应请求是否需要高亮标记、信息提取。
|
> 随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
|
||||||
|
|
||||||
**注**:`HaE`的使用,对测试人员来说需要基本的正则表达式基础,由于`Java`正则表达式的库并没有`Python`的优雅或方便,在使用正则的,HaE要求使用者必须使用`()`将所需提取的表达式内容包含;例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,如果你要提取这段内容的话就需要变成`(rememberMe=delete)`。
|
GitHub项目地址:https://github.com/gh0stkey/HaE
|
||||||
|
|
||||||
|
GitCode项目地址:https://gitcode.com/gh0stkey/HaE
|
||||||
|
|
||||||
|
**所获荣誉**:
|
||||||
|
|
||||||
|
1. [入选2022年KCon兵器谱](https://mp.weixin.qq.com/s/JohMsl1WD29LHCHuLf8mVQ)
|
||||||
|
2. [入选GitCode G-Star项目](https://gitcode.com/gh0stkey/HaE)
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
|
||||||
|
1. HaE 3.0版本开始采用`Montoya API`进行开发,使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。
|
||||||
|
2. HaE 2.6版本后对规则字段进行了更新,因此无法适配<=2.6版本的规则,请用户自行前往[规则转换页面](https://gh0st.cn/HaE/ConversionRule.html)进行转换。
|
||||||
|
3. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
插件装载:`Extender - Extensions - Add - Select File - Next`
|
插件装载: `Extender - Extensions - Add - Select File - Next`
|
||||||
|
|
||||||
初次装载`HaE`会初始化配置文件,默认配置文件内置一个正则:`Email`,初始化的配置文件会放在与`BurpSuite Jar`包同级目录下。
|
初次装载`HaE`会从Jar包中加载离线的规则库,如果更新可以点击`Reinit`进行重新初始化。内置规则库地址可以在Github上找到:`https://github.com/gh0stkey/HaE/blob/master/src/main/resources/rules/Rules.yml`。
|
||||||
|
|
||||||

|
配置文件(`Config.yml`)和规则文件(`Rules.yml`)会放在固定目录下:
|
||||||
|
|
||||||
除了初始化的配置文件外,还有`init.hae`,该文件用于存储配置文件路径;`HaE`支持自定义配置文件路径,你可以通过点击`Select File`按钮进行选择自定义配置文件。
|
1. Linux/Mac用户的配置文件目录:`~/.config/HaE/`
|
||||||
|
2. Windows用户的配置文件目录:`%USERPROFILE%/.config/HaE/`
|
||||||
|
|
||||||

|
除此之外,您也可以选择将配置文件存放在`HaE Jar包`的同级目录下的`/.config/HaE/`中,**以便于离线携带**。
|
||||||
|
|
||||||
HaE支持三个动作:
|
### 规则释义
|
||||||
|
|
||||||
1. 重载规则(Reload):当你不使用HaE UI界面去修改配置文件内的规则时,而是直接基于配置文件进行修改规则时可使用;
|
HaE目前的规则一共有8个字段,详细的含义如下所示:
|
||||||
2. 新建规则(New):新建规则会自动添加一行表格数据,单击或双击进行修改数据即可自动保存;
|
|
||||||
3. 删除规则(Delete):单击选中某条规则时,按下该按钮即可删除规则。
|
|
||||||
|
|
||||||
**注**:HaE的操作都是基于表单UI的方式,操作即会自动保存。
|
| 字段 | 含义 |
|
||||||
|
|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| Name | 规则名称,主要用于简短概括当前规则的作用。 |
|
||||||
|
| F-Regex | 规则正则,主要用于填写正则表达式。在HaE中所需提取匹配的内容需要用`(`、`)`将正则表达式进行包裹。|
|
||||||
|
| S-Regex | 规则正则,作用及使用同F-Regex。S-Regex为二次正则,可以用于对F-Regex匹配的数据结果进行二次的匹配提取,如不需要的情况下可以留空。|
|
||||||
|
| Format | 格式化输出,在NFA引擎的正则表达式中,我们可以通过`{0}`、`{1}`、`{2}`…的方式进行取分组格式化输出。默认情况下使用`{0}`即可。 |
|
||||||
|
| Scope | 规则作用域,主要用于表示当前规则作用于HTTP报文的哪个部分。支持请求、响应的行、头、体,以及完整的报文。 |
|
||||||
|
| Engine | 正则引擎,主要用于表示当前规则的正则表达式所使用的引擎。**DFA引擎**:对于文本串里的每一个字符只需扫描一次,速度快、特性少;**NFA引擎**:要翻来覆去标注字符、取消标注字符,速度慢,但是特性(如:分组、替换、分割)丰富。 |
|
||||||
|
| Color | 规则匹配颜色,主要用于表示当前规则匹配到对应HTTP报文时所需标记的高亮颜色。在HaE中具备颜色升级算法,当出现相同颜色时会自动向上升级一个颜色进行标记。 |
|
||||||
|
| Sensitive | 规则敏感性,主要用于表示当前规则对于大小写字母是否敏感,敏感(`True`)则严格按照大小写要求匹配,不敏感(`False`)则反之。 |
|
||||||
|
|
||||||
## 插件优点
|
## 优势特点
|
||||||
|
|
||||||
1. 多选项自定义控制适配需求;
|
1. **功能**:通过对HTTP报文的颜色高亮、注释和提取,帮助使用者获取有意义的信息,**聚焦高价值报文**。
|
||||||
2. 多颜色高亮分类,将BurpSuite的所有高亮颜色集成:`red, orange, yellow, green, cyan, blue, pink, magenta, gray`;
|
2. **界面**:清晰可视的界面设计,以及**简洁的界面交互**,帮助使用者更轻松的了解和配置项目,**避免`多按钮`式的复杂体验**。
|
||||||
3. 颜色升级算法:利用下标的方式进行优先级排序,当满足2个同颜色条件则以优先级顺序上升颜色。(例如:**两个正则,颜色为橘黄色,该请求两个正则都匹配到了,那么将升级为红色**)
|
3. **查询**:将HTTP报文的高亮、注释和提取到的相关信息**集中在一个数据面板**,可以一键查询、提取信息,从而提高测试和梳理效率。
|
||||||
4. 简单的配置文件格式选用JSON格式,格式为
|
4. **算法**:内置高亮颜色的升级算法,当出现相同颜色时**会自动向上升级一个颜色**进行标记,**避免`屠龙者终成恶龙`场景**。
|
||||||
```
|
5. **管理**:**融入BurpSuite的项目数据管理**,当使用BurpSuite进行项目存储时HaE数据也会一并存储。
|
||||||
{name: {"loaded": isLoaded:,"regex": regexText, "highlight": isHighlight, "extract": isExtract, "color": colorText}}
|
6. **实战**:官方规则库和规则字段作用功能,都是**基于实战化场景总结输出**的,**以此提高数据的有效性、精准性发现**。
|
||||||
```
|
|
||||||
5. 内置简单缓存,在“多正则、大数据”的场景下减少卡顿现象。
|
|
||||||
|
|
||||||
## 实际使用
|
| 界面名称 | 界面展示 |
|
||||||
|
| ------------------------ | ---------------------------------------------------- |
|
||||||
|
| 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%" /> |
|
||||||
|
|
||||||
使用 RGPerson 生成测试数据,放入网站根目录文件中:
|
## 支持项目
|
||||||
|
|
||||||

|
如果你觉得HaE好用,可以打赏一下作者,给作者持续更新下去的动力!
|
||||||
|
|
||||||
访问该地址,在`Proxy - HTTP History`中可以看见高亮请求,响应标签页中含有`MarkINFO`标签,其中将匹配到的信息提取了出来。
|
<div align=center>
|
||||||
|
<img src="images/reward.jpeg" style="width: 30%" />
|
||||||
|
</div>
|
||||||
|
|
||||||

|
## 404StarLink 2.0 - Galaxy
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 正则优化
|
`HaE` 是 404Team [星链计划2.0](https://github.com/knownsec/404StarLink2.0-Galaxy) 中的一环,如果对 `HaE` 有任何疑问又或是想要找小伙伴交流,可以参考星链计划的加群方式。
|
||||||
|
|
||||||
有些正则在实战应用场景中并不理想
|
|
||||||
|
|
||||||
在正则匹配手机号、身份证号码的时候(纯数字类)会存在一些误报(这里匹配身份证号码无法进行校验,误报率很高),但手机号处理这一块可以解决:
|
|
||||||
|
|
||||||
原正则:
|
|
||||||
|
|
||||||
```
|
|
||||||
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
|
|
||||||
|
|
||||||
### 收录正则列表
|
|
||||||
|
|
||||||
身份证号码(来自:https://github.com/gh0stkey/HaE/issues/3):
|
|
||||||
|
|
||||||
```
|
|
||||||
[^0-9]([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])[^0-9]
|
|
||||||
```
|
|
||||||
|
|
||||||
邮箱地址:
|
|
||||||
|
|
||||||
```
|
|
||||||
([\w-]+(?:\.[\w-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
- [https://github.com/knownsec/404StarLink2.0-Galaxy#community](https://github.com/knownsec/404StarLink2.0-Galaxy#community)
|
||||||
|
|||||||
37
build.gradle
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
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: 214 KiB |
|
Before Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 223 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 |
BIN
lib/json.jar
2
settings.gradle
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
rootProject.name = 'HaE'
|
||||||
|
|
||||||
@@ -1,559 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Component;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.json.*;
|
|
||||||
|
|
||||||
import javax.swing.JFrame;
|
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
import javax.swing.DefaultCellEditor;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JTable;
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
import javax.swing.table.DefaultTableModel;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import javax.swing.JCheckBox;
|
|
||||||
import javax.swing.JComboBox;
|
|
||||||
import javax.swing.JFileChooser;
|
|
||||||
import javax.swing.border.EtchedBorder;
|
|
||||||
import javax.swing.border.TitledBorder;
|
|
||||||
import javax.swing.event.TableModelEvent;
|
|
||||||
import javax.swing.event.TableModelListener;
|
|
||||||
import javax.swing.SwingConstants;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEditorTabFactory, ITab {
|
|
||||||
|
|
||||||
private JFrame frame;
|
|
||||||
private JPanel panel;
|
|
||||||
private JTable table;
|
|
||||||
private JTextField textField;
|
|
||||||
private IBurpExtenderCallbacks callbacks;
|
|
||||||
private static String configFilePath = "config.json";
|
|
||||||
private static String initFilePath = "init.hae";
|
|
||||||
private static String initConfigContent = "{\"Email\":{\"loaded\":true,\"highlight\":true,\"regex\":\"([\\\\w-]+(?:\\\\.[\\\\w-]+)*@(?:[\\\\w](?:[\\\\w-]*[\\\\w])?\\\\.)+[\\\\w](?:[\\\\w-]*[\\\\w])?)\",\"extract\":true,\"color\":\"yellow\"}}";
|
|
||||||
private static String endColor = "";
|
|
||||||
private static String[] colorArray = new String[] {"red", "orange", "yellow", "green", "cyan", "blue", "pink", "magenta", "gray"};
|
|
||||||
private static IMessageEditorTab HaETab;
|
|
||||||
private static PrintWriter stdout;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks)
|
|
||||||
{
|
|
||||||
this.callbacks = callbacks;
|
|
||||||
// 设置插件名字
|
|
||||||
callbacks.setExtensionName("HaE - Highlighter and Extractor");
|
|
||||||
|
|
||||||
// 定义输出
|
|
||||||
stdout = new PrintWriter(callbacks.getStdout(), true);
|
|
||||||
stdout.println("@Author: EvilChen");
|
|
||||||
|
|
||||||
// UI
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// 判断"config.json"文件是否具备内容,如若不具备则进行初始化
|
|
||||||
if (configFilePath.equals("config.json")) {
|
|
||||||
if (readFileContent(configFilePath).equals("")) {
|
|
||||||
writeFileContent(configFilePath, initConfigContent);
|
|
||||||
writeFileContent(initFilePath, configFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 判断配置文件是否存在
|
|
||||||
if (fileExists(configFilePath)) {
|
|
||||||
configFilePath = readFileContent(initFilePath);
|
|
||||||
} else {
|
|
||||||
JOptionPane.showMessageDialog(null, "Config File Not Found!", "Error", JOptionPane.ERROR_MESSAGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize();
|
|
||||||
fillTable();
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
callbacks.registerHttpListener(BurpExtender.this);
|
|
||||||
callbacks.registerMessageEditorTabFactory(BurpExtender.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialize() {
|
|
||||||
frame = new JFrame();
|
|
||||||
frame.setBounds(100, 100, 526, 403);
|
|
||||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
|
||||||
|
|
||||||
panel = new JPanel();
|
|
||||||
frame.getContentPane().add(panel, BorderLayout.CENTER);
|
|
||||||
panel.setLayout(new BorderLayout(0, 0));
|
|
||||||
|
|
||||||
JPanel panel_3 = new JPanel();
|
|
||||||
panel.add(panel_3, BorderLayout.NORTH);
|
|
||||||
|
|
||||||
JLabel lblNewLabel_1 = new JLabel("Config File:");
|
|
||||||
panel_3.add(lblNewLabel_1);
|
|
||||||
|
|
||||||
textField = new JTextField();
|
|
||||||
textField.setEditable(false);
|
|
||||||
panel_3.add(textField);
|
|
||||||
textField.setColumns(20);
|
|
||||||
|
|
||||||
textField.setText(configFilePath);
|
|
||||||
|
|
||||||
JButton btnNewButton = new JButton("Select File ...");
|
|
||||||
btnNewButton.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
JFileChooser jfc = new JFileChooser();
|
|
||||||
jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
|
|
||||||
jfc.showDialog(new JLabel(), "Choose");
|
|
||||||
File file = jfc.getSelectedFile();
|
|
||||||
textField.setText(file.getAbsolutePath());
|
|
||||||
configFilePath = textField.getText();
|
|
||||||
writeFileContent(initFilePath, configFilePath);
|
|
||||||
fillTable();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
panel_3.add(btnNewButton);
|
|
||||||
|
|
||||||
JPanel panel_2 = new JPanel();
|
|
||||||
panel.add(panel_2, BorderLayout.CENTER);
|
|
||||||
panel_2.setLayout(new BorderLayout(0, 0));
|
|
||||||
|
|
||||||
JPanel panel_1 = new JPanel();
|
|
||||||
panel_2.add(panel_1, BorderLayout.NORTH);
|
|
||||||
panel_1.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null), "Actions", TitledBorder.LEADING, TitledBorder.TOP, null, new Color(0, 0, 0)));
|
|
||||||
|
|
||||||
JButton btnReloadRule = new JButton("Reload Rule");
|
|
||||||
btnReloadRule.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
fillTable();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
panel_1.add(btnReloadRule);
|
|
||||||
|
|
||||||
JButton btnNewRule = new JButton("New Rule");
|
|
||||||
btnNewRule.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent arg0) {
|
|
||||||
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
|
|
||||||
Vector rules = new Vector();
|
|
||||||
rules.add(true);
|
|
||||||
rules.add("New Rule");
|
|
||||||
rules.add("New Regex");
|
|
||||||
rules.add("red");
|
|
||||||
rules.add(true);
|
|
||||||
rules.add(true);
|
|
||||||
dtm.addRow(rules);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
panel_1.add(btnNewRule);
|
|
||||||
|
|
||||||
JButton btnDeleteRule = new JButton("Delete Rule");
|
|
||||||
btnDeleteRule.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
int selectRows = table.getSelectedRows().length;
|
|
||||||
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
|
|
||||||
if (selectRows == 1) {
|
|
||||||
int selectedRowIndex = table.getSelectedRow();
|
|
||||||
// 在配置文件中删除数据
|
|
||||||
String cellValue = (String) dtm.getValueAt(selectedRowIndex, 1);
|
|
||||||
// System.out.println(cellValue);
|
|
||||||
removeConfig(cellValue);
|
|
||||||
// 在表格中删除数据
|
|
||||||
dtm.removeRow(selectedRowIndex);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
panel_1.add(btnDeleteRule);
|
|
||||||
|
|
||||||
JScrollPane scrollPane = new JScrollPane();
|
|
||||||
panel_2.add(scrollPane, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
table = new JTable();
|
|
||||||
table.setModel(new DefaultTableModel(
|
|
||||||
new Object[][] {
|
|
||||||
},
|
|
||||||
new String[] {
|
|
||||||
"Loaded", "Name", "Regex", "Color", "isExtract", "isHighlight"
|
|
||||||
}
|
|
||||||
));
|
|
||||||
scrollPane.setViewportView(table);
|
|
||||||
|
|
||||||
table.getColumnModel().getColumn(2).setPreferredWidth(172);
|
|
||||||
table.getColumnModel().getColumn(3).setCellEditor(new DefaultCellEditor(new JComboBox(colorArray)));
|
|
||||||
table.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new JCheckBox()));
|
|
||||||
table.getColumnModel().getColumn(4).setCellEditor(new DefaultCellEditor(new JCheckBox()));
|
|
||||||
table.getColumnModel().getColumn(5).setCellEditor(new DefaultCellEditor(new JCheckBox()));
|
|
||||||
|
|
||||||
JLabel lblNewLabel = new JLabel("@EvilChen Love YuChen.");
|
|
||||||
lblNewLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
|
||||||
panel.add(lblNewLabel, BorderLayout.SOUTH);
|
|
||||||
|
|
||||||
table.getModel().addTableModelListener(
|
|
||||||
new TableModelListener() {
|
|
||||||
@Override
|
|
||||||
public void tableChanged(TableModelEvent e) {
|
|
||||||
if (e.getType() == TableModelEvent.INSERT || e.getType() == TableModelEvent.UPDATE) {
|
|
||||||
DefaultTableModel dtm = (DefaultTableModel) table.getModel();
|
|
||||||
int rows = dtm.getRowCount();
|
|
||||||
JSONObject jsonObj = new JSONObject();
|
|
||||||
|
|
||||||
for (int i = 0; i < rows; i++) {
|
|
||||||
JSONObject jsonObj1 = new JSONObject();
|
|
||||||
jsonObj1.put("loaded", (boolean) dtm.getValueAt(i, 0));
|
|
||||||
jsonObj1.put("regex", (String) dtm.getValueAt(i, 2));
|
|
||||||
jsonObj1.put("color", (String) dtm.getValueAt(i, 3));
|
|
||||||
jsonObj1.put("extract", (boolean) dtm.getValueAt(i, 4));
|
|
||||||
jsonObj1.put("highlight", (boolean) dtm.getValueAt(i, 5));
|
|
||||||
// 添加数据
|
|
||||||
jsonObj.put((String) dtm.getValueAt(i, 1), jsonObj1);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileContent(configFilePath, jsonObj.toString());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
callbacks.customizeUiComponent(panel);
|
|
||||||
callbacks.customizeUiComponent(panel_1);
|
|
||||||
callbacks.customizeUiComponent(panel_2);
|
|
||||||
callbacks.customizeUiComponent(panel_3);
|
|
||||||
callbacks.customizeUiComponent(scrollPane);
|
|
||||||
callbacks.addSuiteTab(BurpExtender.this);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IMessageEditorTab createNewInstance(IMessageEditorController controller, boolean editable) {
|
|
||||||
HaETab = new MarkInfoTab(controller, editable);
|
|
||||||
return HaETab;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getTabCaption() {
|
|
||||||
return "HaE";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Component getUiComponent() {
|
|
||||||
return panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 使用processHttpMessage用来做Highlighter
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
|
|
||||||
// 判断是否是响应,且该代码作用域为:REPEATER、INTRUDER、PROXY(分别对应toolFlag 64、32、4)
|
|
||||||
if (!messageIsRequest && (toolFlag == 64 || toolFlag == 32 || toolFlag == 4)) {
|
|
||||||
byte[] content = messageInfo.getResponse();
|
|
||||||
JSONObject jsonObj = matchRegex(content);
|
|
||||||
if (jsonObj.length() > 0) {
|
|
||||||
List<String> colorList = new ArrayList<String>();
|
|
||||||
Iterator<String> k = jsonObj.keys();
|
|
||||||
while (k.hasNext()) {
|
|
||||||
String name = k.next();
|
|
||||||
JSONObject jsonObj2 = new JSONObject(jsonObj.get(name).toString());
|
|
||||||
boolean isHighlight = jsonObj2.getBoolean("highlight");
|
|
||||||
boolean isLoaded = jsonObj2.getBoolean("loaded");
|
|
||||||
if (isHighlight && isLoaded) {
|
|
||||||
colorList.add(jsonObj2.getString("color"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (colorList.size() != 0) {
|
|
||||||
colorUpgrade(getColorKeys(colorList));
|
|
||||||
String color = endColor;
|
|
||||||
messageInfo.setHighlight(color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MarkInfoTab implements IMessageEditorTab {
|
|
||||||
private ITextEditor markInfoText;
|
|
||||||
private byte[] currentMessage;
|
|
||||||
|
|
||||||
public MarkInfoTab(IMessageEditorController controller, boolean editable) {
|
|
||||||
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) {
|
|
||||||
// 这里需要过一次正则匹配决定是否开启Tab
|
|
||||||
if (!isRequest && matchRegex(content).length() != 0) {
|
|
||||||
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) {
|
|
||||||
if (content.length > 0 && !isRequest) {
|
|
||||||
String result = "";
|
|
||||||
JSONObject jsonObj = matchRegex(content);
|
|
||||||
if (jsonObj.length() != 0) {
|
|
||||||
Iterator<String> k = jsonObj.keys();
|
|
||||||
while (k.hasNext()) {
|
|
||||||
String name = k.next();
|
|
||||||
JSONObject jsonObj1 = new JSONObject(jsonObj.get(name).toString());
|
|
||||||
boolean isExtract = jsonObj1.getBoolean("extract");
|
|
||||||
boolean isLoaded = jsonObj1.getBoolean("loaded");
|
|
||||||
if (isExtract && isLoaded) {
|
|
||||||
String tmpStr = String.format("[%s] %s \n", name, jsonObj1.getString("data")).intern();
|
|
||||||
result += tmpStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
markInfoText.setText(result.getBytes());
|
|
||||||
}
|
|
||||||
currentMessage = content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSONObject matchRegex(byte[] content) {
|
|
||||||
JSONObject tabContent = new JSONObject();
|
|
||||||
// 正则匹配提取内容
|
|
||||||
try {
|
|
||||||
String jsonStr = readFileContent(configFilePath);
|
|
||||||
JSONObject jsonObj = new JSONObject(jsonStr);
|
|
||||||
Iterator<String> k = jsonObj.keys();
|
|
||||||
// 遍历json数组
|
|
||||||
while (k.hasNext()) {
|
|
||||||
String contentString = new String(content, "UTF-8").intern();
|
|
||||||
String name = k.next();
|
|
||||||
JSONObject jsonObj1 = new JSONObject(jsonObj.get(name).toString());
|
|
||||||
JSONObject jsonData = new JSONObject();
|
|
||||||
String regex = jsonObj1.getString("regex");
|
|
||||||
boolean isHighligth = jsonObj1.getBoolean("highlight");
|
|
||||||
boolean isExtract = jsonObj1.getBoolean("extract");
|
|
||||||
boolean isLoaded = jsonObj1.getBoolean("loaded");
|
|
||||||
String color = jsonObj1.getString("color");
|
|
||||||
List<String> result = new ArrayList<String>();
|
|
||||||
|
|
||||||
Pattern pattern = Pattern.compile(regex);
|
|
||||||
Matcher matcher = pattern.matcher(contentString);
|
|
||||||
while (matcher.find()) {
|
|
||||||
// 添加匹配数据至list
|
|
||||||
// 强制用户使用()包裹正则
|
|
||||||
result.add(matcher.group(1));
|
|
||||||
}
|
|
||||||
// 去除重复内容
|
|
||||||
HashSet tmpList = new HashSet(result);
|
|
||||||
result.clear();
|
|
||||||
result.addAll(tmpList);
|
|
||||||
|
|
||||||
if (!result.isEmpty()) {
|
|
||||||
jsonData.put("highlight", isHighligth);
|
|
||||||
jsonData.put("extract", isExtract);
|
|
||||||
jsonData.put("color", color);
|
|
||||||
jsonData.put("data", String.join(",", result));
|
|
||||||
jsonData.put("loaded", isLoaded);
|
|
||||||
// 初始化格式
|
|
||||||
tabContent.put(name, jsonData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tabContent;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new JSONObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 颜色下标获取
|
|
||||||
*/
|
|
||||||
private List<Integer> getColorKeys(List<String> keys){
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 颜色升级递归算法
|
|
||||||
*/
|
|
||||||
private static String colorUpgrade(List<Integer> colorList) {
|
|
||||||
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)) {
|
|
||||||
endColor = colorArray[0];
|
|
||||||
} else {
|
|
||||||
endColor = colorArray[stack.get(0)];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
colorUpgrade(stack);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 判断文件是否存在
|
|
||||||
*/
|
|
||||||
private Boolean fileExists(String fileName) {
|
|
||||||
File file = new File(fileName);
|
|
||||||
if(file.exists()){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* 获取文件内容
|
|
||||||
*/
|
|
||||||
private String readFileContent(String fileName) {
|
|
||||||
File file = new File(fileName);
|
|
||||||
BufferedReader reader = null;
|
|
||||||
StringBuffer sbf = new StringBuffer();
|
|
||||||
try {
|
|
||||||
reader = new BufferedReader(new FileReader(file));
|
|
||||||
String tempStr;
|
|
||||||
while ((tempStr = reader.readLine()) != null) {
|
|
||||||
sbf.append(tempStr);
|
|
||||||
}
|
|
||||||
reader.close();
|
|
||||||
return sbf.toString();
|
|
||||||
} catch (IOException e) {
|
|
||||||
} finally {
|
|
||||||
if (reader != null) {
|
|
||||||
try {
|
|
||||||
reader.close();
|
|
||||||
} catch (IOException err) {
|
|
||||||
err.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sbf.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 写入文件内容
|
|
||||||
*/
|
|
||||||
private boolean writeFileContent(String fileName, String fileContent) {
|
|
||||||
try {
|
|
||||||
BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
|
|
||||||
out.write(fileContent);
|
|
||||||
out.close();
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
stdout.println(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 删除单条配置内容
|
|
||||||
*/
|
|
||||||
private void removeConfig(String key) {
|
|
||||||
String jsonStr = readFileContent(configFilePath);
|
|
||||||
JSONObject jsonObj = new JSONObject(jsonStr);
|
|
||||||
jsonObj.remove(key);
|
|
||||||
if (writeFileContent(configFilePath, jsonObj.toString())) {
|
|
||||||
JOptionPane.showMessageDialog(null, "Delete Successfully!", "Info", JOptionPane.INFORMATION_MESSAGE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 初始化表格内容
|
|
||||||
*/
|
|
||||||
private void fillTable() {
|
|
||||||
DefaultTableModel dtm=(DefaultTableModel) table.getModel();
|
|
||||||
dtm.setRowCount(0);
|
|
||||||
String jsonStr = readFileContent(configFilePath);
|
|
||||||
JSONObject jsonObj = new JSONObject(jsonStr);
|
|
||||||
Iterator<String> k = jsonObj.keys();
|
|
||||||
// 遍历json数组
|
|
||||||
while (k.hasNext()) {
|
|
||||||
String name = k.next();
|
|
||||||
JSONObject jsonObj1 = new JSONObject(jsonObj.get(name).toString());
|
|
||||||
boolean loaded = jsonObj1.getBoolean("loaded");
|
|
||||||
String regex = jsonObj1.getString("regex");
|
|
||||||
String color = jsonObj1.getString("color");
|
|
||||||
boolean isExtract = jsonObj1.getBoolean("extract");
|
|
||||||
boolean isHighlight = jsonObj1.getBoolean("highlight");
|
|
||||||
// 填充数据
|
|
||||||
Vector rules = new Vector();
|
|
||||||
rules.add(loaded);
|
|
||||||
rules.add(name);
|
|
||||||
rules.add(regex);
|
|
||||||
rules.add(color);
|
|
||||||
rules.add(isExtract);
|
|
||||||
rules.add(isHighlight);
|
|
||||||
dtm.addRow(rules);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IBurpCollaboratorClientContext.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface represents an instance of a Burp Collaborator client context,
|
|
||||||
* which can be used to generate Burp Collaborator payloads and poll the
|
|
||||||
* Collaborator server for any network interactions that result from using those
|
|
||||||
* payloads. Extensions can obtain new instances of this class by calling
|
|
||||||
* <code>IBurpExtenderCallbacks.createBurpCollaboratorClientContext()</code>.
|
|
||||||
* Note that each Burp Collaborator client context is tied to the Collaborator
|
|
||||||
* server configuration that was in place at the time the context was created.
|
|
||||||
*/
|
|
||||||
public interface IBurpCollaboratorClientContext
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to generate new Burp Collaborator payloads.
|
|
||||||
*
|
|
||||||
* @param includeCollaboratorServerLocation Specifies whether to include the
|
|
||||||
* Collaborator server location in the generated payload.
|
|
||||||
* @return The payload that was generated.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if Burp Collaborator is disabled
|
|
||||||
*/
|
|
||||||
String generatePayload(boolean includeCollaboratorServerLocation);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve all interactions received by the
|
|
||||||
* Collaborator server resulting from payloads that were generated for this
|
|
||||||
* context.
|
|
||||||
*
|
|
||||||
* @return The Collaborator interactions that have occurred resulting from
|
|
||||||
* payloads that were generated for this context.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if Burp Collaborator is disabled
|
|
||||||
*/
|
|
||||||
List<IBurpCollaboratorInteraction> fetchAllCollaboratorInteractions();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve interactions received by the Collaborator
|
|
||||||
* server resulting from a single payload that was generated for this
|
|
||||||
* context.
|
|
||||||
*
|
|
||||||
* @param payload The payload for which interactions will be retrieved.
|
|
||||||
* @return The Collaborator interactions that have occurred resulting from
|
|
||||||
* the given payload.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if Burp Collaborator is disabled
|
|
||||||
*/
|
|
||||||
List<IBurpCollaboratorInteraction> fetchCollaboratorInteractionsFor(String payload);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve all interactions made by Burp Infiltrator
|
|
||||||
* instrumentation resulting from payloads that were generated for this
|
|
||||||
* context.
|
|
||||||
*
|
|
||||||
* @return The interactions triggered by the Burp Infiltrator
|
|
||||||
* instrumentation that have occurred resulting from payloads that were
|
|
||||||
* generated for this context.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if Burp Collaborator is disabled
|
|
||||||
*/
|
|
||||||
List<IBurpCollaboratorInteraction> fetchAllInfiltratorInteractions();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve interactions made by Burp Infiltrator
|
|
||||||
* instrumentation resulting from a single payload that was generated for
|
|
||||||
* this context.
|
|
||||||
*
|
|
||||||
* @param payload The payload for which interactions will be retrieved.
|
|
||||||
* @return The interactions triggered by the Burp Infiltrator
|
|
||||||
* instrumentation that have occurred resulting from the given payload.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if Burp Collaborator is disabled
|
|
||||||
*/
|
|
||||||
List<IBurpCollaboratorInteraction> fetchInfiltratorInteractionsFor(String payload);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the network location of the Collaborator
|
|
||||||
* server.
|
|
||||||
*
|
|
||||||
* @return The hostname or IP address of the Collaborator server.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException if Burp Collaborator is disabled
|
|
||||||
*/
|
|
||||||
String getCollaboratorServerLocation();
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IBurpCollaboratorInteraction.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface represents a network interaction that occurred with the Burp
|
|
||||||
* Collaborator server.
|
|
||||||
*/
|
|
||||||
public interface IBurpCollaboratorInteraction
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve a property of the interaction. Properties
|
|
||||||
* of all interactions are: interaction_id, type, client_ip, and time_stamp.
|
|
||||||
* Properties of DNS interactions are: query_type and raw_query. The
|
|
||||||
* raw_query value is Base64-encoded. Properties of HTTP interactions are:
|
|
||||||
* protocol, request, and response. The request and response values are
|
|
||||||
* Base64-encoded.
|
|
||||||
*
|
|
||||||
* @param name The name of the property to retrieve.
|
|
||||||
* @return A string representing the property value, or null if not present.
|
|
||||||
*/
|
|
||||||
String getProperty(String name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve a map containing all properties of the
|
|
||||||
* interaction.
|
|
||||||
*
|
|
||||||
* @return A map containing all properties of the interaction.
|
|
||||||
*/
|
|
||||||
Map<String, String> getProperties();
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IBurpExtender.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* All extensions must implement this interface.
|
|
||||||
*
|
|
||||||
* Implementations must be called BurpExtender, in the package burp, must be
|
|
||||||
* declared public, and must provide a default (public, no-argument)
|
|
||||||
* constructor.
|
|
||||||
*/
|
|
||||||
public interface IBurpExtender
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is invoked when the extension is loaded. It registers an
|
|
||||||
* instance of the
|
|
||||||
* <code>IBurpExtenderCallbacks</code> interface, providing methods that may
|
|
||||||
* be invoked by the extension to perform various actions.
|
|
||||||
*
|
|
||||||
* @param callbacks An
|
|
||||||
* <code>IBurpExtenderCallbacks</code> object.
|
|
||||||
*/
|
|
||||||
void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks);
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IContextMenuFactory.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerContextMenuFactory()</code> to register
|
|
||||||
* a factory for custom context menu items.
|
|
||||||
*/
|
|
||||||
public interface IContextMenuFactory
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method will be called by Burp when the user invokes a context menu
|
|
||||||
* anywhere within Burp. The factory can then provide any custom context
|
|
||||||
* menu items that should be displayed in the context menu, based on the
|
|
||||||
* details of the menu invocation.
|
|
||||||
*
|
|
||||||
* @param invocation An object that implements the
|
|
||||||
* <code>IContextMenuInvocation</code> interface, which the extension can
|
|
||||||
* query to obtain details of the context menu invocation.
|
|
||||||
* @return A list of custom menu items (which may include sub-menus,
|
|
||||||
* checkbox menu items, etc.) that should be displayed. Extensions may
|
|
||||||
* return
|
|
||||||
* <code>null</code> from this method, to indicate that no menu items are
|
|
||||||
* required.
|
|
||||||
*/
|
|
||||||
List<JMenuItem> createMenuItems(IContextMenuInvocation invocation);
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IContextMenuInvocation.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.awt.event.InputEvent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used when Burp calls into an extension-provided
|
|
||||||
* <code>IContextMenuFactory</code> with details of a context menu invocation.
|
|
||||||
* The custom context menu factory can query this interface to obtain details of
|
|
||||||
* the invocation event, in order to determine what menu items should be
|
|
||||||
* displayed.
|
|
||||||
*/
|
|
||||||
public interface IContextMenuInvocation
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in a request
|
|
||||||
* editor.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_MESSAGE_EDITOR_REQUEST = 0;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in a response
|
|
||||||
* editor.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_MESSAGE_EDITOR_RESPONSE = 1;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in a non-editable
|
|
||||||
* request viewer.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_MESSAGE_VIEWER_REQUEST = 2;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in a non-editable
|
|
||||||
* response viewer.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_MESSAGE_VIEWER_RESPONSE = 3;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in the Target
|
|
||||||
* site map tree.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_TARGET_SITE_MAP_TREE = 4;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in the Target
|
|
||||||
* site map table.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_TARGET_SITE_MAP_TABLE = 5;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in the Proxy
|
|
||||||
* history.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_PROXY_HISTORY = 6;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in the Scanner
|
|
||||||
* results.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_SCANNER_RESULTS = 7;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in the Intruder
|
|
||||||
* payload positions editor.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_INTRUDER_PAYLOAD_POSITIONS = 8;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in an Intruder
|
|
||||||
* attack results.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_INTRUDER_ATTACK_RESULTS = 9;
|
|
||||||
/**
|
|
||||||
* Used to indicate that the context menu is being invoked in a search
|
|
||||||
* results window.
|
|
||||||
*/
|
|
||||||
static final byte CONTEXT_SEARCH_RESULTS = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to retrieve the native Java input event that was
|
|
||||||
* the trigger for the context menu invocation.
|
|
||||||
*
|
|
||||||
* @return The <code>InputEvent</code> that was the trigger for the context
|
|
||||||
* menu invocation.
|
|
||||||
*/
|
|
||||||
InputEvent getInputEvent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to retrieve the Burp tool within which the
|
|
||||||
* context menu was invoked.
|
|
||||||
*
|
|
||||||
* @return A flag indicating the Burp tool within which the context menu was
|
|
||||||
* invoked. Burp tool flags are defined in the
|
|
||||||
* <code>IBurpExtenderCallbacks</code> interface.
|
|
||||||
*/
|
|
||||||
int getToolFlag();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to retrieve the context within which the menu was
|
|
||||||
* invoked.
|
|
||||||
*
|
|
||||||
* @return An index indicating the context within which the menu was
|
|
||||||
* invoked. The indices used are defined within this interface.
|
|
||||||
*/
|
|
||||||
byte getInvocationContext();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to retrieve the bounds of the user's selection
|
|
||||||
* into the current message, if applicable.
|
|
||||||
*
|
|
||||||
* @return An int[2] array containing the start and end offsets of the
|
|
||||||
* user's selection in the current message. If the user has not made any
|
|
||||||
* selection in the current message, both offsets indicate the position of
|
|
||||||
* the caret within the editor. If the menu is not being invoked from a
|
|
||||||
* message editor, the method returns <code>null</code>.
|
|
||||||
*/
|
|
||||||
int[] getSelectionBounds();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to retrieve details of the HTTP requests /
|
|
||||||
* responses that were shown or selected by the user when the context menu
|
|
||||||
* was invoked.
|
|
||||||
*
|
|
||||||
* <b>Note:</b> For performance reasons, the objects returned from this
|
|
||||||
* method are tied to the originating context of the messages within the
|
|
||||||
* Burp UI. For example, if a context menu is invoked on the Proxy intercept
|
|
||||||
* panel, then the
|
|
||||||
* <code>IHttpRequestResponse</code> returned by this method will reflect
|
|
||||||
* the current contents of the interception panel, and this will change when
|
|
||||||
* the current message has been forwarded or dropped. If your extension
|
|
||||||
* needs to store details of the message for which the context menu has been
|
|
||||||
* invoked, then you should query those details from the
|
|
||||||
* <code>IHttpRequestResponse</code> at the time of invocation, or you
|
|
||||||
* should use
|
|
||||||
* <code>IBurpExtenderCallbacks.saveBuffersToTempFiles()</code> to create a
|
|
||||||
* persistent read-only copy of the
|
|
||||||
* <code>IHttpRequestResponse</code>.
|
|
||||||
*
|
|
||||||
* @return An array of <code>IHttpRequestResponse</code> objects
|
|
||||||
* representing the items that were shown or selected by the user when the
|
|
||||||
* context menu was invoked. This method returns <code>null</code> if no
|
|
||||||
* messages are applicable to the invocation.
|
|
||||||
*/
|
|
||||||
IHttpRequestResponse[] getSelectedMessages();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to retrieve details of the Scanner issues that
|
|
||||||
* were selected by the user when the context menu was invoked.
|
|
||||||
*
|
|
||||||
* @return An array of <code>IScanIssue</code> objects representing the
|
|
||||||
* issues that were selected by the user when the context menu was invoked.
|
|
||||||
* This method returns <code>null</code> if no Scanner issues are applicable
|
|
||||||
* to the invocation.
|
|
||||||
*/
|
|
||||||
IScanIssue[] getSelectedIssues();
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)ICookie.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used to hold details about an HTTP cookie.
|
|
||||||
*/
|
|
||||||
public interface ICookie
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the domain for which the cookie is in
|
|
||||||
* scope.
|
|
||||||
*
|
|
||||||
* @return The domain for which the cookie is in scope. <b>Note:</b> For
|
|
||||||
* cookies that have been analyzed from responses (by calling
|
|
||||||
* <code>IExtensionHelpers.analyzeResponse()</code> and then
|
|
||||||
* <code>IResponseInfo.getCookies()</code>, the domain will be
|
|
||||||
* <code>null</code> if the response did not explicitly set a domain
|
|
||||||
* attribute for the cookie.
|
|
||||||
*/
|
|
||||||
String getDomain();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the path for which the cookie is in
|
|
||||||
* scope.
|
|
||||||
*
|
|
||||||
* @return The path for which the cookie is in scope or null if none is set.
|
|
||||||
*/
|
|
||||||
String getPath();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the expiration time for the cookie.
|
|
||||||
*
|
|
||||||
* @return The expiration time for the cookie, or
|
|
||||||
* <code>null</code> if none is set (i.e., for non-persistent session
|
|
||||||
* cookies).
|
|
||||||
*/
|
|
||||||
Date getExpiration();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the name of the cookie.
|
|
||||||
*
|
|
||||||
* @return The name of the cookie.
|
|
||||||
*/
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the value of the cookie.
|
|
||||||
* @return The value of the cookie.
|
|
||||||
*/
|
|
||||||
String getValue();
|
|
||||||
}
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IExtensionHelpers.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface contains a number of helper methods, which extensions can use
|
|
||||||
* to assist with various common tasks that arise for Burp extensions.
|
|
||||||
*
|
|
||||||
* Extensions can call <code>IBurpExtenderCallbacks.getHelpers</code> to obtain
|
|
||||||
* an instance of this interface.
|
|
||||||
*/
|
|
||||||
public interface IExtensionHelpers
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to analyze an HTTP request, and obtain various
|
|
||||||
* key details about it.
|
|
||||||
*
|
|
||||||
* @param request An <code>IHttpRequestResponse</code> object containing the
|
|
||||||
* request to be analyzed.
|
|
||||||
* @return An <code>IRequestInfo</code> object that can be queried to obtain
|
|
||||||
* details about the request.
|
|
||||||
*/
|
|
||||||
IRequestInfo analyzeRequest(IHttpRequestResponse request);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to analyze an HTTP request, and obtain various
|
|
||||||
* key details about it.
|
|
||||||
*
|
|
||||||
* @param httpService The HTTP service associated with the request. This is
|
|
||||||
* optional and may be <code>null</code>, in which case the resulting
|
|
||||||
* <code>IRequestInfo</code> object will not include the full request URL.
|
|
||||||
* @param request The request to be analyzed.
|
|
||||||
* @return An <code>IRequestInfo</code> object that can be queried to obtain
|
|
||||||
* details about the request.
|
|
||||||
*/
|
|
||||||
IRequestInfo analyzeRequest(IHttpService httpService, byte[] request);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to analyze an HTTP request, and obtain various
|
|
||||||
* key details about it. The resulting <code>IRequestInfo</code> object will
|
|
||||||
* not include the full request URL. To obtain the full URL, use one of the
|
|
||||||
* other overloaded <code>analyzeRequest()</code> methods.
|
|
||||||
*
|
|
||||||
* @param request The request to be analyzed.
|
|
||||||
* @return An <code>IRequestInfo</code> object that can be queried to obtain
|
|
||||||
* details about the request.
|
|
||||||
*/
|
|
||||||
IRequestInfo analyzeRequest(byte[] request);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to analyze an HTTP response, and obtain various
|
|
||||||
* key details about it.
|
|
||||||
*
|
|
||||||
* @param response The response to be analyzed.
|
|
||||||
* @return An <code>IResponseInfo</code> object that can be queried to
|
|
||||||
* obtain details about the response.
|
|
||||||
*/
|
|
||||||
IResponseInfo analyzeResponse(byte[] response);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to retrieve details of a specified parameter
|
|
||||||
* within an HTTP request. <b>Note:</b> Use <code>analyzeRequest()</code> to
|
|
||||||
* obtain details of all parameters within the request.
|
|
||||||
*
|
|
||||||
* @param request The request to be inspected for the specified parameter.
|
|
||||||
* @param parameterName The name of the parameter to retrieve.
|
|
||||||
* @return An <code>IParameter</code> object that can be queried to obtain
|
|
||||||
* details about the parameter, or <code>null</code> if the parameter was
|
|
||||||
* not found.
|
|
||||||
*/
|
|
||||||
IParameter getRequestParameter(byte[] request, String parameterName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to URL-decode the specified data.
|
|
||||||
*
|
|
||||||
* @param data The data to be decoded.
|
|
||||||
* @return The decoded data.
|
|
||||||
*/
|
|
||||||
String urlDecode(String data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to URL-encode the specified data. Any characters
|
|
||||||
* that do not need to be encoded within HTTP requests are not encoded.
|
|
||||||
*
|
|
||||||
* @param data The data to be encoded.
|
|
||||||
* @return The encoded data.
|
|
||||||
*/
|
|
||||||
String urlEncode(String data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to URL-decode the specified data.
|
|
||||||
*
|
|
||||||
* @param data The data to be decoded.
|
|
||||||
* @return The decoded data.
|
|
||||||
*/
|
|
||||||
byte[] urlDecode(byte[] data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to URL-encode the specified data. Any characters
|
|
||||||
* that do not need to be encoded within HTTP requests are not encoded.
|
|
||||||
*
|
|
||||||
* @param data The data to be encoded.
|
|
||||||
* @return The encoded data.
|
|
||||||
*/
|
|
||||||
byte[] urlEncode(byte[] data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to Base64-decode the specified data.
|
|
||||||
*
|
|
||||||
* @param data The data to be decoded.
|
|
||||||
* @return The decoded data.
|
|
||||||
*/
|
|
||||||
byte[] base64Decode(String data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to Base64-decode the specified data.
|
|
||||||
*
|
|
||||||
* @param data The data to be decoded.
|
|
||||||
* @return The decoded data.
|
|
||||||
*/
|
|
||||||
byte[] base64Decode(byte[] data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to Base64-encode the specified data.
|
|
||||||
*
|
|
||||||
* @param data The data to be encoded.
|
|
||||||
* @return The encoded data.
|
|
||||||
*/
|
|
||||||
String base64Encode(String data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to Base64-encode the specified data.
|
|
||||||
*
|
|
||||||
* @param data The data to be encoded.
|
|
||||||
* @return The encoded data.
|
|
||||||
*/
|
|
||||||
String base64Encode(byte[] data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to convert data from String form into an array of
|
|
||||||
* bytes. The conversion does not reflect any particular character set, and
|
|
||||||
* a character with the hex representation 0xWXYZ will always be converted
|
|
||||||
* into a byte with the representation 0xYZ. It performs the opposite
|
|
||||||
* conversion to the method <code>bytesToString()</code>, and byte-based
|
|
||||||
* data that is converted to a String and back again using these two methods
|
|
||||||
* is guaranteed to retain its integrity (which may not be the case with
|
|
||||||
* conversions that reflect a given character set).
|
|
||||||
*
|
|
||||||
* @param data The data to be converted.
|
|
||||||
* @return The converted data.
|
|
||||||
*/
|
|
||||||
byte[] stringToBytes(String data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to convert data from an array of bytes into
|
|
||||||
* String form. The conversion does not reflect any particular character
|
|
||||||
* set, and a byte with the representation 0xYZ will always be converted
|
|
||||||
* into a character with the hex representation 0x00YZ. It performs the
|
|
||||||
* opposite conversion to the method <code>stringToBytes()</code>, and
|
|
||||||
* byte-based data that is converted to a String and back again using these
|
|
||||||
* two methods is guaranteed to retain its integrity (which may not be the
|
|
||||||
* case with conversions that reflect a given character set).
|
|
||||||
*
|
|
||||||
* @param data The data to be converted.
|
|
||||||
* @return The converted data.
|
|
||||||
*/
|
|
||||||
String bytesToString(byte[] data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method searches a piece of data for the first occurrence of a
|
|
||||||
* specified pattern. It works on byte-based data in a way that is similar
|
|
||||||
* to the way the native Java method <code>String.indexOf()</code> works on
|
|
||||||
* String-based data.
|
|
||||||
*
|
|
||||||
* @param data The data to be searched.
|
|
||||||
* @param pattern The pattern to be searched for.
|
|
||||||
* @param caseSensitive Flags whether or not the search is case-sensitive.
|
|
||||||
* @param from The offset within <code>data</code> where the search should
|
|
||||||
* begin.
|
|
||||||
* @param to The offset within <code>data</code> where the search should
|
|
||||||
* end.
|
|
||||||
* @return The offset of the first occurrence of the pattern within the
|
|
||||||
* specified bounds, or -1 if no match is found.
|
|
||||||
*/
|
|
||||||
int indexOf(byte[] data,
|
|
||||||
byte[] pattern,
|
|
||||||
boolean caseSensitive,
|
|
||||||
int from,
|
|
||||||
int to);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method builds an HTTP message containing the specified headers and
|
|
||||||
* message body. If applicable, the Content-Length header will be added or
|
|
||||||
* updated, based on the length of the body.
|
|
||||||
*
|
|
||||||
* @param headers A list of headers to include in the message.
|
|
||||||
* @param body The body of the message, of <code>null</code> if the message
|
|
||||||
* has an empty body.
|
|
||||||
* @return The resulting full HTTP message.
|
|
||||||
*/
|
|
||||||
byte[] buildHttpMessage(List<String> headers, byte[] body);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method creates a GET request to the specified URL. The headers used
|
|
||||||
* in the request are determined by the Request headers settings as
|
|
||||||
* configured in Burp Spider's options.
|
|
||||||
*
|
|
||||||
* @param url The URL to which the request should be made.
|
|
||||||
* @return A request to the specified URL.
|
|
||||||
*/
|
|
||||||
byte[] buildHttpRequest(URL url);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method adds a new parameter to an HTTP request, and if appropriate
|
|
||||||
* updates the Content-Length header.
|
|
||||||
*
|
|
||||||
* @param request The request to which the parameter should be added.
|
|
||||||
* @param parameter An <code>IParameter</code> object containing details of
|
|
||||||
* the parameter to be added. Supported parameter types are:
|
|
||||||
* <code>PARAM_URL</code>, <code>PARAM_BODY</code> and
|
|
||||||
* <code>PARAM_COOKIE</code>.
|
|
||||||
* @return A new HTTP request with the new parameter added.
|
|
||||||
*/
|
|
||||||
byte[] addParameter(byte[] request, IParameter parameter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method removes a parameter from an HTTP request, and if appropriate
|
|
||||||
* updates the Content-Length header.
|
|
||||||
*
|
|
||||||
* @param request The request from which the parameter should be removed.
|
|
||||||
* @param parameter An <code>IParameter</code> object containing details of
|
|
||||||
* the parameter to be removed. Supported parameter types are:
|
|
||||||
* <code>PARAM_URL</code>, <code>PARAM_BODY</code> and
|
|
||||||
* <code>PARAM_COOKIE</code>.
|
|
||||||
* @return A new HTTP request with the parameter removed.
|
|
||||||
*/
|
|
||||||
byte[] removeParameter(byte[] request, IParameter parameter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method updates the value of a parameter within an HTTP request, and
|
|
||||||
* if appropriate updates the Content-Length header. <b>Note:</b> This
|
|
||||||
* method can only be used to update the value of an existing parameter of a
|
|
||||||
* specified type. If you need to change the type of an existing parameter,
|
|
||||||
* you should first call <code>removeParameter()</code> to remove the
|
|
||||||
* parameter with the old type, and then call <code>addParameter()</code> to
|
|
||||||
* add a parameter with the new type.
|
|
||||||
*
|
|
||||||
* @param request The request containing the parameter to be updated.
|
|
||||||
* @param parameter An <code>IParameter</code> object containing details of
|
|
||||||
* the parameter to be updated. Supported parameter types are:
|
|
||||||
* <code>PARAM_URL</code>, <code>PARAM_BODY</code> and
|
|
||||||
* <code>PARAM_COOKIE</code>.
|
|
||||||
* @return A new HTTP request with the parameter updated.
|
|
||||||
*/
|
|
||||||
byte[] updateParameter(byte[] request, IParameter parameter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to toggle a request's method between GET and
|
|
||||||
* POST. Parameters are relocated between the URL query string and message
|
|
||||||
* body as required, and the Content-Length header is created or removed as
|
|
||||||
* applicable.
|
|
||||||
*
|
|
||||||
* @param request The HTTP request whose method should be toggled.
|
|
||||||
* @return A new HTTP request using the toggled method.
|
|
||||||
*/
|
|
||||||
byte[] toggleRequestMethod(byte[] request);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method constructs an <code>IHttpService</code> object based on the
|
|
||||||
* details provided.
|
|
||||||
*
|
|
||||||
* @param host The HTTP service host.
|
|
||||||
* @param port The HTTP service port.
|
|
||||||
* @param protocol The HTTP service protocol.
|
|
||||||
* @return An <code>IHttpService</code> object based on the details
|
|
||||||
* provided.
|
|
||||||
*/
|
|
||||||
IHttpService buildHttpService(String host, int port, String protocol);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method constructs an <code>IHttpService</code> object based on the
|
|
||||||
* details provided.
|
|
||||||
*
|
|
||||||
* @param host The HTTP service host.
|
|
||||||
* @param port The HTTP service port.
|
|
||||||
* @param useHttps Flags whether the HTTP service protocol is HTTPS or HTTP.
|
|
||||||
* @return An <code>IHttpService</code> object based on the details
|
|
||||||
* provided.
|
|
||||||
*/
|
|
||||||
IHttpService buildHttpService(String host, int port, boolean useHttps);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method constructs an <code>IParameter</code> object based on the
|
|
||||||
* details provided.
|
|
||||||
*
|
|
||||||
* @param name The parameter name.
|
|
||||||
* @param value The parameter value.
|
|
||||||
* @param type The parameter type, as defined in the <code>IParameter</code>
|
|
||||||
* interface.
|
|
||||||
* @return An <code>IParameter</code> object based on the details provided.
|
|
||||||
*/
|
|
||||||
IParameter buildParameter(String name, String value, byte type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method constructs an <code>IScannerInsertionPoint</code> object
|
|
||||||
* based on the details provided. It can be used to quickly create a simple
|
|
||||||
* insertion point based on a fixed payload location within a base request.
|
|
||||||
*
|
|
||||||
* @param insertionPointName The name of the insertion point.
|
|
||||||
* @param baseRequest The request from which to build scan requests.
|
|
||||||
* @param from The offset of the start of the payload location.
|
|
||||||
* @param to The offset of the end of the payload location.
|
|
||||||
* @return An <code>IScannerInsertionPoint</code> object based on the
|
|
||||||
* details provided.
|
|
||||||
*/
|
|
||||||
IScannerInsertionPoint makeScannerInsertionPoint(
|
|
||||||
String insertionPointName,
|
|
||||||
byte[] baseRequest,
|
|
||||||
int from,
|
|
||||||
int to);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method analyzes one or more responses to identify variations in a
|
|
||||||
* number of attributes and returns an <code>IResponseVariations</code>
|
|
||||||
* object that can be queried to obtain details of the variations.
|
|
||||||
*
|
|
||||||
* @param responses The responses to analyze.
|
|
||||||
* @return An <code>IResponseVariations</code> object representing the
|
|
||||||
* variations in the responses.
|
|
||||||
*/
|
|
||||||
IResponseVariations analyzeResponseVariations(byte[]... responses);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method analyzes one or more responses to identify the number of
|
|
||||||
* occurrences of the specified keywords and returns an
|
|
||||||
* <code>IResponseKeywords</code> object that can be queried to obtain
|
|
||||||
* details of the number of occurrences of each keyword.
|
|
||||||
*
|
|
||||||
* @param keywords The keywords to look for.
|
|
||||||
* @param responses The responses to analyze.
|
|
||||||
* @return An <code>IResponseKeywords</code> object representing the counts
|
|
||||||
* of the keywords appearing in the responses.
|
|
||||||
*/
|
|
||||||
IResponseKeywords analyzeResponseKeywords(List<String> keywords, byte[]... responses);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IExtensionStateListener.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerExtensionStateListener()</code> to
|
|
||||||
* register an extension state listener. The listener will be notified of
|
|
||||||
* changes to the extension's state. <b>Note:</b> Any extensions that start
|
|
||||||
* background threads or open system resources (such as files or database
|
|
||||||
* connections) should register a listener and terminate threads / close
|
|
||||||
* resources when the extension is unloaded.
|
|
||||||
*/
|
|
||||||
public interface IExtensionStateListener
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is called when the extension is unloaded.
|
|
||||||
*/
|
|
||||||
void extensionUnloaded();
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IHttpListener.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerHttpListener()</code> to register an
|
|
||||||
* HTTP listener. The listener will be notified of requests and responses made
|
|
||||||
* by any Burp tool. Extensions can perform custom analysis or modification of
|
|
||||||
* these messages by registering an HTTP listener.
|
|
||||||
*/
|
|
||||||
public interface IHttpListener
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is invoked when an HTTP request is about to be issued, and
|
|
||||||
* when an HTTP response has been received.
|
|
||||||
*
|
|
||||||
* @param toolFlag A flag indicating the Burp tool that issued the request.
|
|
||||||
* Burp tool flags are defined in the
|
|
||||||
* <code>IBurpExtenderCallbacks</code> interface.
|
|
||||||
* @param messageIsRequest Flags whether the method is being invoked for a
|
|
||||||
* request or response.
|
|
||||||
* @param messageInfo Details of the request / response to be processed.
|
|
||||||
* Extensions can call the setter methods on this object to update the
|
|
||||||
* current message and so modify Burp's behavior.
|
|
||||||
*/
|
|
||||||
void processHttpMessage(int toolFlag,
|
|
||||||
boolean messageIsRequest,
|
|
||||||
IHttpRequestResponse messageInfo);
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IHttpRequestResponse.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used to retrieve and update details about HTTP messages.
|
|
||||||
*
|
|
||||||
* <b>Note:</b> The setter methods generally can only be used before the message
|
|
||||||
* has been processed, and not in read-only contexts. The getter methods
|
|
||||||
* relating to response details can only be used after the request has been
|
|
||||||
* issued.
|
|
||||||
*/
|
|
||||||
public interface IHttpRequestResponse
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the request message.
|
|
||||||
*
|
|
||||||
* @return The request message.
|
|
||||||
*/
|
|
||||||
byte[] getRequest();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the request message.
|
|
||||||
*
|
|
||||||
* @param message The new request message.
|
|
||||||
*/
|
|
||||||
void setRequest(byte[] message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the response message.
|
|
||||||
*
|
|
||||||
* @return The response message.
|
|
||||||
*/
|
|
||||||
byte[] getResponse();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the response message.
|
|
||||||
*
|
|
||||||
* @param message The new response message.
|
|
||||||
*/
|
|
||||||
void setResponse(byte[] message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the user-annotated comment for this item,
|
|
||||||
* if applicable.
|
|
||||||
*
|
|
||||||
* @return The user-annotated comment for this item, or null if none is set.
|
|
||||||
*/
|
|
||||||
String getComment();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the user-annotated comment for this item.
|
|
||||||
*
|
|
||||||
* @param comment The comment to be assigned to this item.
|
|
||||||
*/
|
|
||||||
void setComment(String comment);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the user-annotated highlight for this
|
|
||||||
* item, if applicable.
|
|
||||||
*
|
|
||||||
* @return The user-annotated highlight for this item, or null if none is
|
|
||||||
* set.
|
|
||||||
*/
|
|
||||||
String getHighlight();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the user-annotated highlight for this item.
|
|
||||||
*
|
|
||||||
* @param color The highlight color to be assigned to this item. Accepted
|
|
||||||
* values are: red, orange, yellow, green, cyan, blue, pink, magenta, gray,
|
|
||||||
* or a null String to clear any existing highlight.
|
|
||||||
*/
|
|
||||||
void setHighlight(String color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the HTTP service for this request /
|
|
||||||
* response.
|
|
||||||
*
|
|
||||||
* @return An
|
|
||||||
* <code>IHttpService</code> object containing details of the HTTP service.
|
|
||||||
*/
|
|
||||||
IHttpService getHttpService();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the HTTP service for this request /
|
|
||||||
* response.
|
|
||||||
*
|
|
||||||
* @param httpService An
|
|
||||||
* <code>IHttpService</code> object containing details of the new HTTP
|
|
||||||
* service.
|
|
||||||
*/
|
|
||||||
void setHttpService(IHttpService httpService);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IHttpRequestResponsePersisted.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used for an
|
|
||||||
* <code>IHttpRequestResponse</code> object whose request and response messages
|
|
||||||
* have been saved to temporary files using
|
|
||||||
* <code>IBurpExtenderCallbacks.saveBuffersToTempFiles()</code>.
|
|
||||||
*/
|
|
||||||
public interface IHttpRequestResponsePersisted extends IHttpRequestResponse
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is deprecated and no longer performs any action.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
void deleteTempFiles();
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IHttpRequestResponseWithMarkers.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used for an
|
|
||||||
* <code>IHttpRequestResponse</code> object that has had markers applied.
|
|
||||||
* Extensions can create instances of this interface using
|
|
||||||
* <code>IBurpExtenderCallbacks.applyMarkers()</code>, or provide their own
|
|
||||||
* implementation. Markers are used in various situations, such as specifying
|
|
||||||
* Intruder payload positions, Scanner insertion points, and highlights in
|
|
||||||
* Scanner issues.
|
|
||||||
*/
|
|
||||||
public interface IHttpRequestResponseWithMarkers extends IHttpRequestResponse
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method returns the details of the request markers.
|
|
||||||
*
|
|
||||||
* @return A list of index pairs representing the offsets of markers for the
|
|
||||||
* request message. Each item in the list is an int[2] array containing the
|
|
||||||
* start and end offsets for the marker. The method may return
|
|
||||||
* <code>null</code> if no request markers are defined.
|
|
||||||
*/
|
|
||||||
List<int[]> getRequestMarkers();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the details of the response markers.
|
|
||||||
*
|
|
||||||
* @return A list of index pairs representing the offsets of markers for the
|
|
||||||
* response message. Each item in the list is an int[2] array containing the
|
|
||||||
* start and end offsets for the marker. The method may return
|
|
||||||
* <code>null</code> if no response markers are defined.
|
|
||||||
*/
|
|
||||||
List<int[]> getResponseMarkers();
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IHttpService.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used to provide details about an HTTP service, to which
|
|
||||||
* HTTP requests can be sent.
|
|
||||||
*/
|
|
||||||
public interface IHttpService
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method returns the hostname or IP address for the service.
|
|
||||||
*
|
|
||||||
* @return The hostname or IP address for the service.
|
|
||||||
*/
|
|
||||||
String getHost();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the port number for the service.
|
|
||||||
*
|
|
||||||
* @return The port number for the service.
|
|
||||||
*/
|
|
||||||
int getPort();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the protocol for the service.
|
|
||||||
*
|
|
||||||
* @return The protocol for the service. Expected values are "http" or
|
|
||||||
* "https".
|
|
||||||
*/
|
|
||||||
String getProtocol();
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IInterceptedProxyMessage.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.net.InetAddress;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used to represent an HTTP message that has been intercepted
|
|
||||||
* by Burp Proxy. Extensions can register an
|
|
||||||
* <code>IProxyListener</code> to receive details of proxy messages using this
|
|
||||||
* interface. *
|
|
||||||
*/
|
|
||||||
public interface IInterceptedProxyMessage
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This action causes Burp Proxy to follow the current interception rules to
|
|
||||||
* determine the appropriate action to take for the message.
|
|
||||||
*/
|
|
||||||
static final int ACTION_FOLLOW_RULES = 0;
|
|
||||||
/**
|
|
||||||
* This action causes Burp Proxy to present the message to the user for
|
|
||||||
* manual review or modification.
|
|
||||||
*/
|
|
||||||
static final int ACTION_DO_INTERCEPT = 1;
|
|
||||||
/**
|
|
||||||
* This action causes Burp Proxy to forward the message to the remote server
|
|
||||||
* or client, without presenting it to the user.
|
|
||||||
*/
|
|
||||||
static final int ACTION_DONT_INTERCEPT = 2;
|
|
||||||
/**
|
|
||||||
* This action causes Burp Proxy to drop the message.
|
|
||||||
*/
|
|
||||||
static final int ACTION_DROP = 3;
|
|
||||||
/**
|
|
||||||
* This action causes Burp Proxy to follow the current interception rules to
|
|
||||||
* determine the appropriate action to take for the message, and then make a
|
|
||||||
* second call to processProxyMessage.
|
|
||||||
*/
|
|
||||||
static final int ACTION_FOLLOW_RULES_AND_REHOOK = 0x10;
|
|
||||||
/**
|
|
||||||
* This action causes Burp Proxy to present the message to the user for
|
|
||||||
* manual review or modification, and then make a second call to
|
|
||||||
* processProxyMessage.
|
|
||||||
*/
|
|
||||||
static final int ACTION_DO_INTERCEPT_AND_REHOOK = 0x11;
|
|
||||||
/**
|
|
||||||
* This action causes Burp Proxy to skip user interception, and then make a
|
|
||||||
* second call to processProxyMessage.
|
|
||||||
*/
|
|
||||||
static final int ACTION_DONT_INTERCEPT_AND_REHOOK = 0x12;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method retrieves a unique reference number for this
|
|
||||||
* request/response.
|
|
||||||
*
|
|
||||||
* @return An identifier that is unique to a single request/response pair.
|
|
||||||
* Extensions can use this to correlate details of requests and responses
|
|
||||||
* and perform processing on the response message accordingly.
|
|
||||||
*/
|
|
||||||
int getMessageReference();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method retrieves details of the intercepted message.
|
|
||||||
*
|
|
||||||
* @return An <code>IHttpRequestResponse</code> object containing details of
|
|
||||||
* the intercepted message.
|
|
||||||
*/
|
|
||||||
IHttpRequestResponse getMessageInfo();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method retrieves the currently defined interception action. The
|
|
||||||
* default action is
|
|
||||||
* <code>ACTION_FOLLOW_RULES</code>. If multiple proxy listeners are
|
|
||||||
* registered, then other listeners may already have modified the
|
|
||||||
* interception action before it reaches the current listener. This method
|
|
||||||
* can be used to determine whether this has occurred.
|
|
||||||
*
|
|
||||||
* @return The currently defined interception action. Possible values are
|
|
||||||
* defined within this interface.
|
|
||||||
*/
|
|
||||||
int getInterceptAction();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the interception action.
|
|
||||||
*
|
|
||||||
* @param interceptAction The new interception action. Possible values are
|
|
||||||
* defined within this interface.
|
|
||||||
*/
|
|
||||||
void setInterceptAction(int interceptAction);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method retrieves the name of the Burp Proxy listener that is
|
|
||||||
* processing the intercepted message.
|
|
||||||
*
|
|
||||||
* @return The name of the Burp Proxy listener that is processing the
|
|
||||||
* intercepted message. The format is the same as that shown in the Proxy
|
|
||||||
* Listeners UI - for example, "127.0.0.1:8080".
|
|
||||||
*/
|
|
||||||
String getListenerInterface();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method retrieves the client IP address from which the request for
|
|
||||||
* the intercepted message was received.
|
|
||||||
*
|
|
||||||
* @return The client IP address from which the request for the intercepted
|
|
||||||
* message was received.
|
|
||||||
*/
|
|
||||||
InetAddress getClientIpAddress();
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IIntruderAttack.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used to hold details about an Intruder attack.
|
|
||||||
*/
|
|
||||||
public interface IIntruderAttack
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the HTTP service for the attack.
|
|
||||||
*
|
|
||||||
* @return The HTTP service for the attack.
|
|
||||||
*/
|
|
||||||
IHttpService getHttpService();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the request template for the attack.
|
|
||||||
*
|
|
||||||
* @return The request template for the attack.
|
|
||||||
*/
|
|
||||||
byte[] getRequestTemplate();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IIntruderPayloadGenerator.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used for custom Intruder payload generators. Extensions
|
|
||||||
* that have registered an
|
|
||||||
* <code>IIntruderPayloadGeneratorFactory</code> must return a new instance of
|
|
||||||
* this interface when required as part of a new Intruder attack.
|
|
||||||
*/
|
|
||||||
public interface IIntruderPayloadGenerator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used by Burp to determine whether the payload generator is
|
|
||||||
* able to provide any further payloads.
|
|
||||||
*
|
|
||||||
* @return Extensions should return
|
|
||||||
* <code>false</code> when all the available payloads have been used up,
|
|
||||||
* otherwise
|
|
||||||
* <code>true</code>.
|
|
||||||
*/
|
|
||||||
boolean hasMorePayloads();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used by Burp to obtain the value of the next payload.
|
|
||||||
*
|
|
||||||
* @param baseValue The base value of the current payload position. This
|
|
||||||
* value may be
|
|
||||||
* <code>null</code> if the concept of a base value is not applicable (e.g.
|
|
||||||
* in a battering ram attack).
|
|
||||||
* @return The next payload to use in the attack.
|
|
||||||
*/
|
|
||||||
byte[] getNextPayload(byte[] baseValue);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used by Burp to reset the state of the payload generator
|
|
||||||
* so that the next call to
|
|
||||||
* <code>getNextPayload()</code> returns the first payload again. This
|
|
||||||
* method will be invoked when an attack uses the same payload generator for
|
|
||||||
* more than one payload position, for example in a sniper attack.
|
|
||||||
*/
|
|
||||||
void reset();
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IIntruderPayloadGeneratorFactory.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerIntruderPayloadGeneratorFactory()</code>
|
|
||||||
* to register a factory for custom Intruder payloads.
|
|
||||||
*/
|
|
||||||
public interface IIntruderPayloadGeneratorFactory
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used by Burp to obtain the name of the payload generator.
|
|
||||||
* This will be displayed as an option within the Intruder UI when the user
|
|
||||||
* selects to use extension-generated payloads.
|
|
||||||
*
|
|
||||||
* @return The name of the payload generator.
|
|
||||||
*/
|
|
||||||
String getGeneratorName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used by Burp when the user starts an Intruder attack that
|
|
||||||
* uses this payload generator.
|
|
||||||
*
|
|
||||||
* @param attack An
|
|
||||||
* <code>IIntruderAttack</code> object that can be queried to obtain details
|
|
||||||
* about the attack in which the payload generator will be used.
|
|
||||||
* @return A new instance of
|
|
||||||
* <code>IIntruderPayloadGenerator</code> that will be used to generate
|
|
||||||
* payloads for the attack.
|
|
||||||
*/
|
|
||||||
IIntruderPayloadGenerator createNewInstance(IIntruderAttack attack);
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IIntruderPayloadProcessor.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerIntruderPayloadProcessor()</code> to
|
|
||||||
* register a custom Intruder payload processor.
|
|
||||||
*/
|
|
||||||
public interface IIntruderPayloadProcessor
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used by Burp to obtain the name of the payload processor.
|
|
||||||
* This will be displayed as an option within the Intruder UI when the user
|
|
||||||
* selects to use an extension-provided payload processor.
|
|
||||||
*
|
|
||||||
* @return The name of the payload processor.
|
|
||||||
*/
|
|
||||||
String getProcessorName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is invoked by Burp each time the processor should be applied
|
|
||||||
* to an Intruder payload.
|
|
||||||
*
|
|
||||||
* @param currentPayload The value of the payload to be processed.
|
|
||||||
* @param originalPayload The value of the original payload prior to
|
|
||||||
* processing by any already-applied processing rules.
|
|
||||||
* @param baseValue The base value of the payload position, which will be
|
|
||||||
* replaced with the current payload.
|
|
||||||
* @return The value of the processed payload. This may be
|
|
||||||
* <code>null</code> to indicate that the current payload should be skipped,
|
|
||||||
* and the attack will move directly to the next payload.
|
|
||||||
*/
|
|
||||||
byte[] processPayload(
|
|
||||||
byte[] currentPayload,
|
|
||||||
byte[] originalPayload,
|
|
||||||
byte[] baseValue);
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IMenuItemHandler.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerMenuItem()</code> to register a custom
|
|
||||||
* context menu item.
|
|
||||||
*
|
|
||||||
* @deprecated Use
|
|
||||||
* <code>IContextMenuFactory</code> instead.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public interface IMenuItemHandler
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is invoked by Burp Suite when the user clicks on a custom
|
|
||||||
* menu item which the extension has registered with Burp.
|
|
||||||
*
|
|
||||||
* @param menuItemCaption The caption of the menu item which was clicked.
|
|
||||||
* This parameter enables extensions to provide a single implementation
|
|
||||||
* which handles multiple different menu items.
|
|
||||||
* @param messageInfo Details of the HTTP message(s) for which the context
|
|
||||||
* menu was displayed.
|
|
||||||
*/
|
|
||||||
void menuItemClicked(
|
|
||||||
String menuItemCaption,
|
|
||||||
IHttpRequestResponse[] messageInfo);
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IMessageEditor.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.awt.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used to provide extensions with an instance of Burp's HTTP
|
|
||||||
* message editor, for the extension to use in its own UI. Extensions should
|
|
||||||
* call <code>IBurpExtenderCallbacks.createMessageEditor()</code> to obtain an
|
|
||||||
* instance of this interface.
|
|
||||||
*/
|
|
||||||
public interface IMessageEditor
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the UI component of the editor, for extensions to add
|
|
||||||
* to their own UI.
|
|
||||||
*
|
|
||||||
* @return The UI component of the editor.
|
|
||||||
*/
|
|
||||||
Component getComponent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to display an HTTP message in the editor.
|
|
||||||
*
|
|
||||||
* @param message The HTTP message to be displayed.
|
|
||||||
* @param isRequest Flags whether the message is an HTTP request or
|
|
||||||
* response.
|
|
||||||
*/
|
|
||||||
void setMessage(byte[] message, boolean isRequest);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the currently displayed message, which
|
|
||||||
* may have been modified by the user.
|
|
||||||
*
|
|
||||||
* @return The currently displayed HTTP message.
|
|
||||||
*/
|
|
||||||
byte[] getMessage();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to determine whether the current message has been
|
|
||||||
* modified by the user.
|
|
||||||
*
|
|
||||||
* @return An indication of whether the current message has been modified by
|
|
||||||
* the user since it was first displayed.
|
|
||||||
*/
|
|
||||||
boolean isMessageModified();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the data that is currently selected by the user.
|
|
||||||
*
|
|
||||||
* @return The data that is currently selected by the user, or
|
|
||||||
* <code>null</code> if no selection is made.
|
|
||||||
*/
|
|
||||||
byte[] getSelectedData();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to retrieve the bounds of the user's selection
|
|
||||||
* into the displayed message, if applicable.
|
|
||||||
*
|
|
||||||
* @return An int[2] array containing the start and end offsets of the
|
|
||||||
* user's selection within the displayed message. If the user has not made
|
|
||||||
* any selection in the current message, both offsets indicate the position
|
|
||||||
* of the caret within the editor. For some editor views, the concept of
|
|
||||||
* selection within the message does not apply, in which case this method
|
|
||||||
* returns null.
|
|
||||||
*/
|
|
||||||
int[] getSelectionBounds();
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IMessageEditorController.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used by an
|
|
||||||
* <code>IMessageEditor</code> to obtain details about the currently displayed
|
|
||||||
* message. Extensions that create instances of Burp's HTTP message editor can
|
|
||||||
* optionally provide an implementation of
|
|
||||||
* <code>IMessageEditorController</code>, which the editor will invoke when it
|
|
||||||
* requires further information about the current message (for example, to send
|
|
||||||
* it to another Burp tool). Extensions that provide custom editor tabs via an
|
|
||||||
* <code>IMessageEditorTabFactory</code> will receive a reference to an
|
|
||||||
* <code>IMessageEditorController</code> object for each tab instance they
|
|
||||||
* generate, which the tab can invoke if it requires further information about
|
|
||||||
* the current message.
|
|
||||||
*/
|
|
||||||
public interface IMessageEditorController
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the HTTP service for the current message.
|
|
||||||
*
|
|
||||||
* @return The HTTP service for the current message.
|
|
||||||
*/
|
|
||||||
IHttpService getHttpService();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the HTTP request associated with the
|
|
||||||
* current message (which may itself be a response).
|
|
||||||
*
|
|
||||||
* @return The HTTP request associated with the current message.
|
|
||||||
*/
|
|
||||||
byte[] getRequest();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the HTTP response associated with the
|
|
||||||
* current message (which may itself be a request).
|
|
||||||
*
|
|
||||||
* @return The HTTP response associated with the current message.
|
|
||||||
*/
|
|
||||||
byte[] getResponse();
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IMessageEditorTab.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.awt.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extensions that register an
|
|
||||||
* <code>IMessageEditorTabFactory</code> must return instances of this
|
|
||||||
* interface, which Burp will use to create custom tabs within its HTTP message
|
|
||||||
* editors.
|
|
||||||
*/
|
|
||||||
public interface IMessageEditorTab
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method returns the caption that should appear on the custom tab when
|
|
||||||
* it is displayed. <b>Note:</b> Burp invokes this method once when the tab
|
|
||||||
* is first generated, and the same caption will be used every time the tab
|
|
||||||
* is displayed.
|
|
||||||
*
|
|
||||||
* @return The caption that should appear on the custom tab when it is
|
|
||||||
* displayed.
|
|
||||||
*/
|
|
||||||
String getTabCaption();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the component that should be used as the contents of
|
|
||||||
* the custom tab when it is displayed. <b>Note:</b> Burp invokes this
|
|
||||||
* method once when the tab is first generated, and the same component will
|
|
||||||
* be used every time the tab is displayed.
|
|
||||||
*
|
|
||||||
* @return The component that should be used as the contents of the custom
|
|
||||||
* tab when it is displayed.
|
|
||||||
*/
|
|
||||||
Component getUiComponent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The hosting editor will invoke this method before it displays a new HTTP
|
|
||||||
* message, so that the custom tab can indicate whether it should be enabled
|
|
||||||
* for that message.
|
|
||||||
*
|
|
||||||
* @param content The message that is about to be displayed, or a zero-length
|
|
||||||
* array if the existing message is to be cleared.
|
|
||||||
* @param isRequest Indicates whether the message is a request or a
|
|
||||||
* response.
|
|
||||||
* @return The method should return
|
|
||||||
* <code>true</code> if the custom tab is able to handle the specified
|
|
||||||
* message, and so will be displayed within the editor. Otherwise, the tab
|
|
||||||
* will be hidden while this message is displayed.
|
|
||||||
*/
|
|
||||||
boolean isEnabled(byte[] content, boolean isRequest);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The hosting editor will invoke this method to display a new message or to
|
|
||||||
* clear the existing message. This method will only be called with a new
|
|
||||||
* message if the tab has already returned
|
|
||||||
* <code>true</code> to a call to
|
|
||||||
* <code>isEnabled()</code> with the same message details.
|
|
||||||
*
|
|
||||||
* @param content The message that is to be displayed, or
|
|
||||||
* <code>null</code> if the tab should clear its contents and disable any
|
|
||||||
* editable controls.
|
|
||||||
* @param isRequest Indicates whether the message is a request or a
|
|
||||||
* response.
|
|
||||||
*/
|
|
||||||
void setMessage(byte[] content, boolean isRequest);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the currently displayed message.
|
|
||||||
*
|
|
||||||
* @return The currently displayed message.
|
|
||||||
*/
|
|
||||||
byte[] getMessage();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to determine whether the currently displayed message
|
|
||||||
* has been modified by the user. The hosting editor will always call
|
|
||||||
* <code>getMessage()</code> before calling this method, so any pending
|
|
||||||
* edits should be completed within
|
|
||||||
* <code>getMessage()</code>.
|
|
||||||
*
|
|
||||||
* @return The method should return
|
|
||||||
* <code>true</code> if the user has modified the current message since it
|
|
||||||
* was first displayed.
|
|
||||||
*/
|
|
||||||
boolean isModified();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the data that is currently selected by
|
|
||||||
* the user.
|
|
||||||
*
|
|
||||||
* @return The data that is currently selected by the user. This may be
|
|
||||||
* <code>null</code> if no selection is currently made.
|
|
||||||
*/
|
|
||||||
byte[] getSelectedData();
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IMessageEditorTabFactory.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerMessageEditorTabFactory()</code> to
|
|
||||||
* register a factory for custom message editor tabs. This allows extensions to
|
|
||||||
* provide custom rendering or editing of HTTP messages, within Burp's own HTTP
|
|
||||||
* editor.
|
|
||||||
*/
|
|
||||||
public interface IMessageEditorTabFactory
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Burp will call this method once for each HTTP message editor, and the
|
|
||||||
* factory should provide a new instance of an
|
|
||||||
* <code>IMessageEditorTab</code> object.
|
|
||||||
*
|
|
||||||
* @param controller An
|
|
||||||
* <code>IMessageEditorController</code> object, which the new tab can query
|
|
||||||
* to retrieve details about the currently displayed message. This may be
|
|
||||||
* <code>null</code> for extension-invoked message editors where the
|
|
||||||
* extension has not provided an editor controller.
|
|
||||||
* @param editable Indicates whether the hosting editor is editable or
|
|
||||||
* read-only.
|
|
||||||
* @return A new
|
|
||||||
* <code>IMessageEditorTab</code> object for use within the message editor.
|
|
||||||
*/
|
|
||||||
IMessageEditorTab createNewInstance(IMessageEditorController controller,
|
|
||||||
boolean editable);
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IParameter.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used to hold details about an HTTP request parameter.
|
|
||||||
*/
|
|
||||||
public interface IParameter
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Used to indicate a parameter within the URL query string.
|
|
||||||
*/
|
|
||||||
static final byte PARAM_URL = 0;
|
|
||||||
/**
|
|
||||||
* Used to indicate a parameter within the message body.
|
|
||||||
*/
|
|
||||||
static final byte PARAM_BODY = 1;
|
|
||||||
/**
|
|
||||||
* Used to indicate an HTTP cookie.
|
|
||||||
*/
|
|
||||||
static final byte PARAM_COOKIE = 2;
|
|
||||||
/**
|
|
||||||
* Used to indicate an item of data within an XML structure.
|
|
||||||
*/
|
|
||||||
static final byte PARAM_XML = 3;
|
|
||||||
/**
|
|
||||||
* Used to indicate the value of a tag attribute within an XML structure.
|
|
||||||
*/
|
|
||||||
static final byte PARAM_XML_ATTR = 4;
|
|
||||||
/**
|
|
||||||
* Used to indicate the value of a parameter attribute within a multi-part
|
|
||||||
* message body (such as the name of an uploaded file).
|
|
||||||
*/
|
|
||||||
static final byte PARAM_MULTIPART_ATTR = 5;
|
|
||||||
/**
|
|
||||||
* Used to indicate an item of data within a JSON structure.
|
|
||||||
*/
|
|
||||||
static final byte PARAM_JSON = 6;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the parameter type.
|
|
||||||
*
|
|
||||||
* @return The parameter type. The available types are defined within this
|
|
||||||
* interface.
|
|
||||||
*/
|
|
||||||
byte getType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the parameter name.
|
|
||||||
*
|
|
||||||
* @return The parameter name.
|
|
||||||
*/
|
|
||||||
String getName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the parameter value.
|
|
||||||
*
|
|
||||||
* @return The parameter value.
|
|
||||||
*/
|
|
||||||
String getValue();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the start offset of the parameter name
|
|
||||||
* within the HTTP request.
|
|
||||||
*
|
|
||||||
* @return The start offset of the parameter name within the HTTP request,
|
|
||||||
* or -1 if the parameter is not associated with a specific request.
|
|
||||||
*/
|
|
||||||
int getNameStart();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the end offset of the parameter name
|
|
||||||
* within the HTTP request.
|
|
||||||
*
|
|
||||||
* @return The end offset of the parameter name within the HTTP request, or
|
|
||||||
* -1 if the parameter is not associated with a specific request.
|
|
||||||
*/
|
|
||||||
int getNameEnd();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the start offset of the parameter value
|
|
||||||
* within the HTTP request.
|
|
||||||
*
|
|
||||||
* @return The start offset of the parameter value within the HTTP request,
|
|
||||||
* or -1 if the parameter is not associated with a specific request.
|
|
||||||
*/
|
|
||||||
int getValueStart();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the end offset of the parameter value
|
|
||||||
* within the HTTP request.
|
|
||||||
*
|
|
||||||
* @return The end offset of the parameter value within the HTTP request, or
|
|
||||||
* -1 if the parameter is not associated with a specific request.
|
|
||||||
*/
|
|
||||||
int getValueEnd();
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IProxyListener.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerProxyListener()</code> to register a
|
|
||||||
* Proxy listener. The listener will be notified of requests and responses being
|
|
||||||
* processed by the Proxy tool. Extensions can perform custom analysis or
|
|
||||||
* modification of these messages, and control in-UI message interception, by
|
|
||||||
* registering a proxy listener.
|
|
||||||
*/
|
|
||||||
public interface IProxyListener
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is invoked when an HTTP message is being processed by the
|
|
||||||
* Proxy.
|
|
||||||
*
|
|
||||||
* @param messageIsRequest Indicates whether the HTTP message is a request
|
|
||||||
* or a response.
|
|
||||||
* @param message An
|
|
||||||
* <code>IInterceptedProxyMessage</code> object that extensions can use to
|
|
||||||
* query and update details of the message, and control whether the message
|
|
||||||
* should be intercepted and displayed to the user for manual review or
|
|
||||||
* modification.
|
|
||||||
*/
|
|
||||||
void processProxyMessage(
|
|
||||||
boolean messageIsRequest,
|
|
||||||
IInterceptedProxyMessage message);
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IRequestInfo.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used to retrieve key details about an HTTP request.
|
|
||||||
* Extensions can obtain an
|
|
||||||
* <code>IRequestInfo</code> object for a given request by calling
|
|
||||||
* <code>IExtensionHelpers.analyzeRequest()</code>.
|
|
||||||
*/
|
|
||||||
public interface IRequestInfo
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Used to indicate that there is no content.
|
|
||||||
*/
|
|
||||||
static final byte CONTENT_TYPE_NONE = 0;
|
|
||||||
/**
|
|
||||||
* Used to indicate URL-encoded content.
|
|
||||||
*/
|
|
||||||
static final byte CONTENT_TYPE_URL_ENCODED = 1;
|
|
||||||
/**
|
|
||||||
* Used to indicate multi-part content.
|
|
||||||
*/
|
|
||||||
static final byte CONTENT_TYPE_MULTIPART = 2;
|
|
||||||
/**
|
|
||||||
* Used to indicate XML content.
|
|
||||||
*/
|
|
||||||
static final byte CONTENT_TYPE_XML = 3;
|
|
||||||
/**
|
|
||||||
* Used to indicate JSON content.
|
|
||||||
*/
|
|
||||||
static final byte CONTENT_TYPE_JSON = 4;
|
|
||||||
/**
|
|
||||||
* Used to indicate AMF content.
|
|
||||||
*/
|
|
||||||
static final byte CONTENT_TYPE_AMF = 5;
|
|
||||||
/**
|
|
||||||
* Used to indicate unknown content.
|
|
||||||
*/
|
|
||||||
static final byte CONTENT_TYPE_UNKNOWN = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the HTTP method used in the request.
|
|
||||||
*
|
|
||||||
* @return The HTTP method used in the request.
|
|
||||||
*/
|
|
||||||
String getMethod();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the URL in the request.
|
|
||||||
*
|
|
||||||
* @return The URL in the request.
|
|
||||||
*/
|
|
||||||
URL getUrl();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the HTTP headers contained in the request.
|
|
||||||
*
|
|
||||||
* @return The HTTP headers contained in the request.
|
|
||||||
*/
|
|
||||||
List<String> getHeaders();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the parameters contained in the request.
|
|
||||||
*
|
|
||||||
* @return The parameters contained in the request.
|
|
||||||
*/
|
|
||||||
List<IParameter> getParameters();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the offset within the request where the
|
|
||||||
* message body begins.
|
|
||||||
*
|
|
||||||
* @return The offset within the request where the message body begins.
|
|
||||||
*/
|
|
||||||
int getBodyOffset();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the content type of the message body.
|
|
||||||
*
|
|
||||||
* @return An indication of the content type of the message body. Available
|
|
||||||
* types are defined within this interface.
|
|
||||||
*/
|
|
||||||
byte getContentType();
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IResponseInfo.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used to retrieve key details about an HTTP response.
|
|
||||||
* Extensions can obtain an
|
|
||||||
* <code>IResponseInfo</code> object for a given response by calling
|
|
||||||
* <code>IExtensionHelpers.analyzeResponse()</code>.
|
|
||||||
*/
|
|
||||||
public interface IResponseInfo
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the HTTP headers contained in the response.
|
|
||||||
*
|
|
||||||
* @return The HTTP headers contained in the response.
|
|
||||||
*/
|
|
||||||
List<String> getHeaders();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the offset within the response where the
|
|
||||||
* message body begins.
|
|
||||||
*
|
|
||||||
* @return The offset within the response where the message body begins.
|
|
||||||
*/
|
|
||||||
int getBodyOffset();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the HTTP status code contained in the
|
|
||||||
* response.
|
|
||||||
*
|
|
||||||
* @return The HTTP status code contained in the response.
|
|
||||||
*/
|
|
||||||
short getStatusCode();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain details of the HTTP cookies set in the
|
|
||||||
* response.
|
|
||||||
*
|
|
||||||
* @return A list of <code>ICookie</code> objects representing the cookies
|
|
||||||
* set in the response, if any.
|
|
||||||
*/
|
|
||||||
List<ICookie> getCookies();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the MIME type of the response, as stated in
|
|
||||||
* the HTTP headers.
|
|
||||||
*
|
|
||||||
* @return A textual label for the stated MIME type, or an empty String if
|
|
||||||
* this is not known or recognized. The possible labels are the same as
|
|
||||||
* those used in the main Burp UI.
|
|
||||||
*/
|
|
||||||
String getStatedMimeType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the MIME type of the response, as inferred
|
|
||||||
* from the contents of the HTTP message body.
|
|
||||||
*
|
|
||||||
* @return A textual label for the inferred MIME type, or an empty String if
|
|
||||||
* this is not known or recognized. The possible labels are the same as
|
|
||||||
* those used in the main Burp UI.
|
|
||||||
*/
|
|
||||||
String getInferredMimeType();
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IResponseKeywords.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used to represent the counts of keywords appearing in a
|
|
||||||
* number of HTTP responses.
|
|
||||||
*/
|
|
||||||
public interface IResponseKeywords
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the list of keywords whose counts vary
|
|
||||||
* between the analyzed responses.
|
|
||||||
*
|
|
||||||
* @return The keywords whose counts vary between the analyzed responses.
|
|
||||||
*/
|
|
||||||
List<String> getVariantKeywords();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the list of keywords whose counts do not
|
|
||||||
* vary between the analyzed responses.
|
|
||||||
*
|
|
||||||
* @return The keywords whose counts do not vary between the analyzed
|
|
||||||
* responses.
|
|
||||||
*/
|
|
||||||
List<String> getInvariantKeywords();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the number of occurrences of an individual
|
|
||||||
* keyword in a response.
|
|
||||||
*
|
|
||||||
* @param keyword The keyword whose count will be retrieved.
|
|
||||||
* @param responseIndex The index of the response. Note responses are
|
|
||||||
* indexed from zero in the order they were originally supplied to the
|
|
||||||
* <code>IExtensionHelpers.analyzeResponseKeywords()</code> and
|
|
||||||
* <code>IResponseKeywords.updateWith()</code> methods.
|
|
||||||
* @return The number of occurrences of the specified keyword for the
|
|
||||||
* specified response.
|
|
||||||
*/
|
|
||||||
int getKeywordCount(String keyword, int responseIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the analysis based on additional responses.
|
|
||||||
*
|
|
||||||
* @param responses The new responses to include in the analysis.
|
|
||||||
*/
|
|
||||||
void updateWith(byte[]... responses);
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IResponseVariations.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used to represent variations between a number HTTP
|
|
||||||
* responses, according to various attributes.
|
|
||||||
*/
|
|
||||||
public interface IResponseVariations
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the list of attributes that vary between
|
|
||||||
* the analyzed responses.
|
|
||||||
*
|
|
||||||
* @return The attributes that vary between the analyzed responses.
|
|
||||||
*/
|
|
||||||
List<String> getVariantAttributes();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the list of attributes that do not vary
|
|
||||||
* between the analyzed responses.
|
|
||||||
*
|
|
||||||
* @return The attributes that do not vary between the analyzed responses.
|
|
||||||
*/
|
|
||||||
List<String> getInvariantAttributes();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the value of an individual attribute in a
|
|
||||||
* response. Note that the values of some attributes are intrinsically
|
|
||||||
* meaningful (e.g. a word count) while the values of others are less so
|
|
||||||
* (e.g. a checksum of the HTML tag names).
|
|
||||||
*
|
|
||||||
* @param attributeName The name of the attribute whose value will be
|
|
||||||
* retrieved. Extension authors can obtain the list of supported attributes
|
|
||||||
* by generating an <code>IResponseVariations</code> object for a single
|
|
||||||
* response and calling
|
|
||||||
* <code>IResponseVariations.getInvariantAttributes()</code>.
|
|
||||||
* @param responseIndex The index of the response. Note that responses are
|
|
||||||
* indexed from zero in the order they were originally supplied to the
|
|
||||||
* <code>IExtensionHelpers.analyzeResponseVariations()</code> and
|
|
||||||
* <code>IResponseVariations.updateWith()</code> methods.
|
|
||||||
* @return The value of the specified attribute for the specified response.
|
|
||||||
*/
|
|
||||||
int getAttributeValue(String attributeName, int responseIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the analysis based on additional responses.
|
|
||||||
*
|
|
||||||
* @param responses The new responses to include in the analysis.
|
|
||||||
*/
|
|
||||||
void updateWith(byte[]... responses);
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IScanIssue.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used to retrieve details of Scanner issues. Extensions can
|
|
||||||
* obtain details of issues by registering an <code>IScannerListener</code> or
|
|
||||||
* by calling <code>IBurpExtenderCallbacks.getScanIssues()</code>. Extensions
|
|
||||||
* can also add custom Scanner issues by registering an
|
|
||||||
* <code>IScannerCheck</code> or calling
|
|
||||||
* <code>IBurpExtenderCallbacks.addScanIssue()</code>, and providing their own
|
|
||||||
* implementations of this interface. Note that issue descriptions and other
|
|
||||||
* text generated by extensions are subject to an HTML whitelist that allows
|
|
||||||
* only formatting tags and simple hyperlinks.
|
|
||||||
*/
|
|
||||||
public interface IScanIssue
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the URL for which the issue was generated.
|
|
||||||
*
|
|
||||||
* @return The URL for which the issue was generated.
|
|
||||||
*/
|
|
||||||
java.net.URL getUrl();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the name of the issue type.
|
|
||||||
*
|
|
||||||
* @return The name of the issue type (e.g. "SQL injection").
|
|
||||||
*/
|
|
||||||
String getIssueName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns a numeric identifier of the issue type. See the Burp
|
|
||||||
* Scanner documentation for a listing of all the issue types.
|
|
||||||
*
|
|
||||||
* @return A numeric identifier of the issue type.
|
|
||||||
*/
|
|
||||||
int getIssueType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the issue severity level.
|
|
||||||
*
|
|
||||||
* @return The issue severity level. Expected values are "High", "Medium",
|
|
||||||
* "Low", "Information" or "False positive".
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
String getSeverity();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the issue confidence level.
|
|
||||||
*
|
|
||||||
* @return The issue confidence level. Expected values are "Certain", "Firm"
|
|
||||||
* or "Tentative".
|
|
||||||
*/
|
|
||||||
String getConfidence();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns a background description for this type of issue.
|
|
||||||
*
|
|
||||||
* @return A background description for this type of issue, or
|
|
||||||
* <code>null</code> if none applies. A limited set of HTML tags may be
|
|
||||||
* used.
|
|
||||||
*/
|
|
||||||
String getIssueBackground();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns a background description of the remediation for this
|
|
||||||
* type of issue.
|
|
||||||
*
|
|
||||||
* @return A background description of the remediation for this type of
|
|
||||||
* issue, or <code>null</code> if none applies. A limited set of HTML tags
|
|
||||||
* may be used.
|
|
||||||
*/
|
|
||||||
String getRemediationBackground();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns detailed information about this specific instance of
|
|
||||||
* the issue.
|
|
||||||
*
|
|
||||||
* @return Detailed information about this specific instance of the issue,
|
|
||||||
* or <code>null</code> if none applies. A limited set of HTML tags may be
|
|
||||||
* used.
|
|
||||||
*/
|
|
||||||
String getIssueDetail();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns detailed information about the remediation for this
|
|
||||||
* specific instance of the issue.
|
|
||||||
*
|
|
||||||
* @return Detailed information about the remediation for this specific
|
|
||||||
* instance of the issue, or <code>null</code> if none applies. A limited
|
|
||||||
* set of HTML tags may be used.
|
|
||||||
*/
|
|
||||||
String getRemediationDetail();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the HTTP messages on the basis of which the issue was
|
|
||||||
* generated.
|
|
||||||
*
|
|
||||||
* @return The HTTP messages on the basis of which the issue was generated.
|
|
||||||
* <b>Note:</b> The items in this array should be instances of
|
|
||||||
* <code>IHttpRequestResponseWithMarkers</code> if applicable, so that
|
|
||||||
* details of the relevant portions of the request and response messages are
|
|
||||||
* available.
|
|
||||||
*/
|
|
||||||
IHttpRequestResponse[] getHttpMessages();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the HTTP service for which the issue was generated.
|
|
||||||
*
|
|
||||||
* @return The HTTP service for which the issue was generated.
|
|
||||||
*/
|
|
||||||
IHttpService getHttpService();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IScanQueueItem.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used to retrieve details of items in the Burp Scanner
|
|
||||||
* active scan queue. Extensions can obtain references to scan queue items by
|
|
||||||
* calling
|
|
||||||
* <code>IBurpExtenderCallbacks.doActiveScan()</code>.
|
|
||||||
*/
|
|
||||||
public interface IScanQueueItem
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method returns a description of the status of the scan queue item.
|
|
||||||
*
|
|
||||||
* @return A description of the status of the scan queue item.
|
|
||||||
*/
|
|
||||||
String getStatus();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns an indication of the percentage completed for the
|
|
||||||
* scan queue item.
|
|
||||||
*
|
|
||||||
* @return An indication of the percentage completed for the scan queue
|
|
||||||
* item.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
byte getPercentageComplete();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the number of requests that have been made for the
|
|
||||||
* scan queue item.
|
|
||||||
*
|
|
||||||
* @return The number of requests that have been made for the scan queue
|
|
||||||
* item.
|
|
||||||
*/
|
|
||||||
int getNumRequests();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the number of network errors that have occurred for
|
|
||||||
* the scan queue item.
|
|
||||||
*
|
|
||||||
* @return The number of network errors that have occurred for the scan
|
|
||||||
* queue item.
|
|
||||||
*/
|
|
||||||
int getNumErrors();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the number of attack insertion points being used for
|
|
||||||
* the scan queue item.
|
|
||||||
*
|
|
||||||
* @return The number of attack insertion points being used for the scan
|
|
||||||
* queue item.
|
|
||||||
*/
|
|
||||||
int getNumInsertionPoints();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method allows the scan queue item to be canceled.
|
|
||||||
*/
|
|
||||||
void cancel();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns details of the issues generated for the scan queue
|
|
||||||
* item. <b>Note:</b> different items within the scan queue may contain
|
|
||||||
* duplicated versions of the same issues - for example, if the same request
|
|
||||||
* has been scanned multiple times. Duplicated issues are consolidated in
|
|
||||||
* the main view of scan results. Extensions can register an
|
|
||||||
* <code>IScannerListener</code> to get details only of unique, newly
|
|
||||||
* discovered Scanner issues post-consolidation.
|
|
||||||
*
|
|
||||||
* @return Details of the issues generated for the scan queue item.
|
|
||||||
*/
|
|
||||||
IScanIssue[] getIssues();
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IScannerCheck.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerScannerCheck()</code> to register a
|
|
||||||
* custom Scanner check. When performing scanning, Burp will ask the check to
|
|
||||||
* perform active or passive scanning on the base request, and report any
|
|
||||||
* Scanner issues that are identified.
|
|
||||||
*/
|
|
||||||
public interface IScannerCheck
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Scanner invokes this method for each base request / response that is
|
|
||||||
* passively scanned. <b>Note:</b> Extensions should only analyze the
|
|
||||||
* HTTP messages provided during passive scanning, and should not make any
|
|
||||||
* new HTTP requests of their own.
|
|
||||||
*
|
|
||||||
* @param baseRequestResponse The base HTTP request / response that should
|
|
||||||
* be passively scanned.
|
|
||||||
* @return A list of <code>IScanIssue</code> objects, or <code>null</code>
|
|
||||||
* if no issues are identified.
|
|
||||||
*/
|
|
||||||
List<IScanIssue> doPassiveScan(IHttpRequestResponse baseRequestResponse);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Scanner invokes this method for each insertion point that is actively
|
|
||||||
* scanned. Extensions may issue HTTP requests as required to carry out
|
|
||||||
* active scanning, and should use the
|
|
||||||
* <code>IScannerInsertionPoint</code> object provided to build scan
|
|
||||||
* requests for particular payloads.
|
|
||||||
* <b>Note:</b>
|
|
||||||
* Scan checks should submit raw non-encoded payloads to insertion points,
|
|
||||||
* and the insertion point has responsibility for performing any data
|
|
||||||
* encoding that is necessary given the nature and location of the insertion
|
|
||||||
* point.
|
|
||||||
*
|
|
||||||
* @param baseRequestResponse The base HTTP request / response that should
|
|
||||||
* be actively scanned.
|
|
||||||
* @param insertionPoint An <code>IScannerInsertionPoint</code> object that
|
|
||||||
* can be queried to obtain details of the insertion point being tested, and
|
|
||||||
* can be used to build scan requests for particular payloads.
|
|
||||||
* @return A list of <code>IScanIssue</code> objects, or <code>null</code>
|
|
||||||
* if no issues are identified.
|
|
||||||
*/
|
|
||||||
List<IScanIssue> doActiveScan(
|
|
||||||
IHttpRequestResponse baseRequestResponse,
|
|
||||||
IScannerInsertionPoint insertionPoint);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Scanner invokes this method when the custom Scanner check has
|
|
||||||
* reported multiple issues for the same URL path. This can arise either
|
|
||||||
* because there are multiple distinct vulnerabilities, or because the same
|
|
||||||
* (or a similar) request has been scanned more than once. The custom check
|
|
||||||
* should determine whether the issues are duplicates. In most cases, where
|
|
||||||
* a check uses distinct issue names or descriptions for distinct issues,
|
|
||||||
* the consolidation process will simply be a matter of comparing these
|
|
||||||
* features for the two issues.
|
|
||||||
*
|
|
||||||
* @param existingIssue An issue that was previously reported by this
|
|
||||||
* Scanner check.
|
|
||||||
* @param newIssue An issue at the same URL path that has been newly
|
|
||||||
* reported by this Scanner check.
|
|
||||||
* @return An indication of which issue(s) should be reported in the main
|
|
||||||
* Scanner results. The method should return <code>-1</code> to report the
|
|
||||||
* existing issue only, <code>0</code> to report both issues, and
|
|
||||||
* <code>1</code> to report the new issue only.
|
|
||||||
*/
|
|
||||||
int consolidateDuplicateIssues(
|
|
||||||
IScanIssue existingIssue,
|
|
||||||
IScanIssue newIssue);
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IScannerInsertionPoint.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used to define an insertion point for use by active Scanner
|
|
||||||
* checks. Extensions can obtain instances of this interface by registering an
|
|
||||||
* <code>IScannerCheck</code>, or can create instances for use by Burp's own
|
|
||||||
* scan checks by registering an
|
|
||||||
* <code>IScannerInsertionPointProvider</code>.
|
|
||||||
*/
|
|
||||||
public interface IScannerInsertionPoint
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the value of a URL
|
|
||||||
* parameter.
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_URL = 0x00;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the value of a body
|
|
||||||
* parameter.
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_BODY = 0x01;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the value of an HTTP
|
|
||||||
* cookie.
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_COOKIE = 0x02;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the value of an item
|
|
||||||
* of data within an XML data structure.
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_XML = 0x03;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the value of a tag
|
|
||||||
* attribute within an XML structure.
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_XML_ATTR = 0x04;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the value of a
|
|
||||||
* parameter attribute within a multi-part message body (such as the name of
|
|
||||||
* an uploaded file).
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_MULTIPART_ATTR = 0x05;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the value of an item
|
|
||||||
* of data within a JSON structure.
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_JSON = 0x06;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the value of an AMF
|
|
||||||
* parameter.
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_AMF = 0x07;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the value of an HTTP
|
|
||||||
* request header.
|
|
||||||
*/
|
|
||||||
static final byte INS_HEADER = 0x20;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into a URL path folder.
|
|
||||||
*/
|
|
||||||
static final byte INS_URL_PATH_FOLDER = 0x21;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into a URL path folder.
|
|
||||||
* This is now deprecated; use <code>INS_URL_PATH_FOLDER</code> instead.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
static final byte INS_URL_PATH_REST = INS_URL_PATH_FOLDER;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the name of an added
|
|
||||||
* URL parameter.
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_NAME_URL = 0x22;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the name of an added
|
|
||||||
* body parameter.
|
|
||||||
*/
|
|
||||||
static final byte INS_PARAM_NAME_BODY = 0x23;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the body of the HTTP
|
|
||||||
* request.
|
|
||||||
*/
|
|
||||||
static final byte INS_ENTIRE_BODY = 0x24;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted into the URL path
|
|
||||||
* filename.
|
|
||||||
*/
|
|
||||||
static final byte INS_URL_PATH_FILENAME = 0x25;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted at a location manually
|
|
||||||
* configured by the user.
|
|
||||||
*/
|
|
||||||
static final byte INS_USER_PROVIDED = 0x40;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the insertion point is provided by an
|
|
||||||
* extension-registered
|
|
||||||
* <code>IScannerInsertionPointProvider</code>.
|
|
||||||
*/
|
|
||||||
static final byte INS_EXTENSION_PROVIDED = 0x41;
|
|
||||||
/**
|
|
||||||
* Used to indicate where the payload is inserted at an unknown location
|
|
||||||
* within the request.
|
|
||||||
*/
|
|
||||||
static final byte INS_UNKNOWN = 0x7f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the name of the insertion point.
|
|
||||||
*
|
|
||||||
* @return The name of the insertion point (for example, a description of a
|
|
||||||
* particular request parameter).
|
|
||||||
*/
|
|
||||||
String getInsertionPointName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the base value for this insertion point.
|
|
||||||
*
|
|
||||||
* @return the base value that appears in this insertion point in the base
|
|
||||||
* request being scanned, or <code>null</code> if there is no value in the
|
|
||||||
* base request that corresponds to this insertion point.
|
|
||||||
*/
|
|
||||||
String getBaseValue();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to build a request with the specified payload placed
|
|
||||||
* into the insertion point. There is no requirement for extension-provided
|
|
||||||
* insertion points to adjust the Content-Length header in requests if the
|
|
||||||
* body length has changed, although Burp-provided insertion points will
|
|
||||||
* always do this and will return a request with a valid Content-Length
|
|
||||||
* header.
|
|
||||||
* <b>Note:</b>
|
|
||||||
* Scan checks should submit raw non-encoded payloads to insertion points,
|
|
||||||
* and the insertion point has responsibility for performing any data
|
|
||||||
* encoding that is necessary given the nature and location of the insertion
|
|
||||||
* point.
|
|
||||||
*
|
|
||||||
* @param payload The payload that should be placed into the insertion
|
|
||||||
* point.
|
|
||||||
* @return The resulting request.
|
|
||||||
*/
|
|
||||||
byte[] buildRequest(byte[] payload);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to determine the offsets of the payload value within
|
|
||||||
* the request, when it is placed into the insertion point. Scan checks may
|
|
||||||
* invoke this method when reporting issues, so as to highlight the relevant
|
|
||||||
* part of the request within the UI.
|
|
||||||
*
|
|
||||||
* @param payload The payload that should be placed into the insertion
|
|
||||||
* point.
|
|
||||||
* @return An int[2] array containing the start and end offsets of the
|
|
||||||
* payload within the request, or null if this is not applicable (for
|
|
||||||
* example, where the insertion point places a payload into a serialized
|
|
||||||
* data structure, the raw payload may not literally appear anywhere within
|
|
||||||
* the resulting request).
|
|
||||||
*/
|
|
||||||
int[] getPayloadOffsets(byte[] payload);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the type of the insertion point.
|
|
||||||
*
|
|
||||||
* @return The type of the insertion point. Available types are defined in
|
|
||||||
* this interface.
|
|
||||||
*/
|
|
||||||
byte getInsertionPointType();
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IScannerInsertionPointProvider.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerScannerInsertionPointProvider()</code>
|
|
||||||
* to register a factory for custom Scanner insertion points.
|
|
||||||
*/
|
|
||||||
public interface IScannerInsertionPointProvider
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* When a request is actively scanned, the Scanner will invoke this method,
|
|
||||||
* and the provider should provide a list of custom insertion points that
|
|
||||||
* will be used in the scan. <b>Note:</b> these insertion points are used in
|
|
||||||
* addition to those that are derived from Burp Scanner's configuration, and
|
|
||||||
* those provided by any other Burp extensions.
|
|
||||||
*
|
|
||||||
* @param baseRequestResponse The base request that will be actively
|
|
||||||
* scanned.
|
|
||||||
* @return A list of
|
|
||||||
* <code>IScannerInsertionPoint</code> objects that should be used in the
|
|
||||||
* scanning, or
|
|
||||||
* <code>null</code> if no custom insertion points are applicable for this
|
|
||||||
* request.
|
|
||||||
*/
|
|
||||||
List<IScannerInsertionPoint> getInsertionPoints(
|
|
||||||
IHttpRequestResponse baseRequestResponse);
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IScannerListener.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerScannerListener()</code> to register a
|
|
||||||
* Scanner listener. The listener will be notified of new issues that are
|
|
||||||
* reported by the Scanner tool. Extensions can perform custom analysis or
|
|
||||||
* logging of Scanner issues by registering a Scanner listener.
|
|
||||||
*/
|
|
||||||
public interface IScannerListener
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is invoked when a new issue is added to Burp Scanner's
|
|
||||||
* results.
|
|
||||||
*
|
|
||||||
* @param issue An
|
|
||||||
* <code>IScanIssue</code> object that the extension can query to obtain
|
|
||||||
* details about the new issue.
|
|
||||||
*/
|
|
||||||
void newScanIssue(IScanIssue issue);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)IScopeChangeListener.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerScopeChangeListener()</code> to register
|
|
||||||
* a scope change listener. The listener will be notified whenever a change
|
|
||||||
* occurs to Burp's suite-wide target scope.
|
|
||||||
*/
|
|
||||||
public interface IScopeChangeListener
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is invoked whenever a change occurs to Burp's suite-wide
|
|
||||||
* target scope.
|
|
||||||
*/
|
|
||||||
void scopeChanged();
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)ISessionHandlingAction.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Extensions can implement this interface and then call
|
|
||||||
* <code>IBurpExtenderCallbacks.registerSessionHandlingAction()</code> to
|
|
||||||
* register a custom session handling action. Each registered action will be
|
|
||||||
* available within the session handling rule UI for the user to select as a
|
|
||||||
* rule action. Users can choose to invoke an action directly in its own right,
|
|
||||||
* or following execution of a macro.
|
|
||||||
*/
|
|
||||||
public interface ISessionHandlingAction
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used by Burp to obtain the name of the session handling
|
|
||||||
* action. This will be displayed as an option within the session handling
|
|
||||||
* rule editor when the user selects to execute an extension-provided
|
|
||||||
* action.
|
|
||||||
*
|
|
||||||
* @return The name of the action.
|
|
||||||
*/
|
|
||||||
String getActionName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is invoked when the session handling action should be
|
|
||||||
* executed. This may happen as an action in its own right, or as a
|
|
||||||
* sub-action following execution of a macro.
|
|
||||||
*
|
|
||||||
* @param currentRequest The base request that is currently being processed.
|
|
||||||
* The action can query this object to obtain details about the base
|
|
||||||
* request. It can issue additional requests of its own if necessary, and
|
|
||||||
* can use the setter methods on this object to update the base request.
|
|
||||||
* @param macroItems If the action is invoked following execution of a
|
|
||||||
* macro, this parameter contains the result of executing the macro.
|
|
||||||
* Otherwise, it is
|
|
||||||
* <code>null</code>. Actions can use the details of the macro items to
|
|
||||||
* perform custom analysis of the macro to derive values of non-standard
|
|
||||||
* session handling tokens, etc.
|
|
||||||
*/
|
|
||||||
void performAction(
|
|
||||||
IHttpRequestResponse currentRequest,
|
|
||||||
IHttpRequestResponse[] macroItems);
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)ITab.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.awt.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used to provide Burp with details of a custom tab that will
|
|
||||||
* be added to Burp's UI, using a method such as
|
|
||||||
* <code>IBurpExtenderCallbacks.addSuiteTab()</code>.
|
|
||||||
*/
|
|
||||||
public interface ITab
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Burp uses this method to obtain the caption that should appear on the
|
|
||||||
* custom tab when it is displayed.
|
|
||||||
*
|
|
||||||
* @return The caption that should appear on the custom tab when it is
|
|
||||||
* displayed.
|
|
||||||
*/
|
|
||||||
String getTabCaption();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Burp uses this method to obtain the component that should be used as the
|
|
||||||
* contents of the custom tab when it is displayed.
|
|
||||||
*
|
|
||||||
* @return The component that should be used as the contents of the custom
|
|
||||||
* tab when it is displayed.
|
|
||||||
*/
|
|
||||||
Component getUiComponent();
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)ITempFile.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This interface is used to hold details of a temporary file that has been
|
|
||||||
* created via a call to
|
|
||||||
* <code>IBurpExtenderCallbacks.saveToTempFile()</code>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public interface ITempFile
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the contents of the buffer that was saved
|
|
||||||
* in the temporary file.
|
|
||||||
*
|
|
||||||
* @return The contents of the buffer that was saved in the temporary file.
|
|
||||||
*/
|
|
||||||
byte[] getBuffer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is deprecated and no longer performs any action.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
void delete();
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package burp;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @(#)ITextEditor.java
|
|
||||||
*
|
|
||||||
* Copyright PortSwigger Ltd. All rights reserved.
|
|
||||||
*
|
|
||||||
* This code may be used to extend the functionality of Burp Suite Community Edition
|
|
||||||
* and Burp Suite Professional, provided that this usage does not violate the
|
|
||||||
* license terms for those products.
|
|
||||||
*/
|
|
||||||
import java.awt.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface is used to provide extensions with an instance of Burp's raw
|
|
||||||
* text editor, for the extension to use in its own UI. Extensions should call
|
|
||||||
* <code>IBurpExtenderCallbacks.createTextEditor()</code> to obtain an instance
|
|
||||||
* of this interface.
|
|
||||||
*/
|
|
||||||
public interface ITextEditor
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* This method returns the UI component of the editor, for extensions to add
|
|
||||||
* to their own UI.
|
|
||||||
*
|
|
||||||
* @return The UI component of the editor.
|
|
||||||
*/
|
|
||||||
Component getComponent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to control whether the editor is currently editable.
|
|
||||||
* This status can be toggled on and off as required.
|
|
||||||
*
|
|
||||||
* @param editable Indicates whether the editor should be currently
|
|
||||||
* editable.
|
|
||||||
*/
|
|
||||||
void setEditable(boolean editable);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the currently displayed text in the editor.
|
|
||||||
*
|
|
||||||
* @param text The text to be displayed.
|
|
||||||
*/
|
|
||||||
void setText(byte[] text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to retrieve the currently displayed text.
|
|
||||||
*
|
|
||||||
* @return The currently displayed text.
|
|
||||||
*/
|
|
||||||
byte[] getText();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to determine whether the user has modified the
|
|
||||||
* contents of the editor.
|
|
||||||
*
|
|
||||||
* @return An indication of whether the user has modified the contents of
|
|
||||||
* the editor since the last call to
|
|
||||||
* <code>setText()</code>.
|
|
||||||
*/
|
|
||||||
boolean isTextModified();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to obtain the currently selected text.
|
|
||||||
*
|
|
||||||
* @return The currently selected text, or
|
|
||||||
* <code>null</code> if the user has not made any selection.
|
|
||||||
*/
|
|
||||||
byte[] getSelectedText();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method can be used to retrieve the bounds of the user's selection
|
|
||||||
* into the displayed text, if applicable.
|
|
||||||
*
|
|
||||||
* @return An int[2] array containing the start and end offsets of the
|
|
||||||
* user's selection within the displayed text. If the user has not made any
|
|
||||||
* selection in the current message, both offsets indicate the position of
|
|
||||||
* the caret within the editor.
|
|
||||||
*/
|
|
||||||
int[] getSelectionBounds();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is used to update the search expression that is shown in the
|
|
||||||
* search bar below the editor. The editor will automatically highlight any
|
|
||||||
* regions of the displayed text that match the search expression.
|
|
||||||
*
|
|
||||||
* @param expression The search expression.
|
|
||||||
*/
|
|
||||||
void setSearchExpression(String expression);
|
|
||||||
}
|
|
||||||
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.1";
|
||||||
|
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 synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
|
||||||
|
// 添加到全局变量中,便于Databoard检索
|
||||||
|
if (!Objects.equals(host, "") && host != null) {
|
||||||
|
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
||||||
|
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
|
||||||
|
|
||||||
|
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
|
||||||
|
Set<String> combinedSet = new LinkedHashSet<>(existingList);
|
||||||
|
combinedSet.addAll(newList);
|
||||||
|
return new ArrayList<>(combinedSet);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (flag) {
|
||||||
|
// 数据存储在BurpSuite空间内
|
||||||
|
DataManager dataManager = new DataManager(api);
|
||||||
|
PersistedObject persistedObject = PersistedObject.persistedObject();
|
||||||
|
gRuleMap.forEach((kName, vList) -> {
|
||||||
|
PersistedList<String> persistedList = PersistedList.persistedStringList();
|
||||||
|
persistedList.addAll(vList);
|
||||||
|
persistedObject.setStringList(kName, persistedList);
|
||||||
|
});
|
||||||
|
dataManager.putData("data", host, persistedObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gRuleMap;
|
||||||
|
});
|
||||||
|
|
||||||
|
String[] splitHost = host.split("\\.");
|
||||||
|
String onlyHost = host.split(":")[0];
|
||||||
|
|
||||||
|
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
|
||||||
|
|
||||||
|
if (!Config.globalDataMap.containsKey(anyHost) && !anyHost.isEmpty()) {
|
||||||
|
// 添加通配符Host,实际数据从查询哪里将所有数据提取
|
||||||
|
Config.globalDataMap.put(anyHost, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.globalDataMap.containsKey("*")) {
|
||||||
|
// 添加通配符全匹配,同上
|
||||||
|
Config.globalDataMap.put("*", new HashMap<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/main/java/hae/utils/DataManager.java
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package hae.utils;
|
||||||
|
|
||||||
|
import burp.api.montoya.MontoyaApi;
|
||||||
|
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||||
|
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||||
|
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||||
|
import burp.api.montoya.persistence.PersistedList;
|
||||||
|
import burp.api.montoya.persistence.PersistedObject;
|
||||||
|
import burp.api.montoya.persistence.Persistence;
|
||||||
|
import hae.component.board.message.MessageTableModel;
|
||||||
|
import hae.instances.http.utils.RegularMatcher;
|
||||||
|
|
||||||
|
public class DataManager {
|
||||||
|
private final MontoyaApi api;
|
||||||
|
private final Persistence persistence;
|
||||||
|
|
||||||
|
public DataManager(MontoyaApi api) {
|
||||||
|
this.api = api;
|
||||||
|
this.persistence = api.persistence();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void putData(String dataType, String dataName, PersistedObject persistedObject) {
|
||||||
|
if (persistence.extensionData().getChildObject(dataName) != null) {
|
||||||
|
persistence.extensionData().deleteChildObject(dataName);
|
||||||
|
}
|
||||||
|
persistence.extensionData().setChildObject(dataName, persistedObject);
|
||||||
|
saveIndex(dataType, dataName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadData(MessageTableModel messageTableModel) {
|
||||||
|
// 1. 获取索引
|
||||||
|
PersistedList<String> dataIndex = persistence.extensionData().getStringList("data"); // 数据索引
|
||||||
|
PersistedList<String> messageIndex = persistence.extensionData().getStringList("message"); // 消息索引
|
||||||
|
|
||||||
|
// 2. 从索引获取数据
|
||||||
|
loadHaEData(dataIndex);
|
||||||
|
loadMessageData(messageIndex, messageTableModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveIndex(String indexName, String indexValue) {
|
||||||
|
PersistedList<String> indexList = persistence.extensionData().getStringList(indexName);
|
||||||
|
|
||||||
|
if (indexList != null && !indexList.isEmpty()) {
|
||||||
|
persistence.extensionData().deleteStringList(indexName);
|
||||||
|
} else {
|
||||||
|
indexList = PersistedList.persistedStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!indexList.contains(indexValue)) {
|
||||||
|
indexList.add(indexValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
persistence.extensionData().setStringList(indexName, indexList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadHaEData(PersistedList<String> dataIndex) {
|
||||||
|
if (dataIndex != null && !dataIndex.isEmpty()) {
|
||||||
|
dataIndex.parallelStream().forEach(index -> {
|
||||||
|
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||||
|
dataObj.stringListKeys().forEach(dataKey -> {
|
||||||
|
RegularMatcher.putDataToGlobalMap(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMessageData(PersistedList<String> messageIndex, MessageTableModel messageTableModel) {
|
||||||
|
if (messageIndex != null && !messageIndex.isEmpty()) {
|
||||||
|
messageIndex.parallelStream().forEach(index -> {
|
||||||
|
PersistedObject dataObj = persistence.extensionData().getChildObject(index);
|
||||||
|
if (dataObj != null) {
|
||||||
|
HttpRequestResponse messageInfo = dataObj.getHttpRequestResponse("messageInfo");
|
||||||
|
String comment = dataObj.getString("comment");
|
||||||
|
String color = dataObj.getString("color");
|
||||||
|
HttpRequest request = messageInfo.request();
|
||||||
|
HttpResponse response = messageInfo.response();
|
||||||
|
String method = request.method();
|
||||||
|
String url = request.url();
|
||||||
|
String status = String.valueOf(response.statusCode());
|
||||||
|
String length = String.valueOf(response.toByteArray().length());
|
||||||
|
messageTableModel.add(messageInfo, url, method, status, length, comment, color, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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-Z]{1,4})|(?:(?:/|\.\./|\./)?[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}/[^"'><,;|()]{1,}(?:\.[a-zA-Z]{1,4}|action)?)))(?:[\?|#][^"|']{0,})?(?:"|')
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: gray
|
||||||
|
scope: response body
|
||||||
|
engine: nfa
|
||||||
|
sensitive: true
|
||||||
|
- name: Source Map
|
||||||
|
loaded: true
|
||||||
|
f_regex: (\.js\.map)
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: pink
|
||||||
|
scope: response body
|
||||||
|
engine: dfa
|
||||||
|
sensitive: false
|
||||||
|
- name: Create Script
|
||||||
|
loaded: true
|
||||||
|
f_regex: (\{[^{}]*\}\s*\[[^\s]*\]\s*\+\s*"[^\s]*\.js")
|
||||||
|
s_regex: '"?([\w].*?)"?:"(.*?)"'
|
||||||
|
format: '{0}.{1}'
|
||||||
|
color: green
|
||||||
|
scope: response body
|
||||||
|
engine: nfa
|
||||||
|
sensitive: false
|
||||||
|
- name: URL Schemes
|
||||||
|
loaded: true
|
||||||
|
f_regex: (\b(?![\w]{0,10}?https?://)(([-A-Za-z0-9]{1,20})://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]))
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: yellow
|
||||||
|
scope: response body
|
||||||
|
engine: nfa
|
||||||
|
sensitive: false
|
||||||
|
- name: Router Push
|
||||||
|
loaded: true
|
||||||
|
f_regex: (\$router\.push)
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: magenta
|
||||||
|
scope: response body
|
||||||
|
engine: dfa
|
||||||
|
sensitive: false
|
||||||
|
- name: All URL
|
||||||
|
loaded: true
|
||||||
|
f_regex: (https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;\u4E00-\u9FFF]+[-A-Za-z0-9+&@#/%=~_|])
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: gray
|
||||||
|
scope: response body
|
||||||
|
engine: nfa
|
||||||
|
sensitive: true
|
||||||
|
- name: Request URI
|
||||||
|
loaded: true
|
||||||
|
f_regex: ' ((?!.*\.js(\?.*)?$)(.*?[^.js$])) '
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: gray
|
||||||
|
scope: request line
|
||||||
|
engine: nfa
|
||||||
|
sensitive: false
|
||||||
|
- name: 302 Location
|
||||||
|
loaded: true
|
||||||
|
f_regex: 'Location: (.*?)\n'
|
||||||
|
s_regex: ''
|
||||||
|
format: '{0}'
|
||||||
|
color: gray
|
||||||
|
scope: response header
|
||||||
|
engine: nfa
|
||||||
|
sensitive: false
|
||||||