├── .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 | ![v0.4.5](./doc/v0.4.5.png) 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 | ![image-20220511142914622](./doc/image-20220511142914622.png) 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 | ![image](https://github.com/winezer0/passive-scan-client-plus/assets/46115146/251adf85-4e0e-4582-909d-efa74f0bf9d2) 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 | ![流程图](./doc/process.png) 51 | 52 | ## 0x04 下载编译 53 | 54 | ``` 55 | 手动编译: mvn package 56 | 发布版本: release 57 | ``` 58 | 59 | ## 0x05 插件演示 60 | 61 | 可以通过插件将流量转发到各种被动式扫描器中,这里我选`xray`来演示. 62 | 63 | ![动图演示](./doc/show.gif) 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 | ![NOVASEC](doc/NOVASEC.jpg) 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>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 --------------------------------------------------------------------------------