├── .github
└── workflows
│ └── maven.yml
├── .gitignore
├── README.Update.md
├── README.md
├── doc
├── NOVASEC.jpg
├── image-20220511142914622.png
├── process.png
├── show.gif
└── v0.4.5.png
├── passive-scan-client.iml
├── pom.xml
└── src
└── main
├── java
├── burp
│ ├── BurpExtender.java
│ ├── Config.java
│ ├── GUI.java
│ ├── HttpAndHttpsProxy.java
│ ├── HttpLogTable.java
│ ├── HttpLogTableModel.java
│ ├── LogEntry.java
│ └── Utils.java
└── plus
│ ├── Base64.java
│ ├── UtilsPlus.java
│ └── YamlReader.java
└── resources
└── psc.config.yml
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Java CI with Maven
10 |
11 | on:
12 | # 在创建新标签时触发
13 | push:
14 | tags:
15 | - '*' # 匹配任何标签
16 | # 在发布新版本时触发
17 | release:
18 | types: [created]
19 |
20 | jobs:
21 | build:
22 |
23 | runs-on: ubuntu-latest
24 |
25 | steps:
26 | - uses: actions/checkout@v3
27 | - name: Set up JDK 8
28 | uses: actions/setup-java@v3
29 | with:
30 | java-version: '8'
31 | distribution: 'temurin'
32 | cache: maven
33 | - name: Build with Maven
34 | run: mvn -B package --file pom.xml
35 |
36 | - name: Archive Jar file
37 | uses: actions/upload-artifact@v2
38 | with:
39 | name: jar-with-dependencies
40 | path: ./target/*-jar-with-dependencies.jar # 指定二进制文件的路径
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Java template
3 | # Compiled class file
4 | *.class
5 |
6 | # Log file
7 | *.log
8 |
9 | # BlueJ files
10 | *.ctxt
11 |
12 | # Mobile Tools for Java (J2ME)
13 | .mtj.tmp/
14 |
15 | # Package Files #
16 | # *.jar
17 | *.war
18 | *.nar
19 | *.ear
20 | *.zip
21 | *.tar.gz
22 | *.rar
23 |
24 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
25 | hs_err_pid*
26 | target
27 | out
28 | .DS_Store
29 | .idea/*
30 | release
31 |
--------------------------------------------------------------------------------
/README.Update.md:
--------------------------------------------------------------------------------
1 | # 更新记录(本分支)
2 |
3 | ##### v0.4.12.0 优化代码样式
4 |
5 | ```
6 | 修改黑名单域名过滤为黑名单URL过滤,过滤包含指定关键字的url.
7 | //正则判断
8 | Pattern.compile("^.*("+regx+").*$",Pattern.CASE_INSENSITIVE);
9 | //无正则情况全部放行
10 | ```
11 |
12 | ##### v0.4.11.4 优化代码样式
13 |
14 | ```
15 | 1、根据IDE提示优化代码
16 | 2、删除不需要的 DEL_ERROR_KEY 选项
17 | 3、增加DECODE_MAX_TIMES: 3选项,指定最多解码参数值为Josn格式的次数.
18 | ```
19 |
20 | ##### v0.4.11.3 优化请求体内的参数为Json值处理
21 |
22 | ```
23 | 1、优化显示UNIQ按钮为HASH按钮,并修改相应配置名称
24 | 2、优化输出提示,输出按钮点击操作行为结果
25 | 3、增加代理服务器响应502处理
26 | 4、进一步处理参数值为Josn格式的请求报文处理,并最多进行两次URL解码
27 | searchFor=111111&goButton={"a":"b"}
28 | ==> {"goButton.a":"Y","searchFor":"Y","test":"Y"}
29 | 5、修改消息输出级别 SHOW_MSG_LEVEL: 1,
30 | 012可选,数字越大,输出的信息越多
31 | ```
32 |
33 | ##### v0.4.11.1 优化参数名称 非必要更新
34 |
35 | ```
36 | 1、修改配置文件中的所有键 需要修改配置文件
37 | 2、将所有常用字符串修改为常量
38 | ```
39 |
40 | ##### v0.4.11.0 增加AUTH功能按钮
41 |
42 | ```
43 | 1、增加AUTH功能按钮,如果开启该功能,会将URL+auth信息作为hashset和hashmap的键。
44 | DEFAULT_SELECTED_AUTH: false
45 |
46 | 2、 Auth信息从Cookie内、参数内、请求头内获取,如sessionid、token头,参考
47 | DEFAULT_AUTH_INFO_STR: "token|auth|sessid|session"
48 | 注:使用String.lowercase().indexof()匹配
49 |
50 | 3、支持配置转发失败后是否删除hashset和hashmap内的记录。
51 | DEFAULT_DEL_ERROR_KEY: false
52 |
53 | 4、优化嵌套Json格式下的Cookie参数记录.
54 |
55 | 5、统一SMART功能 Json参数存在记录值
56 | {"PHPSESSID":"YES","category.id":"YES","category.name":"YES","id":"YES","name":"YES","photoUrls":"YES","security":"YES","status":"YES","tags":"YES"}
57 | ```
58 |
59 | ##### v0.4.10.1 优化SMART功能 请求类型过滤
60 |
61 | ```
62 | 1、修复SMART模式下,不同请求格式下可能造成的发包遗漏
63 |
64 | 例如:
65 | xml格式下存在漏洞、json格式下不存在漏洞。
66 | 但旧版本会只按照参数值记录,导致同样参数的请求只会发送一次。
67 | 新版本以URL+请求体类型作为hashmap的键,
68 | 同样的URL、同样的请求体类型才会作为相同的请求处理。
69 |
70 | byte CONTENT_TYPE_NONE = 0;
71 | byte CONTENT_TYPE_URL_ENCODED = 1;
72 | byte CONTENT_TYPE_MULTIPART = 2;
73 | byte CONTENT_TYPE_XML = 3;
74 | byte CONTENT_TYPE_JSON = 4;
75 | byte CONTENT_TYPE_AMF = 5;
76 | byte CONTENT_TYPE_UNKNOWN = -1;
77 | ```
78 |
79 | ##### v0.4.10.0 优化域名过滤
80 |
81 | ```
82 | 1、修改Domain为TargetHost,并优化正则匹配过程
83 | //无正则情况全部放行
84 | Pattern pat = Pattern.compile("^.*("+regx+").*$",Pattern.CASE_INSENSITIVE);//正则判断
85 |
86 | 2、增加BlackHost 窗口,按[后缀]过滤目标
87 | //无正则情况全部放行
88 | Pattern pat = Pattern.compile("^.*("+regx+")$",Pattern.CASE_INSENSITIVE);//正则判断
89 |
90 | 3、修改ExcludeSuffix为BlackSuffix
91 | //无正则情况全部放行
92 | //无后缀情况全部放行
93 | Pattern pat = Pattern.compile("^("+regx+")$",Pattern.CASE_INSENSITIVE);//正则判断
94 |
95 | 4、增加SHOW_DEBUG_MSG标志,控制显示调试信息
96 |
97 | 5、PSC默认配置文件已修改,外部配置文件需更新
98 | DEFAULT_TARGET_HOST_REGX: ""
99 | DEFAULT_BLACK_HOST_REGX: "bing.com|baidu.com|microsoft.com|msn.com|nelreports.net|azure.com|bdstatic.com"
100 | DEFAULT_BLACK_SUFFIX_REGX: "js|css|jpeg|gif|jpg|png|pdf|rar|zip|docx|doc|svg|jpeg|ico|woff|woff2|ttf|otf"
101 | DEFAULT_SHOW_DEBUG_MSG: false
102 |
103 | 注意:
104 | 1、右键转发到PSC功能可强制 绕过 正则匹配目标功能 扫描。
105 | 2、匹配顺序: 白名单域名匹配>>黑名单域名匹配>>黑名单后缀匹配
106 |
107 | ```
108 |
109 |
110 |
111 | ##### v0.4.9.2 支持去+重请求数量限制
112 |
113 | ```
114 | SMART功能通过HASHMAP去重
115 | UNIQ功能通过HASHSET去重
116 | 当存在大量不同的请求时,可能会超出内存限制。
117 | 解决方法:
118 | 1、通过Clear按钮手动清空
119 | 2、新增LIMIT配置,当超出指定大小时自动清空(新增功能)
120 |
121 | DEFAULT_HASH_MAP_LIMIT: 500
122 | DEFAULT_HASH_SET_LIMIT: 1000
123 |
124 | 注意:使用自定义启动配置文件的用户需要更新(psc.config.yml)配置文件
125 | ```
126 |
127 | ##### v0.4.9.1 优化SAMRT过滤
128 |
129 | ```
130 | V0.4.9下只对JSon参数做一层解封处理,对于多级Json参数无法处理.本版本实现Json请求包的递归解析.
131 |
132 | 三层Json数据格式处理实例:
133 | {"ocid":true,"targetType":true,"user":true,
134 | "inner":{"ocid":true,"targetType":true,"user":true,
135 | "inner":{"ocid":true,"targetType":true,"user":true}}}
136 |
137 | 被记录的数据:
138 | {"inner.inner.ocid":"1","inner.inner.targetType":"1","inner.inner.user":"1","inner.ocid":"1","inner.targetType":"1","inner.user":"1","ocid":"1","targetType":"1","user":"1"}
139 |
140 | 按钮优先级:
141 | PARAM >SMART> UNIQ
142 | SMART 和 UNIQUE 两个二选一即可, 多选只是增加计算量.
143 | ```
144 |
145 | ##### v0.4.9 增加SMART按钮
146 |
147 | ```
148 | 通过判断请求的参数是否已经请求过,来进行过滤请求报文转发
149 | 示例 如:
150 | 请求1 http://www.baidu.com/index.php?a=1&b=2 放行
151 | HashMap记录 http://www.baidu.com/index.php {a:true,b:true}
152 | 请求2 http://www.baidu.com/index.php?a=2 过滤
153 | 请求3 http://www.baidu.com/index.php?b=2 过滤
154 | 请求4 http://www.baidu.com/index.php?c=2 放行
155 | HashMap记录 http://www.baidu.com/index.php {a:true,b:true,c:true}
156 | DEFAULT_SELECTED_SAMRT: false //新增
157 | ```
158 |
159 |
160 |
161 | ##### v0.4.8.2 扩展默认启动项配置
162 |
163 | ```
164 | 1、可在配置文件配置是否默认启用UNIQ和PARAM按钮
165 | DEFAULT_EXTENSION_NAME: "PSC"
166 | DEFAULT_VERSION: "0.4.8.2"
167 | DEFAULT_PROXY_HOST: "127.0.0.1"
168 | DEFAULT_PROXY_PORT: 7777
169 | DEFAULT_PROXY_USERNAME: ""
170 | DEFAULT_PROXY_PASSWORD: ""
171 | DEFAULT_PROXY_TIMEOUT: 5000
172 | DEFAULT_DOMAIN_REGX: ""
173 | DEFAULT_SUFFIX_REGX: "js|css|jpeg|gif|jpg|png|pdf|rar|zip|docx|doc|svg|jpeg|ico|woff|woff2|ttf|otf"
174 | DEFAULT_INTERVAL_TIME: 5000
175 | DEFAULT_SELECTED_UNIQ: false //新增
176 | DEFAULT_SELECTED_PARAM: false //新增
177 | ```
178 |
179 | ##### v0.4.8.1 优化文件
180 |
181 | ```
182 | 1、清理release文件夹
183 | 2、发布release
184 | 3、清理测试代码
185 | ```
186 |
187 | ##### v0.4.8 修复BUG
188 |
189 | ```
190 | 1、对于没有成功发送到代理服务器的请求,不计入hashset.
191 | 2、修复GetPathExt函数处理Path为[/]时产生的异常.
192 | ```
193 |
194 | ##### v0.4.7 增加自定义默认配置文件功能
195 |
196 | ```
197 | 优先从插件所在目录读取psc.config.yml文件
198 | 文件不存在时,从jar包内部读取psc.config.yml文件
199 |
200 | DEFAULT_EXTENSION_NAME: "Passive Scan Client"
201 | DEFAULT_VERSION: "0.4.7"
202 | DEFAULT_PROXY_HOST: "127.0.0.1"
203 | DEFAULT_PROXY_PORT: 7777
204 | DEFAULT_PROXY_USERNAME: ""
205 | DEFAULT_PROXY_PASSWORD: ""
206 | DEFAULT_PROXY_TIMEOUT: 5000
207 | DEFAULT_DOMAIN_REGX: ""
208 | DEFAULT_SUFFIX_REGX: "js|css|jpeg|gif|jpg|png|pdf|rar|zip|docx|doc|svg|jpeg|ico|woff|woff2|ttf|otf"
209 | DEFAULT_INTERVAL_TIME: 5000
210 | ```
211 |
212 | ##### v0.4.6 优化URL后缀匹配规则
213 |
214 | ```
215 | //无后缀情况全部放行
216 | //正则判断
217 | Pattern pat = Pattern.compile("^("+regx+")$",Pattern.CASE_INSENSITIVE);
218 | 获取URL后缀后再进行正则匹配,防止URL路径中存在类似后缀名的目录时产生的误报。
219 | ```
220 | ##### v0.4.5 无参数请求过滤按钮20221122
221 |
222 | ```
223 | 1、增加PARAM框,支持过滤没有参数的请求,默认关闭。 PS:点击PARAM按钮后,将不会转发没有参数的请求。
224 | ```
225 | 
226 |
227 |
228 | ##### v0.4.4 重复请求过滤按钮 20221122
229 |
230 | ```
231 | 1、修改ReqUinq框为UNIQ按钮,默认关闭。 PS:点击UNIQ按钮后,将不会转发已经转发过的请求。
232 | ```
233 |
234 | ##### v0.4.3 请求去重 20221122
235 |
236 | ```
237 | 1、优化变量名称
238 | 2、添加ReqUinq 框,仅当ReqUinq 内容设置为 true 时,对请求基于URL及Body hash进行hash记录。注: 不会记录40X、50X、访问拒绝等响应状态码。
239 | ```
240 |
241 | ##### v0.4.2 修复新增 20221122
242 |
243 | ```
244 | 1、参考#34 修复代理用户名密码为空时判断BUG. 更新详情: https://github.com/c0ny1/passive-scan-client/pulls.
245 | 2、对所有请求基于URL及Body hash进行hash记录,当下一次遇到相同请求时忽略请求报文, 注:点击clear按钮可清空hashset.
246 | ```
247 |
248 |
249 | ##### v0.4.1 合并更新 20221122
250 |
251 | ```
252 | 1、基于c0ny1的passive-scan-client-0.3.0合并更新Pull #27 #21 #22. 更新详情: https://github.com/c0ny1/passive-scan-client/pulls.
253 | ```
254 |
255 |
256 | ##### v0.4.0 R4ph4e1-0x01 pull
257 |
258 | ```
259 | v0.4.0 更新详情: https://github.com/c0ny1/passive-scan-client/pull/27
260 | 基于https://github.com/Conanjun/passive-scan-client-and-sendto,增加右键手动转发的菜单,拓展插件的灵活性。
261 | ```
262 |
263 | 
264 |
265 | ##### v0.3.0 原版更新20210728
266 |
267 | ```
268 | v0.1 支持流量过滤(域名和后缀)
269 | v0.1 支持用户名密码认证代理
270 | v0.3 增加请求转发间隔时间
271 | 原Git地址:https://github.com/c0ny1/passive-scan-client/
272 | ```
273 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Passive Scan Client | Burp被动扫描流量转发插件
2 |
3 | ## 0x00 UI界面(本分支)
4 |
5 | 
6 |
7 | ## 0x01 功能介绍(本分支)
8 |
9 | 本分支详细更新记录请查看 README.Update.md
10 |
11 | ```
12 | 1、右键转发到PSC进行扫描
13 | 1、右键转发功能不需要开启[run]按钮,[run]是开启Proxy流量自动转发扫描.
14 | 2、右键转发功能能够绕过白名单HOST、黑名单URL和黑名单后缀过滤
15 |
16 | 2、支持默认启动配置。
17 | 1、优先从插件所在目录读取psc.config.yml文件
18 | 2、不存在时,从jar包内部读取psc.config.yml文件
19 | 3、默认功能都是关闭的,机器性能足够建议开启AUTH、SMART功能
20 |
21 | 3、使用多种算法实现请求去重,不转发相同流量.
22 | 1、【HASH】按钮 实现 (URL及参数)完全相同请求去重(记录请求hashset去重)
23 | 2、【PARAM】按钮 实现 (URL及参数)支持忽略无参数请求(由burp内置sdk解析实现)
24 | 3、【SMART】按钮 实现 (URL及参数)去重相同参数的请求(由burp内置sdk解析实现)
25 | 支持Json嵌套请求体自动解码(json递归处理)
26 | 支持Json格式参数值的自动解码(由burp内置sdk解析实现)
27 | 4、【AUTH】按钮 实现 支持根据不同的认证信息作为转发凭证(Cookie、token不同时,会作为不同请求)
28 |
29 | 4、新增黑名单HOST正则过滤。白名单域名匹配>>黑名单URL匹配>>黑名单后缀匹配
30 |
31 | 5、【IGNORE】按钮 不显示请求转发到其他服务的具体响应,如果对性能有要求可以开启,一般不需要开启
32 |
33 | ```
34 |
35 |
36 | ## 0x02 原始项目
37 |
38 | https://github.com/c0ny1/passive-scan-client
39 |
40 | ## 0x03 插件简介
41 |
42 | ```
43 | Q1: 将浏览器代理到被动扫描器上,访问网站变慢,甚至有时被封ip,这该怎么办?
44 | Q2: 需要人工渗透的同时后台进行被动扫描,到底是代理到burp还是被动扫描器?
45 | Q3: ......
46 | ```
47 |
48 | 该插件正是为了解决该问题,将`正常访问网站的流量`与`提交给被动扫描器的流量`分开,互不影响。
49 |
50 | 
51 |
52 | ## 0x04 下载编译
53 |
54 | ```
55 | 手动编译: mvn package
56 | 发布版本: release
57 | ```
58 |
59 | ## 0x05 插件演示
60 |
61 | 可以通过插件将流量转发到各种被动式扫描器中,这里我选`xray`来演示.
62 |
63 | 
64 |
65 |
66 |
67 | ## 0x06 一些被动式漏洞扫描器
68 |
69 | * [GourdScanV2](https://github.com/ysrc/GourdScanV2) 由ysrc出品的基于sqlmapapi的被动式漏洞扫描器
70 | * [xray](https://github.com/chaitin/xray) 由长亭科技出品的一款被动式漏洞扫描器
71 | * [w13scan](https://github.com/boy-hack/w13scan) Passive Security Scanner (被动安全扫描器)
72 | * [Fox-scan](https://github.com/fengxuangit/Fox-scan) 基于sqlmapapi的主动和被动资源发现的漏洞扫描工具
73 | * [SQLiScanner](https://github.com/0xbug/SQLiScanner) 一款基于sqlmapapi和Charles的被动SQL注入漏洞扫描工具
74 | * [sqli-hunter](https://github.com/zt2/sqli-hunter) 基于sqlmapapi,ruby编写的漏洞代理型检测工具
75 | * [passive_scan](https://github.com/netxfly/passive_scan) 基于http代理的web漏洞扫描器的实现
76 |
77 | ## 0x07 NEED STAR And ISSUE
78 |
79 | ```
80 | 1、右上角点击Star支持更新.
81 | 2、ISSUE或NOVASEC提更新需求.
82 | ```
83 |
84 | 
85 |
--------------------------------------------------------------------------------
/doc/NOVASEC.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winezer0/passive-scan-client-plus/ccd0393d8ea6639544c6b25aecb2590c8ffe0a96/doc/NOVASEC.jpg
--------------------------------------------------------------------------------
/doc/image-20220511142914622.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winezer0/passive-scan-client-plus/ccd0393d8ea6639544c6b25aecb2590c8ffe0a96/doc/image-20220511142914622.png
--------------------------------------------------------------------------------
/doc/process.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winezer0/passive-scan-client-plus/ccd0393d8ea6639544c6b25aecb2590c8ffe0a96/doc/process.png
--------------------------------------------------------------------------------
/doc/show.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winezer0/passive-scan-client-plus/ccd0393d8ea6639544c6b25aecb2590c8ffe0a96/doc/show.gif
--------------------------------------------------------------------------------
/doc/v0.4.5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winezer0/passive-scan-client-plus/ccd0393d8ea6639544c6b25aecb2590c8ffe0a96/doc/v0.4.5.png
--------------------------------------------------------------------------------
/passive-scan-client.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | me.gv7.tools.burpextend
8 | passive-scan-client
9 | 0.4.22-jdk8
10 |
11 |
12 |
13 |
14 | net.portswigger.burp.extender
15 | burp-extender-api
16 | 1.7.22
17 |
18 |
19 | org.yaml
20 | snakeyaml
21 | 1.33
22 |
23 |
24 |
25 | com.alibaba
26 | fastjson
27 | 1.2.83
28 |
29 |
30 |
31 |
32 |
33 | org.apache.maven.plugins
34 | maven-assembly-plugin
35 |
36 |
37 | package
38 |
39 | single
40 |
41 |
42 |
43 |
44 |
45 | jar-with-dependencies
46 |
47 |
48 |
49 |
50 | org.apache.maven.plugins
51 | maven-compiler-plugin
52 | 3.1
53 |
54 | 8
55 | 8
56 | utf-8
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/main/java/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import plus.YamlReader;
4 |
5 | import javax.swing.*;
6 | import java.awt.event.ActionEvent;
7 | import java.awt.event.ActionListener;
8 | import java.io.PrintWriter;
9 | import java.util.ArrayList;
10 | import java.util.Arrays;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.concurrent.ExecutorService;
14 | import java.util.concurrent.Executors;
15 |
16 |
17 | public class BurpExtender implements IBurpExtender, IProxyListener, IContextMenuFactory {
18 | public static IBurpExtenderCallbacks callbacks;
19 | public static IExtensionHelpers helpers;
20 | public static PrintWriter stdout;
21 | public static PrintWriter stderr;
22 | public static final List log = new ArrayList();
23 |
24 | private ExecutorService executorService;
25 | public static String extensionName;
26 | public static String version;
27 |
28 | @Override
29 | public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
30 | this.callbacks = callbacks;
31 | this.helpers = callbacks.getHelpers();
32 | this.stdout = new PrintWriter(callbacks.getStdout(),true);
33 | this.stderr = new PrintWriter(callbacks.getStderr(),true);
34 | this.executorService = Executors.newSingleThreadExecutor(); //创建线程的执行器服务
35 |
36 | SwingUtilities.invokeLater(new Runnable() {
37 | public void run() {
38 | initLoadConfigFile(callbacks); //加载配置文件
39 | version = Config.VERSION;
40 | extensionName= Config.EXTENSION_NAME;
41 | callbacks.setExtensionName(extensionName + " " + version);
42 | callbacks.addSuiteTab(new GUI(callbacks, extensionName)); //加载GUI窗口
43 | callbacks.registerProxyListener(BurpExtender.this); //注册监听消息处理
44 | callbacks.registerContextMenuFactory(BurpExtender.this); //注册右键菜单Factory
45 | }
46 | });
47 | }
48 |
49 |
50 | //callbacks.registerContextMenuFactory(this);//必须注册右键菜单Factory
51 | //实现右键 感谢原作者Conanjun
52 | @Override
53 | public List createMenuItems(IContextMenuInvocation invocation) {
54 | final IHttpRequestResponse[] messages = invocation.getSelectedMessages();
55 | JMenuItem i1 = new JMenuItem(String.format("Send to %s", Config.EXTENSION_NAME));
56 | i1.addActionListener(new ActionListener() {
57 | @Override
58 | public void actionPerformed(ActionEvent e) {
59 | for (final IHttpRequestResponse message : messages) {
60 | executorService.submit(new Runnable() {
61 | @Override
62 | public void run() {
63 | synchronized (log) {
64 | int row = log.size();
65 | String method = helpers.analyzeRequest(message).getMethod();
66 | //byte[] req = message.getRequest();
67 | //String req_str = new String(req);
68 | //向代理转发请求
69 | Map mapResult = null;
70 |
71 | String url = helpers.analyzeRequest(message.getHttpService(),message.getRequest()).getUrl().toString();
72 | Utils.showStdoutMsg(0, String.format("[+] Right Click Scanning Url [%s]", url));
73 |
74 | try {
75 | mapResult = HttpAndHttpsProxy.Proxy(message);
76 | } catch (InterruptedException interruptedException) {
77 | interruptedException.printStackTrace();
78 | }
79 |
80 | log.add(new LogEntry(row + 1,
81 | callbacks.saveBuffersToTempFiles(message), helpers.analyzeRequest(message).getUrl(),
82 | method,
83 | mapResult)
84 | );
85 | GUI.logTable.getHttpLogTableModel().fireTableRowsInserted(row, row);
86 | }
87 | }
88 | });
89 | }
90 | }
91 | });
92 |
93 | return Arrays.asList(i1);
94 | }
95 |
96 | @Override
97 | public void processProxyMessage(boolean messageIsRequest, final IInterceptedProxyMessage iInterceptedProxyMessage) {
98 | if (!messageIsRequest && Config.IS_RUNNING) {
99 | IHttpRequestResponse rep_rsp = iInterceptedProxyMessage.getMessageInfo();
100 | IHttpService httpService = rep_rsp.getHttpService();
101 | String host = rep_rsp.getHttpService().getHost();
102 | String path = helpers.analyzeRequest(httpService,rep_rsp.getRequest()).getUrl().getPath();
103 | String url = helpers.analyzeRequest(httpService,rep_rsp.getRequest()).getUrl().toString();
104 |
105 | //白名单域名匹配
106 | if(!Utils.isMatchTargetHost(Config.TARGET_HOST_REGX, host, true)){
107 | return;
108 | }
109 |
110 | //黑名单URL单词匹配
111 | if(Utils.isMatchKeywords(Config.BLACK_URL_REGX, url, false)){
112 | return;
113 | }
114 |
115 | //黑名单后缀匹配
116 | if(Utils.isMatchBlackSuffix(Config.BLACK_SUFFIX_REGX, path, false)){
117 | return;
118 | }
119 |
120 | Utils.showStdoutMsg(0, String.format("[+] Passive Scanning Url [%s]", url));
121 |
122 | final IHttpRequestResponse req_resp = iInterceptedProxyMessage.getMessageInfo();
123 |
124 | //final LogEntry logEntry = new LogEntry(1,callbacks.saveBuffersToTempFiles(iInterceptedProxyMessage.getMessageInfo()),helpers.analyzeRequest(resrsp).getUrl());
125 |
126 | //create a new log entry with the message details
127 | executorService.submit(new Runnable() {
128 | @Override
129 | public void run() {
130 | synchronized(log) {
131 | int row = log.size();
132 | String method = helpers.analyzeRequest(req_resp).getMethod();
133 | Map mapResult = null;
134 | try {
135 | mapResult = HttpAndHttpsProxy.Proxy(req_resp);
136 | } catch (InterruptedException e) {
137 | //TODO Auto-generated catch block
138 | e.printStackTrace();
139 | }
140 |
141 | //log.add(new LogEntry(iInterceptedProxyMessage.getMessageReference(),
142 | log.add(new LogEntry(row + 1,
143 | callbacks.saveBuffersToTempFiles(req_resp), helpers.analyzeRequest(req_resp).getUrl(),
144 | method,
145 | mapResult)
146 | );
147 | GUI.logTable.getHttpLogTableModel().fireTableRowsInserted(row, row);
148 | }
149 | }
150 | });
151 | }
152 | }
153 |
154 |
155 | //读取配置文件
156 | private void initLoadConfigFile(IBurpExtenderCallbacks callbacks) {
157 | Config.EXTENSION_NAME = YamlReader.getInstance(callbacks).getString(Config.EXTENSION_NAME_STR);
158 | Config.VERSION = YamlReader.getInstance(callbacks).getString(Config.VERSION_STR);
159 |
160 | Config.PROXY_HOST = YamlReader.getInstance(callbacks).getString(Config.PROXY_HOST_STR);
161 | Config.PROXY_PORT = YamlReader.getInstance(callbacks).getInteger(Config.PROXY_PORT_STR);
162 | Config.PROXY_USERNAME = YamlReader.getInstance(callbacks).getString(Config.PROXY_USERNAME_STR);
163 | Config.PROXY_PASSWORD = YamlReader.getInstance(callbacks).getString(Config.PROXY_PASSWORD_STR);
164 |
165 | Config.TARGET_HOST_REGX = YamlReader.getInstance(callbacks).getString(Config.TARGET_HOST_REGX_STR);
166 | Config.BLACK_URL_REGX = YamlReader.getInstance(callbacks).getString(Config.BLACK_URL_REGX_STR);
167 | Config.BLACK_SUFFIX_REGX = YamlReader.getInstance(callbacks).getString(Config.BLACK_SUFFIX_REGX_STR);
168 | Config.AUTH_INFO_REGX = YamlReader.getInstance(callbacks).getString(Config.AUTH_INFO_REGX_STR);
169 | Config.DEL_STATUS_REGX = YamlReader.getInstance(callbacks).getString(Config.DEL_STATUS_REGX_STR);
170 |
171 | Config.PROXY_TIMEOUT = YamlReader.getInstance(callbacks).getInteger(Config.PROXY_TIMEOUT_STR);
172 | Config.HASH_MAP_LIMIT = YamlReader.getInstance(callbacks).getInteger(Config.HASH_MAP_LIMIT_STR);
173 | Config.HASH_SET_LIMIT = YamlReader.getInstance(callbacks).getInteger(Config.HASH_SET_LIMIT_STR);
174 | Config.INTERVAL_TIME = YamlReader.getInstance(callbacks).getInteger(Config.INTERVAL_TIME_STR);
175 | Config.DECODE_MAX_TIMES = YamlReader.getInstance(callbacks).getInteger(Config.DECODE_MAX_TIMES_STR);
176 |
177 | Config.SELECTED_HASH = YamlReader.getInstance(callbacks).getBoolean(Config.SELECTED_HASH_STR);
178 | Config.SELECTED_PARAM = YamlReader.getInstance(callbacks).getBoolean(Config.SELECTED_PARAM_STR);
179 | Config.SELECTED_SMART = YamlReader.getInstance(callbacks).getBoolean(Config.SELECTED_SMART_STR);
180 | Config.SELECTED_AUTH = YamlReader.getInstance(callbacks).getBoolean(Config.SELECTED_AUTH_STR);
181 |
182 | Config.SELECTED_IGNORE = YamlReader.getInstance(callbacks).getBoolean(Config.SELECTED_IGNORE_STR);
183 |
184 | //Config.DEL_ERROR_KEY = YamlReader.getInstance(callbacks).getBoolean(Config.DEL_ERROR_KEY_STR);
185 | Config.SHOW_MSG_LEVEL = YamlReader.getInstance(callbacks).getInteger(Config.SHOW_MSG_LEVEL_STR);
186 |
187 | Utils.showStdoutMsg(0, Utils.getBanner(Config.EXTENSION_NAME, Config.VERSION));
188 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.EXTENSION_NAME_STR, Config.EXTENSION_NAME));
189 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.VERSION_STR, Config.VERSION));
190 |
191 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.PROXY_HOST_STR, Config.PROXY_HOST));
192 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.PROXY_PORT_STR, Config.PROXY_PORT));
193 |
194 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.PROXY_USERNAME_STR, Config.PROXY_USERNAME));
195 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.PROXY_PASSWORD_STR, Config.PROXY_PASSWORD));
196 |
197 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.PROXY_TIMEOUT_STR, Config.PROXY_TIMEOUT));
198 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.INTERVAL_TIME_STR, Config.INTERVAL_TIME));
199 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.HASH_MAP_LIMIT_STR, Config.HASH_MAP_LIMIT));
200 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.HASH_SET_LIMIT_STR, Config.HASH_SET_LIMIT));
201 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.DECODE_MAX_TIMES_STR, Config.DECODE_MAX_TIMES));
202 |
203 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.SELECTED_HASH_STR, Config.SELECTED_HASH));
204 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.SELECTED_PARAM_STR, Config.SELECTED_PARAM));
205 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.SELECTED_SMART_STR, Config.SELECTED_SMART));
206 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.SELECTED_AUTH_STR, Config.SELECTED_AUTH));
207 |
208 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.SELECTED_IGNORE_STR, Config.SELECTED_IGNORE));
209 |
210 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.TARGET_HOST_REGX_STR, Config.TARGET_HOST_REGX));
211 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.BLACK_URL_REGX_STR, Config.BLACK_URL_REGX));
212 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.BLACK_SUFFIX_REGX_STR, Config.BLACK_SUFFIX_REGX));
213 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.AUTH_INFO_REGX_STR, Config.AUTH_INFO_REGX));
214 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.DEL_STATUS_REGX_STR, Config.DEL_STATUS_REGX));
215 |
216 | //Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.DEL_ERROR_KEY_STR, Config.DEL_ERROR_KEY));
217 | Utils.showStdoutMsg(1, String.format("[*] INIT %s: %s", Config.SHOW_MSG_LEVEL_STR, Config.SHOW_MSG_LEVEL));
218 | Utils.showStdoutMsg(1, "[*] ####################################");
219 | }
220 |
221 | }
--------------------------------------------------------------------------------
/src/main/java/burp/Config.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.util.HashMap;
4 | import java.util.HashSet;
5 |
6 | public class Config {
7 |
8 | public static HashMap reqInfoHashMap = new HashMap();
9 | public static HashSet reqInfoHashSet = new HashSet<>();
10 |
11 | public static Integer REQUEST_TOTAL = 0;
12 | public static Integer SUCCESS_TOTAL = 0;
13 | public static Integer FAIL_TOTAL = 0;
14 |
15 | public static boolean IS_RUNNING = false;
16 | public static boolean REQ_PARAM = false; //无参过滤模式
17 | public static boolean REQ_AUTH = false; //关注认证信息
18 |
19 | public static boolean IGNORE_RESP = false; //忽略响应信息
20 |
21 | public static boolean REQ_HASH = false; //HASH去重模式
22 | public static String REQ_HASH_STR = "REQ_HASH"; //给内部代码使用的字符串
23 | public static boolean REQ_SMART = false; //参数去重模式
24 | public static String REQ_SMART_STR = "REQ_SMART"; //给内部代码使用的字符串
25 |
26 | public static String EXTENSION_NAME; //从配置文件获取
27 | public static String EXTENSION_NAME_STR = "EXTENSION_NAME";
28 | public static String VERSION; //从配置文件获取
29 | public static String VERSION_STR = "VERSION";
30 |
31 | public static String PROXY_HOST; //从配置文件获取
32 | public static String PROXY_HOST_STR = "PROXY_HOST";
33 | public static Integer PROXY_PORT; //从配置文件获取
34 | public static String PROXY_PORT_STR = "PROXY_PORT";
35 | public static String PROXY_USERNAME; //从配置文件获取
36 | public static String PROXY_USERNAME_STR = "PROXY_USERNAME";
37 | public static String PROXY_PASSWORD; //从配置文件获取
38 | public static String PROXY_PASSWORD_STR = "PROXY_PASSWORD";
39 |
40 | public static Integer PROXY_TIMEOUT; //从配置文件获取
41 | public static String PROXY_TIMEOUT_STR = "PROXY_TIMEOUT";
42 | public static Integer INTERVAL_TIME; //从配置文件获取
43 | public static String INTERVAL_TIME_STR = "INTERVAL_TIME";
44 | public static Integer HASH_MAP_LIMIT; //限制reqInfoHashMap中最大记录的请求数量,
45 | public static String HASH_MAP_LIMIT_STR = "HASH_MAP_LIMIT";
46 | public static Integer HASH_SET_LIMIT; //限制reqInfoHashSet中最大记录的请求数量
47 | public static String HASH_SET_LIMIT_STR = "HASH_SET_LIMIT";
48 |
49 | public static String TARGET_HOST_REGX; //从配置文件获取
50 | public static String TARGET_HOST_REGX_STR = "TARGET_HOST_REGX";
51 | public static String BLACK_URL_REGX; //从配置文件获取
52 | public static String BLACK_URL_REGX_STR = "BLACK_URL_REGX";
53 | public static String BLACK_SUFFIX_REGX; //从配置文件获取
54 | public static String BLACK_SUFFIX_REGX_STR = "BLACK_SUFFIX_REGX";
55 | public static String AUTH_INFO_REGX; //从配置文件获取,去重时应该关注认证头信息字符串
56 | public static String AUTH_INFO_REGX_STR = "AUTH_INFO_REGX";
57 | public static String DEL_STATUS_REGX; //记录需要删除记录的的响应码
58 | public static String DEL_STATUS_REGX_STR = "DEL_STATUS_REGX";
59 |
60 | public static Boolean SELECTED_HASH; //从配置文件获取,HASH去重模式 按钮的默认设置 注:按钮变量可合并到参数
61 | public static String SELECTED_HASH_STR = "SELECTED_HASH";
62 | public static Boolean SELECTED_PARAM; //从配置文件获取,过滤无参数 按钮的默认设置 注:按钮变量可合并到参数
63 | public static String SELECTED_PARAM_STR = "SELECTED_PARAM";
64 | public static Boolean SELECTED_SMART; //从配置文件获取,参数去重模式 按钮的默认设置 注:按钮变量可合并到参数
65 | public static String SELECTED_SMART_STR = "SELECTED_SMART";
66 | public static Boolean SELECTED_AUTH; //从配置文件获取,去重是否关注认证头信息 按钮的默认设置 注:按钮变量可合并到参数
67 | public static String SELECTED_AUTH_STR = "SELECTED_AUTH";
68 |
69 | public static Boolean SELECTED_IGNORE; //从配置文件获取,是否忽略响应详细保存
70 | public static String SELECTED_IGNORE_STR = "SELECTED_IGNORE";
71 |
72 | public static Boolean DEL_ERROR_KEY = true;
73 | //public static String DEL_ERROR_KEY_STR = "DEL_ERROR_KEY";
74 |
75 | public static Integer SHOW_MSG_LEVEL; //从配置文件获取,显示输出信息的级别
76 | public static String SHOW_MSG_LEVEL_STR = "SHOW_MSG_LEVEL";
77 |
78 | public static Integer DECODE_MAX_TIMES; //从配置文件获取,
79 | public static String DECODE_MAX_TIMES_STR = "DECODE_MAX_TIMES"; //从配置文件获取
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/burp/GUI.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.awt.*;
4 | import java.awt.event.ActionEvent;
5 | import javax.swing.*;
6 | import javax.swing.border.EmptyBorder;
7 | import javax.swing.border.LineBorder;
8 | import javax.swing.event.ChangeEvent;
9 | import javax.swing.event.ChangeListener;
10 |
11 | public class GUI implements ITab,IMessageEditorController {
12 | private final String tabName;
13 |
14 | private final JPanel contentPanel;
15 | private final JTextField tfHost;
16 | private final JTextField tfPort;
17 | private final JTextField tfTimeout;
18 | private final JTextField tfIntervalTime;
19 | private final JTextField tfUsername;
20 | private final JTextField tfPassword;
21 | private final JTextField tfTargetHost;
22 | private final JTextField tfBlackUrl;
23 | private final JTextField tfBlackSuffix;
24 | private final JToggleButton btnConn;
25 | private final JToggleButton btnHash;
26 | private final JToggleButton btnParam;
27 | private final JToggleButton btnSmart;
28 | private final JToggleButton btnAuth;
29 | private final JToggleButton btnIgnore;
30 | private final JButton btnClear;
31 |
32 | private final JPanel topPanel;
33 | private final JSplitPane splitPane;
34 | public static HttpLogTable logTable;
35 | public static IHttpRequestResponse currentlyDisplayedItem;
36 | public static JLabel lbRequestCount;
37 | public static JLabel lbSuccessCount;
38 | public static JLabel lbFailCount;
39 |
40 | private static JSplitPane OriginalMsgViewerPane; //请求消息|响应消息 二合一 面板
41 | public static IMessageEditor originalRequestViewer;
42 | public static IMessageEditor originalResponseViewer;
43 |
44 | private static JSplitPane proxyMsgViewerPane; //请求消息|响应消息 二合一 面板
45 | public static IMessageEditor proxyRequestViewer;
46 | public static IMessageEditor proxyResponseViewer;
47 |
48 | public GUI(IBurpExtenderCallbacks burpExtenderCallbacks, String tabName) {
49 | //设置插件名称
50 | this.tabName = tabName;
51 |
52 | contentPanel = new JPanel();
53 | //外边框设置
54 | contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
55 | //添加一个四边各为5像素宽的空白边框。这通常用于增加组件与其相邻组件之间的间距,或者在组件边缘周围添加一些额外的空间。
56 | //内边距设置
57 | contentPanel.setLayout(new BorderLayout(0, 0));
58 | //设置没有额外间隙的 BorderLayout 布局管理器。 添加组件时,组件会紧密地排列在一起
59 |
60 | ////////////////////////////////////////////////////////////////////
61 | //topPanel start 开始设置配置文件部分面板
62 | ////////////////////////////////////////////////////////////////////
63 | topPanel = new JPanel();
64 | //把配置面板添加到主面板中
65 | contentPanel.add(topPanel,BorderLayout.NORTH);
66 | //自增配置面板内的行号
67 | int topPanelY = 0;
68 |
69 | //统一设置行高变量
70 | int LineHeight = 25;
71 | //统一设置行的元素的填充方式
72 | int LineFill = GridBagConstraints.NONE;
73 | //统一设置行内的元素的填充方式
74 | int LineInnerFill = GridBagConstraints.HORIZONTAL;
75 | //统一设置行内的元素的对齐方式
76 | int LineAnchor = GridBagConstraints.WEST;
77 | int LineInnerAnchor = GridBagConstraints.WEST;
78 | //设置文字列元素的宽度
79 | int lenText = 60;
80 | int lenInput = 100;
81 | int lenRight = 0;
82 |
83 | //设置 配置面板的 基本布局配置
84 | if(true){
85 | //GridBagLayout是一个灵活的布局管理器,它允许你创建复杂的网格布局,其中组件可以跨越多行或多列,并且可以有不同的尺寸和填充方式。
86 | GridBagLayout gridBagLayout = new GridBagLayout();
87 | //列数 columnWidths 设置GridBagLayout的列宽度。数组中的每个元素代表一个列的宽度。
88 | //gridBagLayout.columnWidths = new int[] {0};
89 | //各列占宽度比 | 设置列的权重
90 | gridBagLayout.columnWeights = new double[] {1.0D};
91 | //行数 rowHeights 设置 GridBagLayout的行高。
92 | gridBagLayout.rowHeights = new int[] {LineHeight, 0, LineHeight, LineHeight, LineHeight}; //设置四行
93 | //设置行的权重 | 各行占高度比
94 | gridBagLayout.rowWeights = new double[] {1.0D, 1.0D, 1.0D, 1.0D, 1.0D}; //设置四行
95 |
96 | //0.0D: 权重是0,不会接收任何额外的垂直空间。即使容器有额外的空间,这些空间也不会被分配
97 | //1.0D: 接收所有可用的额外空间。 如果有任何额外的垂直空间,它将被分配给第三行。
98 | //Double.MIN_VALUE:设置这样的权重基本上意味着不会接收任何额外的空间。
99 |
100 | topPanel.setLayout(gridBagLayout);
101 | }
102 |
103 | //设置 按钮 面板
104 | if(true){
105 | //按钮面板
106 | JPanel buttonPanel = new JPanel();
107 | //把按钮面板添加到配置面板中
108 | topPanel.add(buttonPanel, getGridBagConstraints(GridBagConstraints.NONE, GridBagConstraints.CENTER,5, 0, topPanelY));
109 | topPanelY += 1;
110 |
111 | //设置按钮面板的格式
112 | GridBagLayout gbl_panel = new GridBagLayout();
113 | //列数量和列宽| 列数和 innerX的最终值是一样的 列数不完善,暂时忽略
114 | gbl_panel.columnWidths = new int[] { lenInput, lenInput, lenInput, lenInput, lenInput, lenInput, lenInput};
115 | //列权重 | 列数不完善,暂时忽略
116 | gbl_panel.columnWeights = new double[] {1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D}; //6个按钮
117 | //行数量和行高 | 因为在一行,所以应该固定是0
118 | gbl_panel.rowHeights = new int[] {LineHeight};
119 | //行权重
120 | gbl_panel.rowWeights = new double[] {1.0D};
121 | buttonPanel.setLayout(gbl_panel);
122 |
123 | //设置 按钮 行的内容
124 | if(true){
125 | int buttonRight = 5;
126 |
127 | int innerX = 0; //内部列号 起始
128 | //增加URL HASH 去重开关
129 | btnHash = new JToggleButton("HASH");
130 | btnHash.setToolTipText("忽略转发HASH值相同的请求URL");
131 | btnHash.addChangeListener(new ChangeListener() {
132 | public void stateChanged(ChangeEvent arg0) {
133 | boolean isSelected = btnHash.isSelected();
134 | boolean oldStatus = Config.REQ_HASH;
135 |
136 | Config.REQ_HASH = isSelected;
137 | btnHash.setSelected(isSelected);
138 |
139 | boolean newStatus = Config.REQ_HASH;
140 | //判断状态是否改变,改变了就输出
141 | if(oldStatus != newStatus){
142 | Utils.showStdoutMsg(1, String.format("[*] Click Button [%s]: %s --> %s", "HASH", oldStatus, newStatus));
143 | }
144 | }
145 | });
146 | //根据配置文件设置HASH按钮的默认选择行为
147 | if(Config.SELECTED_HASH){
148 | btnHash.setSelected(true);
149 | }
150 |
151 | buttonPanel.add(btnHash, getGridBagConstraints(LineInnerFill,LineInnerAnchor, buttonRight,innerX,0));
152 | innerX += 1;
153 |
154 | //增加无参数URL去除开关
155 | btnParam = new JToggleButton("PARAM");
156 | btnParam.setToolTipText("忽略转发没有参数的请求URL");
157 | btnParam.addChangeListener(new ChangeListener() {
158 | public void stateChanged(ChangeEvent arg0) {
159 | boolean isSelected = btnParam.isSelected();
160 | boolean oldStatus = Config.REQ_PARAM;
161 | Config.REQ_PARAM = isSelected;
162 | btnParam.setSelected(isSelected);
163 | //判断状态是否改变,改变了就输出
164 | boolean newStatus = Config.REQ_PARAM;
165 | if(oldStatus != newStatus) {
166 | Utils.showStdoutMsg(1, String.format("[*] Click Button [%s]: %s --> %s", "PARAM", oldStatus, newStatus));
167 | }
168 | }
169 | });
170 | //根据配置文件设置PARAM按钮的默认选择行为
171 | if(Config.SELECTED_PARAM){
172 | btnParam.setSelected(true);
173 | }
174 |
175 | buttonPanel.add(btnParam, getGridBagConstraints(LineInnerFill,LineInnerAnchor, buttonRight,innerX,0));
176 | innerX += 1;
177 |
178 | //增加重复参数URL去除开关
179 | btnSmart = new JToggleButton("SMART");
180 | btnSmart.setToolTipText("忽略转发参数重复的请求URL");
181 | btnSmart.addChangeListener(new ChangeListener() {
182 | public void stateChanged(ChangeEvent arg0) {
183 | boolean isSelected = btnSmart.isSelected();
184 | boolean oldStatus = Config.REQ_SMART;
185 | Config.REQ_SMART = isSelected;
186 | btnSmart.setSelected(isSelected);
187 | boolean newStatus = Config.REQ_SMART;
188 | //判断状态是否改变,改变了就输出
189 | if(oldStatus != newStatus){
190 | Utils.showStdoutMsg(1, String.format("[*] Click Button [%s]: %s --> %s", "SMART", oldStatus, newStatus));
191 | }
192 | }
193 | });
194 | //根据配置文件设置SMART按钮的默认选择行为
195 | if(Config.SELECTED_SMART){
196 | btnSmart.setSelected(true);
197 | }
198 | buttonPanel.add(btnSmart, getGridBagConstraints(LineInnerFill,LineInnerAnchor, buttonRight,innerX,0));
199 | innerX += 1;
200 |
201 | //增加去重时关注认证信息的开关
202 | btnAuth = new JToggleButton("AUTH");
203 | btnAuth.setToolTipText("转发URL完全相同但认证信息不同的请求");
204 | btnAuth.addChangeListener(new ChangeListener() {
205 | public void stateChanged(ChangeEvent arg0) {
206 | boolean isSelected = btnAuth.isSelected();
207 | boolean oldStatus = Config.REQ_AUTH;
208 | Config.REQ_AUTH = isSelected;
209 | btnAuth.setSelected(isSelected);
210 | boolean newStatus = Config.REQ_AUTH;
211 | //判断状态是否改变,改变了就输出
212 | if(oldStatus != newStatus){
213 | Utils.showStdoutMsg(1, String.format("[*] Click Button [%s]: %s --> %s", "AUTH", oldStatus, newStatus));
214 | }
215 | }
216 | });
217 | //根据配置文件设置AUTH按钮的默认选择行为
218 | if(Config.SELECTED_AUTH){
219 | btnAuth.setSelected(true);
220 | }
221 | buttonPanel.add(btnAuth, getGridBagConstraints(LineInnerFill,LineInnerAnchor, buttonRight,innerX,0));
222 | innerX += 1;
223 |
224 |
225 | //增加显示转发响应结果的内容开关
226 | btnIgnore = new JToggleButton("IGNORE");
227 | btnIgnore.setToolTipText("忽略保存转发到代理服务器时的响应");
228 | btnIgnore.addChangeListener(new ChangeListener() {
229 | public void stateChanged(ChangeEvent arg0) {
230 | boolean isSelected = btnIgnore.isSelected();
231 | boolean oldStatus = Config.IGNORE_RESP;
232 | Config.IGNORE_RESP = isSelected;
233 | btnIgnore.setSelected(isSelected);
234 | boolean newStatus = Config.IGNORE_RESP;
235 | //判断状态是否改变,改变了就输出
236 | if(oldStatus != newStatus){
237 | Utils.showStdoutMsg(1, String.format("[*] Click Button [%s]: %s --> %s", "IGNORE", oldStatus, newStatus));
238 | }
239 | }
240 | });
241 | //根据配置文件设置AUTH按钮的默认选择行为
242 | if(Config.IGNORE_RESP){
243 | btnIgnore.setSelected(true);
244 | }
245 | buttonPanel.add(btnIgnore, getGridBagConstraints(LineInnerFill,LineInnerAnchor, buttonRight,innerX,0));
246 | innerX += 1;
247 |
248 |
249 | // 增加运行按钮
250 | btnConn = new JToggleButton("Run");
251 | btnConn.setToolTipText("被动监听运行模式控制开关");
252 | btnConn.addChangeListener(new ChangeListener() {
253 | public void stateChanged(ChangeEvent arg0) {
254 | boolean isSelected = btnConn.isSelected();
255 |
256 | if(isSelected){
257 | btnConn.setText("Stop");
258 | Config.IS_RUNNING = true;
259 | Config.PROXY_HOST = tfHost.getText();
260 | Config.PROXY_PORT = Integer.valueOf(tfPort.getText());
261 | Config.PROXY_TIMEOUT = Integer.valueOf(tfTimeout.getText());
262 | Config.PROXY_USERNAME = tfUsername.getText();
263 | Config.PROXY_PASSWORD = tfPassword.getText();
264 | Config.TARGET_HOST_REGX = tfTargetHost.getText();
265 | Config.BLACK_URL_REGX = tfBlackUrl.getText();
266 | Config.BLACK_SUFFIX_REGX = tfBlackSuffix.getText();
267 | Config.INTERVAL_TIME = Integer.valueOf(tfIntervalTime.getText());
268 | setAllEnabled(false);
269 | }else{
270 | btnConn.setText("Run");
271 | Config.IS_RUNNING = false;
272 | setAllEnabled(true);
273 | }
274 | btnConn.setSelected(isSelected);
275 | }
276 | });
277 |
278 | buttonPanel.add(btnConn, getGridBagConstraints(LineInnerFill,LineInnerAnchor, buttonRight,innerX,0));
279 | innerX += 1;
280 |
281 | //增加清除按钮
282 | btnClear = new JButton("Clear");
283 | btnClear.setToolTipText("清除所有请求转发记录");
284 | btnClear.addActionListener(new AbstractAction() {
285 | @Override
286 | public void actionPerformed(ActionEvent e) {
287 | int n = JOptionPane.showConfirmDialog(null, "Are you sure you want to clear the data?", "Passive Scan Client prompt", JOptionPane.YES_NO_OPTION);
288 | if(n == 0) {
289 | Config.REQUEST_TOTAL = 0;
290 | lbRequestCount.setText("0");
291 | Config.SUCCESS_TOTAL = 0;
292 | lbSuccessCount.setText("0");
293 | Config.FAIL_TOTAL = 0;
294 | lbFailCount.setText("0");
295 | BurpExtender.log.clear();
296 | logTable.getHttpLogTableModel().fireTableDataChanged();//通知模型更新
297 | logTable.updateUI();//刷新表格
298 |
299 | proxyRequestViewer.setMessage("".getBytes(),true);
300 | originalRequestViewer.setMessage("".getBytes(),true);
301 |
302 | proxyResponseViewer.setMessage("".getBytes(),false);
303 | originalResponseViewer.setMessage("".getBytes(),false);
304 | clearHashSet(); //新增URL去重
305 | }
306 | }
307 | });
308 |
309 | buttonPanel.add(btnClear, getGridBagConstraints(LineInnerFill,LineInnerAnchor, buttonRight,innerX,0));
310 | innerX += 1;
311 | }
312 | }
313 |
314 | //设置 分割边框 面板
315 | if(true){
316 | //按钮面板
317 | JPanel buttonPanel = new JPanel();
318 | buttonPanel.setBorder(new LineBorder(Color.RED, 1)); //添加边框
319 | //把按钮面板添加到配置面板中
320 | topPanel.add(buttonPanel, getGridBagConstraints(GridBagConstraints.HORIZONTAL, GridBagConstraints.CENTER,0, 0, topPanelY));
321 | topPanelY += 1;
322 |
323 | //设置按钮面板的格式
324 | GridBagLayout gbl_panel = new GridBagLayout();
325 | gbl_panel.rowHeights = new int[] {0};
326 | gbl_panel.rowWeights = new double[] {1.0D};
327 | buttonPanel.setLayout(gbl_panel);
328 | }
329 |
330 | //设置 代理 面板
331 | if(true){
332 | //按钮面板
333 | JPanel proxyPanel = new JPanel();
334 |
335 | //把按钮面板添加到配置面板中
336 | topPanel.add(proxyPanel,getGridBagConstraints(LineFill, LineAnchor,0, 0, topPanelY));
337 | topPanelY += 1;
338 |
339 | //设置按钮面板的格式
340 | GridBagLayout gbl_panel = new GridBagLayout();
341 |
342 | //列数量和列宽| 列数和 innerX的最终值是一样的 列最后设计完才知道
343 | gbl_panel.columnWidths = new int[] {lenText, lenInput, lenText, lenInput, lenText, lenInput, lenText, lenInput, lenText, lenInput, lenText, lenInput};
344 | //列权重 | 列数最后设计完才知道
345 | gbl_panel.columnWeights = new double[] {1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D};
346 | //行数量和行高
347 | gbl_panel.rowHeights = new int[] {LineHeight};
348 | //行权重
349 | gbl_panel.rowWeights = new double[] {1.0D};
350 | proxyPanel.setLayout(gbl_panel);
351 |
352 | //设置 按钮 行的内容
353 | if(true){
354 | int innerX = 0; //内部列号 起始
355 |
356 | //HOST输入框
357 | JLabel lbHost = new JLabel("ProxyHost:");
358 | lbHost.setToolTipText("被转发的代理服务监听IP");
359 | proxyPanel.add(lbHost, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
360 | innerX += 1;
361 |
362 | // HOST输入框
363 | tfHost = new JTextField(10);
364 | tfHost.setText(Config.PROXY_HOST);
365 | proxyPanel.add(tfHost, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
366 | innerX += 1;
367 |
368 | //Port输入框
369 | JLabel lbPort = new JLabel("ProxyPort:");
370 | lbPort.setToolTipText("被转发的代理服务监听端口");
371 | proxyPanel.add(lbPort, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
372 | innerX += 1;
373 |
374 | //Port输入框
375 | tfPort = new JTextField(10);
376 | tfPort.setText(String.valueOf(Config.PROXY_PORT));
377 | proxyPanel.add(tfPort, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
378 | innerX += 1;
379 |
380 | //用户名输入框
381 | JLabel lbUsername = new JLabel("ProxyUser:");
382 | lbUsername.setToolTipText("被转发的代理服务监听账号");
383 | proxyPanel.add(lbUsername, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
384 | innerX += 1;
385 |
386 | //用户名输入框
387 | tfUsername = new JTextField(10);
388 | tfUsername.setText(Config.PROXY_USERNAME);
389 | proxyPanel.add(tfUsername, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
390 | innerX += 1;
391 |
392 | //密码输入框
393 | JLabel lbPassword = new JLabel("ProxyPwd:");
394 | lbPassword.setToolTipText("被转发的代理服务监听密码");
395 | proxyPanel.add(lbPassword, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
396 | innerX += 1;
397 |
398 | //密码输入框
399 | tfPassword = new JTextField(10);
400 | tfPassword.setText(Config.PROXY_PASSWORD);
401 | proxyPanel.add(tfPassword, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
402 | innerX += 1;
403 |
404 | //超时输入框
405 | JLabel lbTimeout = new JLabel("Timeout:");
406 | lbTimeout.setToolTipText("请求转发超时时间");
407 | proxyPanel.add(lbTimeout, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
408 | innerX += 1;
409 |
410 | //超时输入框
411 | tfTimeout = new JTextField(10);
412 | tfTimeout.setText(String.valueOf(Config.PROXY_TIMEOUT));
413 | proxyPanel.add(tfTimeout, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
414 | innerX += 1;
415 |
416 | //增加间隔时间
417 | JLabel lbIntervalTime = new JLabel("Interval Time:");
418 | lbIntervalTime.setToolTipText("请求转发间隔时间");
419 | proxyPanel.add(lbIntervalTime, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
420 | innerX += 1;
421 |
422 | //增加间隔时间
423 | tfIntervalTime = new JTextField(10);
424 | tfIntervalTime.setText(String.valueOf(Config.INTERVAL_TIME));
425 | proxyPanel.add(tfIntervalTime, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
426 | innerX += 1;
427 | }
428 | }
429 |
430 | //设置 过滤 面板
431 | if(true){
432 | JPanel filterPanel = new JPanel();
433 | //把过滤host面板添加到配置面板中
434 | topPanel.add(filterPanel, getGridBagConstraints(LineFill,LineAnchor, 0, 0, topPanelY));
435 | topPanelY += 1;
436 |
437 | //设置股过滤面板的格式
438 | GridBagLayout gbl_panel = new GridBagLayout();
439 | gbl_panel.columnWidths = new int[] {lenText, lenInput * 4, lenText, lenInput * 4}; //四个元素
440 | gbl_panel.columnWeights = new double[] {1.0D, 1.0D, 1.0D, 1.0D}; //四个元素
441 | gbl_panel.rowHeights = new int[] {LineHeight};
442 | gbl_panel.rowWeights = new double[] {1.0D};
443 | filterPanel.setLayout(gbl_panel);
444 |
445 | //设置过滤行的内容
446 | if(true){
447 | int innerX = 0;
448 | //新增黑名单主机控制
449 | JLabel lbBlackUrl = new JLabel("BlackUrls:");
450 | lbBlackUrl.setToolTipText("禁止转发的黑名单URL正则");
451 | filterPanel.add(lbBlackUrl, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
452 | innerX += 1;
453 |
454 | tfBlackUrl = new JTextField(10);
455 | tfBlackUrl.setText(Config.BLACK_URL_REGX);
456 | filterPanel.add(tfBlackUrl, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
457 | innerX += 1;
458 |
459 | //黑名单后缀处理
460 | JLabel lbBlackSuffix = new JLabel("BlackSuffix:");
461 | lbBlackSuffix.setToolTipText("禁止转发的黑名单后缀正则");
462 | filterPanel.add(lbBlackSuffix, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
463 | innerX += 1;
464 |
465 | tfBlackSuffix = new JTextField(10);
466 | tfBlackSuffix.setText(Config.BLACK_SUFFIX_REGX);
467 | filterPanel.add(tfBlackSuffix, getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
468 | innerX += 1;
469 | }
470 | }
471 |
472 | //设置 目标 面板
473 | if(true){
474 | //HOST面板和后缀面板
475 | JPanel targetPanel = new JPanel();
476 | topPanel.add(targetPanel, getGridBagConstraints(LineFill,LineAnchor, 0, 0, topPanelY));
477 | topPanelY += 1;
478 |
479 | //设置 目标 行 的格式
480 | GridBagLayout gbl_panel_1 = new GridBagLayout();
481 | gbl_panel_1.columnWidths = new int[] {lenText, lenInput * 4, lenText, lenText, lenText, lenText, lenText,lenText};
482 | gbl_panel_1.columnWeights = new double[] { 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D, 1.0D,0.0D};
483 | gbl_panel_1.rowHeights = new int[] {LineHeight};
484 | gbl_panel_1.rowWeights = new double[] {1.0D};
485 | targetPanel.setLayout(gbl_panel_1);
486 |
487 | //设置目标行的内容
488 | if (true){
489 | int innerX = 0;
490 | JLabel lbTargetHost = new JLabel("TargetHost:");
491 | lbTargetHost.setToolTipText("允许被转发的请求目标HOST正则");
492 | targetPanel.add(lbTargetHost,getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
493 | innerX += 1;
494 |
495 | tfTargetHost = new JTextField(10);
496 | tfTargetHost.setText(Config.TARGET_HOST_REGX);
497 | targetPanel.add(tfTargetHost,getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
498 | innerX += 1;
499 |
500 |
501 | // 转发url总数,默认0
502 | JLabel lbRequest = new JLabel("Total:");
503 | lbRequest.setToolTipText("监听转发请求总数");
504 | targetPanel.add(lbRequest,getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
505 | innerX += 1;
506 |
507 | lbRequestCount = new JLabel("0");
508 | lbRequestCount.setForeground(new Color(0,0,255));
509 | targetPanel.add(lbRequestCount,getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
510 | innerX += 1;
511 |
512 | // 转发成功url数,默认0
513 | JLabel lbSuccess = new JLabel("Success:");
514 | lbSuccess.setToolTipText("转发请求成功总数");
515 | targetPanel.add(lbSuccess,getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
516 |
517 | lbRequestCount.setForeground(new Color(0,0,255));
518 | innerX += 1;
519 |
520 |
521 | lbSuccessCount = new JLabel("0");
522 | lbSuccessCount.setForeground(new Color(0, 255, 0));
523 | targetPanel.add(lbSuccessCount,getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
524 | innerX += 1;
525 |
526 | // 转发失败url数,默认0
527 | JLabel lbFail = new JLabel("Fail:");
528 | lbFail.setToolTipText("转发请求失败总数");
529 | targetPanel.add(lbFail,getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
530 | innerX += 1;
531 |
532 |
533 | lbFailCount = new JLabel("0");
534 | lbFailCount.setForeground(new Color(255, 0, 0));
535 | targetPanel.add(lbFailCount,getGridBagConstraints(LineInnerFill,LineInnerAnchor, lenRight,innerX,0));
536 | innerX += 1;
537 | }
538 |
539 | }
540 |
541 | ////////////////////////////////////////////////////////////////////
542 | //topPanel end
543 | ////////////////////////////////////////////////////////////////////
544 | splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
545 | splitPane.setDividerLocation(0.5);
546 | //把响应信息面板加到主面板中
547 | contentPanel.add(splitPane, BorderLayout.CENTER);
548 |
549 | HttpLogTableModel model = new HttpLogTableModel();
550 | logTable = new HttpLogTable(model);
551 | //JTable表头排序,以下两种方法均存在问题,导致界面混乱。
552 | //方式一
553 | //TableRowSorter tableRowSorter=new TableRowSorter(model);
554 | //logTable.setRowSorter(tableRowSorter);
555 | //方式二
556 | //logTable.setAutoCreateRowSorter(true);
557 |
558 | JScrollPane jspLogTable = new JScrollPane(logTable);
559 | splitPane.setTopComponent(jspLogTable);
560 |
561 | //添加最后的响应信息面板
562 | JTabbedPane tabs = new JTabbedPane();
563 | proxyRequestViewer = BurpExtender.callbacks.createMessageEditor(this, false);
564 | proxyResponseViewer = BurpExtender.callbacks.createMessageEditor(this, false);
565 |
566 | originalRequestViewer = BurpExtender.callbacks.createMessageEditor(this, false);
567 | originalResponseViewer = BurpExtender.callbacks.createMessageEditor(this, false);
568 |
569 | //添加请求和响应信息面板到一个面板中
570 | proxyMsgViewerPane = new JSplitPane(1);
571 | proxyMsgViewerPane.setLeftComponent(proxyRequestViewer.getComponent());
572 | proxyMsgViewerPane.setRightComponent(proxyResponseViewer.getComponent());
573 |
574 | OriginalMsgViewerPane = new JSplitPane(1);
575 | OriginalMsgViewerPane.setLeftComponent(originalRequestViewer.getComponent());
576 | OriginalMsgViewerPane.setRightComponent(originalResponseViewer.getComponent());
577 |
578 | tabs.addTab("ProxyMsg", proxyMsgViewerPane);
579 | tabs.addTab("Original", OriginalMsgViewerPane);
580 | splitPane.setBottomComponent(tabs);
581 |
582 | burpExtenderCallbacks.customizeUiComponent(topPanel);
583 | burpExtenderCallbacks.customizeUiComponent(splitPane);
584 | burpExtenderCallbacks.customizeUiComponent(contentPanel);
585 | }
586 |
587 | @Override
588 | public String getTabCaption() {
589 | return this.tabName;
590 | }
591 |
592 | @Override
593 | public Component getUiComponent() {
594 | return this.getComponent();
595 | }
596 |
597 | private GridBagConstraints getGridBagConstraints(int fill, int anchor, int top, int left, int bottom, int right, int grid_x, int grid_y) {
598 | GridBagConstraints gbc = new GridBagConstraints();
599 | // fill属性用来处理 GridBagLayout 网格布局时子节点渲染的占位大小
600 | gbc.fill = fill;
601 | //组件对齐方式
602 | gbc.anchor = anchor;
603 | //内边距设置
604 | gbc.insets = new Insets(top, left, bottom, right); //insets 内边距 代表上、左、下、右四个方向的外部间距。
605 | //所在列
606 | gbc.gridx = grid_x; //组件应该放在哪个网格列
607 | //所在行
608 | gbc.gridy = grid_y; //组件应该放在哪个网格行
609 | return gbc;
610 | }
611 |
612 | //无内框 设置方案
613 | private GridBagConstraints getGridBagConstraints(int fill, int anchor, int right, int grid_x, int grid_y) {
614 | return getGridBagConstraints(fill,anchor,0,0,0,right,grid_x,grid_y);
615 | }
616 |
617 | public Component getComponent(){
618 | return contentPanel;
619 | }
620 |
621 | public IHttpService getHttpService() {
622 | return currentlyDisplayedItem.getHttpService();
623 | }
624 |
625 | public byte[] getRequest() {
626 | return currentlyDisplayedItem.getRequest();
627 | }
628 |
629 | public byte[] getResponse() {
630 | return currentlyDisplayedItem.getResponse();
631 | }
632 |
633 | public void setAllEnabled(boolean is){
634 | tfHost.setEnabled(is);
635 | tfPort.setEnabled(is);
636 | tfUsername.setEnabled(is);
637 | tfPassword.setEnabled(is);
638 | tfTimeout.setEnabled(is);
639 | tfTargetHost.setEnabled(is);
640 | tfBlackUrl.setEnabled(is);
641 | tfBlackSuffix.setEnabled(is);
642 | tfIntervalTime.setEnabled(is);
643 | }
644 |
645 | //更新失败计数
646 | public static void updateFailCount(){
647 | synchronized(Config.SUCCESS_TOTAL){
648 | Config.REQUEST_TOTAL++;
649 | Config.FAIL_TOTAL++;
650 | lbRequestCount.setText(String.valueOf(Config.REQUEST_TOTAL));
651 | lbFailCount.setText(String.valueOf(Config.FAIL_TOTAL));
652 | }
653 | }
654 |
655 | //更新成功计数
656 | public static void updateSuccessCount(){
657 | synchronized(Config.FAIL_TOTAL){
658 | Config.REQUEST_TOTAL++;
659 | Config.SUCCESS_TOTAL++;
660 | lbRequestCount.setText(String.valueOf(Config.REQUEST_TOTAL));
661 | lbSuccessCount.setText(String.valueOf(Config.SUCCESS_TOTAL));
662 | }
663 | }
664 |
665 | //新增URL去重
666 | private void clearHashSet(){
667 | int HashSetSizeBefore = Config.reqInfoHashSet.size();
668 | Config.reqInfoHashSet.clear();
669 | int HashSetSizeAfter = Config.reqInfoHashSet.size();
670 | Utils.showStdoutMsg(0, String.format("[*] Clear HashSet By Button, HashSet Size %s --> %s.",HashSetSizeBefore, HashSetSizeAfter));
671 |
672 | int HashMapSizeBefore = Config.reqInfoHashMap.size();
673 | Config.reqInfoHashMap.clear();
674 | int HashMapSizeAfter = Config.reqInfoHashMap.size();
675 | Utils.showStdoutMsg(0, String.format("[*] Clear HashSet By Button, HashMap Size %s --> %s.",HashMapSizeBefore, HashMapSizeAfter));
676 | }
677 |
678 | /**
679 | * 当左边极小时 设置请求体和响应体各占一半空间
680 | */
681 | public static void msgViewerAutoSetSplitCenter(JSplitPane msgViewerPane) {
682 | SwingUtilities.invokeLater(new Runnable() {
683 | public void run() {
684 | if (msgViewerPane.getLeftComponent().getWidth() <= 20)
685 | msgViewerPane.setDividerLocation(msgViewerPane.getParent().getWidth() / 2);
686 | }
687 | });
688 | }
689 |
690 | public static void proxyMsgViewerAutoSetSplitCenter(){
691 | msgViewerAutoSetSplitCenter(proxyMsgViewerPane);
692 | }
693 |
694 | public static void originalMsgViewerAutoSetSplitCenter() {
695 | msgViewerAutoSetSplitCenter(OriginalMsgViewerPane);
696 | }
697 | }
--------------------------------------------------------------------------------
/src/main/java/burp/HttpAndHttpsProxy.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import plus.Base64;
4 | import plus.UtilsPlus;
5 |
6 | import java.io.*;
7 | import java.net.HttpURLConnection;
8 | import java.net.InetSocketAddress;
9 | import java.net.Proxy;
10 | import java.net.URL;
11 | import java.net.Proxy.Type;
12 | import java.nio.charset.StandardCharsets;
13 | import java.security.cert.CertificateException;
14 | import java.security.cert.X509Certificate;
15 | import java.util.*;
16 | import javax.net.ssl.HostnameVerifier;
17 | import javax.net.ssl.HttpsURLConnection;
18 | import javax.net.ssl.SSLContext;
19 | import javax.net.ssl.SSLSession;
20 | import javax.net.ssl.TrustManager;
21 | import javax.net.ssl.X509TrustManager;
22 |
23 | import static burp.Utils.*;
24 |
25 |
26 | //JAVA设置代理的两种方式(HTTP和HTTPS) https://blog.csdn.net/sbc1232123321/article/details/79334130
27 | public class HttpAndHttpsProxy {
28 |
29 | public static Map Proxy(IHttpRequestResponse requestResponse) throws InterruptedException{
30 | byte[] request = requestResponse.getRequest();
31 | IHttpService httpService = requestResponse.getHttpService();
32 | IRequestInfo reqInfo = BurpExtender.helpers.analyzeRequest(httpService,request);
33 |
34 | String reqUrl = reqInfo.getUrl().toString();
35 | List reqHeaders = reqInfo.getHeaders();
36 | List reqParams = reqInfo.getParameters();
37 | byte[] reqBodyBytes = null;
38 | String reqBodyString = null;
39 | //HASHMAP 记录当前请求的Key,传递给下一个函数支持错误删除
40 | HashMap ReqKeyHashMap = new HashMap<>();
41 |
42 | //忽略无参数目标
43 | if(Config.REQ_PARAM){
44 | //判断是否存在参数
45 | if(reqParams.size()<=0){
46 | Utils.showStderrMsg(1, String.format("[-] Ignored By Param Blank: %s", reqUrl));
47 | return null;
48 | }
49 | }
50 |
51 | if(reqInfo.getMethod().equals("POST")){
52 | int bodyOffset = reqInfo.getBodyOffset();
53 | //reqBodyString = new String(request, bodyOffset, request.length - bodyOffset, StandardCharsets.UTF_8);
54 | //reqBodyBytes = reqBodyString.getBytes(StandardCharsets.UTF_8);
55 | reqBodyBytes = UtilsPlus.getBodyBytes(request, bodyOffset);
56 | reqBodyString = new String(reqBodyBytes, StandardCharsets.UTF_8);
57 | }
58 |
59 | //计算认证相关的头信息
60 | String authParamsJsonStr = "";
61 |
62 | if(Config.REQ_AUTH) {
63 | //考虑添加auth头相关的信息 //将所有认证头信息组成一个Json字符串追加到URL后面
64 | HashMap authParamsHashMap = ExtractIParamsAuthParam(reqParams, reqHeaders, true);
65 | authParamsJsonStr = Utils.paramsHashMapToJsonStr(authParamsHashMap, true);
66 | }
67 |
68 | //忽略重复参数的请求
69 | if(Config.REQ_SMART) {
70 | String reqUrlNoParam = reqUrl.split("\\?",2)[0];
71 | byte contentType = reqInfo.getContentType();
72 |
73 | //确定HASHMAP请求的key
74 | String reqUrlKey;
75 | if(Config.REQ_AUTH){
76 | //添加auth头相关的信息
77 | reqUrlKey = String.format("%s[type:%s][auth:%s]", reqUrlNoParam, contentType, authParamsJsonStr);
78 | }else {
79 | reqUrlKey = String.format("%s[type:%s]", reqUrlNoParam, contentType);
80 | }
81 |
82 | //格式化处理每个请求的参数, Burp默认处理的参数对包含Cookie值
83 | String reqParamsJsonStr;
84 | //额外处理 多层 Json格式的请求
85 | if(contentType == IRequestInfo.CONTENT_TYPE_JSON
86 | && !Utils.isEmpty(reqBodyString)
87 | && Utils.countStr(Utils.decodeUrl(reqBodyString),"{" ,2, true)
88 | && Utils.isJson(Utils.decodeUrl(reqBodyString))){
89 | HashMap reqParamsMap = Utils.JsonParamsToHashMap(reqBodyString, false);
90 | ParamsHashMapAddIParams(reqParamsMap, reqParams);
91 | reqParamsJsonStr = Utils.paramsHashMapToJsonStr(reqParamsMap, false);
92 | }else if(Utils.paramValueHasJson(reqParams)){
93 | //如果有参数的值有Json格式,需要进一步进行处理
94 | Utils.showStdoutMsg(2, "[!] Parameter Value Has Json format, Need Depth Processing ...");
95 | reqParamsJsonStr = Utils.IParametersToJsonStrPlus(reqParams, false);
96 | }else {
97 | //通用的参数Json获取方案
98 | reqParamsJsonStr = Utils.IParametersToJsonStr(reqParams, false);
99 | }
100 | Boolean isUniq = Utils.isUniqReqInfo(Config.reqInfoHashMap, reqUrlKey, reqParamsJsonStr, false);
101 | if(!isUniq){
102 | Utils.showStderrMsg(1, String.format("[-] Ignored By Param Duplication: %s %s", reqUrlKey, reqParamsJsonStr));
103 | return null;
104 | }
105 |
106 | //记录请求的URL 到 reqKeyHASHMAP
107 | ReqKeyHashMap.put(Config.REQ_SMART_STR, reqUrlKey);
108 |
109 | //内存记录数量超过限制,清空 reqInfoHashMap
110 | if(Config.HASH_MAP_LIMIT <= Config.reqInfoHashMap.size()){
111 | Utils.showStdoutMsg(1, String.format("[-] Clear HashMap Content By Exceed Limit %s.", Config.HASH_MAP_LIMIT));
112 | Config.reqInfoHashMap.clear();
113 | }
114 | }
115 |
116 |
117 | //忽略完全重复的请求信息
118 | if(Config.REQ_HASH) {
119 | String reqUrlKey;
120 | if(Config.REQ_AUTH){
121 | //添加auth头相关的信息
122 | reqUrlKey = String.format("%s[auth:%s]", reqUrl, authParamsJsonStr);
123 | }else {
124 | reqUrlKey = String.format("%s", reqUrl);
125 | }
126 | //计算请求信息Hash
127 | String reqInfoHash = Utils.calcReqInfoHash(reqUrlKey, reqBodyBytes);
128 | //新增 输出url去重处理 记录请求URL和body对应hash
129 | if (Config.reqInfoHashSet.contains(reqInfoHash)) {
130 | Utils.showStderrMsg(1, String.format("[-] Ignored By URL&Body(md5): %s", reqInfoHash));
131 | return null;
132 | } else {
133 | Utils.showStdoutMsg(1, String.format("[+] Firstly REQ URL&Body(md5): %s", reqInfoHash));
134 | Config.reqInfoHashSet.add(reqInfoHash);
135 | }
136 |
137 | //记录请求的URL 到 reqKeyHASHMAP
138 | ReqKeyHashMap.put(Config.REQ_HASH_STR, reqInfoHash);
139 |
140 | //内存记录数量超过限制,清空 reqInfoHashSet
141 | if(Config.HASH_SET_LIMIT <= Config.reqInfoHashSet.size()){
142 | Utils.showStdoutMsg(1, String.format("[-] Clear HashSet Content By Exceed Limit %s.", Config.HASH_SET_LIMIT));
143 | Config.reqInfoHashSet.clear();
144 | }
145 | }
146 |
147 | //延迟转发
148 | Thread.sleep(Config.INTERVAL_TIME);
149 |
150 | Map resultMap;
151 | if(httpService.getProtocol().equals("https")){
152 | //修改 输出url去重处理
153 | resultMap = HttpsProxy(ReqKeyHashMap, reqUrl, reqHeaders, reqBodyBytes, Config.PROXY_HOST, Config.PROXY_PORT, Config.PROXY_USERNAME, Config.PROXY_PASSWORD);
154 | }else {
155 | //修改 输出url去重处理
156 | resultMap = HttpProxy(ReqKeyHashMap, reqUrl, reqHeaders, reqBodyBytes, Config.PROXY_HOST, Config.PROXY_PORT,Config.PROXY_USERNAME,Config.PROXY_PASSWORD);
157 | }
158 |
159 | putResultMapOuter(resultMap, requestResponse);
160 | return resultMap;
161 | }
162 |
163 | //感谢chen1sheng的pr,已经修改了我漏修复的https转发bug,并解决了header截断的bug。
164 | public static Map HttpsProxy(HashMap ReqKeyHashMap, String url, List headers,byte[] body, String proxy, int port,String username,String password){
165 | //public static Map HttpsProxy(Set reqBodyHashSet, String url_body, String url, List headers,byte[] body, String proxy, int port,String username,String password){
166 | Map mapResult = new HashMap<>();
167 | String status;
168 | String rspHeader = null;
169 | byte[] respBody = new byte[0];
170 |
171 | HttpsURLConnection urlConnection = null;
172 | PrintWriter out = null;
173 | BufferedReader reader = null;
174 | try {
175 | URL urlClient = new URL(url);
176 | SSLContext sslContext = SSLContext.getInstance("SSL");
177 | //指定信任https
178 | sslContext.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
179 | //创建代理虽然是https也是Type.HTTP
180 | Proxy proxy1=new Proxy(Type.HTTP, new InetSocketAddress(proxy, port));
181 | //设置代理
182 | urlConnection = (HttpsURLConnection) urlClient.openConnection(proxy1);
183 |
184 | //设置账号密码
185 | SetProxyAuth(username, password, urlConnection);
186 |
187 | urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
188 | urlConnection.setHostnameVerifier(new TrustAnyHostnameVerifier());
189 |
190 | //设置控制请求方法的Flag
191 | String methodFlag = "";
192 | //设置通用的请求属性
193 | for(String header:headers){
194 | if(header.startsWith("GET") || header.startsWith("POST") || header.startsWith("PUT")){
195 | if(header.startsWith("GET")){
196 | methodFlag = "GET";
197 | }
198 | else if(header.startsWith("POST")|| header.startsWith("PUT")){
199 | methodFlag = "POST";
200 | }//在循环中重复设置了methodFlag,代码非常的丑陋冗余,请见谅
201 | continue;
202 | }//判断结束后以键值对的方式获取header
203 | String[] h = header.split(":",2);
204 | String header_key = h[0].trim();
205 | String header_value = h[1].trim();
206 | urlConnection.setRequestProperty(header_key, header_value);
207 | //BurpExtender.stdout.println(header_key + ":" + header_value);
208 | }
209 |
210 | if (methodFlag.equals("GET")){
211 | //发送GET请求必须设置如下两行
212 | urlConnection.setDoOutput(false);
213 | urlConnection.setDoInput(true);
214 |
215 | //获取URLConnection对象的连接
216 | urlConnection.connect();
217 | }
218 | else if(methodFlag.equals("POST")){
219 | //发送POST请求必须设置如下两行
220 | urlConnection.setDoOutput(true);
221 | urlConnection.setDoInput(true);
222 |
223 | //获取URLConnection对象对应的输出流
224 | out = new PrintWriter(urlConnection.getOutputStream());
225 | if(body != null) {
226 | //发送请求参数
227 | out.print(new String(body));
228 | }
229 | //flush输出流的缓冲
230 | out.flush();
231 | }
232 |
233 | //忽略响应存储
234 | if(!Config.IGNORE_RESP){
235 | //读取URL的响应
236 | respBody = UtilsPlus.readBytesFromStream((urlConnection.getInputStream()));
237 |
238 | // 检查是否有Content-Encoding头,并且值为gzip
239 | String contentEncoding = urlConnection.getHeaderField("Content-Encoding");
240 | if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
241 | respBody = UtilsPlus.gzipDecompress(respBody);
242 | }
243 | }
244 |
245 | //断开连接
246 | urlConnection.disconnect();
247 |
248 | //获取响应头
249 | rspHeader = UtilsPlus.getHeaderByHeaderFields(urlConnection.getHeaderFields());
250 |
251 | //BurpExtender.stdout.println("返回结果https:" + httpsConn.getResponseMessage());
252 | status = String.valueOf(urlConnection.getResponseCode());
253 | GUI.updateSuccessCount();
254 | } catch (Exception e) {
255 | //e.printStackTrace();
256 | respBody = e.getMessage().getBytes(StandardCharsets.UTF_8);
257 | Utils.showStderrMsg(1, "[!] First Times: " + e.getMessage());
258 | GUI.updateFailCount();
259 |
260 | //不记录错误响应的请求
261 | String cause = "Response Error";
262 | DeleteErrorKey(ReqKeyHashMap, cause);
263 | } finally {
264 | ErrorHandle(out, null, reader);
265 | }
266 |
267 | //再次获取状态码 // 影响服务器状态码的获取
268 | try {
269 | status = String.valueOf(urlConnection.getResponseCode());
270 | } catch (IOException e) {
271 | status = e.getMessage();
272 | Utils.showStderrMsg(1, "[!] Second Times: " + e.getMessage());
273 | }
274 |
275 | //修复rspHeader为空导致的空行
276 | if("".equals(rspHeader.trim())){
277 | rspHeader = "Failed to obtain the response header";
278 | }
279 |
280 | //不记录指定响应状态码的请求
281 | if(Utils.isEqualKeywords(Config.DEL_STATUS_REGX,status,false)){
282 | String cause = String.format("Status %s In %s", status, Config.DEL_STATUS_REGX);
283 | DeleteErrorKey(ReqKeyHashMap, cause);
284 | }
285 |
286 | //保存结果
287 | putResultMapInner(mapResult, status, rspHeader, respBody, proxy, port);
288 | return mapResult;
289 | }
290 |
291 |
292 | public static Map HttpProxy(HashMap ReqKeyHashMap, String url,List headers,byte[] body, String proxy, int port,String username,String password) {
293 | //public static Map HttpProxy(Set reqBodyHashSet, String url_body,String url,List headers,byte[] body, String proxy, int port,String username,String password) {
294 | Map mapResult = new HashMap<>();
295 | String status;
296 | String rspHeader = "";
297 | byte[] respBody = new byte[0];
298 |
299 | HttpURLConnection urlConnection = null;
300 | PrintWriter out = null;
301 | BufferedReader reader = null;
302 | try {
303 | URL urlClient = new URL(url);
304 | SSLContext sc = SSLContext.getInstance("SSL");
305 | //指定信任https
306 | sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
307 | //创建代理
308 | Proxy proxy1=new Proxy(Type.HTTP, new InetSocketAddress(proxy, port));
309 | //设置代理
310 | urlConnection = (HttpURLConnection) urlClient.openConnection(proxy1);
311 |
312 | //设置账号密码
313 | SetProxyAuth(username, password, urlConnection);
314 |
315 | //设置控制请求方法的Flag
316 | String methodFlag = "";
317 | //设置通用的请求属性
318 | for(String header:headers){
319 | if(header.startsWith("GET") || header.startsWith("POST") || header.startsWith("PUT")){
320 | if(header.startsWith("GET")){
321 | methodFlag = "GET";
322 | }
323 | else if(header.startsWith("POST")|| header.startsWith("PUT")){
324 | methodFlag = "POST";
325 | }//在循环中重复设置了methodFlag,代码非常的丑陋冗余,请见谅
326 | continue;
327 | }//判断结束后以键值对的方式获取header
328 | String[] h = header.split(":",2);
329 | String header_key = h[0].trim();
330 | String header_value = h[1].trim();
331 | urlConnection.setRequestProperty(header_key, header_value);
332 | //BurpExtender.stdout.println(header_key + ":" + header_value);
333 | }
334 |
335 | if (methodFlag.equals("GET")){
336 | //发送GET请求必须设置如下两行
337 | urlConnection.setDoOutput(false);
338 | urlConnection.setDoInput(true);
339 |
340 | //获取URLConnection对象的连接
341 | urlConnection.connect();
342 | } else if(methodFlag.equals("POST")){
343 | //发送POST请求必须设置如下两行
344 | urlConnection.setDoOutput(true);
345 | urlConnection.setDoInput(true);
346 |
347 | //获取URLConnection对象对应的输出流
348 | out = new PrintWriter(urlConnection.getOutputStream());
349 | if(body != null) {
350 | //发送请求参数
351 | out.print(new String(body));
352 | }
353 | //flush输出流的缓冲
354 | out.flush();
355 | }
356 |
357 | //忽略响应存储
358 | if(!Config.IGNORE_RESP){
359 | //读取URL的响应
360 | respBody = UtilsPlus.readBytesFromStream((urlConnection.getInputStream()));
361 |
362 | // 检查是否有Content-Encoding头,并且值为gzip
363 | String contentEncoding = urlConnection.getHeaderField("Content-Encoding");
364 | if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
365 | respBody = UtilsPlus.gzipDecompress(respBody);
366 | }
367 | }
368 |
369 | //断开连接
370 | urlConnection.disconnect();
371 |
372 | //获取响应头
373 | rspHeader = UtilsPlus.getHeaderByHeaderFields(urlConnection.getHeaderFields());
374 |
375 | //BurpExtender.stdout.println("返回结果http:" + httpConn.getResponseMessage());
376 | status = String.valueOf(urlConnection.getResponseCode());
377 | GUI.updateSuccessCount();
378 | } catch (Exception e) {
379 | //e.printStackTrace();
380 | respBody = e.getMessage().getBytes(StandardCharsets.UTF_8);
381 | Utils.showStderrMsg(1, "[!] First Times: " + e.getMessage());
382 | GUI.updateFailCount();
383 |
384 | //不记录错误响应的请求
385 | String cause = "Response Error";
386 | DeleteErrorKey(ReqKeyHashMap, cause);
387 | } finally {
388 | ErrorHandle(out, null, reader);
389 | }
390 |
391 | //再次获取状态码 // 影响服务器状态码的获取
392 | try {
393 | status = String.valueOf(urlConnection.getResponseCode());
394 | } catch (IOException e) {
395 | status = e.getMessage();
396 | Utils.showStderrMsg(1, "[!] Second Times: " + e.getMessage());
397 | }
398 |
399 | //修复rspHeader为空导致的空行
400 | if("".equals(rspHeader.trim())){
401 | rspHeader = "Failed to obtain the response header";
402 | }
403 |
404 | //不记录指定响应状态码的请求
405 | if(Utils.isEqualKeywords(Config.DEL_STATUS_REGX,status,false)){
406 | String cause = String.format("Status %s In %s", status, Config.DEL_STATUS_REGX);
407 | DeleteErrorKey(ReqKeyHashMap, cause);
408 | }
409 |
410 | //保存结果
411 | putResultMapInner(mapResult, status, rspHeader, respBody, proxy, port);
412 | return mapResult;
413 | }
414 |
415 |
416 | public static void SetProxyAuth(String username, String password, HttpURLConnection httpConn) {
417 | if(username != null && password != null && username.trim().length() > 0 && password.trim().length() > 0){
418 | String user_pass = String.format("%s:%s", username, password);
419 | String headerKey = "Proxy-Authorization";
420 | String headerValue = "Basic " + Base64.encode(user_pass.getBytes());
421 | Utils.showStdoutMsg(1, String.format("[*] Set [%s] Proxy-Authorization Data: [%s]", user_pass, headerValue));
422 | httpConn.setRequestProperty(headerKey, headerValue);
423 | }
424 | }
425 |
426 | private static class TrustAnyTrustManager implements X509TrustManager {
427 |
428 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
429 | }
430 |
431 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
432 | }
433 |
434 | public X509Certificate[] getAcceptedIssuers() {
435 | return new X509Certificate[] {};
436 | }
437 | }
438 |
439 | private static class TrustAnyHostnameVerifier implements HostnameVerifier {
440 | public boolean verify(String hostname, SSLSession session) {
441 | return true;
442 | }
443 | }
444 |
445 | //报错数据处理
446 | public static void ErrorHandle(PrintWriter out, BufferedReader in, BufferedReader reader) {
447 | try {
448 | if (reader != null) {
449 | reader.close();
450 | }
451 | } catch (IOException e) {
452 | }
453 | try {
454 | if (in != null) {
455 | in.close();
456 | }
457 | } catch (IOException e) {
458 | //e.printStackTrace();
459 | }
460 | if (out != null) {
461 | out.close();
462 | }
463 | }
464 |
465 | //不记录错误响应的请求
466 | private static void DeleteErrorKey(HashMap reqKeyHashMap, String cause){
467 | if(Config.DEL_ERROR_KEY){
468 | if(Config.REQ_HASH){
469 | String reqKey = reqKeyHashMap.get(Config.REQ_HASH_STR);
470 | if(Config.reqInfoHashSet.contains(reqKey)){
471 | Config.reqInfoHashSet.remove(reqKey);
472 | Utils.showStderrMsg(1, String.format("[-] Cause [%s] So Remove Hashset Record: %s", cause, reqKey) );
473 | }
474 | }
475 | if(Config.REQ_SMART){
476 | String reqKey = reqKeyHashMap.get(Config.REQ_SMART_STR);
477 | if(Config.reqInfoHashMap.containsKey(reqKey)){
478 | Config.reqInfoHashMap.remove(reqKey);
479 | Utils.showStderrMsg(1, String.format("[-] Cause [%s] So Remove Hashmap Record: %s", cause, reqKey) );
480 | }
481 | }
482 | }
483 | }
484 |
485 | private static void putResultMapInner(Map mapResult, String status, String rspHeader, byte[] respBody, String proxy, int port) {
486 | mapResult.put("respStatus", status);
487 | mapResult.put("header", rspHeader);
488 | mapResult.put("respBody", respBody);
489 | mapResult.put("proxyHost", String.format("%s:%s", proxy, port));
490 | }
491 |
492 | private static void putResultMapOuter(Map mapResult, IHttpRequestResponse rawRequestResponse) {
493 | //添加对比结果
494 | byte[] rawResponse = rawRequestResponse.getResponse();
495 | boolean equalStatus = false; //代理响应状态码是否和原始响应状态码相同
496 | boolean equalLength = false; //代理响应长度是否和原始响应长度相同
497 | if (rawResponse.length > 0) {
498 | IResponseInfo rawRespInfo = BurpExtender.helpers.analyzeResponse(rawResponse);
499 | //获取原始响应状态码
500 | String respStatus = String.valueOf(rawRespInfo.getStatusCode());
501 | equalStatus = mapResult.get("respStatus").equals(respStatus);
502 | //获取原始响应体长度
503 | int bodyOffset = rawRespInfo.getBodyOffset();
504 | int respLength = UtilsPlus.getBodyBytes(rawResponse, bodyOffset).length;
505 | equalLength = ((byte[])mapResult.get("respBody")).length == respLength;
506 | }
507 | mapResult.put("equalStatus", equalStatus);
508 | mapResult.put("equalLength", equalLength);
509 | }
510 | }
--------------------------------------------------------------------------------
/src/main/java/burp/HttpLogTable.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 | import javax.swing.table.TableModel;
5 |
6 | public class HttpLogTable extends JTable {
7 | private HttpLogTableModel httpLogTableModel;
8 |
9 | public HttpLogTableModel getHttpLogTableModel() {
10 | return httpLogTableModel;
11 | }
12 |
13 |
14 | public HttpLogTable(TableModel tableModel) {
15 | super(tableModel);
16 | this.httpLogTableModel = (HttpLogTableModel) tableModel;
17 | }
18 |
19 | @Override
20 | public void changeSelection(int row, int col, boolean toggle, boolean extend) {
21 | super.changeSelection(row, col, toggle, extend);
22 | //show the log entry for the selected row
23 | LogEntry logEntry = BurpExtender.log.get(row);
24 |
25 | //自动设置请求响应宽度
26 | GUI.proxyMsgViewerAutoSetSplitCenter();
27 | GUI.originalMsgViewerAutoSetSplitCenter();
28 |
29 | GUI.proxyRequestViewer.setMessage(logEntry.requestResponse.getRequest(), true);
30 | GUI.proxyResponseViewer.setMessage(logEntry.proxyResponse, false);
31 | GUI.originalRequestViewer.setMessage(logEntry.requestResponse.getRequest(), true);
32 | GUI.originalResponseViewer.setMessage(logEntry.requestResponse.getResponse(), false);
33 | GUI.currentlyDisplayedItem = logEntry.requestResponse;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/burp/HttpLogTableModel.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.table.AbstractTableModel;
4 |
5 | public class HttpLogTableModel extends AbstractTableModel {
6 | public int getRowCount() {
7 | return BurpExtender.log.size();
8 | }
9 |
10 | public int getColumnCount() {
11 | return 9;
12 | }
13 |
14 | @Override
15 | public String getColumnName(int columnIndex) {
16 |
17 | switch (columnIndex)
18 | {
19 | case 0:
20 | return "#";
21 | case 1:
22 | return "ProxyHost";
23 | case 2:
24 | return "Method";
25 | case 3:
26 | return "URL";
27 | case 4:
28 | return "RespStatus";
29 | case 5:
30 | return "RespLength";
31 | case 6:
32 | return "EqualStatus";
33 | case 7:
34 | return "EqualLength";
35 | case 8:
36 | return "Time";
37 | default:
38 | return "";
39 | }
40 | }
41 |
42 | @Override
43 | public Class> getColumnClass(int columnIndex)
44 | {
45 | return String.class;
46 | }
47 |
48 |
49 | public Object getValueAt(int rowIndex, int columnIndex) {
50 | LogEntry logEntry = BurpExtender.log.get(rowIndex);
51 |
52 | switch (columnIndex) {
53 | case 0:
54 | return logEntry.id;
55 | case 1:
56 | return logEntry.proxyHost;
57 | case 2:
58 | return logEntry.method;
59 | case 3:
60 | return logEntry.url.toString();
61 | case 4:
62 | return logEntry.respStatus;
63 | case 5:
64 | return logEntry.respLength;
65 | case 6:
66 | return logEntry.equalStatus;
67 | case 7:
68 | return logEntry.equalLength;
69 | case 8:
70 | return logEntry.requestTime;
71 | default:
72 | return "";
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/burp/LogEntry.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import plus.UtilsPlus;
4 |
5 | import java.net.URL;
6 | import java.text.SimpleDateFormat;
7 | import java.util.Date;
8 | import java.util.Map;
9 |
10 | public class LogEntry {
11 | final int id;
12 | final IHttpRequestResponsePersisted requestResponse;
13 | final URL url;
14 | final String method;
15 | final String respStatus;
16 | byte[] proxyResponse;
17 | public String requestTime;
18 | public String proxyHost;
19 | public String respLength;
20 | public String equalStatus;
21 | public String equalLength;
22 |
23 | LogEntry(int id, IHttpRequestResponsePersisted requestResponse, URL url, String method, Map mapResult) {
24 | this.id = id;
25 | this.requestResponse = requestResponse;
26 | this.url = url;
27 | this.method = method;
28 | this.respStatus = (String) mapResult.get("respStatus");
29 | byte[] headBytes = ((String) mapResult.get("header")).getBytes();
30 | byte[] bodyBytes = (byte[]) mapResult.get("respBody");
31 | this.proxyResponse = UtilsPlus.concatenateByteArrays(headBytes,bodyBytes);
32 | this.requestTime = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
33 | this.proxyHost = (String) mapResult.get("proxyHost");
34 | this.respLength = String.format("%s|%s", headBytes.length, bodyBytes.length);
35 | this.equalStatus = String.format("%s", mapResult.get("equalStatus"));
36 | this.equalLength = String.format("%s", mapResult.get("equalLength"));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/burp/Utils.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.security.MessageDigest;
4 | import java.util.*;
5 | import java.util.regex.Matcher;
6 | import java.util.regex.Pattern;
7 |
8 | import com.alibaba.fastjson.JSON;
9 | import com.alibaba.fastjson.JSONObject;
10 | import com.alibaba.fastjson.serializer.SerializerFeature;
11 |
12 | public class Utils {
13 |
14 | private static final Object FLAG_EXIST = "y";
15 |
16 | public static String MD5(String key) {
17 | //import java.security.MessageDigest;
18 | char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
19 | try {
20 | byte[] btInput = key.getBytes();
21 | //获得MD5摘要算法的 MessageDigest 对象
22 | MessageDigest mdInst = MessageDigest.getInstance("MD5");
23 | //使用指定的字节更新摘要
24 | mdInst.update(btInput);
25 | //获得密文
26 | byte[] md = mdInst.digest();
27 | //把密文转换成十六进制的字符串形式
28 | int j = md.length;
29 | char[] str = new char[j * 2];
30 | int k = 0;
31 | for (int i = 0; i < j; i++) {
32 | byte byte0 = md[i];
33 | str[k++] = hexDigits[byte0 >>> 4 & 0xf];
34 | str[k++] = hexDigits[byte0 & 0xf];
35 | }
36 | return new String(str);
37 | } catch (Exception e) {
38 | return null;
39 | }
40 | }
41 |
42 | public static String getBanner(String extensionName, String version){
43 | String bannerInfo =
44 | "[+] " + extensionName + " is loaded\n"
45 | + "[+] #####################################\n"
46 | + "[+] " + extensionName + " v" + version +"\n"
47 | + "[+] anthor: c0ny1\n"
48 | + "[+] github: https://github.com/c0ny1/passive-scan-client\n"
49 | + "[+] update: https://github.com/winezer0/passive-scan-client-plus\n"
50 | + "[+] ####################################";
51 | return bannerInfo;
52 | }
53 |
54 | public static void showStderrMsg(Integer msgLevel,String msg){
55 | if(msgLevel <= Config.SHOW_MSG_LEVEL){
56 | BurpExtender.stderr.println(msg);
57 | }
58 | }
59 |
60 | public static void showStdoutMsg(Integer msgLevel, String msg){
61 | if(msgLevel <= Config.SHOW_MSG_LEVEL){
62 | BurpExtender.stdout.println(msg);
63 | }
64 | }
65 |
66 | //完全关键字匹配正则
67 | public static boolean isEqualKeywords(String regx, String str, Boolean NoRegxValue){
68 | //如果没有正在表达式,的情况下返回指定值 NoRegxValue
69 | if (regx.trim().length() == 0){
70 | return NoRegxValue;
71 | }
72 |
73 | Pattern pat = Pattern.compile("^("+regx+")$",Pattern.CASE_INSENSITIVE);//正则判断
74 | Matcher mc= pat.matcher(str);//条件匹配
75 | return mc.find();
76 | }
77 |
78 | //包含关键字匹配正则
79 | public static boolean isMatchKeywords(String regx, String str, Boolean NoRegxValue){
80 | //如果没有正在表达式,的情况下返回指定值 NoRegxValue
81 | if (regx.trim().length() == 0){
82 | return NoRegxValue;
83 | }
84 |
85 | Pattern pat = Pattern.compile("^.*("+regx+").*$",Pattern.CASE_INSENSITIVE);//正则判断
86 | Matcher mc= pat.matcher(str);//条件匹配
87 | return mc.find();
88 | }
89 |
90 | //域名匹配
91 | public static boolean isMatchTargetHost(String regx, String str, Boolean NoRegxValue){
92 | return isMatchKeywords(regx, str, NoRegxValue);
93 | }
94 |
95 | //域名匹配
96 | public static boolean isMatchBlackHost(String regx, String str, Boolean NoRegxValue){
97 | //如果没有正则表达式,的情况下返回指定值 NoRegxValue
98 | if (regx.trim().length() == 0){
99 | return NoRegxValue;
100 | }
101 |
102 | Pattern pat = Pattern.compile("^.*("+regx+")$",Pattern.CASE_INSENSITIVE);//正则判断
103 | Matcher mc= pat.matcher(str);//条件匹配
104 | return mc.find();
105 | }
106 |
107 | //获取请求路径的扩展名
108 | public static String getPathExtension(String path) {
109 | String extension="";
110 |
111 | if("/".equals(path)||"".equals(path)){
112 | return extension;
113 | }
114 |
115 | try {
116 | String[] pathContents = path.split("[\\\\/]");
117 | int pathContentsLength = pathContents.length;
118 | String lastPart = pathContents[pathContentsLength-1];
119 | String[] lastPartContents = lastPart.split("\\.");
120 | if(lastPartContents.length > 1){
121 | int lastPartContentLength = lastPartContents.length;
122 | //extension
123 | extension = lastPartContents[lastPartContentLength -1];
124 | }
125 | }catch (Exception exception){
126 | Utils.showStderrMsg(2, String.format("[*] GetPathExtension [%s] Occur Error [%s]", path, exception.getMessage()));
127 | }
128 | //BurpExtender.out.println("Extension: " + extension);
129 | return extension;
130 | }
131 |
132 | //后缀匹配
133 | public static boolean isMatchBlackSuffix(String regx, String path, Boolean NoRegxValue){
134 | //如果没有正在表达式,的情况下返回指定值 NoRegxValue
135 | if (regx.trim().length() == 0){
136 | return NoRegxValue;
137 | }
138 |
139 | String ext = getPathExtension(path);
140 | //无后缀情况全部放行
141 | if("".equalsIgnoreCase(ext)){
142 | return false;
143 | }else {
144 | //Pattern pat = Pattern.compile("([\\w]+[\\.]|)("+regx+")",Pattern.CASE_INSENSITIVE);//正则判断
145 | Pattern pat = Pattern.compile("^("+regx+")$",Pattern.CASE_INSENSITIVE);//正则判断
146 | Matcher mc= pat.matcher(ext);//条件匹配
147 | return mc.find();
148 | }
149 | }
150 |
151 | //判断字符串是否为空
152 | public static boolean isEmpty(CharSequence str) {
153 | return str == null || str.length() == 0;
154 | }
155 |
156 | //判断字符串是否是Json格式
157 | public static boolean isJson(Object obj) {
158 | try{
159 | String str = obj.toString().trim();
160 |
161 | if (str.charAt(0) == '{' && str.charAt(str.length() - 1) == '}') {
162 | JSONObject.parseObject(str);
163 | return true;
164 | }
165 | return false;
166 | }catch (Exception e){
167 | return false;
168 | }
169 | }
170 |
171 | //获取请求信息(URL和Body)的HASH
172 | public static String calcReqInfoHash(String reqUrl, byte[] reqBody ) {
173 | String reqInfoHash = "";
174 | try {
175 | if(reqBody == null){
176 | reqInfoHash = reqUrl;
177 | }else {
178 | reqInfoHash = reqUrl + "&" + Utils.MD5(Arrays.toString(reqBody));
179 | }
180 | }catch (Exception exception){
181 | Utils.showStderrMsg(2, String.format("[*] getReqInfoHash [%s] Occur Error [%s]", reqUrl, exception.getMessage()));
182 | }
183 | return reqInfoHash;
184 | }
185 |
186 | //获取所有请求参数的JSON格式字符串
187 | public static String IParametersToJsonStr(List reqParams, Boolean useValue) {
188 | JSONObject reqParamsJson = new JSONObject();
189 | for (IParameter param:reqParams) {
190 | if(useValue){
191 | //默认按照值内容组成参数键值对,该方式比较字符串会更耗时
192 | reqParamsJson.put(param.getName(), param.getValue());
193 | }else {
194 | //当前根据是否存在值,该方式比较字符串会省一点时间
195 | reqParamsJson.put(param.getName(), FLAG_EXIST);
196 | }
197 | //后续可能考虑 多种参数情况,处理起来会比较耗时 如有无参数[false,true] 参数类型[None,int,string,bytes等]
198 | }
199 | //排序输出URL JSON
200 | String reqParamsJsonStr = JSON.toJSONString(reqParamsJson, SerializerFeature.MapSortField);
201 | return reqParamsJsonStr;
202 | }
203 |
204 | //判断Json是否是不重复的(不存在于HashMap中),独特返回true,重复返回false
205 | public static Boolean isUniqReqInfo(HashMap reqInfoHashMap, String reqUrl, String newReqParamsJsonStr, Boolean useValue) {
206 | //判断全局HashMap里面是否已经存在URL对应的参数字符串,有就合并新旧参数字符串,没有就直接存入
207 | String oldReqParamsJsonStr = reqInfoHashMap.get(reqUrl);
208 | showStderrMsg(2, String.format("[*] Old ReqParamsJsonStr:%s", oldReqParamsJsonStr));
209 | showStderrMsg(2, String.format("[*] New ReqParamsJsonStr:%s", newReqParamsJsonStr));
210 |
211 | //不存在历史参数列表,直接存入,返回false
212 | if(Utils.isEmpty(oldReqParamsJsonStr)){
213 | reqInfoHashMap.put(reqUrl, newReqParamsJsonStr);
214 | Utils.showStdoutMsg(1, String.format("[+] reqInfoHashMap Add By None:%s", reqInfoHashMap.get(reqUrl)));
215 | return true;
216 | }
217 |
218 | //如果新旧的参数JsonStr相同,直接返回false
219 | if(newReqParamsJsonStr.equals(oldReqParamsJsonStr)){
220 | return false;
221 | }
222 |
223 | //如果对应请求目标已经存在参数Json,且不完全相同,需要解开参数JsonStr进行对比
224 | JSONObject oldReqParamsJsonObj = JSONObject.parseObject(oldReqParamsJsonStr);
225 |
226 | //如果旧的JsonStr内没有数据,就直接存入新的参数JsonStr //大概是不会到达这个情况
227 | if(oldReqParamsJsonObj == null){
228 | reqInfoHashMap.put(reqUrl, newReqParamsJsonStr);
229 | Utils.showStdoutMsg(1, String.format("[+] reqInfoHashMap Add By Null:%s",reqInfoHashMap.get(reqUrl) ));
230 | return true;
231 | }
232 |
233 | //hasNewParam 记录是否存在新的参数
234 | boolean hasNewParam = false;
235 | //解析新的json参数对象,并进行便利
236 | Map newReqParamsJsonMap = JSONObject.parseObject(newReqParamsJsonStr, Map.class);
237 | for(Map.Entry newReqParamEntry : newReqParamsJsonMap.entrySet()){
238 | //判断旧的参数对象是否包含新的参数key,否则跳过,
239 | if(oldReqParamsJsonObj.containsKey(newReqParamEntry.getKey())){
240 | continue;
241 | }
242 | //是则往旧的Json参数对象 存入 新的参数
243 | if(useValue){
244 | oldReqParamsJsonObj.put(newReqParamEntry.getKey(), newReqParamEntry.getValue());
245 | }else {
246 | oldReqParamsJsonObj.put(newReqParamEntry.getKey(), FLAG_EXIST);
247 | }
248 | hasNewParam = true;
249 | }
250 |
251 | //如果有新参数加入就重新整理HashMap
252 | if(hasNewParam){
253 | //将新的参数Json对象转换后放入HASHMAP集合
254 | reqInfoHashMap.put(reqUrl, oldReqParamsJsonObj.toJSONString());
255 | Utils.showStdoutMsg(1, String.format("[+] reqInfoHashMap Add By New:%s", reqInfoHashMap.get(reqUrl)));
256 | return true;
257 | }else {
258 | return false;
259 | }
260 | }
261 |
262 | //根据 hashmap格式的请求参数:值 键值对 ,获取参数对应的参数Json
263 | public static String paramsHashMapToJsonStr(HashMap paramsHashMap, Boolean useValue) {
264 | //组合成参数json
265 | JSONObject reqParamsJson = new JSONObject();
266 | for(String param : paramsHashMap.keySet()) {
267 | if(useValue){
268 | reqParamsJson.put(param, paramsHashMap.get(param));
269 | }else {
270 | reqParamsJson.put(param, FLAG_EXIST);
271 | }
272 | }
273 |
274 | //排序输出URL的参数JSONStr,防止相同参数不同顺序导致的判断错误.
275 | String reqParamsJsonStr = JSON.toJSONString(reqParamsJson, SerializerFeature.MapSortField);
276 | return reqParamsJsonStr;
277 | }
278 |
279 | //处理JSon格式的参数字符串,获取 参数:值 键值对 hashmap
280 | public static HashMap JsonParamsToHashMap(String ParamsStr, Boolean useValue) {
281 | HashMap paramHashMap = new HashMap<>();
282 | Map map = JSONObject.parseObject(ParamsStr, Map.class);
283 | for(Map.Entry obj : map.entrySet()){
284 | String tempKey = obj.getKey();
285 | ParamValueHandle(useValue, paramHashMap, obj, tempKey);
286 | }
287 | return paramHashMap;
288 | }
289 |
290 | //递归处理Json内的子Json
291 | public static HashMap subJsonStrToHashMap(String prefix, String subParamsStr, Boolean useValue){
292 | HashMap subParamHashMap = new HashMap();
293 | Map subMap = JSONObject.parseObject(subParamsStr, Map.class);
294 | for(Map.Entry subObj : subMap.entrySet()) {
295 | String tempKey = String.format("%s.%s", prefix, subObj.getKey());
296 | ParamValueHandle(useValue, subParamHashMap, subObj, tempKey);
297 | }
298 | return subParamHashMap;
299 | }
300 |
301 | //参数值处理
302 | public static void ParamValueHandle(Boolean useValue, HashMap subParamHashMap, Map.Entry subObj, String tempKey) {
303 | String tempValue = subObj.getValue();
304 | String tempValueUrlDecode = decodeUrl(tempValue);
305 | if(isJson(tempValueUrlDecode)){
306 | HashMap subSubParamsHashMap = subJsonStrToHashMap(tempKey, tempValueUrlDecode, useValue);
307 | subParamHashMap.putAll(subSubParamsHashMap);
308 | }else {
309 | if(useValue){
310 | subParamHashMap.put(tempKey, tempValue);
311 | }else {
312 | subParamHashMap.put(tempKey, FLAG_EXIST);
313 | }
314 | }
315 | }
316 |
317 | //往人工解析json请求体得到的hashmap内添加 burp 自动解析出来的 参数:值对
318 | public static HashMap ParamsHashMapAddIParams(HashMap paramsHashMap, List parameters) {
319 | for (IParameter param:parameters) {
320 | paramsHashMap.put(param.getName(), param.getValue());
321 | }
322 | return paramsHashMap;
323 | }
324 |
325 | //计算字符串内是否至少包含limit个指定字符 //只需要处理多层级别的Json就可以了,单层的用内置方法即可
326 | public static boolean countStr(String longStr, String mixStr, int limit, boolean decode) {
327 | //进行URL解码
328 | if (decode) {
329 | longStr = decodeUrl(longStr);
330 | }
331 |
332 | int count = 0;
333 | int index = 0;
334 | while((index = longStr.indexOf(mixStr,index))!= -1){
335 | index = index + mixStr.length();
336 | count++;
337 | if(count >= limit){
338 | return true;
339 | }
340 | }
341 | return false;
342 | }
343 |
344 | //获取参数列表里面的认证信息字符串
345 | public static HashMap ExtractIParamsAuthParam(List parameters,List headers, Boolean addHeaderAuth) {
346 | HashMap authParamsHashMap = new HashMap<>();
347 |
348 | //常见的情况2,Cookie中的SESSION ID|JSEESION|PHPSSIONID| |user.id=
349 | //当前输入的是参数列表
350 | for (IParameter param:parameters) {
351 | if(isMatchKeywords(Config.AUTH_INFO_REGX, param.getName(), false)){ //关键字正则匹配
352 | authParamsHashMap.put(param.getName(), param.getValue());
353 | //showStdoutMsgDebug(String.format("[*] Auth Param: %s --- %s",param.getName(), param.getValue() ));
354 | }
355 | }
356 |
357 | //常见的情况1,AUTH头、Token头、
358 | if(addHeaderAuth){
359 | for (String header : headers) {
360 | List headerInfo = Arrays.asList(header.split(": ", 2));
361 | if (headerInfo.size() == 2){
362 | String headerName = headerInfo.get(0);
363 | String headerValue = headerInfo.get(1);
364 | if(isMatchKeywords(Config.AUTH_INFO_REGX, headerName, false)){ //关键字正则匹配
365 | authParamsHashMap.put(headerName, headerValue);
366 | //showStdoutMsgDebug(String.format("[*] Auth Header: %s --- %s",headerName, headerValue));
367 | }
368 | }
369 | }
370 | }
371 | return authParamsHashMap;
372 | }
373 |
374 | //判断参数值是否含有Json //需要URL解码
375 | public static Boolean paramValueHasJson(List parameters){
376 | for (IParameter param:parameters) {
377 | String tempValue = param.getValue();
378 | String tempValueUrlDecode = decodeUrl(tempValue);
379 | if(isJson(tempValueUrlDecode)){
380 | return true;
381 | }
382 | }
383 | return false;
384 | }
385 |
386 | //进行URL解码
387 | public static String decodeUrl(String str) {
388 | if (!isEmpty(str)) {
389 | for (int i = 0; i < Config.DECODE_MAX_TIMES; i++) {
390 | if (str.contains("%")) {
391 | str = BurpExtender.helpers.urlDecode(str);
392 | }else {
393 | break;
394 | }
395 | }
396 | }
397 | return str;
398 | }
399 |
400 | //获取所有请求参数的JSON格式字符串//支持处理参数值为Json的格式
401 | public static String IParametersToJsonStrPlus(List reqParams, Boolean useValue) {
402 | JSONObject reqParamsJson = new JSONObject();
403 | for (IParameter param:reqParams) {
404 | String tempKey = param.getName();
405 | String tempValue = param.getValue();
406 | String tempValueUrlDecode = decodeUrl(tempValue);
407 |
408 | if(isJson(tempValueUrlDecode)){
409 | HashMap subParamsHashMap = subJsonStrToHashMap(tempKey, tempValueUrlDecode, useValue);
410 | reqParamsJson.putAll(subParamsHashMap);
411 | }else{
412 | if(useValue){
413 | //默认按照值内容组成参数键值对,该方式比较字符串会更耗时
414 | reqParamsJson.put(tempKey, param.getValue());
415 | }else {
416 | //当前根据是否存在值,该方式比较字符串会省一点时间
417 | reqParamsJson.put(tempKey, FLAG_EXIST);
418 | }
419 | }
420 | }
421 | //排序输出URL JSON
422 | String reqParamsJsonStr = JSON.toJSONString(reqParamsJson, SerializerFeature.MapSortField);
423 | return reqParamsJsonStr;
424 | }
425 | }
426 |
--------------------------------------------------------------------------------
/src/main/java/plus/Base64.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
3 | * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
4 | */
5 | /*
6 | * Licensed to the Apache Software Foundation (ASF) under one or more
7 | * contributor license agreements. See the NOTICE file distributed with
8 | * this work for additional information regarding copyright ownership.
9 | * The ASF licenses this file to You under the Apache License, Version 2.0
10 | * (the "License"); you may not use this file except in compliance with
11 | * the License. You may obtain a copy of the License at
12 | *
13 | * http://www.apache.org/licenses/LICENSE-2.0
14 | *
15 | * Unless required by applicable law or agreed to in writing, software
16 | * distributed under the License is distributed on an "AS IS" BASIS,
17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 | * See the License for the specific language governing permissions and
19 | * limitations under the License.
20 | */
21 |
22 | package plus;
23 |
24 | /**
25 | * This class provides encode/decode for RFC 2045 Base64 as
26 | * defined by RFC 2045, N. Freed and N. Borenstein.
27 | * RFC 2045: Multipurpose Internet Mail Extensions (MIME)
28 | * Part One: Format of Internet Message Bodies. Reference
29 | * 1996 Available at: http://www.ietf.org/rfc/rfc2045.txt
30 | * This class is used by XML Schema binary format validation
31 | *
32 | * This implementation does not encode/decode streaming
33 | * data. You need the data that you will encode/decode
34 | * already on a byte arrray.
35 | *
36 | * @xerces.internal
37 | *
38 | * @author Jeffrey Rodriguez
39 | * @author Sandy Gao
40 | */
41 | public final class Base64 {
42 |
43 | static private final int BASELENGTH = 128;
44 | static private final int LOOKUPLENGTH = 64;
45 | static private final int TWENTYFOURBITGROUP = 24;
46 | static private final int EIGHTBIT = 8;
47 | static private final int SIXTEENBIT = 16;
48 | static private final int SIXBIT = 6;
49 | static private final int FOURBYTE = 4;
50 | static private final int SIGN = -128;
51 | static private final char PAD = '=';
52 | static private final boolean fDebug = false;
53 | static final private byte [] base64Alphabet = new byte[BASELENGTH];
54 | static final private char [] lookUpBase64Alphabet = new char[LOOKUPLENGTH];
55 |
56 | static {
57 |
58 | for (int i = 0; i < BASELENGTH; ++i) {
59 | base64Alphabet[i] = -1;
60 | }
61 | for (int i = 'Z'; i >= 'A'; i--) {
62 | base64Alphabet[i] = (byte) (i-'A');
63 | }
64 | for (int i = 'z'; i>= 'a'; i--) {
65 | base64Alphabet[i] = (byte) ( i-'a' + 26);
66 | }
67 |
68 | for (int i = '9'; i >= '0'; i--) {
69 | base64Alphabet[i] = (byte) (i-'0' + 52);
70 | }
71 |
72 | base64Alphabet['+'] = 62;
73 | base64Alphabet['/'] = 63;
74 |
75 | for (int i = 0; i<=25; i++)
76 | lookUpBase64Alphabet[i] = (char)('A'+i);
77 |
78 | for (int i = 26, j = 0; i<=51; i++, j++)
79 | lookUpBase64Alphabet[i] = (char)('a'+ j);
80 |
81 | for (int i = 52, j = 0; i<=61; i++, j++)
82 | lookUpBase64Alphabet[i] = (char)('0' + j);
83 | lookUpBase64Alphabet[62] = (char)'+';
84 | lookUpBase64Alphabet[63] = (char)'/';
85 |
86 | }
87 |
88 | protected static boolean isWhiteSpace(char octect) {
89 | return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
90 | }
91 |
92 | protected static boolean isPad(char octect) {
93 | return (octect == PAD);
94 | }
95 |
96 | protected static boolean isData(char octect) {
97 | return (octect < BASELENGTH && base64Alphabet[octect] != -1);
98 | }
99 |
100 | protected static boolean isBase64(char octect) {
101 | return (isWhiteSpace(octect) || isPad(octect) || isData(octect));
102 | }
103 |
104 | /**
105 | * Encodes hex octects into Base64
106 | *
107 | * @param binaryData Array containing binaryData
108 | * @return Encoded Base64 array
109 | */
110 | public static String encode(byte[] binaryData) {
111 |
112 | if (binaryData == null)
113 | return null;
114 |
115 | int lengthDataBits = binaryData.length*EIGHTBIT;
116 | if (lengthDataBits == 0) {
117 | return "";
118 | }
119 |
120 | int fewerThan24bits = lengthDataBits%TWENTYFOURBITGROUP;
121 | int numberTriplets = lengthDataBits/TWENTYFOURBITGROUP;
122 | int numberQuartet = fewerThan24bits != 0 ? numberTriplets+1 : numberTriplets;
123 | char encodedData[] = null;
124 |
125 | encodedData = new char[numberQuartet*4];
126 |
127 | byte k=0, l=0, b1=0,b2=0,b3=0;
128 |
129 | int encodedIndex = 0;
130 | int dataIndex = 0;
131 | if (fDebug) {
132 | System.out.println("number of triplets = " + numberTriplets );
133 | }
134 |
135 | for (int i=0; i>2) );
171 | }
172 | byte val1 = ((b1 & SIGN)==0)?(byte)(b1>>2):(byte)((b1)>>2^0xc0);
173 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ val1 ];
174 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ k<<4 ];
175 | encodedData[encodedIndex++] = PAD;
176 | encodedData[encodedIndex++] = PAD;
177 | } else if (fewerThan24bits == SIXTEENBIT) {
178 | b1 = binaryData[dataIndex];
179 | b2 = binaryData[dataIndex +1 ];
180 | l = ( byte ) ( b2 &0x0f );
181 | k = ( byte ) ( b1 &0x03 );
182 |
183 | byte val1 = ((b1 & SIGN)==0)?(byte)(b1>>2):(byte)((b1)>>2^0xc0);
184 | byte val2 = ((b2 & SIGN)==0)?(byte)(b2>>4):(byte)((b2)>>4^0xf0);
185 |
186 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ val1 ];
187 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ val2 | ( k<<4 )];
188 | encodedData[encodedIndex++] = lookUpBase64Alphabet[ l<<2 ];
189 | encodedData[encodedIndex++] = PAD;
190 | }
191 |
192 | return new String(encodedData);
193 | }
194 |
195 | /**
196 | * Decodes Base64 data into octects
197 | *
198 | * @param encoded string containing Base64 data
199 | * @return Array containind decoded data.
200 | */
201 | public static byte[] decode(String encoded) {
202 |
203 | if (encoded == null)
204 | return null;
205 |
206 | char[] base64Data = encoded.toCharArray();
207 | // remove white spaces
208 | int len = removeWhiteSpace(base64Data);
209 |
210 | if (len%FOURBYTE != 0) {
211 | return null;//should be divisible by four
212 | }
213 |
214 | int numberQuadruple = (len/FOURBYTE );
215 |
216 | if (numberQuadruple == 0)
217 | return new byte[0];
218 |
219 | byte decodedData[] = null;
220 | byte b1=0,b2=0,b3=0,b4=0;
221 | char d1=0,d2=0,d3=0,d4=0;
222 |
223 | int i = 0;
224 | int encodedIndex = 0;
225 | int dataIndex = 0;
226 | decodedData = new byte[ (numberQuadruple)*3];
227 |
228 | for (; i>4 ) ;
242 | decodedData[encodedIndex++] = (byte)(((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) );
243 | decodedData[encodedIndex++] = (byte)( b3<<6 | b4 );
244 | }
245 |
246 | if (!isData( (d1 = base64Data[dataIndex++]) ) ||
247 | !isData( (d2 = base64Data[dataIndex++]) )) {
248 | return null;//if found "no data" just return null
249 | }
250 |
251 | b1 = base64Alphabet[d1];
252 | b2 = base64Alphabet[d2];
253 |
254 | d3 = base64Data[dataIndex++];
255 | d4 = base64Data[dataIndex++];
256 | if (!isData( (d3 ) ) ||
257 | !isData( (d4 ) )) {//Check if they are PAD characters
258 | if (isPad( d3 ) && isPad( d4)) { //Two PAD e.g. 3c[Pad][Pad]
259 | if ((b2 & 0xf) != 0)//last 4 bits should be zero
260 | return null;
261 | byte[] tmp = new byte[ i*3 + 1 ];
262 | System.arraycopy( decodedData, 0, tmp, 0, i*3 );
263 | tmp[encodedIndex] = (byte)( b1 <<2 | b2>>4 ) ;
264 | return tmp;
265 | } else if (!isPad( d3) && isPad(d4)) { //One PAD e.g. 3cQ[Pad]
266 | b3 = base64Alphabet[ d3 ];
267 | if ((b3 & 0x3 ) != 0)//last 2 bits should be zero
268 | return null;
269 | byte[] tmp = new byte[ i*3 + 2 ];
270 | System.arraycopy( decodedData, 0, tmp, 0, i*3 );
271 | tmp[encodedIndex++] = (byte)( b1 <<2 | b2>>4 );
272 | tmp[encodedIndex] = (byte)(((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) );
273 | return tmp;
274 | } else {
275 | return null;//an error like "3c[Pad]r", "3cdX", "3cXd", "3cXX" where X is non data
276 | }
277 | } else { //No PAD e.g 3cQl
278 | b3 = base64Alphabet[ d3 ];
279 | b4 = base64Alphabet[ d4 ];
280 | decodedData[encodedIndex++] = (byte)( b1 <<2 | b2>>4 ) ;
281 | decodedData[encodedIndex++] = (byte)(((b2 & 0xf)<<4 ) |( (b3>>2) & 0xf) );
282 | decodedData[encodedIndex++] = (byte)( b3<<6 | b4 );
283 |
284 | }
285 |
286 | return decodedData;
287 | }
288 |
289 | /**
290 | * remove WhiteSpace from MIME containing encoded Base64 data.
291 | *
292 | * @param data the byte array of base64 data (with WS)
293 | * @return the new length
294 | */
295 | protected static int removeWhiteSpace(char[] data) {
296 | if (data == null)
297 | return 0;
298 |
299 | // count characters that's not whitespace
300 | int newSize = 0;
301 | int len = data.length;
302 | for (int i = 0; i < len; i++) {
303 | if (!isWhiteSpace(data[i]))
304 | data[newSize++] = data[i];
305 | }
306 | return newSize;
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/src/main/java/plus/UtilsPlus.java:
--------------------------------------------------------------------------------
1 | package plus;
2 | import java.io.*;
3 | import java.nio.charset.StandardCharsets;
4 | import java.util.Arrays;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.zip.GZIPInputStream;
8 |
9 | public class UtilsPlus {
10 | /**
11 | * 实现多个bytes数组的相加
12 | * @param arrays
13 | * @return
14 | */
15 | public static byte[] concatenateByteArrays(byte[]... arrays) {
16 | int length = 0;
17 | for (byte[] array : arrays) {
18 | length += array.length;
19 | }
20 |
21 | byte[] result = new byte[length];
22 | int offset = 0;
23 | for (byte[] array : arrays) {
24 | System.arraycopy(array, 0, result, offset, array.length);
25 | offset += array.length;
26 | }
27 | return result;
28 | }
29 |
30 | /**
31 | * 实现Gzip数据的解压
32 | * @param compressed
33 | * @return
34 | * @throws IOException
35 | */
36 | public static byte[] gzipDecompress(byte[] compressed) throws IOException {
37 | if (compressed == null || compressed.length == 0) {
38 | return null;
39 | }
40 |
41 | ByteArrayOutputStream out = new ByteArrayOutputStream();
42 | GZIPInputStream gunzip = new GZIPInputStream(new ByteArrayInputStream(compressed));
43 |
44 | byte[] buffer = new byte[256];
45 | int n;
46 | while ((n = gunzip.read(buffer)) >= 0) {
47 | out.write(buffer, 0, n);
48 | }
49 |
50 | // Close the streams
51 | gunzip.close();
52 | out.close();
53 |
54 | // Get the uncompressed data
55 | return out.toByteArray();
56 | }
57 |
58 | // 读取响应流并编码
59 | public static String readBodyFromStream(InputStream inputStream, String encoding) throws Exception {
60 | // 如果编码为null,则使用UTF-8作为默认编码
61 | if (encoding == null) {
62 | encoding = String.valueOf(StandardCharsets.UTF_8);
63 | }
64 |
65 | try (Reader reader = new InputStreamReader(inputStream, encoding)) {
66 | try (BufferedReader bufferedReader = new BufferedReader(reader)) {
67 | StringBuilder response = new StringBuilder();
68 | String line;
69 | while ((line = bufferedReader.readLine()) != null) {
70 | response.append(line).append("\n");
71 | }
72 | return response.toString();
73 | }
74 | }
75 | }
76 |
77 | //从 http conn 对象的头字典整理出原始响应头
78 | public static String getHeaderByHeaderFields(Map> mapHeaders){
79 | // 获取响应头字段映射
80 | //Map> mapHeaders = httpsConn.getHeaderFields();
81 |
82 | //存放首行
83 | String head_line = "";
84 | //存放其他行
85 | String other_line = "";
86 |
87 | for (Map.Entry> entry : mapHeaders.entrySet()) {
88 | String key = entry.getKey();
89 |
90 | List values = entry.getValue();
91 | StringBuilder value = new StringBuilder();
92 | for(String v:values){
93 | value.append(v);
94 | }
95 |
96 | if (key==null){
97 | head_line = String.format("%s\r\n", mapHeaders.get(null).get(0));
98 | } else {
99 | other_line += String.format("%s: %s\r\n", key, value);
100 | //BurpExtender.stdout.println(String.format("%s: %s\r\n", key, value));
101 | }
102 | }
103 | //BurpExtender.stdout.println(head_line + other_line);
104 | return head_line + other_line + "\r\n";
105 | }
106 |
107 | //从 http conn 对象信息中 获取响应体编码
108 | private static String getEncoding(String contentType){
109 | // 假设默认编码为UTF-8
110 | String encoding = StandardCharsets.UTF_8.name();
111 |
112 | // 尝试从内容类型中解析出字符编码
113 | if (contentType != null && contentType.contains("charset=")) {
114 | String charsetPart = contentType.split("charset=")[1];
115 | if (charsetPart != null && !charsetPart.trim().isEmpty()) {
116 | encoding = charsetPart.trim();
117 | }
118 | }
119 |
120 | return encoding;
121 | }
122 |
123 | // 读取响应流 使用系统编码
124 | public static String readBodyFromStream(InputStream inputStream) throws Exception {
125 | try (Reader reader = new InputStreamReader(inputStream)) {
126 | try (BufferedReader bufferedReader = new BufferedReader(reader)) {
127 | StringBuilder response = new StringBuilder();
128 | String line;
129 | while ((line = bufferedReader.readLine()) != null) {
130 | response.append(line).append("\n");
131 | }
132 | return response.toString();
133 | }
134 | }
135 | }
136 |
137 | // 读取响应流 使用字节码
138 | public static byte[] readBytesFromStream(InputStream inputStream) throws IOException {
139 | try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
140 | byte[] buffer = new byte[4096];
141 | int bytesRead;
142 | while ((bytesRead = inputStream.read(buffer)) != -1) {
143 | byteArrayOutputStream.write(buffer, 0, bytesRead);
144 | }
145 | return byteArrayOutputStream.toByteArray();
146 | }
147 | }
148 |
149 | public static byte[] getBodyBytes(byte[] respBytes, int bodyOffset) {
150 | // 确保bodyOffset不会导致数组越界
151 | int maxLength = respBytes.length - bodyOffset;
152 | int bodyLength = Math.max(0, maxLength);
153 |
154 | // 从request数组中复制请求体的部分
155 | byte[] body = Arrays.copyOfRange(respBytes, bodyOffset, bodyOffset + bodyLength);
156 | return body;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/main/java/plus/YamlReader.java:
--------------------------------------------------------------------------------
1 | package plus;
2 |
3 | import burp.BurpExtender;
4 | import burp.IBurpExtenderCallbacks;
5 | import org.yaml.snakeyaml.Yaml;
6 |
7 | import java.io.*;
8 | import java.util.HashMap;
9 | import java.util.LinkedHashMap;
10 | import java.util.List;
11 | import java.util.Map;
12 |
13 | public class YamlReader {
14 | private static YamlReader instance;
15 |
16 | private static Map> properties = new HashMap<>();
17 |
18 | private YamlReader(IBurpExtenderCallbacks callbacks) throws FileNotFoundException, UnsupportedEncodingException {
19 | String config = getExtensionFilePath(callbacks) + "psc.config.yml";
20 | File file = new File(config);
21 | if(file.exists()){
22 | BurpExtender.stdout.println(String.format("[+] Custom Config File Path: %s", file.getPath()));
23 | properties = new Yaml().load(new FileInputStream(file));
24 | } else {
25 | BurpExtender.stdout.println(String.format("[+] User Inner Config File Path: %s", file.getPath()));
26 | InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("psc.config.yml");
27 | InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "GBK");
28 | properties = new Yaml().load(inputStreamReader);
29 | }
30 | }
31 |
32 | public static synchronized YamlReader getInstance(IBurpExtenderCallbacks callbacks) {
33 | if (instance == null) {
34 | try {
35 | instance = new YamlReader(callbacks);
36 | } catch (FileNotFoundException e) {
37 | e.printStackTrace(new PrintWriter(callbacks.getStderr(), true));
38 | } catch (UnsupportedEncodingException e) {
39 | e.printStackTrace();
40 | }
41 | }
42 | return instance;
43 | }
44 |
45 | /**
46 | * 获取yaml属性
47 | * 可通过 "." 循环调用
48 | * 例如这样调用: YamlReader.getInstance().getValueByKey("a.b.c.d")
49 | *
50 | * @param key
51 | * @return
52 | */
53 | public Object getValueByKey(String key) {
54 | String separator = ".";
55 | String[] separatorKeys = null;
56 | if (key.contains(separator)) {
57 | separatorKeys = key.split("\\.");
58 | } else {
59 | return properties.get(key);
60 | }
61 | Map> finalValue = new HashMap<>();
62 | for (int i = 0; i < separatorKeys.length - 1; i++) {
63 | if (i == 0) {
64 | finalValue = (Map) properties.get(separatorKeys[i]);
65 | continue;
66 | }
67 | if (finalValue == null) {
68 | break;
69 | }
70 | finalValue = (Map) finalValue.get(separatorKeys[i]);
71 | }
72 | return finalValue == null ? null : finalValue.get(separatorKeys[separatorKeys.length - 1]);
73 | }
74 |
75 | public String getString(String key) {
76 | return String.valueOf(this.getValueByKey(key));
77 | }
78 |
79 | public String getString(String key, String defaultValue) {
80 | if (null == this.getValueByKey(key)) {
81 | return defaultValue;
82 | }
83 | return String.valueOf(this.getValueByKey(key));
84 | }
85 |
86 | public Boolean getBoolean(String key) {
87 | return (boolean) this.getValueByKey(key);
88 | }
89 |
90 | public Integer getInteger(String key) {
91 | return (Integer) this.getValueByKey(key);
92 | }
93 |
94 | public double getDouble(String key) {
95 | return (double) this.getValueByKey(key);
96 | }
97 |
98 | public List getStringList(String key) {
99 | return (List) this.getValueByKey(key);
100 | }
101 |
102 | public LinkedHashMap getLinkedHashMap(String key) {
103 | return (LinkedHashMap) this.getValueByKey(key);
104 | }
105 |
106 | //获取-插件运行路径
107 | public String getExtensionFilePath(IBurpExtenderCallbacks callbacks) {
108 | String path = "";
109 | Integer lastIndex = callbacks.getExtensionFilename().lastIndexOf(File.separator);
110 | path = callbacks.getExtensionFilename().substring(0, lastIndex) + File.separator;
111 | return path;
112 | }
113 | }
--------------------------------------------------------------------------------
/src/main/resources/psc.config.yml:
--------------------------------------------------------------------------------
1 | EXTENSION_NAME: "PSC"
2 | VERSION: "0.4.22"
3 | PROXY_HOST: "127.0.0.1"
4 | PROXY_PORT: 7777
5 | PROXY_USERNAME: ""
6 | PROXY_PASSWORD: ""
7 | PROXY_TIMEOUT: 5000
8 | INTERVAL_TIME: 500
9 | HASH_MAP_LIMIT: 500
10 | HASH_SET_LIMIT: 1000
11 | SELECTED_HASH: false
12 | SELECTED_PARAM: false
13 | SELECTED_SMART: false
14 | SELECTED_AUTH: false
15 | SELECTED_IGNORE: false
16 | TARGET_HOST_REGX: ""
17 | BLACK_URL_REGX: "bing.com|baidu.com|microsoft.com|msn.com|nelreports.net|azure.com|bdstatic.com|logout"
18 | BLACK_SUFFIX_REGX: "js|css|jpeg|gif|jpg|png|pdf|rar|zip|docx|doc|svg|jpeg|ico|woff|woff2|ttf|otf"
19 | AUTH_INFO_REGX: "token|auth|sessid|session"
20 | DEL_STATUS_REGX: "502|504"
21 | DECODE_MAX_TIMES: 3
22 | SHOW_MSG_LEVEL: 1
--------------------------------------------------------------------------------