├── .gitattributes ├── .github └── workflows │ └── maven.yml ├── .gitignore ├── README-EN.MD ├── README.md ├── doc ├── APIFinder运行流程.png ├── webpack简单格式.字符型.png └── webpack简单格式.数字型.png ├── pom.xml ├── rule ├── finger-important-cn.json └── finger-important-en.json └── src └── main ├── java ├── EnumType │ ├── LocationType.java │ ├── MatchType.java │ └── RiskLevel.java ├── burp │ ├── AnalyseInfo.java │ ├── BurpExtender.java │ └── IProxyScanner.java ├── database │ ├── AnalyseHostResultTable.java │ ├── AnalyseHostUnVisitedUrls.java │ ├── AnalyseUrlResultTable.java │ ├── CommonDeleteLine.java │ ├── CommonFetchData.java │ ├── CommonUpdateStatus.java │ ├── Constants.java │ ├── DBService.java │ ├── PathTreeTable.java │ ├── RecordPathTable.java │ ├── RecordUrlTable.java │ ├── ReqDataTable.java │ ├── ReqMsgDataTable.java │ ├── TableLineDataModelBasicHostSQL.java │ ├── TableLineDataModelBasicUrlSQL.java │ └── UnionTableSql.java ├── model │ ├── AccessedUrlInfo.java │ ├── AnalyseHostResultModel.java │ ├── AnalyseUrlResultModel.java │ ├── BasicHostTableLineDataModel.java │ ├── BasicHostTableTabDataModel.java │ ├── BasicUrlTableLineDataModel.java │ ├── BasicUrlTableTabDataModel.java │ ├── FindPathModel.java │ ├── FingerPrintRule.java │ ├── FingerPrintRulesWrapper.java │ ├── HttpMsgInfo.java │ ├── HttpRespInfo.java │ ├── HttpUrlInfo.java │ ├── PathToUrlsModel.java │ ├── PathTreeModel.java │ ├── RecordHashMap.java │ ├── RecordPathDirsModel.java │ ├── RecordPathModel.java │ ├── ReqMsgDataModel.java │ ├── ReqUrlRespStatusModel.java │ ├── RespFieldsModel.java │ └── UnVisitedUrlsModel.java ├── ui │ ├── BasicHostConfigPanel.java │ ├── BasicHostInfoPanel.java │ ├── BasicUrlConfigPanel.java │ ├── BasicUrlInfoPanel.java │ ├── FingerTabRender │ │ ├── ButtonEditor.java │ │ ├── ButtonRenderer.java │ │ ├── CenterRenderer.java │ │ ├── HeaderIconTypeRenderer.java │ │ └── LeftRenderer.java │ ├── MainTabRender │ │ ├── ColorInfoCellRenderer.java │ │ ├── HasImportantCellRenderer.java │ │ ├── RunStatusCellRenderer.java │ │ └── TableHeaderWithTips.java │ ├── RuleConfigPanel.java │ └── Tabs.java ├── utilbox │ ├── ByteArrayUtils.java │ ├── CharsetUtils.java │ ├── DomainUtils.java │ ├── HelperPlus.java │ ├── IPAddressUtils.java │ └── TextUtils.java └── utils │ ├── AnalyseInfoUtils.java │ ├── AnalyseUriFilter.java │ ├── BurpFileUtils.java │ ├── BurpHttpUtils.java │ ├── BurpPrintUtils.java │ ├── BurpSitemapUtils.java │ ├── CastUtils.java │ ├── ConfigUtils.java │ ├── ElementUtils.java │ ├── PathTreeUtils.java │ ├── RegularUtils.java │ ├── RespFieldCompareutils.java │ ├── RespHashUtils.java │ ├── RespTitleUtils.java │ ├── RespWebpackJsParser.java │ └── UiUtils.java └── resources ├── conf └── finger-important.json └── icon ├── addButtonIcon.png ├── convenientOperationIcon.png ├── copyIcon.png ├── customizeIcon.png ├── deleteButton.png ├── editButton.png ├── exportItem.png ├── filterIcon.png ├── findUrlFromJS.png ├── importItem.png ├── importantButtonIcon.png ├── insertNewPathIcon.png ├── moreButton.png ├── noFindUrlFromJS.png ├── openButtonIcon.png ├── refreshButton.png ├── refreshButton2.png ├── resetItem.png ├── runningButton.png ├── saveItem.png ├── searchButton.png ├── shutdownButtonIcon.png └── urlIcon.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.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 | 21 | jobs: 22 | build: 23 | 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Set up JDK 8 29 | uses: actions/setup-java@v3 30 | with: 31 | java-version: '8' 32 | distribution: 'temurin' 33 | cache: maven 34 | - name: Build with Maven 35 | run: mvn -B package --file pom.xml 36 | 37 | - name: Archive Jar file 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: jar-with-dependencies 41 | path: ./target/*-jar-with-dependencies.jar # 指定二进制文件的路径 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | replay_pid* 25 | .idea/ 26 | target/ 27 | *.db 28 | -------------------------------------------------------------------------------- /README-EN.MD: -------------------------------------------------------------------------------- 1 | # BurpAPIFinder-Refactor 2 | 3 | This plugin is a refactored development based on APIFinder UI [https://github.com/shuanx/BurpAPIFinder] 4 | 5 | ### Tips: 6 | 7 | Hover over the buttons to see their descriptions; currently, there is no documentation available. 8 | 9 | To ensure data integrity, all response body data is saved. If no whitelist is specified, it is recommended to clear the database for each new project to avoid occupying too much space. 10 | 11 | ### Download Link 12 | 13 | Visit https://github.com/winezer0/APIFinderPlus/actions 14 | ``` 15 | -> Latest workflow [Green indicates valid] 16 | -> Summary 17 | -> Download jar-with-dependencies at the bottom 18 | ``` 19 | 20 | ### Plugin Goals 21 | 22 | Create the most comprehensive API mining tool, 23 | Reduce manual path extraction and testing, 24 | Supplement operations that cannot be handled automatically, 25 | 26 | 1. Support extraction of sensitive information, URLs, and URI information from response data. 27 | 2. Support automatic calculation of actual URLs based on known path information. 28 | 3. Support automatic access to mined URL information for recursive information extraction. 29 | 4. Support simple format extraction and splicing of webpack's JS files (limited format but high accuracy). 30 | 31 | Combination format abcd.xxxx.js 32 | ![Simple webpack format. Character type](./doc/webpack简单格式.字符型.png) 33 | 34 | Combination format 1234.xxxx.js 35 | ![Simple webpack format. Numeric type](./doc/webpack简单格式.数字型.png) 36 | 37 | ### Notes 38 | 39 | 1. All data is stored and read using SQLite, which is slower than in-memory operations. 40 | 2. When there are too many targets, executing tasks like refreshing unvisited URLs or automatic recursive scanning may consume a significant amount of memory. 41 | 3. Due to the abundance of features, hover over text or buttons to view operation descriptions. 42 | 43 | ### Basic Workflow [Old Version] 44 | 45 | ![APIFinder Workflow](./doc/APIFinder运行流程.png) 46 | 47 | 48 | ### Main Tasks [Updated] 49 | 50 | ``` 51 | Scheduled Task Thread: 52 | - Query the ReqDataTable in the database 53 | - Check if there are any unanalyzed messages 54 | - Match and extract sensitive information, URLs, and PATHs from requests/responses based on rule configurations 55 | - Save analysis results to the AnalyseUrlResultTable in the database 56 | - Query the AnalyseUrlResultTable in the database 57 | - Categorize new results in the AnalyseUrlResultTable by RootUrl and insert them into the AnalyseHostResultTable 58 | 59 | - autoPathsToUrlsIsOpen: Enable automatic URL calculation based on paths (disabled by default, supports manual operation) 60 | - Query the RecordPathTable in the database 61 | - Check if there are valid request PATHs not yet added to the website path tree 62 | - Calculate/update the PathTree based on recorded URL paths 63 | - Save analysis results to the PathTree table 64 | 65 | - Query the database by combining PathTreeTable and AnalyseHostResultTable 66 | - Check if there are updated PathTrees that have not been recalculated for PATH URLs 67 | - Calculate possible prefixes for new PATHs based on the updated PathTree 68 | - Save analysis results to the PATH-calculated URL in the AnalyseHostResultTable 69 | 70 | - autoRecursiveIsOpen: Enable automatic access to unvisited URLs 71 | - Query the AnalyseHostResultTable in the database 72 | - Check if all URLs have been visited 73 | - Construct HTTP requests for unvisited URLs 74 | ``` 75 | 76 | ### Internal Rule Explanation 77 | ``` 78 | Note: Rules starting with CONF_ and located in "config" are internal rules and are not used for information matching. 79 | 80 | CONF_DEFAULT_PERFORMANCE: Default performance configuration 81 | "maxPatterChunkSizeDefault=1000000": Maximum response length for regex matching in one operation. Changes take effect immediately after saving. 82 | "maxStoreRespBodyLenDefault=1000000": Maximum size of response body saved in the database. Changes take effect immediately after saving. 83 | "monitorExecutorIntervalsDefault=4": Interval (in seconds) for executing extraction checks. Changes take effect immediately after saving. 84 | Other default UI button-related parameters take effect after saving and restarting the plugin. 85 | 86 | Custom automatic request scanning methods: 87 | 1. CONF_RECURSE_REQ_HTTP_METHODS: Customize supported HTTP request methods (one method per line). 88 | 2. CONF_RECURSE_REQ_HTTP_PARAMS: Configure keywords to prevent automatic scanning of URLs, such as [logout, del], to avoid accidental deletions. 89 | 3. CONF_BLACK_RECURSE_REQ_PATH_KEYS: Support custom request parameters (write one set of request parameters per line; multiple lines will be iterated). 90 | Note: The request headers for the current request are dynamically obtained from the current URL request body. Custom request header functionality will be added based on user needs. 91 | 92 | 93 | CONF_WHITE_ROOT_URL: Keywords for allowed RootUrls to scan. 94 | CONF_BLACK_ROOT_URL: Keywords for RootUrls prohibited from [monitoring scans | URL extraction | PATH extraction]. 95 | CONF_BLACK_URI_PATH_KEYS: Keywords for URI paths prohibited from [monitoring scans | URL extraction | PATH extraction]. 96 | CONF_BLACK_URI_EXT_EQUAL: File extensions prohibited from [monitoring scans | URL extraction | PATH extraction]. 97 | CONF_BLACK_AUTO_RECORD_PATH: Keywords for RootUrls prohibited from automatic PATH recording. 98 | CONF_BLACK_AUTO_RECURSE_SCAN: Keywords for RootUrls prohibited from automatic scanning of unvisited URLs. 99 | CONF_WHITE_RECORD_PATH_STATUS: Response status codes allowed for automatic PATH recording. 100 | CONF_BLACK_RECORD_PATH_TITLE: Response titles prohibited from automatic PATH recording. 101 | CONF_BLACK_EXTRACT_PATH_EQUAL: URI paths prohibited from extraction [equal to any element in this list]. 102 | CONF_BLACK_EXTRACT_INFO_KEYS: Sensitive information prohibited from extraction [containing any element in this list]. 103 | CONF_REGULAR_EXTRACT_URIS: Regular expressions for extracting response URIs/URLs. 104 | ``` 105 | 106 | ### Matching Rule Explanation 107 | 108 | ``` 109 | 110 | Matching Location ("location" field): 111 | locations = 112 | CONFIG("config"): Configuration rules, not involved in matching. 113 | PATH("path"): Request path. 114 | TITLE("title"): Response title (.*). 115 | BODY("body"): Response body. 116 | HEADER("header"): Response header. 117 | RESPONSE("response"): Entire response content. 118 | ICON_HASH("icon_hash"): Hash of the ico file, only obtained when the file is of ico type. 119 | 120 | 121 | Matching Method ("matchType" field): 122 | 1. Keyword Matching 123 | ANY_KEYWORDS("any_keywords"): Match any keyword rule (common). Supports || and && syntax. 124 | ALL_KEYWORDS("all_keywords"): Match all keyword rules (rare). Supports || and && syntax. 125 | 2. Regex Matching 126 | ANY_REGULAR("any_regular"): Match any regex rule (common). 127 | ALL_REGULAR("all_regular"): Match all regex rules (rare). 128 | 129 | Actual Matching Rules ("matchKeys": [] list): 130 | 1. Keyword Matching Rule Writing 131 | Each line is a keyword extraction matching rule. 132 | Each line's content supports multiple keywords joined together using the symbol 【|】. 133 | Example: 134 | "matchType": "any_keywords" + "matchKeys": ["fzhm1&&total1&&rows1", "fzhm2&&total2&&rows2"], 135 | Indicates that it requires fzhm1, total1, and rows1 keywords simultaneously or fzhm2, total2, and rows2 simultaneously. 136 | 137 | "matchType": "all_keywords" + "matchKeys": ["fzhm1||fzhm2","total1||total2"], 138 | Indicates that it requires at least one fzhm* and at least one total* keyword simultaneously. 139 | Note: 140 | 1. This rule differs from the original version. 141 | 2. Since it uses syntax symbols 【|| &&】, matching keywords cannot contain 【|| &&】. 142 | 143 | 2. Regex Matching Rule Writing 144 | Each line is a regex extraction matching rule. 145 | 146 | 147 | 148 | Other Keywords: 149 | "accuracy": Rule accuracy. 150 | "describe": Rule description. 151 | "isImportant": Whether the matching result is important. 152 | "isOpen": Whether the rule is enabled. 153 | "type": Rule category. 154 | 155 | ``` 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BurpAPIFinder-Refactor 2 | 3 | 本插件参考 APIFinder UI 进行重构开发 [https://github.com/shuanx/BurpAPIFinder] 4 | 5 | 6 | ### TODO规划 7 | 8 | ``` 9 | 优先修复BUG 10 | 优先增强功能 11 | 延后文档编写 12 | ``` 13 | 14 | 欢迎提交增强功能思路. 15 | 16 | 17 | ### 使用提示: 18 | 19 | 目前没有文档描述, 暂时没有时间进行详细文档编写, 希望用户可以提交初步使用文档,我将进行更新 20 | 21 | 按钮功能说明: 请将鼠标悬浮在按钮上,会进行提示 22 | 23 | 数据库占用大: 为了数据完整性基本保存了所有响应体数据 如果没有指定白名单的话,建议每个新项目都清理一次数据库,否则将会占用大量空间 24 | 25 | 自定义配置文件: 将自己的配置文件存放在插件所在目录即可. 26 | 27 | ### 下载地址 28 | 29 | 访问 https://github.com/winezer0/APIFinderPlus/actions 30 | ``` 31 | -> 最新工作流【绿色表示有效】 32 | -> Summary 33 | -> 最下面下载 jar-with-dependencies 34 | ``` 35 | 36 | ### 插件目标 37 | 38 | 做最全面的API挖掘工具、 39 | 减少手动拼接path的提取测试、 40 | 补充无法自动处理的操作、 41 | 42 | 1、支持 响应 信息中的敏感信息、URL、URI信息提取. 43 | 2、支持 自动基于 已知路径信息 计算PATH 对应的实际URL. 44 | 3、支持 自动访问 挖掘出来的URL信息 进行递归式的信息提取. 45 | 4、支持 对webpack的js的简单格式的拼接提取 (限制格式,但准确度高) 46 | 47 | 组合形式 abcd.xxxx.js 48 | ![webpack简单格式.字符型](./doc/webpack简单格式.字符型.png) 49 | 50 | 组合形式 1234.xxxx.js 51 | ![webpack简单格式.数字型](./doc/webpack简单格式.数字型.png) 52 | 53 | ### 注意事项 54 | 55 | 1、所有数据都是存储sqlite进行读写、比内存操作慢一些. 56 | 2、当目标数量过多时、执行 刷新未访问URL、自动递归扫描 任务时,占用的内存应该是较大的。 57 | 3、因为功能过多,使用请将鼠标悬浮到文本或按钮上,查看操作描述 58 | 59 | ### 基本流程 【旧版】 60 | 61 | ![APIFinder运行流程](./doc/APIFinder运行流程.png) 62 | 63 | 64 | ### 主要任务 【更新】 65 | 66 | ``` 67 | 定时任务线程: 68 | - 查询数据库 ReqDataTable 表 69 | - 是否存在未分析的 消息 70 | - 根据规则配置 匹配 提取 请求|响应中的敏感信息和URL、PATH 71 | - 分析结果存入数据库 AnalyseUrlResultTable 表 72 | - 查询数据库AnalyseUrlResultTable 表 73 | - 将 AnalyseUrlResultTable 表中的新结果按照RootUrl分类插入到 AnalyseHostResultTable 表 74 | 75 | - autoPathsToUrlsIsOpen 开启自动基于路径计算URL功能 (默认关闭、支持手动) 76 | - 查询数据库 RecordPathTable 77 | - 检查是否存在没有加入到 网站路径树 的有效请求PATH 78 | - 根据已记录的URL路径计算/更新Pathree 79 | - 分析结果存入 PathTree 表 80 | 81 | - 查询数据库 联合分析 PathTreeTable 和 AnalyseHostResultTable 表 82 | - 检查是否存在已经更新的PathTree 但是还没有重新计算过PATH URL的数据 83 | - 根据已更新的Pathree计算新的PATH可能的前缀 84 | - 分析结果存入 AnalyseHostResultTable 的 PATH计算URL 85 | 86 | - autoRecursiveIsOpen 开启自动访问未访问的URL 87 | - 查询数据库 AnalyseHostResultTable 表 88 | - 判断是否URL是否都已经被访问 89 | - 对未访问URL构造HTTP请求 90 | ``` 91 | ### 内部规则说明 92 | ``` 93 | 注意:对于CONF_开头和location为config的规则,属于内部规则,不用于信息匹配。 94 | 95 | CONF_DEFAULT_PERFORMANCE: 默认的性能配置 96 | "maxPatterChunkSizeDefault=1000000", 正则匹配一次性处理的响应长度 修改保存后立即生效 97 | "maxStoreRespBodyLenDefault=1000000", 数据库保存响应体的最大大小 修改保存后立即生效 98 | "monitorExecutorIntervalsDefault=4", 几秒钟执行一次检查提取操作 修改保存后立即生效 99 | 其他默认UI按钮相关的参数,修改保存后,重启插件生效 100 | 101 | 自定义自动请求扫描的方法 102 | 1、CONF_RECURSE_REQ_HTTP_METHODS 自定义 任意 请求方法列表支持 【每行一种请求方法】 103 | 2、CONF_RECURSE_REQ_HTTP_PARAMS 配置禁止自动扫描的URL关键字 如【logout、del】等 防止误删除 104 | 3、CONF_BLACK_RECURSE_REQ_PATH_KEYS 支持自定义请求参数 【一次的请求参数写在一行即可,多行会遍历】 105 | 注意:当前请求的请求头是基于当前URL请求体中动态获取的,后续根据用户需求添加自定义请求头功能。 106 | 107 | 108 | CONF_WHITE_ROOT_URL: 允许扫描的目标RootUrl关键字 109 | CONF_BLACK_ROOT_URL: 禁止进行[监听扫描|URL提取|PATH提取]的 RootUrl关键字 110 | CONF_BLACK_URI_PATH_KEYS: 禁止进行[监听扫描|URL提取|PATH提取]的 URI 路径关键字 111 | CONF_BLACK_URI_EXT_EQUAL: 禁止进行[监听扫描|URL提取|PATH提取]的 URI 文件扩展名 112 | CONF_BLACK_AUTO_RECORD_PATH: 禁止自动进行有效PATH记录的目标RootUrl关键字 113 | CONF_BLACK_AUTO_RECURSE_SCAN: 禁止自动进行未访问URL扫描的目标RootUrl关键字 114 | CONF_WHITE_RECORD_PATH_STATUS: 允许自动进行有效PATH记录的响应状态码 115 | CONF_BLACK_RECORD_PATH_TITLE: 禁止自动进行有效PATH记录的响应标题 116 | CONF_BLACK_EXTRACT_PATH_EQUAL: 禁止提取的URI路径[等于]此项任一元素 117 | CONF_BLACK_EXTRACT_INFO_KEYS: 禁止提取的敏感信息[包含]此项任一元素 118 | CONF_REGULAR_EXTRACT_URIS: 提取响应URI|URL的正则表达式 119 | ``` 120 | 121 | ### 匹配规则说明 122 | 123 | ``` 124 | 125 | 匹配位置("location" 字段): 126 | locations = 127 | CONFIG("config"), //配置规则、不参与匹配 128 | PATH("path"), //请求路径 129 | TITLE("title"), //响应标题 (.*) 130 | BODY("body"), //响应正文 131 | HEADER("header"), //响应头部 132 | RESPONSE("response"), //全部响应内容 133 | ICON_HASH("icon_hash"); //ico文件hash 只有在文件是ico类型时才会获取 134 | 135 | 136 | 匹配方法("matchType"字段): 137 | 1、关键字匹配 138 | ANY_KEYWORDS("any_keywords"), //匹配任意关键字规则 常见 支持|| &&语法 139 | ALL_KEYWORDS("all_keywords"), //匹配所有关键字规则 少见 支持|| &&语法 140 | 2、正则匹配 141 | ANY_REGULAR("any_regular"), //要求匹配任意正则 常见 142 | ALL_REGULAR("all_regular"); //要求匹配所有正则 少见 143 | 144 | 实际匹配规则("matchKeys" : [] 列表): 145 | 1、关键字匹配规则编写 146 | 每行是一个关键字提取匹配规则、 147 | 每行的内容支持由多个关键字拼接组成,拼接符号是 【|】 148 | 举例: 149 | "matchType": "any_keywords" + "matchKeys": ["fzhm1&&total1&&rows1", "fzhm2&&total2&&rows2"], 150 | 表示要求 同时含有fzhm1、total1、rows1 关键字 或 同时完全含有fzhm2、total2、rows2 151 | 152 | "matchType": "all_keywords" + "matchKeys": ["fzhm1||fzhm2","total1||total2"], 153 | 表示要求 同时含有 至少一个fzhm*、 至少一个total* 关键字 154 | 注意: 155 | 1、本规则和原版的有差异, 156 | 2、由于使用了语法符号 【|| && 】 ,因此不能让匹配关键字中包含【|| &&】 157 | 158 | 2、正则匹配规则编写 159 | 每行是一个正则提取匹配规则 160 | 161 | 162 | 163 | 其他关键字: 164 | "accuracy": 规则准确度 165 | "describe": 规则描述 166 | "isImportant": 匹配结果是否重要信 167 | "isOpen": 是否启用规则 168 | "type": 规则分类 169 | 170 | ``` 171 | -------------------------------------------------------------------------------- /doc/APIFinder运行流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/doc/APIFinder运行流程.png -------------------------------------------------------------------------------- /doc/webpack简单格式.字符型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/doc/webpack简单格式.字符型.png -------------------------------------------------------------------------------- /doc/webpack简单格式.数字型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/doc/webpack简单格式.数字型.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | APIFinderPlus 9 | 1.1.8 10 | 11 | 12 | 8 13 | 8 14 | 15 | 16 | 17 | 18 | 19 | net.portswigger.burp.extender 20 | burp-extender-api 21 | 2.3 22 | 23 | 24 | 25 | com.alibaba.fastjson2 26 | fastjson2 27 | 2.0.40 28 | 29 | 30 | 31 | 32 | org.xerial 33 | sqlite-jdbc 34 | 3.45.3.0 35 | 36 | 37 | 38 | 39 | org.slf4j 40 | slf4j-nop 41 | 1.6.1 42 | compile 43 | 44 | 45 | 46 | 47 | 48 | commons-net 49 | commons-net 50 | 3.10.0 51 | 52 | 53 | com.github.seancfoley 54 | ipaddress 55 | 5.3.3 56 | 57 | 58 | 59 | 60 | com.google.guava 61 | guava 62 | 33.1.0-jre 63 | 64 | 65 | 66 | dnsjava 67 | dnsjava 68 | 2.1.9 69 | 70 | 71 | 72 | commons-io 73 | commons-io 74 | 2.16.1 75 | 76 | 77 | 78 | org.apache.commons 79 | commons-text 80 | 1.12.0 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-assembly-plugin 88 | 89 | 90 | package 91 | 92 | single 93 | 94 | 95 | 96 | 97 | 98 | jar-with-dependencies 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-compiler-plugin 105 | 3.1 106 | 107 | 8 108 | 8 109 | utf-8 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /src/main/java/EnumType/LocationType.java: -------------------------------------------------------------------------------- 1 | package EnumType; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public enum LocationType { 7 | CONFIG("config"), 8 | PATH("path"), 9 | TITLE("title"), 10 | BODY("body"), 11 | HEADER("header"), 12 | RESPONSE("response"), 13 | ICON_HASH("icon_hash"); 14 | 15 | private final String value; 16 | 17 | // 构造方法 18 | LocationType(String value) { 19 | this.value = value; 20 | } 21 | 22 | // 获取枚举对应的字符串值 23 | public String getValue() { 24 | return value; 25 | } 26 | 27 | // 根据字符串值获取对应的枚举 28 | public static LocationType fromValue(String value) { 29 | for (LocationType type : LocationType.values()) { 30 | if (type.getValue().equalsIgnoreCase(value)) { 31 | return type; 32 | } 33 | } 34 | throw new IllegalArgumentException("Invalid location type: " + value); 35 | } 36 | 37 | /** 38 | * 将 LocationType 枚举的所有值转换为 List。 39 | * 40 | * @return 包含所有 LocationType 值的列表 41 | */ 42 | public static String[] getValues() { 43 | // 创建一个可变的列表来存储字符串值 44 | List locationTypes = new ArrayList<>(); 45 | // 遍历枚举值并提取其对应的字符串值 46 | for (LocationType type : LocationType.values()) { 47 | locationTypes.add(type.getValue()); 48 | } 49 | // 将 List 转换为 String[] 50 | return locationTypes.toArray(new String[0]); 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/EnumType/MatchType.java: -------------------------------------------------------------------------------- 1 | package EnumType; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public enum MatchType { 7 | CONFIG("config"), 8 | ANY_KEYWORDS("any_keywords"), //匹配任意关键字规则 常见 支持||&&语法 9 | ALL_KEYWORDS("all_keywords"), //匹配所有关键字规则 少见 支持||&&语法 10 | ANY_REGULAR("any_regular"), //要求匹配任意正则 常见 11 | ALL_REGULAR("all_regular"); //要求匹配所有正则 少见 12 | 13 | private final String value; 14 | 15 | // 构造方法 16 | MatchType(String value) { 17 | this.value = value; 18 | } 19 | 20 | // 获取枚举对应的字符串值 21 | public String getValue() { 22 | return value; 23 | } 24 | 25 | // 根据字符串值获取对应的枚举 26 | public static MatchType fromValue(String value) { 27 | for (MatchType type : MatchType.values()) { 28 | if (type.getValue().equalsIgnoreCase(value)) { 29 | return type; 30 | } 31 | } 32 | throw new IllegalArgumentException("Invalid match type: " + value); 33 | } 34 | 35 | /** 36 | * 将 matchType 枚举的所有值转换为 List。 37 | * 38 | * @return 包含所有 matchType 值的列表 39 | */ 40 | public static String[] getValues() { 41 | // 创建一个可变的列表来存储字符串值 42 | List matchTypes = new ArrayList<>(); 43 | // 遍历枚举值并提取其对应的字符串值 44 | for (MatchType type : MatchType.values()) { 45 | matchTypes.add(type.getValue()); 46 | } 47 | // 将 List 转换为 String[] 48 | return matchTypes.toArray(new String[0]); 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/EnumType/RiskLevel.java: -------------------------------------------------------------------------------- 1 | package EnumType; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public enum RiskLevel { 7 | CONFIG("config"), //要求全匹配任意关键字规则 少见 8 | HIGH("high"), 9 | MEDIUM("medium"), 10 | LOWER("lower"); 11 | 12 | private final String value; 13 | 14 | // 构造方法 15 | RiskLevel(String value) { 16 | this.value = value; 17 | } 18 | 19 | // 获取枚举对应的字符串值 20 | public String getValue() { 21 | return value; 22 | } 23 | 24 | // 根据字符串值获取对应的枚举 25 | public static RiskLevel fromValue(String value) { 26 | for (RiskLevel type : RiskLevel.values()) { 27 | if (type.getValue().equalsIgnoreCase(value)) { 28 | return type; 29 | } 30 | } 31 | throw new IllegalArgumentException("Invalid match type: " + value); 32 | } 33 | 34 | /** 35 | * 将 riskLevel 枚举的所有值转换为 List。 36 | * 37 | * @return 包含所有 riskLevel 值的列表 38 | */ 39 | public static String[] getValues() { 40 | // 创建一个可变的列表来存储字符串值 41 | List riskLevels = new ArrayList<>(); 42 | // 遍历枚举值并提取其对应的字符串值 43 | for (RiskLevel type : RiskLevel.values()) { 44 | riskLevels.add(type.getValue()); 45 | } 46 | // 将 List 转换为 String[] 47 | return riskLevels.toArray(new String[0]); 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/database/AnalyseHostUnVisitedUrls.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import model.UnVisitedUrlsModel; 5 | import utils.CastUtils; 6 | 7 | import java.sql.Connection; 8 | import java.sql.PreparedStatement; 9 | import java.sql.ResultSet; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static utils.BurpPrintUtils.LOG_ERROR; 14 | import static utils.BurpPrintUtils.stderr_println; 15 | 16 | public class AnalyseHostUnVisitedUrls { 17 | 18 | /** 19 | * 实现 基于 rootUrls 列表 删除 unvisitedUrls 20 | */ 21 | public static synchronized int clearUnVisitedUrlsByRootUrls(List rootUrls) { 22 | int totalRowsAffected = 0; 23 | if (rootUrls.isEmpty()) return totalRowsAffected; 24 | 25 | // 构建SQL语句 26 | String updateSQL = ("UPDATE "+ AnalyseHostResultTable.tableName + " SET unvisited_url = ?, unvisited_url_num = 0" + 27 | " WHERE root_url IN $buildInParamList$;") 28 | .replace("$buildInParamList$", DBService.buildInParamList(rootUrls.size())); 29 | 30 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(updateSQL)) { 31 | // 设置第一个参数为JSON数组的toJSONString() 32 | stmt.setString(1, new JSONArray().toJSONString()); 33 | 34 | // 循环设置参数 // 开始于第二个参数位置,第一个参数已被设置 35 | for (int i = 0; i < rootUrls.size(); i++) { 36 | stmt.setString(i + 2, rootUrls.get(i)); 37 | } 38 | // 执行更新操作并获取受影响行数 39 | totalRowsAffected = stmt.executeUpdate(); 40 | 41 | } catch (Exception e) { 42 | stderr_println(LOG_ERROR, String.format("[-] Error clearing unvisited URLs by RootUrls: %s", e.getMessage())); 43 | } 44 | return totalRowsAffected; 45 | } 46 | 47 | /** 48 | * 获取 所有未访问URl (unvisited_url_num > 0) 49 | * @return 50 | */ 51 | public static synchronized List fetchAllUnVisitedUrlsWithLimit(Integer limit){ 52 | List list = new ArrayList<>(); 53 | 54 | String selectSQL = "SELECT id,root_url,unvisited_url FROM "+ AnalyseHostResultTable.tableName + " WHERE unvisited_url_num > 0 ORDER BY id ASC Limit ?;"; 55 | 56 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 57 | stmt.setInt(1, limit); 58 | ResultSet rs = stmt.executeQuery(); 59 | while (rs.next()) { 60 | UnVisitedUrlsModel unVisitedUrlsModel = new UnVisitedUrlsModel( 61 | rs.getInt("id"), 62 | rs.getString("root_url"), 63 | rs.getString("unvisited_url") 64 | ); 65 | list.add(unVisitedUrlsModel); 66 | } 67 | } catch (Exception e) { 68 | stderr_println(LOG_ERROR, String.format("[-] Error fetch [%s] All UnVisited Urls: %s", AnalyseHostResultTable.tableName, e.getMessage())); 69 | } 70 | return list; 71 | } 72 | 73 | /** 74 | * 基于rootUrls查询对应的未访问URl 75 | */ 76 | public static List fetchUnVisitedUrlsByRootUrls(List rootUrls) { 77 | List arrayList = new ArrayList<>(); 78 | 79 | if (rootUrls.isEmpty()) return arrayList; 80 | 81 | String selectSQL = ("SELECT id,root_url,unvisited_url FROM " + AnalyseHostResultTable.tableName + 82 | " WHERE root_url IN $buildInParamList$;") 83 | .replace("$buildInParamList$", DBService.buildInParamList(rootUrls.size())); 84 | 85 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 86 | for (int i = 0; i < rootUrls.size(); i++) { 87 | stmt.setString(i + 1, rootUrls.get(i)); 88 | } 89 | 90 | ResultSet rs = stmt.executeQuery(); 91 | while (rs.next()) { 92 | UnVisitedUrlsModel tabDataModel = new UnVisitedUrlsModel( 93 | rs.getInt("id"), 94 | rs.getString("root_url"), 95 | rs.getString("unvisited_url") 96 | ); 97 | arrayList.add(tabDataModel); 98 | } 99 | } catch (Exception e) { 100 | stderr_println(LOG_ERROR, String.format("[-] Error fetch [%s] Result Data By MsgHash List: %s", AnalyseHostResultTable.tableName, e.getMessage())); 101 | } 102 | return arrayList; 103 | } 104 | 105 | /** 106 | * 实现 基于 ID 更新 unvisitedUrls 107 | */ 108 | public static synchronized int updateUnVisitedUrlsByModel(UnVisitedUrlsModel unVisitedUrlsModel) { 109 | int affectedRows = -1; // 默认ID值,如果没有生成ID,则保持此值 110 | 111 | String updateSQL = "UPDATE " + AnalyseHostResultTable.tableName +" SET unvisited_url = ?, unvisited_url_num = ? WHERE id = ?;"; 112 | 113 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(updateSQL)) { 114 | stmt.setString(1, CastUtils.toJsonString(unVisitedUrlsModel.getUnvisitedUrls())); 115 | stmt.setInt(2, unVisitedUrlsModel.getUnvisitedUrls().size()); 116 | stmt.setInt(3, unVisitedUrlsModel.getId()); 117 | affectedRows = stmt.executeUpdate(); 118 | } catch (Exception e) { 119 | stderr_println(LOG_ERROR, String.format("[-] Error update unvisited Urls By Id: %s", e.getMessage())); 120 | } 121 | return affectedRows; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/database/CommonDeleteLine.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import java.sql.Connection; 4 | import java.sql.PreparedStatement; 5 | import java.util.List; 6 | 7 | import static utils.BurpPrintUtils.stderr_println; 8 | import static utils.CastUtils.isEmptyObj; 9 | 10 | public class CommonDeleteLine { 11 | /** 12 | * 执行删除数据行的SQL语句 13 | */ 14 | private static int runDeleteByStringsSQL(String tableName, List stringList, String deleteSQL) { 15 | int totalRowsAffected = 0; 16 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(deleteSQL)) { 17 | // 设置SQL语句中的参数值 i+1表示从第一个?号开始设置 18 | for (int i = 0; i < stringList.size(); i++) { 19 | stmt.setString(i + 1, stringList.get(i)); 20 | } 21 | // 执行删除操作 22 | totalRowsAffected = stmt.executeUpdate(); 23 | } catch (Exception e) { 24 | stderr_println(String.format("[-] runDeleteSql: [%s] -> Error: %s", tableName, e.getMessage())); 25 | e.printStackTrace(); 26 | } 27 | return totalRowsAffected; 28 | } 29 | 30 | //基于 rootUrls 列表 同时删除多行 31 | public static synchronized int deleteLineByRootUrls(String tableName, List rootUrls) { 32 | if (isEmptyObj(rootUrls)) return 0; 33 | 34 | // 构建SQL语句,使用占位符 ? 来代表每个ID 35 | String deleteSQL = ("DELETE FROM "+ tableName +" WHERE root_url IN $buildInParamList$;") 36 | .replace("$buildInParamList$", DBService.buildInParamList(rootUrls.size())); 37 | 38 | return runDeleteByStringsSQL(tableName, rootUrls, deleteSQL); 39 | } 40 | 41 | //基于 msgHash 列表 同时删除多个 行 42 | public static synchronized int deleteLineByMsgHashList(String tableName, List msgHashList) { 43 | if (isEmptyObj(msgHashList)) return 0; 44 | 45 | // 构建SQL语句,使用占位符 ? 来代表每个ID 46 | String deleteSQL = ("DELETE FROM "+ tableName + " WHERE msg_hash IN $buildInParamList$;") 47 | .replace("$buildInParamList$", DBService.buildInParamList(msgHashList.size())); 48 | 49 | return runDeleteByStringsSQL(tableName, msgHashList, deleteSQL); 50 | } 51 | 52 | /** 53 | * 基于 id 列表 同时删除多个 行 54 | */ 55 | public static synchronized int deleteLineByIds(String tableName, List ids) { 56 | int totalRowsAffected = 0; 57 | 58 | if (ids.isEmpty()) return totalRowsAffected; 59 | 60 | // 构建SQL语句,使用占位符 ? 来代表每个ID 61 | String deleteSQL = ("DELETE FROM "+ tableName + " WHERE id IN $buildInParamList$;") 62 | .replace("$buildInParamList$", DBService.buildInParamList(ids.size())); 63 | 64 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(deleteSQL)) { 65 | // 设置SQL语句中的参数值 i+1表示从第一个?号开始设置 66 | for (int i = 0; i < ids.size(); i++) { 67 | stmt.setInt(i + 1, ids.get(i)); 68 | } 69 | // 执行删除操作 70 | totalRowsAffected = stmt.executeUpdate(); 71 | 72 | } catch (Exception e) { 73 | stderr_println(String.format("[-] Error deleting Data By Ids On Table [%s] -> Error:[%s]", tableName, e.getMessage())); 74 | e.printStackTrace(); 75 | } 76 | 77 | return totalRowsAffected; 78 | } 79 | 80 | /** 81 | * 基于 多个url前缀 列表 删除行 82 | */ 83 | public static synchronized int deleteLineByUrlLikeRootUrls(String tableName, List rootUrlList) { 84 | if (isEmptyObj(rootUrlList)) return 0; 85 | 86 | int totalRowsAffected = 0; 87 | String deleteSQL = "DELETE FROM "+ tableName + " WHERE req_url LIKE ?;"; 88 | 89 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(deleteSQL)) { 90 | // 开启批处理 91 | conn.setAutoCommit(false); 92 | // 遍历rootUrlList,为每个rootUrl准备并添加到批处理队列 93 | for (String rootUrl : rootUrlList) { 94 | stmt.setString(1, rootUrl + "%"); 95 | stmt.addBatch(); 96 | } 97 | // 执行批处理 98 | int[] rowsAffected = stmt.executeBatch(); 99 | conn.commit(); 100 | // 计算受影响的总行数 101 | for (int row : rowsAffected) { 102 | totalRowsAffected += row; 103 | } 104 | } catch (Exception e) { 105 | stderr_println(String.format("[-] Error deleting [%s] Data By Starts With rootUrl List: %s", tableName, e.getMessage())); 106 | } 107 | return totalRowsAffected; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/database/CommonFetchData.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import java.sql.Connection; 4 | import java.sql.PreparedStatement; 5 | import java.sql.ResultSet; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static utils.BurpPrintUtils.*; 10 | 11 | public class CommonFetchData { 12 | /** 13 | * 统计数据表行数大小 14 | */ 15 | public static synchronized int fetchTableCounts(String tableName) { 16 | int count = 0; 17 | 18 | String selectSQL = "SELECT COUNT(*) FROM "+ tableName +" ;"; 19 | 20 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL); 21 | ResultSet rs = stmt.executeQuery()) { 22 | 23 | if (rs.next()) { 24 | count = rs.getInt(1); // 获取第一列的值,即 COUNT(*) 的结果 25 | } 26 | } catch (Exception e) { 27 | stderr_println(String.format("Error Counts [%s]: %s",tableName, e.getMessage() )); 28 | } 29 | return count; 30 | } 31 | 32 | /** 33 | * 统计所有已经识别完成的URL的数量 34 | * @return 35 | */ 36 | public static synchronized int fetchTableCountsByStatus(String analyseStatus) { 37 | int count = 0; 38 | 39 | String selectSQL = "SELECT COUNT(*) FROM "+ ReqDataTable.tableName + " WHERE run_status = ?;"; 40 | 41 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)){ 42 | stmt.setString(1, analyseStatus); 43 | ResultSet rs = stmt.executeQuery(); 44 | if (rs.next()) { 45 | count = rs.getInt(1); // 获取第一列的值,即 COUNT(*) 的结果 46 | } 47 | } catch (Exception e) { 48 | stderr_println(String.format("Counts Table [%s] Error: %s", ReqDataTable.tableName, e.getMessage() )); 49 | } 50 | return count; 51 | } 52 | 53 | /** 54 | * 根据运行状态取获取对应 ID list 55 | */ 56 | public static synchronized List fetchIdsByRunStatus(String tableName, String analyseStatus, int limit) { 57 | List ids = new ArrayList<>(); 58 | String selectSQL = "SELECT id FROM " + tableName + " WHERE run_status = ? ORDER BY id ASC LIMIT ?;"; 59 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 60 | stmt.setString(1, analyseStatus); 61 | stmt.setInt(2, limit); // Set the limit parameter 62 | ResultSet rs = stmt.executeQuery(); 63 | while (rs.next()) { 64 | int id = rs.getInt("id"); 65 | ids.add(id); 66 | } 67 | } catch (Exception e) { 68 | stderr_println(LOG_DEBUG, String.format("[-] Error fetching [%s] ids: %s", tableName, e.getMessage())); 69 | } 70 | return ids; 71 | } 72 | 73 | /** 74 | * 根据运行状态取获取对应请求 msghash list 75 | * @return 76 | */ 77 | public static synchronized List fetchMsgHashByRunStatus(String tableName, String analyseStatus, int limit) { 78 | List msgHashList = new ArrayList<>(); 79 | String selectSQL = "SELECT msg_hash FROM " + tableName + " WHERE run_status = ? ORDER BY id ASC LIMIT ?;"; 80 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 81 | stmt.setString(1, analyseStatus); 82 | stmt.setInt(2, limit); // Set the limit parameter 83 | ResultSet rs = stmt.executeQuery(); 84 | while (rs.next()) { 85 | String msgHash = rs.getString("msg_hash"); 86 | msgHashList.add(msgHash); 87 | } 88 | } catch (Exception e) { 89 | stderr_println(LOG_DEBUG, String.format("[-] Error fetching [%s] MsgHash List from Analysis: %s",tableName, e.getMessage())); 90 | } 91 | return msgHashList; 92 | } 93 | 94 | ///////////////////////////// 95 | /** 96 | * 获取任意表的任意列的字符串列表 【基于msgHashList】 97 | */ 98 | public static synchronized List fetchColumnStrListByMsgHashList(String tableName, String columnName, List msgHashList) { 99 | List stringList = new ArrayList<>(); 100 | 101 | if (msgHashList.isEmpty()) 102 | return stringList; 103 | 104 | String selectSQL = ("SELECT " + columnName + " FROM "+ tableName +" WHERE msg_hash IN $buildInParameterList$;") 105 | .replace("$buildInParameterList$", DBService.buildInParamList(msgHashList.size())); 106 | 107 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 108 | for (int i = 0; i < msgHashList.size(); i++) { 109 | stmt.setString(i + 1, msgHashList.get(i)); 110 | } 111 | try (ResultSet rs = stmt.executeQuery()) { 112 | while (rs.next()) { 113 | stringList.add(rs.getString(columnName)); 114 | } 115 | } 116 | } catch (Exception e) { 117 | stderr_println(LOG_ERROR, String.format("[-] fetchColumnStrListByMsgHashList: [%s] [%s] -> Error: %s",tableName, columnName, e.getMessage())); 118 | } 119 | return stringList; 120 | } 121 | ///////////////////////////// 122 | /** 123 | * 获取任意表的任意列的字符串拼接 【获取所有行】 124 | */ 125 | public static synchronized String fetchColumnGroupConcatString(String tableName, String columnName) { 126 | String concatenatedURLs = null; 127 | 128 | String concatSQL = ("SELECT GROUP_CONCAT($columnName$,',') AS concatenated_urls FROM "+ tableName +";") 129 | .replace("$columnName$",columnName); 130 | 131 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(concatSQL)) { 132 | ResultSet rs = stmt.executeQuery(); 133 | if (rs.next()) { 134 | concatenatedURLs = rs.getString("concatenated_urls"); 135 | } 136 | } catch (Exception e) { 137 | stderr_println(LOG_ERROR, String.format("[-] Error fetching [%s] concatenating [%s]: %s",tableName, columnName, e.getMessage())); 138 | e.printStackTrace(); 139 | } 140 | return concatenatedURLs; 141 | } 142 | 143 | /** 144 | * 获取任意表的任意列的字符串拼接 InRootUrls 145 | */ 146 | public static synchronized String fetchColumnGroupConcatStringInRootUrls(String tableName, String columnName, List rootUrls) { 147 | String concatenatedURLs = null; 148 | 149 | String concatSQL = ("SELECT GROUP_CONCAT($columnName$,',') AS concatenated_urls FROM "+ tableName + 150 | " WHERE root_url IN $buildInParameterList$;") 151 | .replace("$columnName$",columnName) 152 | .replace("$buildInParameterList$", DBService.buildInParamList(rootUrls.size())); 153 | 154 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(concatSQL)) { 155 | for (int i = 0; i < rootUrls.size(); i++) { 156 | stmt.setString(i + 1, rootUrls.get(i)); 157 | } 158 | 159 | ResultSet rs = stmt.executeQuery(); 160 | if (rs.next()) { 161 | concatenatedURLs = rs.getString("concatenated_urls"); 162 | } 163 | } catch (Exception e) { 164 | stderr_println(LOG_ERROR, String.format("[-] Error fetch [%s] [%s] Column Group Concat String In RootUrls concatenating: %s",tableName, columnName, e.getMessage())); 165 | e.printStackTrace(); 166 | } 167 | return concatenatedURLs; 168 | } 169 | 170 | /** 171 | * 获取任意表的任意列的字符串拼接 NotInRootUrls 172 | */ 173 | public static synchronized String fetchColumnGroupConcatStringNotInRootUrls(String tableName, String columnName, List rootUrls) { 174 | String concatenatedURLs = null; 175 | 176 | String concatSQL = ("SELECT GROUP_CONCAT($columnName$,',') AS concatenated_urls FROM "+ tableName + 177 | " WHERE root_url NOT IN $buildInParameterList$;") 178 | .replace("$columnName$",columnName) 179 | .replace("$buildInParameterList$", DBService.buildInParamList(rootUrls.size())); 180 | 181 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(concatSQL)) { 182 | for (int i = 0; i < rootUrls.size(); i++) { 183 | stmt.setString(i + 1, rootUrls.get(i)); 184 | } 185 | 186 | ResultSet rs = stmt.executeQuery(); 187 | if (rs.next()) { 188 | concatenatedURLs = rs.getString("concatenated_urls"); 189 | } 190 | } catch (Exception e) { 191 | stderr_println(LOG_ERROR, String.format("[-] Error fetch [%s] [%s] Column Group Concat String Not In RootUrls concatenating: %s",tableName, columnName, e.getMessage())); 192 | e.printStackTrace(); 193 | } 194 | return concatenatedURLs; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/database/CommonUpdateStatus.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import java.sql.Connection; 4 | import java.sql.PreparedStatement; 5 | import java.util.List; 6 | 7 | import static utils.BurpPrintUtils.*; 8 | 9 | public class CommonUpdateStatus { 10 | /** 11 | * 更新多个 ID列表 的状态 12 | */ 13 | public static synchronized int updateStatusByIds(String tableName, List ids, String updateStatus) { 14 | int updatedCount = -1; 15 | 16 | String updateSQL = ("UPDATE " + tableName + " SET run_status = ? WHERE id IN $buildInParamList$;") 17 | .replace("$buildInParamList$", DBService.buildInParamList(ids.size())); 18 | 19 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmtUpdate = conn.prepareStatement(updateSQL)) { 20 | stmtUpdate.setString(1, updateStatus); 21 | 22 | for (int i = 0; i < ids.size(); i++) { 23 | stmtUpdate.setInt(i + 2, ids.get(i)); 24 | } 25 | 26 | updatedCount = stmtUpdate.executeUpdate(); 27 | 28 | if (updatedCount != ids.size()) { 29 | stderr_println(LOG_DEBUG, "[!] Number of updated rows does not match number of selected rows."); 30 | } 31 | } catch (Exception e) { 32 | stderr_println(LOG_ERROR, String.format("[-] Error updating [%s] Data Status: %s", tableName, e.getMessage())); 33 | } 34 | return updatedCount; 35 | } 36 | 37 | /** 38 | * 更新多个 msgHash 的状态 39 | */ 40 | public static synchronized int updateStatusByMsgHashList(String tableName, List msgHashList, String updateStatus) { 41 | int updatedCount = -1; 42 | 43 | String updateSQL = ("UPDATE " + tableName + " SET run_status = ? WHERE msg_hash IN $buildInParamList$;") 44 | .replace("$buildInParamList$", DBService.buildInParamList(msgHashList.size())); 45 | 46 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmtUpdate = conn.prepareStatement(updateSQL)) { 47 | stmtUpdate.setString(1, updateStatus); 48 | 49 | for (int i = 0; i < msgHashList.size(); i++) { 50 | stmtUpdate.setString(i + 2, msgHashList.get(i)); 51 | } 52 | 53 | updatedCount = stmtUpdate.executeUpdate(); 54 | 55 | if (updatedCount != msgHashList.size()) { 56 | stderr_println(LOG_DEBUG, "[!] Number of updated rows does not match number of selected rows."); 57 | } 58 | } catch (Exception e) { 59 | stderr_println(LOG_ERROR, String.format("[-] Error updating [%s] Data Status: %s",tableName, e.getMessage())); 60 | } 61 | return updatedCount; 62 | } 63 | 64 | /** 65 | * 基于 msgDataIndexList 更新 状态 66 | */ 67 | public static synchronized int updateStatusByMsgDataIndexList(String tableName, List msgDataIndexList, String updateStatus) { 68 | int updatedCount = -1; 69 | 70 | String updateSQL = ("UPDATE " + tableName + " SET run_status = ? WHERE msg_data_index IN $buildInParamList$;") 71 | .replace("$buildInParamList$", DBService.buildInParamList(msgDataIndexList.size())); 72 | 73 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmtUpdate = conn.prepareStatement(updateSQL)) { 74 | stmtUpdate.setString(1, updateStatus); 75 | 76 | for (int i = 0; i < msgDataIndexList.size(); i++) { 77 | stmtUpdate.setInt(i + 2, msgDataIndexList.get(i)); 78 | } 79 | 80 | updatedCount = stmtUpdate.executeUpdate(); 81 | 82 | if (updatedCount != msgDataIndexList.size()) { 83 | stderr_println(LOG_DEBUG, "[!] Number of updated rows does not match number of selected rows."); 84 | } 85 | } catch (Exception e) { 86 | stderr_println(LOG_ERROR, String.format("[-] Error updating Req Data Status: %s", e.getMessage())); 87 | } 88 | return updatedCount; 89 | } 90 | 91 | /** 92 | * 当达到某个状态条件时 更新 msgHash 对应数据 的状态 93 | */ 94 | public static synchronized int updateStatusWhenStatusByMsgHash(String tableName, String msgHash, String updateStatus, String whenStatus) { 95 | int updatedCount = -1; 96 | 97 | String updateSQL = "UPDATE " + tableName + " SET run_status = ? WHERE run_status = ? and msg_hash = ?;"; 98 | 99 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmtUpdate = conn.prepareStatement(updateSQL)) { 100 | stmtUpdate.setString(1, updateStatus); 101 | stmtUpdate.setString(2, whenStatus); 102 | stmtUpdate.setString(3, msgHash); 103 | 104 | updatedCount = stmtUpdate.executeUpdate(); 105 | } catch (Exception e) { 106 | stderr_println(LOG_ERROR, String.format("[-] updateStatusWhenStatusByMsgHash: [%s] Error->: %s",tableName, e.getMessage())); 107 | } 108 | return updatedCount; 109 | } 110 | 111 | /** 112 | * 当达到某个状态条件时 更新 root_url 对应数据 的状态 113 | */ 114 | public static synchronized int updateStatusWhenStatusByRootUrl(String tableName, String rootUrl, String updateStatus, String whenStatus) { 115 | int updatedCount = -1; 116 | 117 | String updateSQL = "UPDATE " + tableName + " SET run_status = ? WHERE run_status = ? and root_url = ?;"; 118 | 119 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmtUpdate = conn.prepareStatement(updateSQL)) { 120 | stmtUpdate.setString(1, updateStatus); 121 | stmtUpdate.setString(2, whenStatus); 122 | stmtUpdate.setString(3, rootUrl); 123 | 124 | updatedCount = stmtUpdate.executeUpdate(); 125 | } catch (Exception e) { 126 | stderr_println(LOG_ERROR, String.format("[-] updateStatusWhenStatusByRootUrl: [%s] Error->: %s",tableName, e.getMessage())); 127 | } 128 | return updatedCount; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/database/Constants.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | public class Constants { 4 | public static final String ANALYSE_WAIT = "Waiting"; //等待自动处理 5 | public static final String ANALYSE_ING = "Analysing"; //自动处理中 6 | public static final String ANALYSE_END = "Analysed"; //自动处理完毕 7 | 8 | public static final String HANDLE_WAIT = "Pending"; //等待手动处理 9 | public static final String HANDLE_ING = "Handling"; //手动处理中 10 | public static final String HANDLE_END = "Handled"; //手动处理完毕 11 | 12 | public static final String SPLIT_SYMBOL = "<->"; 13 | public static final String RULE_CONF_PREFIX = "CONF_"; //配置文件中 配置规则的开头 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/database/DBService.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import burp.BurpExtender; 4 | import org.sqlite.SQLiteConfig; 5 | import utils.BurpFileUtils; 6 | 7 | import java.io.File; 8 | import java.nio.file.Path; 9 | import java.sql.*; 10 | 11 | import static utils.BurpPrintUtils.*; 12 | 13 | public class DBService { 14 | private static DBService instance; 15 | private Connection connection; 16 | private String CONNECTION_STRING; //数据库链接字符串 17 | 18 | //指定sqlite数据库配置文件路径 19 | private String DBFileName = "APIFinderPlus.db"; 20 | 21 | private DBService() { 22 | Path DBFilePath = BurpFileUtils.getPluginDirFilePath(BurpExtender.getCallbacks(), DBFileName); 23 | CONNECTION_STRING = String.format("jdbc:sqlite:%s?journal_mode=WAL", DBFilePath); 24 | } 25 | 26 | public static synchronized DBService getInstance() { 27 | // 单例模式配置 28 | if (instance == null) { 29 | instance = new DBService(); 30 | } 31 | return instance; 32 | } 33 | 34 | //创建数据库链接 35 | public synchronized void initDBConnection() { 36 | try { 37 | // 自动注册 SQLite 驱动程序 38 | Class.forName("org.sqlite.JDBC"); 39 | 40 | // 建立数据库连接 41 | connection = DriverManager.getConnection(CONNECTION_STRING); 42 | 43 | // 启用外键支持 44 | try (Statement stmt = connection.createStatement()) { 45 | stmt.execute("PRAGMA foreign_keys = ON"); 46 | } catch (SQLException e) { 47 | stderr_println(String.format("[!] set foreign_keys error. -> %s", e.getMessage())); 48 | e.printStackTrace(); 49 | } 50 | 51 | stdout_println(LOG_INFO, "[+] SQLite database connection initialized successfully. "); 52 | } catch (ClassNotFoundException e) { 53 | stderr_println(String.format("[!] JDBC driver not found. -> %s", e.getMessage())); 54 | e.printStackTrace(); 55 | } catch (SQLException e) { 56 | stderr_println(String.format("[!] Failed to connect db. -> %s", e.getMessage())); 57 | e.printStackTrace(); 58 | } 59 | } 60 | 61 | //创建数据表结构 62 | public synchronized void initCreateTables() { 63 | // RecordUrlTable URL 访问记录表 用于后续排除已访问过的UR了 64 | execCreatTableSql(RecordUrlTable.creatTableSQL, RecordUrlTable.tableName); 65 | 66 | // RecordPathTable URL PATH记录表 用于后续路径猜测记录 67 | execCreatTableSql(RecordPathTable.creatTableSQL, RecordPathTable.tableName); 68 | 69 | // ReqMsgDataTable 用于存储 实际的请求体和响应体 70 | execCreatTableSql(ReqMsgDataTable.creatTableSQL, ReqMsgDataTable.tableName); 71 | 72 | // ReqDataTable 存储需要提取敏感信息的数据 73 | execCreatTableSql(ReqDataTable.creatTableSQL, ReqDataTable.tableName); 74 | 75 | // AnalyseUrlResultTable 存储分析后的数据 76 | execCreatTableSql(AnalyseUrlResultTable.creatTableSQL, AnalyseUrlResultTable.tableName); 77 | 78 | // AnalyseHostResultTable 存储分析后的数据 的 集合 79 | execCreatTableSql(AnalyseHostResultTable.creatTableSQL, AnalyseHostResultTable.tableName); 80 | 81 | // 创建存储根树的表 82 | execCreatTableSql(PathTreeTable.creatTableSQL, PathTreeTable.tableName); 83 | } 84 | 85 | //创建数据表的语句 86 | private void execCreatTableSql(String creatTableSql, String tableName) { 87 | try (Statement stmt = connection.createStatement()) { 88 | stmt.execute(creatTableSql); 89 | stdout_println(LOG_INFO, String.format("[+] create db %s success ...", tableName)); 90 | } catch (Exception e) { 91 | stderr_println(String.format("[!] create db %s failed -> %s", tableName, e.getMessage())); 92 | e.printStackTrace(); 93 | } 94 | } 95 | 96 | //获取一个数据库语句 97 | public Connection getNewConn() throws SQLException { 98 | //勉强解决 [SQLITE_BUSY] The database file is locked (database is locked) 错误 99 | SQLiteConfig config = new SQLiteConfig(); 100 | config.setBusyTimeout(5000); // 设置超时时间,单位是毫秒 101 | return DriverManager.getConnection(CONNECTION_STRING, config.toProperties()); 102 | } 103 | 104 | // 关闭数据库连接的方法 105 | public void closeConnection() { 106 | try { 107 | if (this.connection != null && !this.connection.isClosed()) { 108 | this.connection.close(); 109 | } 110 | } catch (SQLException e) { 111 | stderr_println(String.format("关闭数据库连接时发生错误: %s", e.getMessage())); 112 | e.printStackTrace(); 113 | } 114 | } 115 | 116 | /** 117 | * 清空表数据 118 | * @param tableName 119 | */ 120 | private static void clearTable(String tableName) { 121 | // 用 DELETE 语句来清空表 122 | String deleteSql = "DELETE FROM "+ tableName +" ;"; 123 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(deleteSql)) { 124 | stmt.executeUpdate(); 125 | stdout_println(String.format("[-] table [%s] has been cleared.", tableName)); 126 | } catch (Exception e) { 127 | stderr_println(String.format("Error clearing table [%s] -> Error: %s",tableName, e.getMessage())); 128 | } 129 | } 130 | 131 | /** 132 | * 清空常用表的数据 133 | */ 134 | public static void clearModelTables(){ 135 | clearTable(AnalyseUrlResultTable.tableName); 136 | clearTable(ReqDataTable.tableName); 137 | clearTable(ReqMsgDataTable.tableName); 138 | } 139 | 140 | 141 | /** 142 | * 清空记录表的数据 143 | */ 144 | public static void clearRecordTables(){ 145 | clearTable(PathTreeTable.tableName); 146 | clearTable(RecordPathTable.tableName); 147 | clearTable(RecordUrlTable.tableName); 148 | clearTable(AnalyseHostResultTable.tableName); 149 | } 150 | 151 | 152 | /** 153 | * 清空所有表的数据 154 | */ 155 | public static void clearAllTables(){ 156 | clearModelTables(); 157 | clearRecordTables(); 158 | } 159 | 160 | 161 | /** 162 | * 清空已访问URL表 163 | */ 164 | public static void clearRecordUrlTable(){ 165 | clearTable(RecordUrlTable.tableName); 166 | } 167 | 168 | /** 169 | * 构建一个函数,实现根据参数列表数量自动拼接 IN (?,?,?)语句 170 | * @param size 171 | * @return 172 | */ 173 | public static String buildInParamList(int size) { 174 | StringBuilder inParameterList = new StringBuilder(" ("); 175 | for (int i = 0; i < size; i++) { 176 | inParameterList.append("?"); 177 | if (i < size - 1) { 178 | inParameterList.append(", "); 179 | } 180 | } 181 | inParameterList.append(") "); 182 | return inParameterList.toString(); 183 | } 184 | 185 | 186 | //判断大小是否超过 x G 超过就清空数据库文件 187 | public boolean clearBigDB(int limit) { 188 | try { 189 | Path sqliteDBFilePath = BurpFileUtils.getPluginDirFilePath(BurpExtender.getCallbacks(), DBFileName); 190 | File dbFile = new File(sqliteDBFilePath.toString()); 191 | if (dbFile.length() > 1024 * 1024 * 1024 * limit){ 192 | DBService.clearModelTables(); 193 | stdout_println(String.format("DB File Is Big, Clear Model Tables Success ...")); 194 | return true; 195 | } 196 | } catch (Exception e){ 197 | stderr_println(String.format("DB File Is Big, Clear Model Tables Occur Error: [%s]", e.getMessage())); 198 | } 199 | return false; 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/database/PathTreeTable.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import model.PathTreeModel; 5 | 6 | import java.sql.*; 7 | import java.util.ArrayList; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | import static utils.CastUtils.isNotEmptyObj; 13 | import static utils.PathTreeUtils.deepMergeJsonTree; 14 | import static utils.BurpPrintUtils.*; 15 | 16 | public class PathTreeTable { 17 | //数据表名称 18 | public static String tableName = "PATH_TREE"; 19 | 20 | //创建 基于 record_urls 生成的每个域名的 路径结构 树 21 | static String creatTableSQL = "CREATE TABLE IF NOT EXISTS "+ tableName +" (\n" 22 | + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" //自增的id 23 | + " root_url TEXT NOT NULL,\n" 24 | + " path_tree TEXT NOT NULL,\n" //根树的序列化Json数据 25 | + " basic_path_num INTEGER NOT NULL DEFAULT 0\n" //基于多少个路径计算出来的根树,最好使用根树的稳定 hash 26 | + ");"; 27 | 28 | //插入数据库 29 | public static synchronized int insertOrUpdatePathTree(PathTreeModel pathTreeModel) { 30 | String rootUrl = pathTreeModel.getRootUrl(); 31 | Integer newBasicPathNum = pathTreeModel.getBasicPathNum(); 32 | JSONObject newPathTree = pathTreeModel.getPathTree(); 33 | 34 | int generatedId = -1; // 默认ID值,如果没有生成ID,则保持此值 35 | 36 | //查询 是否已存在记录 37 | String checkSql = "SELECT id,path_tree,basic_path_num FROM " + tableName + " WHERE root_url = ?;"; 38 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement checkStmt = conn.prepareStatement(checkSql)) { 39 | checkStmt.setString(1, rootUrl); 40 | 41 | ResultSet rs = checkStmt.executeQuery(); 42 | if (rs.next()) { 43 | int selectedId = rs.getInt("id"); 44 | String oldPathTree = rs.getString("path_tree"); 45 | int oldBasicPathNum = rs.getInt("basic_path_num"); 46 | 47 | //合并新旧pathNum 输入的PATH TREE 是基于新找到的PATH 因此是增量的 48 | newBasicPathNum = Math.max(0, oldBasicPathNum) + Math.max(0, newBasicPathNum); 49 | 50 | //合并新旧Json树 51 | if (isNotEmptyObj(oldPathTree)){ 52 | JSONObject oldTree = JSONObject.parse(oldPathTree); 53 | newPathTree = deepMergeJsonTree(oldTree, newPathTree); 54 | } 55 | 56 | //更新索引对应的数据 57 | String updateSQL = "UPDATE "+ tableName +" SET path_tree = ?, basic_path_num = ? WHERE id = ?;"; 58 | try (PreparedStatement updateStatement = conn.prepareStatement(updateSQL)) { 59 | updateStatement.setString(1, newPathTree.toJSONString()); 60 | updateStatement.setInt(2, newBasicPathNum); 61 | updateStatement.setInt(3, selectedId); 62 | int affectedRows = updateStatement.executeUpdate(); 63 | if (affectedRows > 0) { 64 | generatedId = selectedId; 65 | } 66 | } 67 | } else { 68 | // 记录不存在,插入新记录 69 | String insertSql = "INSERT INTO "+ tableName +" (root_url, path_tree, basic_path_num) VALUES (?, ?, ?);"; 70 | try (PreparedStatement insertStmt = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) { 71 | insertStmt.setString(1, rootUrl); 72 | insertStmt.setString(2, newPathTree.toJSONString()); 73 | insertStmt.setInt(3, newBasicPathNum); 74 | 75 | int affectedRows = insertStmt.executeUpdate(); 76 | 77 | if (affectedRows > 0) { 78 | // 获取生成的键值 79 | try (ResultSet generatedKeys = insertStmt.getGeneratedKeys()) { 80 | if (generatedKeys.next()) { 81 | generatedId = generatedKeys.getInt(1); 82 | } else { 83 | throw new SQLException("Creating user failed, no ID obtained."); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } catch (Exception e) { 90 | stderr_println(String.format("[-] Error inserting or updating table [%s] -> Error:[%s]", tableName, e.getMessage())); 91 | e.printStackTrace(); 92 | } 93 | 94 | return generatedId; // 返回ID值,无论是更新还是插入 95 | } 96 | 97 | //根据域名查询对应的路径树 98 | public static synchronized List fetchPathTreeByRootUrls(List rootUrls) { 99 | List pathTreeModels = new ArrayList<>(); 100 | 101 | if (rootUrls.isEmpty()) return pathTreeModels; 102 | 103 | //查询 104 | String selectSql = ("SELECT root_url, path_tree, basic_path_num FROM "+ tableName + 105 | " WHERE root_url IN $buildInParamList$;") 106 | .replace("$buildInParamList$", DBService.buildInParamList(rootUrls.size())); 107 | 108 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSql)) { 109 | 110 | for (int i = 0; i < rootUrls.size(); i++) { 111 | stmt.setString(i + 1, rootUrls.get(i)); 112 | } 113 | 114 | ResultSet rs = stmt.executeQuery(); 115 | while (rs.next()) { 116 | PathTreeModel pathTreeModel = new PathTreeModel( 117 | rs.getString("root_url"), 118 | rs.getInt("basic_path_num"), 119 | rs.getString("path_tree") 120 | ); 121 | pathTreeModels.add(pathTreeModel); 122 | } 123 | } catch (Exception e) { 124 | stderr_println(String.format("[-] Error Fetch [%s] Data By Req Host Port List: %s", tableName, e.getMessage())); 125 | e.printStackTrace(); 126 | } 127 | 128 | return pathTreeModels; 129 | } 130 | 131 | 132 | //根据 rootUrl 查询对应的path_tree 133 | public static synchronized PathTreeModel fetchPathTreeByRootUrl(String rootUrl) { 134 | PathTreeModel pathTreeModel= null; 135 | 136 | //查询 137 | String selectSql = "SELECT root_url, path_tree, basic_path_num FROM "+ tableName +" WHERE root_url = ? LIMIT 1;"; 138 | 139 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSql)) { 140 | stmt.setString(1, rootUrl); 141 | 142 | ResultSet rs = stmt.executeQuery(); 143 | if (rs.next()) { 144 | pathTreeModel = new PathTreeModel( 145 | rs.getString("root_url"), 146 | rs.getInt("basic_path_num"), 147 | rs.getString("path_tree") 148 | ); 149 | } 150 | } catch (Exception e) { 151 | stderr_println(String.format("[-] Error Fetch [%s] Data: %s", tableName, e.getMessage())); 152 | e.printStackTrace(); 153 | } 154 | 155 | return pathTreeModel; 156 | } 157 | 158 | 159 | /** 160 | * 获取 所有 表中记录的 URL前置 161 | * @return 162 | */ 163 | public static synchronized Set fetchAllRecordPathRootUrls(){ 164 | Set set = new HashSet<>(); 165 | String selectSQL = "SELECT DISTINCT root_url FROM "+ tableName + ";"; 166 | 167 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 168 | ResultSet rs = stmt.executeQuery(); 169 | while (rs.next()) { 170 | String urlPrefix = rs.getString("root_url"); 171 | set.add(urlPrefix); 172 | } 173 | } catch (Exception e) { 174 | stderr_println(String.format("[-] Error fetch [%s] All Root URL: %s", tableName, e.getMessage())); 175 | e.printStackTrace(); 176 | } 177 | 178 | return set; 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/database/RecordPathTable.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import model.HttpMsgInfo; 4 | import model.HttpUrlInfo; 5 | import model.RecordPathDirsModel; 6 | import model.RecordPathModel; 7 | 8 | import java.sql.*; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static utils.BurpPrintUtils.*; 13 | 14 | public class RecordPathTable { 15 | //数据表名称 16 | public static String tableName = "RECORD_PATH"; 17 | 18 | //创建用于存储所有 访问成功的 URL的数据库 record_urls 19 | static String creatTableSQL = "CREATE TABLE IF NOT EXISTS "+ tableName +" (\n" 20 | + "id INTEGER PRIMARY KEY AUTOINCREMENT,\n" //自增的id 21 | + "req_hash TEXT UNIQUE, \n" // 添加一列 req_hash 作为 root_url req_path_dir resp_status_code 的 特征值 22 | + "root_url TEXT NOT NULL,\n" 23 | + "req_path_dir TEXT NOT NULL,\n" 24 | + "resp_status_code TEXT NOT NULL, \n" 25 | + "run_status TEXT NOT NULL DEFAULT 'RUN_STATUS'".replace("RUN_STATUS", Constants.ANALYSE_WAIT) 26 | + ");"; 27 | 28 | 29 | /** 30 | * 插入一条路径记录 31 | */ 32 | public static synchronized int insertOrUpdateRecordPath(RecordPathModel recordPathModel) { 33 | int generatedId = -1; // 默认ID值,如果没有生成ID,则保持此值 34 | String selectSql = "SELECT id FROM "+ tableName +" WHERE req_hash = ?;"; 35 | 36 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSql)) 37 | { 38 | // 检查记录是否存在 39 | stmt.setString(1, recordPathModel.getReqHash()); 40 | ResultSet rs = stmt.executeQuery(); 41 | if (rs.next()) { 42 | // 记录存在,忽略操作 43 | return 0; 44 | } else { 45 | // 记录不存在,插入新记录 46 | String insertSql = "INSERT INTO "+ tableName + 47 | " (root_url, req_path_dir, resp_status_code, req_hash)" + 48 | " VALUES (?, ?, ?, ?);"; 49 | try (PreparedStatement insertStmt = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) { 50 | insertStmt.setString(1, recordPathModel.getRootUrl()); 51 | insertStmt.setString(2, recordPathModel.getReqPathDir()); 52 | insertStmt.setInt(3, recordPathModel.getRespStatusCode()); 53 | insertStmt.setString(4, recordPathModel.getReqHash()); 54 | insertStmt.executeUpdate(); 55 | 56 | // 获取生成的键值 57 | try (ResultSet generatedKeys = insertStmt.getGeneratedKeys()) { 58 | if (generatedKeys.next()) { 59 | generatedId = generatedKeys.getInt(1); // 获取生成的ID 60 | } 61 | } 62 | } 63 | } 64 | } catch (Exception e) { 65 | stderr_println(String.format("[-] Error inserting or updating table -> Error:[%s]", tableName, e.getMessage())); 66 | e.printStackTrace(); 67 | } 68 | 69 | return generatedId; // 返回ID值,无论是更新还是插入 70 | } 71 | 72 | 73 | /** 74 | * 插入一条路径记录 复用insertOrUpdateRecordPath 75 | */ 76 | public static synchronized int insertOrUpdateRecordPath(HttpMsgInfo msgInfo) { 77 | RecordPathModel recordPathModel = new RecordPathModel(msgInfo.getUrlInfo(), msgInfo.getRespStatusCode()); 78 | return insertOrUpdateRecordPath(recordPathModel); 79 | } 80 | 81 | /** 82 | * 插入一条路径记录 复用insertOrUpdateRecordPath 83 | */ 84 | public static synchronized int insertOrUpdateRecordPath(String reqUrl, int respStatusCode) { 85 | RecordPathModel recordPathModel = new RecordPathModel(new HttpUrlInfo(reqUrl), respStatusCode ); 86 | return insertOrUpdateRecordPath(recordPathModel); 87 | } 88 | 89 | /** 90 | * 批量插入 recordPathModels 91 | */ 92 | public static int[] insertOrUpdateRecordPathsBatch(List recordPathModels) { 93 | int[] generatedIds = null; 94 | 95 | String insertSql = "INSERT INTO "+ tableName + 96 | " (root_url, req_path_dir, resp_status_code, req_hash)" + 97 | " VALUES (?, ?, ?, ?)" + 98 | " ON CONFLICT(req_hash) DO NOTHING"; 99 | 100 | // 这个语句的作用是在尝试向表中插入一条记录时,如果发现有与之冲突的唯一约束 101 | // (即在 root_url, req_path_dir, resp_status_code 这些字段上已经存在相同的值组合), 102 | // 那么数据库将不会执行任何操作,也不会抛出错误,而是简单地跳过这条记录的插入。 103 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement insertStmt = conn.prepareStatement(insertSql)) { 104 | conn.setAutoCommit(false); // 开启事务处理 105 | for (RecordPathModel record : recordPathModels) { 106 | insertStmt.setString(1, record.getRootUrl()); 107 | insertStmt.setString(2, record.getReqPathDir()); 108 | insertStmt.setInt(3, record.getRespStatusCode()); 109 | insertStmt.setString(4, record.getReqHash()); 110 | insertStmt.addBatch(); // 添加到批处理 111 | } 112 | generatedIds = insertStmt.executeBatch(); 113 | conn.commit(); // 提交事务 114 | } catch (Exception e) { 115 | stderr_println(String.format("[-] Error [%s] executing batch insert/update: %s",tableName, e.getMessage())); 116 | e.printStackTrace(); 117 | } 118 | return generatedIds; 119 | } 120 | 121 | /** 122 | * 实现URL批量插入 复用batchInsertOrUpdateRecordPath 123 | */ 124 | public static int[] insertOrUpdateRecordPathsBatch(List findUrls, int respStatusCode) { 125 | List recordPathModels = new ArrayList<>(); 126 | for (String findUrl: findUrls){ 127 | HttpUrlInfo urlInfo = new HttpUrlInfo(findUrl); 128 | RecordPathModel recordPathModel = new RecordPathModel( 129 | urlInfo.getRootUrlUsual(), 130 | urlInfo.getPathToDir(), 131 | respStatusCode 132 | ); 133 | recordPathModels.add(recordPathModel); 134 | } 135 | return insertOrUpdateRecordPathsBatch(recordPathModels); 136 | } 137 | 138 | /** 139 | * 获取 指定状态的数据 并封装为 路径模型 140 | */ 141 | public static List fetchPathRecordsByStatus(String analyseStatus) { 142 | // 创建一个列表或集合来存储查询结果 143 | List recordPathModels = new ArrayList<>(); 144 | 145 | String selectSQL = "SELECT root_url,GROUP_CONCAT(req_path_dir, ?) AS req_path_dirs " + 146 | "FROM "+ tableName +" WHERE run_status = ? GROUP BY root_url;"; 147 | 148 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)){ 149 | //2、获取 解析中 状态的 Host、数据、ID列表 150 | stmt.setString(1, Constants.SPLIT_SYMBOL); 151 | stmt.setString(2, analyseStatus); 152 | 153 | //获取查询数据 154 | ResultSet rs = stmt.executeQuery(); 155 | while (rs.next()) { 156 | RecordPathDirsModel recordPathDirsModel = new RecordPathDirsModel( 157 | rs.getString("root_url"), 158 | rs.getString("req_path_dirs") 159 | ); 160 | recordPathModels.add(recordPathDirsModel); 161 | } 162 | 163 | } catch (Exception e) { 164 | stderr_println(String.format("[-] Error fetch [%s] Data To Analysis: %s", tableName, e.getMessage())); 165 | e.printStackTrace(); 166 | } 167 | return recordPathModels; 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/database/RecordUrlTable.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import model.AccessedUrlInfo; 4 | import model.HttpMsgInfo; 5 | import model.HttpUrlInfo; 6 | import utils.RespHashUtils; 7 | 8 | import java.sql.*; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class RecordUrlTable { 13 | //数据表名称 14 | public static String tableName = "RECORD_URL"; 15 | public static String urlHashName = "url_hash"; 16 | 17 | //创建用于存储所有 访问成功的 URL的数据库 record_urls 18 | static String creatTableSQL = "CREATE TABLE IF NOT EXISTS "+ tableName +" (\n" 19 | + "id INTEGER PRIMARY KEY AUTOINCREMENT,\n" //自增的id 20 | + "url_hash TEXT UNIQUE,\n" 21 | + "root_url TEXT NOT NULL,\n" 22 | + "req_url TEXT NOT NULL,\n" //记录访问过的URL 23 | + "resp_status_code INTEGER\n" //记录访问过的URL状态 24 | + ");"; 25 | 26 | 27 | //插入访问的URl 28 | public static synchronized int insertOrUpdateAccessedUrl(String reqUrl,String rootUrl, int respStatusCode, String urlHash) { 29 | int generatedId = -1; 30 | String upsertSql = "INSERT INTO "+ tableName + 31 | " (req_url, root_url, resp_status_code, url_hash)" + 32 | " VALUES (?,?, ?, ?)" + 33 | " ON CONFLICT(url_hash) DO UPDATE SET resp_status_code = EXCLUDED.resp_status_code;"; 34 | 35 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(upsertSql, Statement.RETURN_GENERATED_KEYS)) { 36 | stmt.setString(1, reqUrl); 37 | stmt.setString(2, rootUrl); 38 | stmt.setInt(3, respStatusCode); 39 | stmt.setString(4, urlHash); 40 | 41 | stmt.executeUpdate(); 42 | 43 | try (ResultSet generatedKeys = stmt.getGeneratedKeys()) { 44 | if (generatedKeys.next()) { 45 | generatedId = generatedKeys.getInt(1); 46 | } 47 | } 48 | } catch (SQLException e) { 49 | System.err.println(String.format("Error insert Or Update Accessed Url On table [%s] -> Error:[%s]", tableName, e.getMessage())); 50 | } 51 | 52 | return generatedId; 53 | } 54 | 55 | //插入访问的URl 复用 56 | public static synchronized int insertOrUpdateAccessedUrl(HttpMsgInfo msgInfo) { 57 | return insertOrUpdateAccessedUrl( 58 | msgInfo.getUrlInfo().getRawUrlUsual(), 59 | msgInfo.getUrlInfo().getRootUrlUsual(), 60 | msgInfo.getRespStatusCode(), 61 | RespHashUtils.calcCRC32(msgInfo.getUrlInfo().getRawUrlUsual()) 62 | ); 63 | } 64 | 65 | //插入访问的URl 复用 66 | public static synchronized int insertOrUpdateAccessedUrl(String reqUrl, int respStatusCode) { 67 | return insertOrUpdateAccessedUrl( 68 | reqUrl, 69 | new HttpUrlInfo(reqUrl).getRootUrlUsual(), 70 | respStatusCode, 71 | RespHashUtils.calcCRC32(reqUrl) 72 | ); 73 | } 74 | 75 | //实现批量插入访问信息 76 | public static synchronized int[] insertOrUpdateAccessedUrlsBatch(List accessedUrlInfos) { 77 | int[] generatedIds = null; 78 | 79 | String upsertSql = "INSERT INTO "+ tableName + 80 | " (req_url, root_url, resp_status_code, url_hash)" + 81 | " VALUES (?, ?, ?, ?)" + 82 | " ON CONFLICT(url_hash) DO UPDATE SET resp_status_code = EXCLUDED.resp_status_code;"; 83 | 84 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(upsertSql, Statement.RETURN_GENERATED_KEYS)) { 85 | // 添加到批处理队列 86 | conn.setAutoCommit(false); // 开启事务 87 | for (AccessedUrlInfo accessedUrlInfo : accessedUrlInfos) { 88 | stmt.setString(1, accessedUrlInfo.getReqUrl()); 89 | stmt.setString(2, accessedUrlInfo.getRootUrl()); 90 | stmt.setInt(3, accessedUrlInfo.getRespStatusCode()); 91 | stmt.setString(4, accessedUrlInfo.getUrlHash()); 92 | stmt.addBatch(); 93 | } 94 | // 执行批处理 95 | generatedIds = stmt.executeBatch(); 96 | conn.commit(); // 提交事务 97 | 98 | } catch (Exception e) { 99 | System.err.println(String.format("Error [%s] batch insert Or Update Accessed Urls: %s", tableName, e.getMessage())); 100 | } 101 | 102 | return generatedIds; 103 | } 104 | 105 | 106 | //实现批量插入访问信息 复用 107 | public static synchronized int[] insertOrUpdateAccessedUrlsBatch(List accessedUrls, int respStatusCode){ 108 | List accessedUrlInfos = new ArrayList<>(); 109 | for (String reqUrl : accessedUrls){ 110 | AccessedUrlInfo accessedUrlInfo = new AccessedUrlInfo(reqUrl, new HttpUrlInfo(reqUrl).getRootUrlUsual(),respStatusCode); 111 | accessedUrlInfos.add(accessedUrlInfo); 112 | } 113 | return insertOrUpdateAccessedUrlsBatch(accessedUrlInfos); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/database/ReqDataTable.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import model.HttpMsgInfo; 4 | import model.ReqUrlRespStatusModel; 5 | 6 | import java.sql.Connection; 7 | import java.sql.PreparedStatement; 8 | import java.sql.ResultSet; 9 | import java.sql.Statement; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static utils.BurpPrintUtils.*; 14 | 15 | public class ReqDataTable { 16 | //数据表名称 17 | public static String tableName = "REQ_DATA"; 18 | 19 | //创建用于存储 需要处理的URL的原始请求响应 20 | static String creatTableSQL = "CREATE TABLE IF NOT EXISTS "+ tableName +" (" 21 | + "id INTEGER PRIMARY KEY AUTOINCREMENT," 22 | 23 | + "msg_hash TEXT UNIQUE," //作为实际的消息独立标记 24 | 25 | + "req_url TEXT NOT NULL," 26 | + "req_method TEXT NOT NULL," 27 | 28 | + "resp_status_code INTEGER NOT NULL," 29 | + "resp_length INTEGER NOT NULL," //响应长度 30 | 31 | + "msg_data_index INTEGER NOT NULL," 32 | + "req_source TEXT NOT NULL," //请求来源 33 | + "run_status TEXT NOT NULL DEFAULT 'RUN_STATUS'".replace("RUN_STATUS", Constants.ANALYSE_WAIT) 34 | 35 | + ");"; 36 | 37 | 38 | //插入请求消息到数据库 39 | public static synchronized int insertOrUpdateReqData(HttpMsgInfo msgInfo, int msgDataIndex, String reqSource) { 40 | int generatedId = -1; // 默认ID值,如果没有生成ID,则保持此值 41 | 42 | String checkSql = "SELECT id FROM "+ tableName +" WHERE msg_hash = ? ;"; 43 | 44 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement checkStmt = conn.prepareStatement(checkSql)) { 45 | // 检查记录是否存在 46 | checkStmt.setString(1, msgInfo.getMsgHash()); 47 | ResultSet rs = checkStmt.executeQuery(); 48 | if (rs.next()) { 49 | // 记录存在,忽略操作 50 | //stdout_println(LOG_INFO, String.format("[*] Ignore Update [%s] %s -> %s", tableName, msgInfo.getUrlInfo().getReqUrl(), msgInfo.getMsgHash())); 51 | return 0; 52 | } else { 53 | // 记录不存在,插入新记录 54 | String insertSql = "INSERT INTO "+ tableName + 55 | " (msg_hash, req_url, req_method, resp_status_code, msg_data_index, req_source, resp_length)" + 56 | " VALUES (?, ?, ?, ?, ?, ?, ?)"; 57 | 58 | try (PreparedStatement insertStmt = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) { 59 | insertStmt.setString(1, msgInfo.getMsgHash()); 60 | insertStmt.setString(2, msgInfo.getUrlInfo().getRawUrlUsual()); 61 | insertStmt.setString(3, msgInfo.getReqMethod()); 62 | insertStmt.setInt(4, msgInfo.getRespStatusCode()); 63 | insertStmt.setInt(5, msgDataIndex); 64 | insertStmt.setString(6, reqSource); 65 | insertStmt.setInt(7, msgInfo.getRespBytes().length); 66 | insertStmt.executeUpdate(); 67 | 68 | // 获取生成的键值 69 | try (ResultSet generatedKeys = insertStmt.getGeneratedKeys()) { 70 | if (generatedKeys.next()) { 71 | generatedId = generatedKeys.getInt(1); // 获取生成的ID 72 | } 73 | } 74 | } 75 | } 76 | } catch (Exception e) { 77 | stderr_println(String.format("[-] Error inserting or updating table [%s] -> Error:[%s]", tableName, e.getMessage())); 78 | e.printStackTrace(); 79 | } 80 | 81 | return generatedId; // 返回ID值,无论是更新还是插入 82 | } 83 | 84 | /** 85 | * 根据运行状态取获取对应请求的实际消息ID 86 | */ 87 | public static synchronized List fetchMsgDataIndexListByRunStatus(int limit, String analyseStatus) { 88 | List msgDataIndexList = new ArrayList<>(); 89 | String selectSQL = "SELECT msg_data_index FROM " + tableName + " WHERE run_status = ? ORDER BY msg_data_index ASC LIMIT ?;"; 90 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 91 | stmt.setString(1, analyseStatus); 92 | stmt.setInt(2, limit); // Set the limit parameter 93 | ResultSet rs = stmt.executeQuery(); 94 | while (rs.next()) { 95 | int msgDataIndex = rs.getInt("msg_data_index"); 96 | msgDataIndexList.add(msgDataIndex); 97 | } 98 | } catch (Exception e) { 99 | stderr_println(LOG_DEBUG, String.format("[-] Error fetching [%s] Req Data Index: %s",tableName, e.getMessage())); 100 | } 101 | return msgDataIndexList; 102 | } 103 | 104 | /** 105 | * 根据运行状态取获取对应请求的实际消息ID 106 | */ 107 | public static synchronized List fetchReqUrlRespStatusByUrls(List urls) { 108 | List requestStatusModels = new ArrayList<>(); 109 | 110 | String selectSQL = ("SELECT * FROM " + tableName + " WHERE req_url IN $buildInParamList$;") 111 | .replace("$buildInParamList$", DBService.buildInParamList(urls.size())); 112 | 113 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 114 | for (int i = 0; i < urls.size(); i++) { 115 | stmt.setString(i + 1, urls.get(i)); 116 | } 117 | 118 | ResultSet rs = stmt.executeQuery(); 119 | while (rs.next()) { 120 | ReqUrlRespStatusModel requestStatusModel = new ReqUrlRespStatusModel( 121 | rs.getInt("id"), 122 | rs.getString("req_url"), 123 | rs.getString("req_method"), 124 | rs.getInt("resp_status_code"), 125 | rs.getInt("resp_length") 126 | ); 127 | requestStatusModels.add(requestStatusModel); 128 | } 129 | } catch (Exception e) { 130 | stderr_println(LOG_DEBUG, String.format("[-] Error fetching [%s] Request Status By Urls: %s",tableName, e.getMessage())); 131 | } 132 | return requestStatusModels; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/database/ReqMsgDataTable.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import model.HttpMsgInfo; 4 | import model.ReqMsgDataModel; 5 | 6 | import java.sql.Connection; 7 | import java.sql.PreparedStatement; 8 | import java.sql.ResultSet; 9 | import java.sql.Statement; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static utils.BurpPrintUtils.*; 14 | 15 | public class ReqMsgDataTable { 16 | //数据表名称 17 | public static String tableName = "REQ_MSG_DATA"; 18 | 19 | //创建用于存储 需要处理的URL的原始请求响应 20 | static String creatTableSQL = "CREATE TABLE IF NOT EXISTS "+ tableName +" (\n" 21 | + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" 22 | + " msg_hash TEXT UNIQUE,\n" 23 | + " req_url TEXT NOT NULL,\n" 24 | + " req_bytes BLOB,\n" 25 | + " resp_bytes BLOB\n" 26 | + ");"; 27 | 28 | //插入数据库 29 | public static synchronized int insertOrUpdateMsgData(HttpMsgInfo msgInfo) { 30 | int generatedId = -1; // 默认ID值,如果没有生成ID,则保持此值 31 | String checkSql = "SELECT id FROM "+ tableName +" WHERE msg_hash = ? ;"; 32 | 33 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(checkSql)) { 34 | // 检查记录是否存在 35 | stmt.setString(1, msgInfo.getMsgHash()); 36 | ResultSet rs = stmt.executeQuery(); 37 | if (rs.next()) { 38 | // 记录存在,忽略操作 39 | // stdout_println(LOG_INFO, String.format("[*] Ignore Update [%s] %s -> %s", tableName, msgInfo.getUrlInfo().getReqUrl(), msgInfo.getMsgHash())); 40 | return 0; 41 | } else { 42 | // 记录不存在,插入新记录 43 | String insertSql = "INSERT INTO "+ tableName + 44 | " (msg_hash, req_url, req_bytes, resp_bytes)" + 45 | " VALUES (?, ?, ?, ?)"; 46 | try (PreparedStatement insertStmt = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) { 47 | insertStmt.setString(1, msgInfo.getMsgHash()); 48 | insertStmt.setString(2, msgInfo.getUrlInfo().getRawUrlUsual()); 49 | insertStmt.setBytes(3, msgInfo.getReqBytes()); 50 | insertStmt.setBytes(4, msgInfo.getRespBytes()); 51 | insertStmt.executeUpdate(); 52 | 53 | // 获取生成的键值 54 | try (ResultSet generatedKeys = insertStmt.getGeneratedKeys()) { 55 | if (generatedKeys.next()) { 56 | generatedId = generatedKeys.getInt(1); // 获取生成的ID 57 | } 58 | } 59 | } 60 | } 61 | } catch (Exception e) { 62 | stderr_println(String.format("[-] Error inserting or updating table [%s] -> Error:[%s]", tableName, e.getMessage())); 63 | e.printStackTrace(); 64 | } 65 | 66 | return generatedId; // 返回ID值,无论是更新还是插入 67 | } 68 | 69 | /** 70 | * 基于id获取对应的数据 考虑更换为msg_hash 71 | * @return 72 | */ 73 | public static synchronized ReqMsgDataModel fetchMsgDataById(Integer msgDataIndex){ 74 | ReqMsgDataModel msgData = null; 75 | 76 | String sql = "SELECT * FROM "+ tableName +" WHERE id = ?;"; 77 | 78 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(sql)) { 79 | stmt.setInt(1, msgDataIndex); 80 | try (ResultSet rs = stmt.executeQuery()) { 81 | if (rs.next()) { 82 | msgData = new ReqMsgDataModel( 83 | rs.getString("msg_hash"), 84 | rs.getString("req_url"), 85 | rs.getBytes("req_bytes"), 86 | rs.getBytes("resp_bytes") 87 | ); 88 | } 89 | } 90 | } catch (Exception e) { 91 | stderr_println(LOG_ERROR, String.format("[-] Error Select Msg Data By Id: %s -> %s", msgDataIndex, e.getMessage())); 92 | } 93 | return msgData; 94 | } 95 | 96 | /** 97 | * 根据消息ID查询请求内容 98 | * @return 99 | */ 100 | public static synchronized ReqMsgDataModel fetchMsgDataByMsgHash(String msgHash){ 101 | ReqMsgDataModel msgData = null; 102 | 103 | String sql = "SELECT * FROM "+ tableName + " WHERE msg_hash = ?;"; 104 | 105 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(sql)) { 106 | stmt.setString(1, msgHash); 107 | try (ResultSet rs = stmt.executeQuery()) { 108 | if (rs.next()) { 109 | msgData = new ReqMsgDataModel( 110 | rs.getString("msg_hash"), 111 | rs.getString("req_url"), 112 | rs.getBytes("req_bytes"), 113 | rs.getBytes("resp_bytes") 114 | ); 115 | } 116 | } 117 | } catch (Exception e) { 118 | stderr_println(LOG_ERROR, String.format("[-] Error Select Msg Data By Msg Hash: %s -> %s", msgHash, e.getMessage())); 119 | } 120 | return msgData; 121 | } 122 | 123 | /** 124 | * 根据 RootUrl 查询请求内容 最新的一条 125 | * @return 126 | */ 127 | public static ReqMsgDataModel fetchMsgDataByRootUrlDesc(String rootUrl) { 128 | ReqMsgDataModel msgData = null; 129 | 130 | String sql = "SELECT * FROM "+ tableName + " WHERE req_url like ? ORDER BY id DESC Limit 1;"; 131 | 132 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(sql)) { 133 | stmt.setString(1, rootUrl + '%'); 134 | 135 | ResultSet rs = stmt.executeQuery(); 136 | if (rs.next()) { 137 | msgData = new ReqMsgDataModel( 138 | rs.getString("msg_hash"), 139 | rs.getString("req_url"), 140 | rs.getBytes("req_bytes"), 141 | rs.getBytes("resp_bytes") 142 | ); 143 | } 144 | } catch (Exception e) { 145 | stderr_println(LOG_ERROR, String.format("[-] Error Select Msg Data By rootUrl: %s -> %s", rootUrl, e.getMessage())); 146 | } 147 | return msgData; 148 | } 149 | 150 | /** 151 | * 根据消息ID查询请求内容 152 | */ 153 | public static synchronized List fetchMsgDataByMsgHashList(List msgHashList){ 154 | List reqMsgDataModelList = new ArrayList<>(); 155 | if (msgHashList.isEmpty()) return reqMsgDataModelList; 156 | 157 | String selectSQL = ("SELECT * FROM "+ tableName + " WHERE msg_hash IN $buildInParameterList$;") 158 | .replace("$buildInParameterList$", DBService.buildInParamList(msgHashList.size())); 159 | 160 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 161 | for (int i = 0; i < msgHashList.size(); i++) { 162 | stmt.setString(i + 1, msgHashList.get(i)); 163 | } 164 | 165 | try (ResultSet rs = stmt.executeQuery()) { 166 | while (rs.next()) { 167 | ReqMsgDataModel msgData = new ReqMsgDataModel( 168 | rs.getString("msg_hash"), 169 | rs.getString("req_url"), 170 | rs.getBytes("req_bytes"), 171 | rs.getBytes("resp_bytes") 172 | ); 173 | reqMsgDataModelList.add(msgData); 174 | } 175 | } 176 | } catch (Exception e) { 177 | stderr_println(LOG_ERROR, String.format("[-] Error Batch Select Msg Data By Msg Hash: %s -> %s", msgHashList, e.getMessage())); 178 | } 179 | return reqMsgDataModelList; 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/database/TableLineDataModelBasicHostSQL.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import model.BasicHostTableLineDataModel; 4 | 5 | import java.sql.Connection; 6 | import java.sql.PreparedStatement; 7 | import java.sql.ResultSet; 8 | import java.util.ArrayList; 9 | 10 | import static utils.BurpPrintUtils.LOG_ERROR; 11 | import static utils.BurpPrintUtils.stderr_println; 12 | 13 | /** 14 | * 存储基于主机相关的SQL查询函数 15 | */ 16 | public class TableLineDataModelBasicHostSQL { 17 | 18 | 19 | private static String genHostTableSqlByWhereCondition(String WhereCondition){ 20 | String selectSQL = ("SELECT id,root_url,find_info_num,has_important,find_url_num," + 21 | "find_path_num,find_api_num,path_to_url_num,unvisited_url_num,basic_path_num,all_url_num,run_status " + 22 | "FROM $tableName$ $WHERE$;") 23 | .replace("$tableName$", AnalyseHostResultTable.tableName); 24 | if (WhereCondition == null) WhereCondition= ""; 25 | return selectSQL.replace("$WHERE$", WhereCondition); 26 | } 27 | 28 | //联合 获取所有行数据 29 | public static synchronized ArrayList fetchHostTableLineBySQl(String selectSQL){ 30 | ArrayList apiDataModels = new ArrayList<>(); 31 | 32 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 33 | try (ResultSet rs = stmt.executeQuery()) { 34 | while (rs.next()) { 35 | BasicHostTableLineDataModel apiDataModel = new BasicHostTableLineDataModel( 36 | rs.getInt("id"), 37 | rs.getString("root_url"), 38 | rs.getInt("find_info_num"), 39 | rs.getBoolean("has_important"), 40 | rs.getInt("find_url_num"), 41 | rs.getInt("find_path_num"), 42 | rs.getInt("find_api_num"), 43 | rs.getInt("path_to_url_num"), 44 | rs.getInt("unvisited_url_num"), 45 | rs.getInt("all_url_num"), 46 | rs.getInt("basic_path_num"), 47 | rs.getString("run_status") 48 | ); 49 | apiDataModels.add(apiDataModel); 50 | } 51 | } 52 | } catch (Exception e) { 53 | stderr_println(LOG_ERROR, String.format("[-] Error Fetch All ReqData Left Join InfoAnalyse On SQL: %s", e.getMessage())); 54 | } 55 | return apiDataModels; 56 | } 57 | 58 | // 获取当前所有记录 59 | public static synchronized ArrayList fetchHostTableLineAll() { 60 | String selectSQL = genHostTableSqlByWhereCondition(null); 61 | return fetchHostTableLineBySQl(selectSQL); 62 | } 63 | 64 | //获取有效数据的行 65 | public static synchronized ArrayList fetchHostTableLineHasInfoOrUri() { 66 | // 获取当前所有记录的数据 67 | String WhereCondition = "Where find_url_num>0 or find_path_num>0 or find_info_num>0"; 68 | String selectSQL = genHostTableSqlByWhereCondition(WhereCondition); 69 | return fetchHostTableLineBySQl(selectSQL); 70 | } 71 | 72 | //获取有效数据的行 并且忽略已经处理的项 73 | public static synchronized ArrayList fetchHostTableLineHasInfoOrUriNotHandle() { 74 | // 获取当前所有记录的数据 75 | String WhereCondition = ("Where (find_url_num>0 or find_path_num>0 or find_info_num>0) and run_status != 'RUN_STATUS'") 76 | .replace("RUN_STATUS", Constants.HANDLE_END); 77 | String selectSQL = genHostTableSqlByWhereCondition(WhereCondition); 78 | return fetchHostTableLineBySQl(selectSQL); 79 | } 80 | 81 | //获取敏感数据的行 82 | public static synchronized ArrayList fetchHostTableLineHasInfo() { 83 | // 获取当前所有记录的数据 84 | String WhereCondition = "where find_info_num>0"; 85 | String selectSQL = genHostTableSqlByWhereCondition(WhereCondition); 86 | return fetchHostTableLineBySQl(selectSQL); 87 | } 88 | 89 | //获取敏感数据的行 并且忽略已经处理的项 90 | public static synchronized ArrayList fetchHostTableLineHasInfoNotHandle() { 91 | // 获取当前所有记录的数据 92 | String WhereCondition = ("where find_info_num>0 and run_status != 'RUN_STATUS'") 93 | .replace("RUN_STATUS", Constants.HANDLE_END); 94 | String selectSQL = genHostTableSqlByWhereCondition(WhereCondition); 95 | return fetchHostTableLineBySQl(selectSQL); 96 | } 97 | 98 | public static synchronized ArrayList fetchHostTableLineHasUnVisitedUrls() { 99 | // 获取当前所有记录的数据 100 | String WhereCondition = "where unvisited_url_num>0"; 101 | String selectSQL = genHostTableSqlByWhereCondition(WhereCondition); 102 | return fetchHostTableLineBySQl(selectSQL); 103 | } 104 | 105 | public static synchronized ArrayList fetchHostTableLineAnyIsNull() { 106 | // 获取当前所有记录的数据 107 | String WhereCondition = "where (find_url_num is null and find_path_num is null and find_info_num is null) or (find_url_num <1 and find_path_num <1 and find_info_num <1) "; 108 | String selectSQL = genHostTableSqlByWhereCondition(WhereCondition); 109 | return fetchHostTableLineBySQl(selectSQL); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/database/TableLineDataModelBasicUrlSQL.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import model.BasicUrlTableLineDataModel; 4 | 5 | import java.sql.Connection; 6 | import java.sql.PreparedStatement; 7 | import java.sql.ResultSet; 8 | import java.util.ArrayList; 9 | 10 | import static utils.BurpPrintUtils.*; 11 | 12 | public class TableLineDataModelBasicUrlSQL { 13 | private static String genUrlTableSqlByWhereCondition(String WhereCondition){ 14 | String selectSQL = ("SELECT A.id,A.msg_hash,A.req_url,A.req_method,A.resp_status_code,A.req_source,A.run_status,A.resp_length," + 15 | "B.find_url_num,B.find_path_num,B.find_info_num,B.has_important,B.find_api_num " + 16 | "from $tableName1$ A LEFT JOIN $tableName2$ B ON A.msg_hash = B.msg_hash $WHERE$ order by A.id;") 17 | .replace("$tableName1$", ReqDataTable.tableName) 18 | .replace("$tableName2$", AnalyseUrlResultTable.tableName); 19 | 20 | if (WhereCondition == null) WhereCondition=""; 21 | 22 | return selectSQL.replace("$WHERE$", WhereCondition); 23 | } 24 | 25 | //联合 获取所有行数据 26 | public static synchronized ArrayList fetchUrlTableLineBySQl(String selectSQL){ 27 | ArrayList apiDataModels = new ArrayList<>(); 28 | 29 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 30 | try (ResultSet rs = stmt.executeQuery()) { 31 | while (rs.next()) { 32 | BasicUrlTableLineDataModel apiDataModel = new BasicUrlTableLineDataModel( 33 | rs.getInt("id"), 34 | rs.getString("msg_hash"), 35 | rs.getString("req_url"), 36 | rs.getString("req_method"), 37 | rs.getInt("resp_status_code"), 38 | rs.getString("req_source"), 39 | rs.getInt("find_url_num"), 40 | rs.getInt("find_path_num"), 41 | rs.getInt("find_info_num"), 42 | rs.getBoolean("has_important"), 43 | rs.getInt("find_api_num"), 44 | rs.getString("run_status"), 45 | rs.getInt("resp_length") 46 | ); 47 | apiDataModels.add(apiDataModel); 48 | } 49 | } 50 | } catch (Exception e) { 51 | stderr_println(LOG_ERROR, String.format("[-] Error Fetch All ReqData Left Join InfoAnalyse On MsgHash: %s", e.getMessage())); 52 | } 53 | return apiDataModels; 54 | } 55 | 56 | // 获取当前所有记录 57 | public static synchronized ArrayList fetchUrlTableLineAll() { 58 | String selectSQL = genUrlTableSqlByWhereCondition(null); 59 | return fetchUrlTableLineBySQl(selectSQL); 60 | } 61 | 62 | //获取有效数据的行 63 | public static synchronized ArrayList fetchUrlTableLineHasInfoOrUri() { 64 | // 获取当前所有记录的数据 65 | String WhereCondition = "Where find_url_num>0 or find_path_num>0 or find_info_num>0"; 66 | String selectSQL = genUrlTableSqlByWhereCondition(WhereCondition); 67 | return fetchUrlTableLineBySQl(selectSQL); 68 | } 69 | 70 | //获取有效数据的行 并且忽略已处理的项 71 | public static synchronized ArrayList fetchUrlTableLineHasInfoOrUriNotHandle() { 72 | // 获取当前所有记录的数据 73 | String WhereCondition = ("Where (find_url_num>0 or find_path_num>0 or find_info_num>0) and A.run_status != 'RUN_STATUS'") 74 | .replace("RUN_STATUS", Constants.HANDLE_END); 75 | String selectSQL = genUrlTableSqlByWhereCondition(WhereCondition); 76 | return fetchUrlTableLineBySQl(selectSQL); 77 | } 78 | 79 | //获取存在敏感信息的行 80 | public static synchronized ArrayList fetchUrlTableLineHasInfo() { 81 | // 获取当前所有记录的数据 82 | String WhereCondition = "where find_info_num>0"; 83 | String selectSQL = genUrlTableSqlByWhereCondition(WhereCondition); 84 | return fetchUrlTableLineBySQl(selectSQL); 85 | } 86 | 87 | //获取存在敏感信息的行 并且忽略已处理的项 88 | public static synchronized ArrayList fetchUrlTableLineHasInfoNotHandle() { 89 | // 获取当前所有记录的数据 90 | String WhereCondition = ("where find_info_num>0 and A.run_status != 'RUN_STATUS'") 91 | .replace("RUN_STATUS", Constants.HANDLE_END); 92 | String selectSQL = genUrlTableSqlByWhereCondition(WhereCondition); 93 | return fetchUrlTableLineBySQl(selectSQL); 94 | } 95 | 96 | //获取没有数据的行,备用,用于后续删除数据 97 | public static synchronized ArrayList fetchUrlTableLineAnyIsNull() { 98 | // 获取当前所有记录的数据 99 | String WhereCondition = "where (find_url_num is null and find_path_num is null and find_info_num is null) or (find_url_num <1 and find_path_num <1 and find_info_num <1) "; 100 | String selectSQL = genUrlTableSqlByWhereCondition(WhereCondition); 101 | return fetchUrlTableLineBySQl(selectSQL); 102 | } 103 | 104 | //获取没有数据的行,备用,用于后续删除数据 105 | public static synchronized int clearUrlTableLineAnyIsNull() { 106 | int rowsAffected = -1; 107 | 108 | // 获取当前所有记录的数据 109 | String deleteSQL = ("DELETE FROM $tableName1$ WHERE id IN (" + 110 | "SELECT A.id FROM $tableName1$ A LEFT JOIN $tableName2$ B ON A.msg_hash=B.msg_hash " + 111 | "WHERE (find_url_num IS NULL AND find_path_num IS NULL AND find_info_num IS NULL) " + 112 | "OR (find_url_num < 1 AND find_path_num < 1 AND find_info_num < 1));") 113 | .replace("$tableName1$", ReqDataTable.tableName) 114 | .replace("$tableName2$", AnalyseUrlResultTable.tableName); 115 | 116 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(deleteSQL)) { 117 | rowsAffected = stmt.executeUpdate(); 118 | stdout_println(LOG_DEBUG, String.format(String.format("[-] table [%s] cleared Useless Data [%s] line.", ReqDataTable.tableName, rowsAffected))); 119 | } catch (Exception e) { 120 | stderr_println(LOG_ERROR, String.format("[-] Error clear Useless Data On Table [%s] -> Error:[%s]", ReqDataTable.tableName, e.getMessage())); 121 | e.printStackTrace(); 122 | } 123 | 124 | return rowsAffected; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/database/UnionTableSql.java: -------------------------------------------------------------------------------- 1 | package database; 2 | 3 | import model.FindPathModel; 4 | 5 | import java.sql.Connection; 6 | import java.sql.PreparedStatement; 7 | import java.sql.ResultSet; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import static utils.BurpPrintUtils.*; 12 | 13 | public class UnionTableSql { 14 | //联合 获取一条需要更新的Path数据 15 | public static synchronized List fetchHostTableNeedUpdatePathDataList(int limit){ 16 | List findPathModels = new ArrayList<>(); 17 | 18 | // 首先选取一条记录的ID 状态是已经分析完毕,并且 当前 PathTree 的 基本路径数量 大于 生成分析数据时的 基本路径数量 19 | String selectSQL = ("SELECT A.id, A.root_url, A.find_path " + 20 | "From $tableName1$ A LEFT JOIN $tableName2$ B ON A.root_url = B.root_url " + 21 | "WHERE B.basic_path_num > A.basic_path_num Limit ?;") 22 | .replace("$tableName1$", AnalyseHostResultTable.tableName) 23 | .replace("$tableName2$", PathTreeTable.tableName); 24 | 25 | try (Connection conn = DBService.getInstance().getNewConn(); PreparedStatement stmt = conn.prepareStatement(selectSQL)) { 26 | stmt.setInt(1, limit); 27 | 28 | try (ResultSet rs = stmt.executeQuery()) { 29 | while (rs.next()) { 30 | FindPathModel findPathModel = new FindPathModel( 31 | rs.getInt("id"), 32 | rs.getString("root_url"), 33 | rs.getString("find_path") 34 | ); 35 | findPathModels.add(findPathModel); 36 | } 37 | } 38 | } catch (Exception e) { 39 | stderr_println(LOG_ERROR, String.format("[-] Error fetch Need Update Path Data List: %s", e.getMessage())); 40 | } 41 | return findPathModels; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/model/AccessedUrlInfo.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import static utils.RespHashUtils.calcCRC32; 4 | 5 | public class AccessedUrlInfo { 6 | public String rootUrl; 7 | public String reqUrl; 8 | public String urlHash; 9 | public int respStatusCode; 10 | 11 | public AccessedUrlInfo(String reqUrl, String rootUrl, int respStatusCode) { 12 | this.reqUrl = reqUrl; 13 | this.rootUrl = rootUrl; 14 | this.respStatusCode = respStatusCode; 15 | this.urlHash = calcCRC32(reqUrl); 16 | } 17 | 18 | public String getUrlHash() { 19 | return urlHash; 20 | } 21 | 22 | public String getReqUrl() { 23 | return reqUrl; 24 | } 25 | 26 | public String getRootUrl() { 27 | return rootUrl; 28 | } 29 | 30 | public int getRespStatusCode() { 31 | return respStatusCode; 32 | } 33 | 34 | 35 | } -------------------------------------------------------------------------------- /src/main/java/model/AnalyseHostResultModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import utils.CastUtils; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | 9 | /** 10 | * 用来存储基于主机的结果的模型 11 | */ 12 | public class AnalyseHostResultModel { 13 | private String rootUrl; 14 | private HashMap urlInfoArrayMap; 15 | private List urlList; 16 | private List pathList; 17 | private List apiList; 18 | private Boolean hasImportant; 19 | 20 | // 中转构造函数 21 | public AnalyseHostResultModel(AnalyseUrlResultModel analyseUrlResultModel) { 22 | this.rootUrl = new HttpUrlInfo(analyseUrlResultModel.getReqUrl()).getRootUrlUsual(); 23 | this.urlInfoArrayMap = analyseUrlResultModel.getUrlInfoArrayMap(); 24 | this.urlList = analyseUrlResultModel.getUrlList(); 25 | this.pathList = analyseUrlResultModel.getPathList(); 26 | this.apiList = analyseUrlResultModel.getApiList(); 27 | this.hasImportant = analyseUrlResultModel.getHasImportant(); 28 | } 29 | 30 | public String getRootUrl() { 31 | return rootUrl; 32 | } 33 | 34 | public HashMap getUrlInfoArrayMap() { 35 | return urlInfoArrayMap; 36 | } 37 | 38 | public List getUrlList() { 39 | return urlList; 40 | } 41 | 42 | public List getPathList() { 43 | return pathList; 44 | } 45 | 46 | public List getApiList() { 47 | return apiList; 48 | } 49 | 50 | public Boolean getHasImportant() { 51 | return hasImportant; 52 | } 53 | 54 | public List getUnvisitedUrlList() { 55 | return CastUtils.listAddList(this.urlList, this.apiList); 56 | } 57 | 58 | public List getUnvisitedUrlList(boolean addApiList) { 59 | if (addApiList) 60 | return CastUtils.listAddList(this.urlList, this.apiList); 61 | else 62 | return this.urlList; 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/model/AnalyseUrlResultModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import utils.CastUtils; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class AnalyseUrlResultModel { 11 | private String reqUrl; 12 | private JSONArray infoArray; 13 | private List urlList; 14 | private List pathList; 15 | private List apiList; 16 | private Boolean hasImportant; 17 | 18 | //新增一个URL类型的 19 | public AnalyseUrlResultModel(String reqUrl, JSONArray infoArray, List urlList, List pathList, List apiList, Boolean hasImportant) { 20 | this.reqUrl = reqUrl; 21 | this.infoArray = infoArray; 22 | this.urlList = urlList; 23 | this.pathList = pathList; 24 | this.apiList = apiList; 25 | this.hasImportant = hasImportant; 26 | } 27 | 28 | public AnalyseUrlResultModel(String reqUrl, String infoJsonArrayStr, String urlListStr, String pathListStr, String apiListStr, Boolean hasImportant) { 29 | this.reqUrl = reqUrl; 30 | this.infoArray = CastUtils.toJsonArray(infoJsonArrayStr); 31 | this.urlList = CastUtils.toStringList(urlListStr); 32 | this.pathList = CastUtils.toStringList(pathListStr); 33 | this.apiList = CastUtils.toStringList(apiListStr); 34 | this.hasImportant = hasImportant; 35 | } 36 | 37 | public String getReqUrl() { 38 | return reqUrl; 39 | } 40 | 41 | public JSONArray getInfoArray() { 42 | return infoArray; 43 | } 44 | 45 | public List getUrlList() { 46 | return urlList; 47 | } 48 | 49 | public List getPathList() { 50 | return pathList; 51 | } 52 | 53 | public List getApiList() { 54 | return apiList; 55 | } 56 | 57 | public Boolean getHasImportant() { 58 | return hasImportant; 59 | } 60 | 61 | public HashMap getUrlInfoArrayMap() { 62 | HashMap urlInfoArrayMap= new HashMap<>(); 63 | if (!infoArray.isEmpty()){ 64 | urlInfoArrayMap.put(reqUrl,infoArray); 65 | } 66 | return urlInfoArrayMap; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/model/BasicHostTableLineDataModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | 4 | public class BasicHostTableLineDataModel { 5 | private Integer id; 6 | private String rootUrl; 7 | private String host; 8 | private String domain; 9 | 10 | private Integer findInfoNum; 11 | private Boolean hasImportant; 12 | 13 | private Integer findUrlNum; 14 | private Integer findPathNum; 15 | private Integer findApiNum; 16 | 17 | private Integer pathToUrlNum; 18 | private Integer unvisitedUrlNum; 19 | private Integer allUrlNum; 20 | private Integer basicPathNum; 21 | 22 | private String runStatus; 23 | 24 | // 构造函数 25 | public BasicHostTableLineDataModel(int id, String rootUrl, 26 | int findInfoNum, boolean hasImportant, 27 | int findUrlNum, int findPathNum, int findApiNum, 28 | int pathToUrlNum, int unvisitedUrlNum, 29 | int allUrlNum, int basicPathNum, String runStatus) { 30 | this.id = id; 31 | this.rootUrl = rootUrl; 32 | this.host = parseHostFromUrl(rootUrl); 33 | this.domain = parseDomainFromUrl(rootUrl); 34 | 35 | this.findInfoNum = findInfoNum; 36 | this.hasImportant = hasImportant; 37 | 38 | 39 | this.findUrlNum = findUrlNum; 40 | this.findPathNum = findPathNum; 41 | this.findApiNum = findApiNum; 42 | 43 | this.pathToUrlNum = pathToUrlNum; 44 | this.unvisitedUrlNum = unvisitedUrlNum; 45 | 46 | this.allUrlNum = allUrlNum; 47 | this.basicPathNum = basicPathNum; 48 | 49 | this.runStatus = runStatus; 50 | } 51 | 52 | private String parseHostFromUrl(String rootUrl) { 53 | //从URL中解析出host 54 | return new HttpUrlInfo(rootUrl).getHostPort(); 55 | } 56 | 57 | private String parseDomainFromUrl(String rootUrl) { 58 | //从URL中解析出domain 59 | return new HttpUrlInfo(rootUrl).getRootDomain(); 60 | } 61 | 62 | 63 | public Object[] toRowDataArray() { 64 | return new Object[]{ 65 | this.getId(), 66 | this.getRootUrl(), 67 | this.getHost(), 68 | this.getDomain(), 69 | this.getHasImportant(), 70 | this.getFindInfoNum(), 71 | this.getFindUrlNum(), 72 | this.getFindPathNum(), 73 | this.getFindApiNum(), 74 | this.getPathToUrlNum(), 75 | this.getUnvisitedUrlNum(), 76 | this.getAllUrlNum(), 77 | this.getBasicPathNum(), 78 | this.getRunStatus() 79 | }; 80 | } 81 | 82 | public Integer getId() { 83 | return id; 84 | } 85 | 86 | public String getRootUrl() { 87 | return rootUrl; 88 | } 89 | 90 | public Integer getFindInfoNum() { 91 | return findInfoNum; 92 | } 93 | 94 | public Boolean getHasImportant() { 95 | return hasImportant; 96 | } 97 | 98 | public Integer getFindUrlNum() { 99 | return findUrlNum; 100 | } 101 | 102 | public Integer getFindPathNum() { 103 | return findPathNum; 104 | } 105 | 106 | public Integer getFindApiNum() { 107 | return findApiNum; 108 | } 109 | 110 | public Integer getPathToUrlNum() { 111 | return pathToUrlNum; 112 | } 113 | 114 | public Integer getUnvisitedUrlNum() { 115 | return unvisitedUrlNum; 116 | } 117 | 118 | public Integer getBasicPathNum() { 119 | return basicPathNum; 120 | } 121 | 122 | public String getRunStatus() { 123 | return runStatus; 124 | } 125 | 126 | public Integer getAllUrlNum() { 127 | return allUrlNum; 128 | } 129 | 130 | public String getHost() { 131 | return host; 132 | } 133 | public String getDomain() { 134 | return domain; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/model/BasicHostTableTabDataModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | public class BasicHostTableTabDataModel { 4 | private String rootUrl; 5 | 6 | private String findInfo; 7 | private String findUrl; 8 | private String findPath; 9 | private String findApi; 10 | 11 | private String pathToUrl; 12 | private String unvisitedUrl; 13 | 14 | private String allUrlStatus; 15 | 16 | 17 | public BasicHostTableTabDataModel(String rootUrl, String findInfo, String findUrl, String findPath, 18 | String findApi, String pathToUrl, String unvisitedUrl, String allUrlStatus) { 19 | this.rootUrl = rootUrl; 20 | this.findUrl = findUrl; 21 | this.findPath = findPath; 22 | this.findInfo = findInfo; 23 | this.findApi = findApi; 24 | this.pathToUrl = pathToUrl; 25 | this.unvisitedUrl = unvisitedUrl; 26 | this.allUrlStatus = allUrlStatus; 27 | } 28 | 29 | public String getRootUrl() { 30 | return rootUrl; 31 | } 32 | 33 | public String getFindUrl() { 34 | return findUrl; 35 | } 36 | 37 | public String getFindPath() { 38 | return findPath; 39 | } 40 | 41 | public String getFindInfo() { 42 | return findInfo; 43 | } 44 | 45 | public String getFindApi() { 46 | return findApi; 47 | } 48 | 49 | public String getPathToUrl() { 50 | return pathToUrl; 51 | } 52 | 53 | public String getUnvisitedUrl() { 54 | return unvisitedUrl; 55 | } 56 | 57 | public String getAllUrlStatus() { 58 | return allUrlStatus; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/model/BasicUrlTableLineDataModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | 4 | public class BasicUrlTableLineDataModel { 5 | private Integer id; 6 | private String msgHash; 7 | private String reqUrl; 8 | private String reqMethod; 9 | private Integer respStatusCode; 10 | private String reqSource; 11 | private Integer findUrlNum; 12 | private Integer findPathNum; 13 | private Integer findInfoNum; 14 | private Integer findApiNum; 15 | private String runStatus; 16 | private Integer respLength; 17 | private Boolean hasImportant; 18 | // 构造函数 19 | public BasicUrlTableLineDataModel(int id, String msgHash, String reqUrl, String reqMethod, int respStatusCode, 20 | String reqSource, int findUrlNum, int findPathNum, int findInfoNum, 21 | boolean hasImportant, int findApiNum, String runStatus, int respLength) { 22 | this.id = id; 23 | this.msgHash = msgHash; 24 | this.reqUrl = reqUrl; 25 | this.reqMethod = reqMethod; 26 | this.respStatusCode = respStatusCode; 27 | this.reqSource = reqSource; 28 | this.findUrlNum = findUrlNum; 29 | this.findPathNum = findPathNum; 30 | this.findInfoNum = findInfoNum; 31 | this.findApiNum = findApiNum; 32 | this.runStatus = runStatus; 33 | this.respLength = respLength; 34 | this.hasImportant = hasImportant; 35 | } 36 | 37 | public Object[] toRowDataArray() { 38 | return new Object[]{ 39 | this.getId(), 40 | this.getReqSource(), 41 | this.getMsgHash(), 42 | this.getReqUrl(), 43 | this.getReqMethod(), 44 | this.getRespStatusCode(), 45 | this.getRespLength(), 46 | this.getHasImportant(), 47 | this.getFindInfoNum(), 48 | this.getFindUrlNum(), 49 | this.getFindPathNum(), 50 | this.getFindApiNum(), 51 | this.getRunStatus() 52 | }; 53 | } 54 | 55 | public Integer getId() { 56 | return id; 57 | } 58 | 59 | public String getMsgHash() { 60 | return msgHash; 61 | } 62 | 63 | public String getReqUrl() { 64 | return reqUrl; 65 | } 66 | 67 | public String getReqMethod() { 68 | return reqMethod; 69 | } 70 | 71 | public Integer getRespStatusCode() { 72 | return respStatusCode; 73 | } 74 | 75 | public String getReqSource() { 76 | return reqSource; 77 | } 78 | 79 | public Integer getFindUrlNum() { 80 | return findUrlNum; 81 | } 82 | 83 | public Integer getFindPathNum() { 84 | return findPathNum; 85 | } 86 | 87 | public Integer getFindInfoNum() { 88 | return findInfoNum; 89 | } 90 | 91 | public Integer getFindApiNum() { 92 | return findApiNum; 93 | } 94 | 95 | public String getRunStatus() { 96 | return runStatus; 97 | } 98 | 99 | public Integer getRespLength() { 100 | return respLength; 101 | } 102 | 103 | public Boolean getHasImportant() { 104 | return hasImportant; 105 | } 106 | 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/model/BasicUrlTableTabDataModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | public class BasicUrlTableTabDataModel { 4 | private String msgHash; 5 | private String findUrl; 6 | private String findPath; 7 | private String findInfo; 8 | private String findApi; 9 | 10 | public BasicUrlTableTabDataModel(String msgHash, String findUrl, String findPath, String findInfo, 11 | String findApi) { 12 | this.msgHash = msgHash; 13 | this.findUrl = findUrl; 14 | this.findPath = findPath; 15 | this.findInfo = findInfo; 16 | this.findApi = findApi; 17 | } 18 | 19 | public String getMsgHash() { 20 | return msgHash; 21 | } 22 | 23 | public String getFindUrl() { 24 | return findUrl; 25 | } 26 | 27 | public String getFindPath() { 28 | return findPath; 29 | } 30 | 31 | public String getFindInfo() { 32 | return findInfo; 33 | } 34 | 35 | public String getFindApi() { 36 | return findApi; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/model/FindPathModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import utils.PathTreeUtils; 5 | 6 | import java.util.LinkedHashSet; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | public class FindPathModel { 11 | private int id; 12 | private String rootUrl; 13 | private JSONArray findPath; 14 | 15 | 16 | public FindPathModel(int id, String rootUrl, String findPath) { 17 | this.id = id; 18 | this.rootUrl = rootUrl; 19 | this.findPath = JSONArray.parse(findPath); 20 | } 21 | 22 | public int getId() { 23 | return id; 24 | } 25 | 26 | 27 | public String getRootUrl() { 28 | return rootUrl; 29 | } 30 | 31 | 32 | public JSONArray getFindPath() { 33 | return findPath; 34 | } 35 | 36 | 37 | /** 38 | * 从路径模型中获取单层路径 39 | * @param findPathModelList 40 | * @return 41 | */ 42 | public static Set getSingleLayerPathSet(List findPathModelList) { 43 | Set pathSet = new LinkedHashSet<>(); 44 | //查询msgHash列表对应的所有数据find path 数据 45 | for (FindPathModel findPathModel: findPathModelList){ 46 | //逐个提取PATH 并 加入 pathSet 47 | JSONArray findPaths = findPathModel.getFindPath(); 48 | if (!findPaths.isEmpty()){ 49 | // 提取 path中的单层路径 50 | for (Object uriPath : findPaths){ 51 | List uriPart = PathTreeUtils.getUrlPart((String) uriPath); 52 | if (uriPart.size() == 1){ 53 | pathSet.add(PathTreeUtils.formatUriPath((String) uriPath)); 54 | } 55 | } 56 | } 57 | } 58 | return pathSet; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/model/FingerPrintRule.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import utils.CastUtils; 4 | 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | public class FingerPrintRule { 10 | private String matchType; 11 | private String location; 12 | private String describe; 13 | private List matchKeys; 14 | private boolean isImportant; 15 | private String type; 16 | private boolean isOpen; 17 | private String accuracy; 18 | 19 | // 新添加的构造函数 20 | public FingerPrintRule(String type, String describe, boolean isImportant, String matchType, String location, List matchKeys, boolean isOpen, String accuracy) { 21 | this.matchType = matchType; 22 | this.describe = describe; 23 | this.location = location; 24 | this.matchKeys = matchKeys; 25 | this.type = type; 26 | this.isImportant = isImportant; 27 | this.isOpen = isOpen; 28 | this.accuracy = accuracy; 29 | } 30 | 31 | public boolean getIsOpen(){ 32 | return isOpen; 33 | } 34 | 35 | public void setOpen(boolean isOpen){ 36 | this.isOpen = isOpen; 37 | } 38 | 39 | public String getAccuracy(){ 40 | return accuracy; 41 | } 42 | 43 | public void setAccuracy(String accuracy){ 44 | this.accuracy = accuracy; 45 | } 46 | 47 | public String getDescribe(){return describe;} 48 | 49 | public void setDescribe(String describe){ 50 | this.describe = describe; 51 | } 52 | 53 | public String getType(){return type;} 54 | 55 | public void setType(String type){this.type = type;} 56 | 57 | public boolean getIsImportant(){return isImportant;} 58 | 59 | public void setIsImportant(boolean isImportant){this.isImportant = isImportant;} 60 | 61 | public String getMatchType() { 62 | return matchType; 63 | } 64 | 65 | public void setMatchType(String matchType) { 66 | this.matchType = matchType; 67 | } 68 | 69 | public String getLocation() { 70 | return location; 71 | } 72 | 73 | public void setLocation(String location) { 74 | this.location = location; 75 | } 76 | 77 | public List getMatchKeys() { 78 | return matchKeys; 79 | } 80 | 81 | public void setMatchKeys(List matchKeys) { 82 | this.matchKeys = matchKeys; 83 | } 84 | 85 | public String getInfo(String color){ 86 | return "Time: " + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()) + "
matchType: " + matchType + "
Type: " + type + "
accuracy: " + accuracy + "
describe: " + describe + "
location: " + location + "
matchKeys: " + CastUtils.listToString(matchKeys) + "
"; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/model/FingerPrintRulesWrapper.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import java.util.List; 4 | 5 | 6 | public class FingerPrintRulesWrapper { 7 | private List fingerprint; 8 | 9 | public List getFingerprint() { 10 | return fingerprint; 11 | } 12 | 13 | public void setFingerprint(List fingerprint) { 14 | this.fingerprint = fingerprint; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/model/HttpMsgInfo.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import burp.*; 4 | 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.zip.CRC32; 7 | 8 | import static utils.CastUtils.isNotEmptyObj; 9 | 10 | //创建一个类用于存储 代理 流量的解析结果 11 | public class HttpMsgInfo { 12 | private static final IExtensionHelpers helpers = BurpExtender.getHelpers(); 13 | private byte[] reqBytes; 14 | private byte[] respBytes; 15 | private String reqMethod; 16 | private HttpUrlInfo urlInfo; 17 | private HttpRespInfo respInfo; 18 | private int respStatusCode; 19 | private String respTitle; 20 | private String msgHash; 21 | 22 | // 构造函数 23 | public HttpMsgInfo(IInterceptedProxyMessage iInterceptedProxyMessage) { 24 | IHttpRequestResponse messageInfo = iInterceptedProxyMessage.getMessageInfo(); 25 | reqBytes = messageInfo.getRequest(); 26 | 27 | //请求方法 28 | IRequestInfo requestInfoBetter = helpers.analyzeRequest(messageInfo); 29 | reqMethod = requestInfoBetter.getMethod(); 30 | 31 | //从请求URL解析部分信息 //直接从请求体是没有办法获取到请求URL信息的, URL此时只能从外部传入 32 | String reqUrl = requestInfoBetter.getUrl().toString(); 33 | urlInfo = new HttpUrlInfo(reqUrl); 34 | 35 | //从响应结果解析部分信息 36 | respBytes = messageInfo.getResponse(); 37 | respInfo = new HttpRespInfo(respBytes); 38 | 39 | //响应码是常用的 40 | respStatusCode = respInfo.getStatusCode(); 41 | respTitle = respInfo.getRespTitle(); 42 | 43 | //请求响应信息的简单hash值 44 | msgHash = calcMsgHash(urlInfo.getUrlToFileUsual(),reqMethod,respStatusCode,respInfo.getBodyLenVague()); 45 | } 46 | 47 | // 构造函数 48 | public HttpMsgInfo(IHttpRequestResponse iHttpRequestResponse) { 49 | //请求信息 50 | reqBytes = iHttpRequestResponse.getRequest(); 51 | 52 | //请求方法 53 | IHttpService httpService = iHttpRequestResponse.getHttpService(); 54 | IRequestInfo requestInfoBetter = helpers.analyzeRequest(httpService,reqBytes); 55 | reqMethod = requestInfoBetter.getMethod(); 56 | 57 | //从请求URL解析部分信息 58 | String reqUrl = requestInfoBetter.getUrl().toString(); 59 | urlInfo = new HttpUrlInfo(reqUrl); 60 | 61 | //从响应结果解析部分信息 62 | respBytes = iHttpRequestResponse.getResponse(); 63 | respInfo = new HttpRespInfo(respBytes); 64 | 65 | //响应码是常用的 66 | respStatusCode = respInfo.getStatusCode(); 67 | respTitle = respInfo.getRespTitle(); 68 | 69 | //请求响应信息的简单hash值 70 | msgHash = calcMsgHash(urlInfo.getUrlToFileUsual(),reqMethod,respStatusCode,respInfo.getBodyLenVague()); 71 | } 72 | 73 | 74 | // 构造函数 75 | public HttpMsgInfo(String requestUrl, byte[] requestBytes, byte[] responseBytes, String msgInfoHash) { 76 | //请求信息 77 | reqBytes = requestBytes; 78 | 79 | //请求方法 80 | IRequestInfo requestInfoSimple = helpers.analyzeRequest(reqBytes); 81 | reqMethod = requestInfoSimple.getMethod(); 82 | 83 | //从请求URL解析部分信息 84 | String reqUrl = requestUrl; 85 | urlInfo = new HttpUrlInfo(reqUrl); 86 | 87 | //从响应结果解析部分信息 88 | respBytes = responseBytes; 89 | respInfo = new HttpRespInfo(respBytes); 90 | 91 | //响应码是常用的 92 | respStatusCode = respInfo.getStatusCode(); 93 | respTitle = respInfo.getRespTitle(); 94 | 95 | //请求响应信息的简单hash值 因为中间可能截断了超大的响应体 , 因此最好手动传入 msgHash 96 | msgHash = msgInfoHash; 97 | } 98 | 99 | /** 100 | * 计算消息Hash 101 | */ 102 | private String calcMsgHash(String urlToFileUsual, String reqMethod, int respStatusCode, int respBodyLenVague) { 103 | return calcCRC32(String.format("%s|%s|%s|%s", urlToFileUsual, reqMethod, respStatusCode, respBodyLenVague)); 104 | } 105 | 106 | /** 107 | * 计算给定字符串的CRC32校验和,并以十六进制字符串形式返回。 108 | * @param string 要计算CRC32的字符串 109 | * @return 字符串的CRC32校验和的十六进制表示 110 | */ 111 | private static String calcCRC32(String string) { 112 | // 使用 UTF-8 编码将字符串转换为字节数组 113 | byte[] inputBytes = string.getBytes(StandardCharsets.UTF_8); 114 | // 初始化CRC32对象 115 | CRC32 crc32 = new CRC32(); 116 | // 更新CRC值 117 | crc32.update(inputBytes, 0, inputBytes.length); 118 | // 将计算后的CRC32值转换为十六进制字符串并返回 119 | return Long.toHexString(crc32.getValue()).toLowerCase(); 120 | } 121 | 122 | public String getReqMethod() { 123 | return reqMethod; 124 | } 125 | 126 | public byte[] getRespBytes() { 127 | return respBytes; 128 | } 129 | 130 | public byte[] getReqBytes() { 131 | return reqBytes; 132 | } 133 | 134 | public String getMsgHash() { 135 | return msgHash; 136 | } 137 | 138 | public void setRespBytes(byte[] respBytes) { 139 | this.respBytes = respBytes; 140 | } 141 | 142 | public HttpUrlInfo getUrlInfo() { 143 | return urlInfo; 144 | } 145 | 146 | public HttpRespInfo getRespInfo() { 147 | return respInfo; 148 | } 149 | 150 | public int getRespStatusCode() { 151 | return respStatusCode; 152 | } 153 | 154 | public String getRespTitle() { 155 | return respTitle; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/model/HttpRespInfo.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import burp.BurpExtender; 4 | import burp.IExtensionHelpers; 5 | import burp.IResponseInfo; 6 | import utils.RespHashUtils; 7 | import utils.RespTitleUtils; 8 | 9 | import java.util.Arrays; 10 | 11 | public class HttpRespInfo { 12 | private static final IExtensionHelpers helpers = BurpExtender.getHelpers(); 13 | private byte[] respBytes = "".getBytes(); 14 | private int statusCode = -1; 15 | private int respLength = -1; 16 | private int bodyLength = -1; 17 | private int bodyLenVague = -1; 18 | private String inferredMimeType = ""; 19 | private String statedMimeType = ""; 20 | private int bodyOffset = -1; 21 | private String respTitle = ""; 22 | 23 | private String iconHash = ""; //记录响应体的hash值 24 | 25 | public String getIconHash() { 26 | return iconHash; 27 | } 28 | 29 | HttpRespInfo(byte[] responseBytes) { 30 | if (responseBytes == null || responseBytes.length <= 0){ 31 | // Warning: That response body is empty !!! 32 | return; 33 | } 34 | 35 | respBytes = responseBytes; 36 | //响应长度 37 | respLength = respBytes.length; 38 | //响应信息 39 | IResponseInfo responseInfo = helpers.analyzeResponse(respBytes); 40 | //响应状态码 41 | statusCode = responseInfo.getStatusCode(); 42 | //获取响应类型 43 | inferredMimeType = responseInfo.getInferredMimeType(); //根据响应的内容自动推断出的 MIME 类型 44 | statedMimeType = responseInfo.getStatedMimeType(); //由服务器明确声明的内容类型 45 | //响应体分割标记 46 | bodyOffset = responseInfo.getBodyOffset(); 47 | bodyLength = getBodyBytes().length; 48 | //大致的响应长度 49 | bodyLenVague = bodyLength / 200; 50 | //响应文本标题 51 | respTitle = RespTitleUtils.parseTextTitle(respBytes); 52 | //当响应类型是 ico 类型时计算一下hash值 53 | if (getStatedMimeType() != null && getStatedMimeType().contains("ico")){ 54 | iconHash = RespHashUtils.getFaviconHash(getBodyBytes()); 55 | } 56 | } 57 | 58 | 59 | /** 60 | * 获取 请求体或响应体的body部分 61 | */ 62 | public byte[] getBodyBytes() { 63 | // 确保 bodyOffset 不会导致数组越界 64 | int bodyLength = Math.max(0, respBytes.length - bodyOffset); 65 | 66 | // 从 bytes 数组中复制 body 的部分 67 | return Arrays.copyOfRange(respBytes, bodyOffset, bodyOffset + bodyLength); 68 | } 69 | 70 | /** 71 | * 获取 请求或响应的头部信息部分 72 | */ 73 | public byte[] getHeaderBytes() { 74 | // 确保 headerOffset 不会导致数组越界,并且至少是从0开始 75 | int headerLength = Math.max(0, bodyOffset); 76 | // 从 bytes 数组中复制 header 的部分 77 | return Arrays.copyOfRange(respBytes, 0, headerLength); 78 | } 79 | 80 | public int getStatusCode() { 81 | return statusCode; 82 | } 83 | 84 | public int getRespLength() { 85 | return respLength; 86 | } 87 | 88 | public int getBodyLength() { 89 | return bodyLength; 90 | } 91 | 92 | public int getBodyLenVague() { 93 | return bodyLenVague; 94 | } 95 | 96 | public String getInferredMimeType() { 97 | return inferredMimeType; 98 | } 99 | 100 | public String getStatedMimeType() { 101 | return statedMimeType; 102 | } 103 | 104 | public int getBodyOffset() { 105 | return bodyOffset; 106 | } 107 | 108 | public byte[] getRespBytes() { 109 | return respBytes; 110 | } 111 | 112 | public String getRespTitle() { 113 | return respTitle; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/model/PathToUrlsModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import com.alibaba.fastjson2.JSONArray; 4 | import utils.CastUtils; 5 | 6 | import java.util.List; 7 | 8 | public class PathToUrlsModel { 9 | private int id; 10 | private int basicPathNum; 11 | private List pathToUrls; 12 | private List unvisitedUrls; 13 | 14 | public PathToUrlsModel(int id, int basic_path_num, JSONArray pathToUrls, JSONArray unvisitedUrls) { 15 | this.id = id; 16 | this.basicPathNum = basic_path_num; 17 | this.pathToUrls = CastUtils.toStringList(pathToUrls); 18 | this.unvisitedUrls = CastUtils.toStringList(unvisitedUrls); 19 | } 20 | 21 | public PathToUrlsModel(int id, int basic_path_num, String pathToUrls, String unvisitedUrls) { 22 | this.id = id; 23 | this.basicPathNum = basic_path_num; 24 | this.pathToUrls = CastUtils.toStringList(pathToUrls); 25 | this.unvisitedUrls = CastUtils.toStringList(unvisitedUrls); 26 | } 27 | 28 | public int getId() { 29 | return id; 30 | } 31 | 32 | public void setId(int id) { 33 | this.id = id; 34 | } 35 | 36 | public int getBasicPathNum() { 37 | return basicPathNum; 38 | } 39 | 40 | public void setBasicPathNum(int basicPathNum) { 41 | this.basicPathNum = basicPathNum; 42 | } 43 | 44 | public List getPathToUrls() { 45 | return pathToUrls; 46 | } 47 | 48 | public void setPathToUrls(List pathToUrls) { 49 | this.pathToUrls = pathToUrls; 50 | } 51 | 52 | public List getUnvisitedUrls() { 53 | return unvisitedUrls; 54 | } 55 | 56 | public void setUnvisitedUrls(List unvisitedUrls) { 57 | this.unvisitedUrls = unvisitedUrls; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/model/PathTreeModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import com.alibaba.fastjson2.JSONObject; 4 | import utils.CastUtils; 5 | 6 | public class PathTreeModel { 7 | private String rootUrl; 8 | private Integer basicPathNum; 9 | private JSONObject pathTree; 10 | 11 | public PathTreeModel(String rootUrl, Integer basicPathNum, JSONObject pathTree) { 12 | this.rootUrl = rootUrl; 13 | this.basicPathNum = basicPathNum; 14 | this.pathTree = pathTree; 15 | } 16 | 17 | public PathTreeModel(String rootUrl, int basicPathNum, String pathTree) { 18 | this.rootUrl = rootUrl; 19 | this.basicPathNum = basicPathNum; 20 | this.pathTree = CastUtils.toJsonObject(pathTree); 21 | } 22 | 23 | 24 | 25 | public Integer getBasicPathNum() { 26 | return basicPathNum; 27 | } 28 | 29 | public JSONObject getPathTree() { 30 | return pathTree; 31 | } 32 | 33 | public String getRootUrl() { 34 | return rootUrl; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/model/RecordHashMap.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | public class RecordHashMap { 7 | 8 | private final ConcurrentHashMap countMap; 9 | 10 | public RecordHashMap() { 11 | this.countMap = new ConcurrentHashMap<>(); 12 | } 13 | 14 | public Map getStringMap() { 15 | return this.countMap; 16 | } 17 | 18 | public Integer get(String key) { 19 | Integer ret = this.countMap.get(key); 20 | if (ret == null) { 21 | return 0; 22 | } else { 23 | return ret; 24 | } 25 | } 26 | 27 | public void add(String key) { 28 | if (key == null || key.length() <= 0) { 29 | throw new IllegalArgumentException("Key 不能为空"); 30 | } 31 | 32 | synchronized (this.getStringMap()) { 33 | this.countMap.put(key, (this.get(key) + 1)); 34 | } 35 | } 36 | 37 | public void del(String key) { 38 | if (this.countMap.get(key) != null) { 39 | this.countMap.remove(key); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/model/RecordPathDirsModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | public class RecordPathDirsModel { 4 | private String rootUrl; 5 | private String reqPathDirs; 6 | 7 | // 构造函数 8 | public RecordPathDirsModel(String rootUrl, String reqPathDirs) { 9 | this.rootUrl = rootUrl; 10 | this.reqPathDirs = reqPathDirs; 11 | } 12 | 13 | public String getRootUrl() { 14 | return rootUrl; 15 | } 16 | 17 | public String getReqPathDirs() { 18 | return reqPathDirs; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/model/RecordPathModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.zip.CRC32; 5 | 6 | public class RecordPathModel { 7 | private String reqHash; 8 | private String rootUrl; 9 | private String reqPathDir; 10 | private int respStatusCode; 11 | 12 | public RecordPathModel(String rootUrl, String reqPathDir, int respStatusCode) { 13 | this.rootUrl = rootUrl; 14 | this.reqPathDir = reqPathDir; 15 | this.respStatusCode = respStatusCode; 16 | this.reqHash = getCalcCRC32(); 17 | } 18 | 19 | public RecordPathModel(HttpUrlInfo urlInfo, int respStatusCode) { 20 | this.rootUrl = urlInfo.getRootUrlUsual(); 21 | this.reqPathDir = urlInfo.getPathToDir(); 22 | this.respStatusCode = respStatusCode; 23 | this.reqHash = getCalcCRC32(); 24 | } 25 | 26 | private String getCalcCRC32() { 27 | return calcCRC32(String.format("%s|%s|%s", this.rootUrl, this.reqPathDir, this.respStatusCode)); 28 | } 29 | 30 | /** 31 | * 计算给定字符串的CRC32校验和,并以十六进制字符串形式返回。 32 | * @param string 要计算CRC32的字符串 33 | * @return 字符串的CRC32校验和的十六进制表示 34 | */ 35 | private String calcCRC32(String string) { 36 | // 使用 UTF-8 编码将字符串转换为字节数组 37 | byte[] inputBytes = string.getBytes(StandardCharsets.UTF_8); 38 | // 初始化CRC32对象 39 | CRC32 crc32 = new CRC32(); 40 | // 更新CRC值 41 | crc32.update(inputBytes, 0, inputBytes.length); 42 | // 将计算后的CRC32值转换为十六进制字符串并返回 43 | return Long.toHexString(crc32.getValue()).toLowerCase(); 44 | } 45 | 46 | 47 | public String getReqPathDir() { 48 | return reqPathDir; 49 | } 50 | 51 | public int getRespStatusCode() { 52 | return respStatusCode; 53 | } 54 | 55 | public String getReqHash() { 56 | return reqHash; 57 | } 58 | 59 | public String getRootUrl() { 60 | return rootUrl; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/model/ReqMsgDataModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | public class ReqMsgDataModel { 4 | private String msgHash; 5 | private String reqUrl; 6 | private byte[] reqBytes; 7 | private byte[] respBytes; 8 | 9 | public ReqMsgDataModel(String msgHash, String reqUrl, byte[] reqBytes, byte[] respBytes) { 10 | this.msgHash = msgHash; 11 | this.reqUrl = reqUrl; 12 | this.reqBytes = reqBytes; 13 | this.respBytes = respBytes; 14 | } 15 | 16 | public String getMsgHash() { 17 | return msgHash; 18 | } 19 | 20 | public String getReqUrl() { 21 | return reqUrl; 22 | } 23 | 24 | public byte[] getReqBytes() { 25 | return reqBytes; 26 | } 27 | 28 | public byte[] getRespBytes() { 29 | return respBytes; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/model/ReqUrlRespStatusModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | public class ReqUrlRespStatusModel { 4 | private Integer id; 5 | private String reqUrl; 6 | private String reqMethod; 7 | private Integer respStatusCode; 8 | private Integer respLength; 9 | 10 | // 有参构造函数 11 | public ReqUrlRespStatusModel(Integer id, String reqUrl, String reqMethod, Integer respStatusCode, Integer respLength) { 12 | this.id = id; 13 | this.reqUrl = reqUrl; 14 | this.reqMethod = reqMethod; 15 | this.respStatusCode = respStatusCode; 16 | this.respLength = respLength; 17 | } 18 | 19 | public Integer getId() { 20 | return id; 21 | } 22 | 23 | public String getReqUrl() { 24 | return reqUrl; 25 | } 26 | 27 | public String getReqMethod() { 28 | return reqMethod; 29 | } 30 | 31 | public Integer getRespStatusCode() { 32 | return respStatusCode; 33 | } 34 | 35 | public Integer getRespLength() { 36 | return respLength; 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/model/RespFieldsModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import com.alibaba.fastjson2.JSON; 4 | import utils.CastUtils; 5 | import utils.RespHashUtils; 6 | 7 | import java.util.*; 8 | 9 | /** 10 | * 用于响应信息对比的数据模型 11 | */ 12 | public class RespFieldsModel { 13 | private Integer statusCode; // 响应状态码,需要忽略 200 的情况 14 | private Integer respLength; // 响应头中的长度 需要忽略小于0的情况 15 | private Integer respBodyLength; // 响应内容大小 16 | private String respTextTitle; // 响应文本标题 17 | private String respHashContent; // 响应内容HASH 18 | private String respRedirectUrl; // 响应重定向URL 19 | 20 | public RespFieldsModel(HttpRespInfo respInfo) { 21 | this.statusCode = respInfo.getStatusCode(); 22 | this.respLength = respInfo.getRespLength(); 23 | this.respBodyLength = respInfo.getBodyLength(); 24 | this.respTextTitle = respInfo.getRespTitle(); 25 | this.respRedirectUrl = CastUtils.parseRespRedirectUrl(respInfo.getHeaderBytes()); 26 | this.respHashContent = RespHashUtils.calcCRC32(respInfo.getBodyBytes()); 27 | } 28 | 29 | public String toJSONString(){ 30 | return JSON.toJSONString(getAllFieldsAsMap()); 31 | } 32 | 33 | // 新增方法:获取所有属性的名称和值 34 | public Map getAllFieldsAsMap() { 35 | Map fieldMap = new HashMap<>(); 36 | fieldMap.put("StatusCode", statusCode); 37 | fieldMap.put("RespLength", respLength); 38 | fieldMap.put("BodyLength", respBodyLength); 39 | fieldMap.put("RespTitle", respTextTitle); 40 | fieldMap.put("RespHash", respHashContent); 41 | fieldMap.put("RedirectUrl", respRedirectUrl); 42 | return fieldMap; 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/model/UnVisitedUrlsModel.java: -------------------------------------------------------------------------------- 1 | package model; 2 | 3 | import utils.CastUtils; 4 | 5 | import java.util.List; 6 | 7 | public class UnVisitedUrlsModel { 8 | private int id; 9 | private String rootUrl; 10 | private List unvisitedUrls; 11 | 12 | public UnVisitedUrlsModel(int id, String rootUrl, String unvisitedUrl) { 13 | this.id = id; 14 | this.rootUrl = rootUrl; 15 | this.unvisitedUrls = CastUtils.toStringList(unvisitedUrl); 16 | } 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | public String getRootUrl() { 23 | return rootUrl; 24 | } 25 | 26 | public List getUnvisitedUrls() { 27 | return unvisitedUrls; 28 | } 29 | 30 | public void setUnvisitedUrls(List unvisitedUrls) { 31 | this.unvisitedUrls = unvisitedUrls; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/ui/FingerTabRender/ButtonEditor.java: -------------------------------------------------------------------------------- 1 | package ui.FingerTabRender; 2 | 3 | import burp.BurpExtender; 4 | import model.FingerPrintRule; 5 | import ui.RuleConfigPanel; 6 | import utils.ConfigUtils; 7 | import utils.UiUtils; 8 | 9 | import javax.swing.*; 10 | import javax.swing.table.DefaultTableModel; 11 | import javax.swing.table.TableCellEditor; 12 | import java.awt.*; 13 | import java.awt.event.ActionEvent; 14 | import java.awt.event.ActionListener; 15 | 16 | public class ButtonEditor extends AbstractCellEditor implements TableCellEditor { 17 | private final JPanel buttonsPanel; 18 | private final JButton editButton; 19 | private final JButton deleteButton; 20 | private final JButton toggleButton; 21 | private final Icon EDIT_ICON = UiUtils.getImageIcon("/icon/editButton.png"); 22 | private final Icon DELETE_ICON = UiUtils.getImageIcon("/icon/deleteButton.png"); 23 | private final Icon openIcon = UiUtils.getImageIcon("/icon/openButtonIcon.png"); 24 | private final Icon closeIcon = UiUtils.getImageIcon("/icon/shutdownButtonIcon.png"); 25 | 26 | public ButtonEditor(JTable sourceTable) { 27 | toggleButton = new JButton(); //开关按钮 28 | toggleButton.setIcon(openIcon); 29 | 30 | editButton = new JButton(); //编辑按钮 31 | editButton.setIcon(EDIT_ICON); 32 | 33 | deleteButton = new JButton(); //删除按钮 34 | deleteButton.setIcon(DELETE_ICON); 35 | 36 | editButton.setPreferredSize(new Dimension(17, 17)); 37 | deleteButton.setPreferredSize(new Dimension(17, 17)); 38 | toggleButton.setPreferredSize(new Dimension(17, 17)); 39 | 40 | toggleButton.addActionListener(new ActionListener() { 41 | @Override 42 | public void actionPerformed(ActionEvent e) { 43 | int viewRow = sourceTable.getSelectedRow(); // 获取视图中选中的行 44 | if (viewRow < 0) { 45 | return; // 如果没有选中任何行,就不执行编辑操作 46 | } 47 | int modelRow = sourceTable.convertRowIndexToModel(viewRow); // 转换为模型索引 48 | int dataIndex = RuleConfigPanel.tableToModelIndexMap.get(modelRow); // 使用模型索引查找原始数据列表中的索引 49 | 50 | RuleConfigPanel.editingRow = dataIndex; // 更新编辑行索引为原始数据列表中的索引 51 | FingerPrintRule rule = BurpExtender.fingerprintRules.get(dataIndex); 52 | if (rule.getIsOpen()) { 53 | toggleButton.setIcon(closeIcon); 54 | rule.setOpen(false); 55 | } else { 56 | toggleButton.setIcon(openIcon); 57 | rule.setOpen(true); 58 | } 59 | fireEditingStopped(); 60 | sourceTable.repaint(); 61 | } 62 | }); 63 | 64 | // 在编辑按钮的 ActionListener 中添加以下代码来设置 matchKeyField 的值 65 | editButton.addActionListener(new ActionListener() { 66 | public void actionPerformed(ActionEvent e) { 67 | int viewRow = sourceTable.getSelectedRow(); // 获取视图中选中的行 68 | if (viewRow < 0) { 69 | return; // 如果没有选中任何行,就不执行编辑操作 70 | } 71 | int modelRow = sourceTable.convertRowIndexToModel(viewRow); // 转换为模型索引 72 | 73 | //加载规则编辑面板 74 | RuleConfigPanel.showRuleEditorPanel(modelRow); 75 | 76 | /* 77 | //跟随标签显示 优化版本 78 | Point btnLocation = ((JButton) e.getSource()).getLocationOnScreen(); 79 | // 计算面板的左上角新位置 80 | int newX = btnLocation.x - fingerConfigTab.editRulePanel.getWidth() - 20; //水平方向,从左向右增加。 81 | int newY = btnLocation.y + ((JButton) e.getSource()).getHeight(); //垂直方向,从上向下增加。 82 | // 获取容器的大小 83 | Dimension containerSize = sourceTable.getSize(); 84 | // 获取面板的大小 85 | Dimension panelSize = fingerConfigTab.editRulePanel.getPreferredSize(); 86 | // 检查面板是否会超出容器的底部边界 87 | if (newY + panelSize.height > containerSize.height){ 88 | // 如果会超出底部边界,则将面板移到按钮上方 89 | newY = btnLocation.y - panelSize.height - 50; 90 | } 91 | */ 92 | 93 | fireEditingStopped(); // 停止表格的编辑状态 94 | } 95 | }); 96 | 97 | deleteButton.addActionListener(new ActionListener() { 98 | public void actionPerformed(ActionEvent e) { 99 | fireEditingStopped(); // 确保停止编辑状态 100 | int viewRow = sourceTable.getSelectedRow(); // 获取视图中选中的行 101 | if (viewRow < 0) { 102 | return; // 如果没有选中任何行,就不执行删除操作 103 | } 104 | int modelRow = sourceTable.convertRowIndexToModel(viewRow); // 转换为模型索引 105 | int dataIndex = RuleConfigPanel.tableToModelIndexMap.get(modelRow); // 获取实际数据索引 106 | 107 | // 删除数据源中的数据 108 | BurpExtender.fingerprintRules.remove(dataIndex); 109 | 110 | // 更新映射 111 | RuleConfigPanel.tableToModelIndexMap.remove(modelRow); 112 | 113 | // 由于删除了一个元素,需要更新所有后续元素的索引 114 | for (int i = modelRow; i < RuleConfigPanel.tableToModelIndexMap.size(); i++) { 115 | RuleConfigPanel.tableToModelIndexMap.set(i, RuleConfigPanel.tableToModelIndexMap.get(i) - 1); 116 | } 117 | 118 | // 删除表格模型中的数据 119 | ((DefaultTableModel) sourceTable.getModel()).removeRow(viewRow); 120 | 121 | // 在删除行之后,重新验证和重绘表格 122 | sourceTable.revalidate(); 123 | sourceTable.repaint(); 124 | 125 | //重新加载系统CONF_配置 126 | ConfigUtils.reloadConfigArrayListFromRules(BurpExtender.fingerprintRules); 127 | } 128 | }); 129 | 130 | //把三个按钮放在一个小面板中 131 | buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0)); 132 | buttonsPanel.add(toggleButton); 133 | buttonsPanel.add(editButton); 134 | buttonsPanel.add(deleteButton); 135 | buttonsPanel.setBorder(BorderFactory.createEmptyBorder()); 136 | } 137 | 138 | @Override 139 | public Object getCellEditorValue() { 140 | return null; 141 | } 142 | 143 | @Override 144 | public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 145 | return buttonsPanel; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/ui/FingerTabRender/ButtonRenderer.java: -------------------------------------------------------------------------------- 1 | package ui.FingerTabRender; 2 | 3 | import burp.BurpExtender; 4 | import model.FingerPrintRule; 5 | import ui.RuleConfigPanel; 6 | import utils.UiUtils; 7 | 8 | import javax.swing.*; 9 | import javax.swing.table.TableCellRenderer; 10 | import java.awt.*; 11 | 12 | 13 | public class ButtonRenderer extends JPanel implements TableCellRenderer { 14 | private static final Icon EDIT_ICON = UiUtils.getImageIcon("/icon/editButton.png"); 15 | private static final Icon DELETE_ICON = UiUtils.getImageIcon("/icon/deleteButton.png"); 16 | private static final Icon OPEN_ICON = UiUtils.getImageIcon("/icon/openButtonIcon.png"); 17 | private static final Icon CLOSE_ICON = UiUtils.getImageIcon("/icon/shutdownButtonIcon.png"); 18 | private final JButton editButton; 19 | private final JButton deleteButton; 20 | private final JButton toggleButton; 21 | 22 | public ButtonRenderer() { 23 | setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0)); 24 | editButton = createButton(EDIT_ICON); 25 | deleteButton = createButton(DELETE_ICON); 26 | toggleButton = createButton(OPEN_ICON); 27 | add(toggleButton); 28 | add(editButton); 29 | add(deleteButton); 30 | setOpaque(true); // 设置为不透明,这样背景颜色变更才会生效 31 | } 32 | 33 | private JButton createButton(Icon icon) { 34 | JButton button = new JButton(icon); 35 | button.setPreferredSize(new Dimension(17, 17)); 36 | button.setMargin(new Insets(0, 0, 0, 0)); // 设置按钮边距为0 37 | // 设置按钮边界为透明,以免在不同的LookAndFeel下显示不一致 38 | button.setBorder(BorderFactory.createEmptyBorder()); 39 | button.setContentAreaFilled(false); 40 | return button; 41 | } 42 | 43 | @Override 44 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 45 | // 注意:这里使用传入的 `row` 参数,而不是 `table.getSelectedRow()` 46 | int modelRow = table.convertRowIndexToModel(row); // 转换为模型索引 47 | int dataIndex = RuleConfigPanel.tableToModelIndexMap.get(modelRow); // 使用模型索引查找原始数据列表中的索引 48 | 49 | FingerPrintRule rule = BurpExtender.fingerprintRules.get(dataIndex); 50 | if (rule.getIsOpen()) { 51 | toggleButton.setIcon(OPEN_ICON); // 如果规则是打开状态,设置为打开图标 52 | } else { 53 | toggleButton.setIcon(CLOSE_ICON); // 如果规则是关闭状态,设置为关闭图标 54 | } 55 | 56 | // 设置背景色,根据是否选中来决定 57 | if (isSelected) { 58 | setBackground(table.getSelectionBackground()); 59 | } else { 60 | setBackground(table.getBackground()); 61 | } 62 | // 重要:这里要返回包含正确图标的 toggleButton 63 | return this; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/ui/FingerTabRender/CenterRenderer.java: -------------------------------------------------------------------------------- 1 | package ui.FingerTabRender; 2 | 3 | import javax.swing.*; 4 | import javax.swing.table.DefaultTableCellRenderer; 5 | 6 | public class CenterRenderer extends DefaultTableCellRenderer { 7 | public CenterRenderer() { 8 | setHorizontalAlignment(JLabel.CENTER); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/ui/FingerTabRender/HeaderIconTypeRenderer.java: -------------------------------------------------------------------------------- 1 | package ui.FingerTabRender; 2 | 3 | 4 | import utils.UiUtils; 5 | 6 | import javax.swing.*; 7 | import javax.swing.table.DefaultTableCellRenderer; 8 | import java.awt.*; 9 | 10 | public class HeaderIconTypeRenderer extends DefaultTableCellRenderer { 11 | 12 | // 预加载图标 13 | private static final Icon FILTER_ICON = UiUtils.getImageIcon("/icon/filterIcon.png"); 14 | 15 | public HeaderIconTypeRenderer() { 16 | super(); 17 | setHorizontalAlignment(JLabel.CENTER); //仅需设置一次 设置水平对齐方式 18 | setHorizontalTextPosition(JLabel.LEFT); //设置文本相对于图标的水平位置 19 | setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); //更改鼠标光标形状 设置为手形 20 | } 21 | 22 | @Override 23 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 24 | // 调用super方法来保留原始行为 25 | super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 26 | 27 | // 根据列设置图标 28 | if (column == 1) { 29 | setIcon(FILTER_ICON); 30 | } else { 31 | setIcon(null); 32 | setHorizontalAlignment(JLabel.LEADING); // 文本对齐方式恢复默认 33 | } 34 | 35 | // Since we're modifying the renderer itself, return 'this' 36 | return this; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/ui/FingerTabRender/LeftRenderer.java: -------------------------------------------------------------------------------- 1 | package ui.FingerTabRender; 2 | 3 | import javax.swing.*; 4 | import javax.swing.table.DefaultTableCellRenderer; 5 | 6 | public class LeftRenderer extends DefaultTableCellRenderer { 7 | public LeftRenderer() { 8 | setHorizontalAlignment(JLabel.LEFT); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/ui/MainTabRender/ColorInfoCellRenderer.java: -------------------------------------------------------------------------------- 1 | package ui.MainTabRender; 2 | 3 | import javax.swing.*; 4 | import javax.swing.table.DefaultTableCellRenderer; 5 | import java.awt.*; 6 | 7 | // 自定义渲染器类 8 | public class ColorInfoCellRenderer extends DefaultTableCellRenderer { 9 | 10 | private static final long serialVersionUID = 1L; 11 | 12 | public ColorInfoCellRenderer() { 13 | setHorizontalAlignment(CENTER); // 设置居中 14 | } 15 | 16 | @Override 17 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 18 | super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 19 | 20 | Integer infoNum = (Integer) value; 21 | 22 | if (infoNum != null) { 23 | if (infoNum > 0) { 24 | setBackground(Color.GREEN); // 状态为 true 时的背景颜色 25 | } else { 26 | setBackground(Color.RED); // 状态为 false 时的背景颜色 27 | } 28 | } else { 29 | setBackground(table.getBackground()); // 如果值为空或不是布尔值,则使用默认背景色 30 | } 31 | 32 | return this; 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/ui/MainTabRender/HasImportantCellRenderer.java: -------------------------------------------------------------------------------- 1 | package ui.MainTabRender; 2 | 3 | import utils.UiUtils; 4 | 5 | import javax.swing.*; 6 | import javax.swing.table.DefaultTableCellRenderer; 7 | import java.awt.*; 8 | 9 | public class HasImportantCellRenderer extends DefaultTableCellRenderer { 10 | 11 | // 预加载并缓存图标 12 | private final Icon importantIcon = UiUtils.getImageIcon("/icon/importantButtonIcon.png", 15, 15); 13 | //private final Icon notImportantIcon = UiUtils.getImageIcon("/icon/normalIcon.png", 15, 15); 14 | public HasImportantCellRenderer() { 15 | setHorizontalAlignment(CENTER); // 设置居中 16 | } 17 | 18 | @Override 19 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 20 | // 调用父类以保留默认行为 21 | super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 22 | 23 | 24 | // 根据单元格值设置相应图标 25 | if (value instanceof Boolean) { 26 | if ((Boolean) value){ 27 | setIcon(importantIcon); 28 | // 设置文本为空,因为我们只显示图标 29 | setText(""); 30 | }else{ 31 | setIcon(null); 32 | setText("NO"); 33 | } 34 | } else { 35 | setIcon(null); 36 | setText((String)value); // 如果值不是布尔类型,则不显示图标 37 | } 38 | 39 | return this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/ui/MainTabRender/RunStatusCellRenderer.java: -------------------------------------------------------------------------------- 1 | package ui.MainTabRender; 2 | 3 | import database.Constants; 4 | import utils.UiUtils; 5 | 6 | import javax.swing.*; 7 | import javax.swing.table.DefaultTableCellRenderer; 8 | import java.awt.*; 9 | 10 | public class RunStatusCellRenderer extends DefaultTableCellRenderer { 11 | // 预加载并缓存图标 12 | private final Icon pendingIcon = UiUtils.getImageIcon("/icon/convenientOperationIcon.png", 15, 15); 13 | private final Icon handlingIcon = UiUtils.getImageIcon("/icon/searchButton.png", 15, 15); 14 | private final Icon handledIcon = UiUtils.getImageIcon("/icon/findUrlFromJS.png", 15, 15); 15 | 16 | public RunStatusCellRenderer() { 17 | setHorizontalAlignment(CENTER); // 设置居中 18 | } 19 | 20 | @Override 21 | public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 22 | // 调用父类以保留默认行为 23 | super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 24 | 25 | // 根据单元格值设置相应图标 26 | if (value instanceof String) { 27 | String stringValue = (String) value; 28 | if (Constants.HANDLE_WAIT.equals(stringValue)|| Constants.ANALYSE_END.equals(stringValue)) { 29 | setIcon(pendingIcon); 30 | setText(""); // 设置文本为空,因为我们只显示图标 31 | } else if (Constants.HANDLE_ING.equals(stringValue)) { 32 | setIcon(handlingIcon); 33 | setText(""); // 设置文本为空,因为我们只显示图标 34 | } else if (Constants.HANDLE_END.equals(stringValue)) { 35 | setIcon(handledIcon); 36 | setText(""); // 设置文本为空,因为我们只显示图标 37 | } else { 38 | setIcon(null); 39 | setText(stringValue); // 显示字符串值 40 | } 41 | } else { 42 | // 其他类型的值保持不变 43 | setIcon(null); 44 | setText((String)value); 45 | } 46 | 47 | return this; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/ui/MainTabRender/TableHeaderWithTips.java: -------------------------------------------------------------------------------- 1 | package ui.MainTabRender; 2 | 3 | import javax.swing.table.JTableHeader; 4 | import javax.swing.table.TableColumnModel; 5 | import java.awt.event.MouseEvent; 6 | 7 | public class TableHeaderWithTips extends JTableHeader { 8 | 9 | private final String[] tooltips; 10 | 11 | public TableHeaderWithTips(TableColumnModel columnModel, String[] tooltips) { 12 | super(columnModel); // do everything a normal JTableHeader does 13 | this.tooltips = tooltips; // plus extra data 14 | } 15 | 16 | @Override 17 | public String getToolTipText(MouseEvent e) { 18 | int index = columnModel.getColumnIndexAtX(e.getPoint().x); 19 | int realIndex = columnModel.getColumn(index).getModelIndex(); 20 | 21 | // 检查索引是否在 tooltips 数组的有效范围内 22 | if (realIndex >= 0 && realIndex < tooltips.length) { 23 | return tooltips[realIndex]; 24 | } else { 25 | return null; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/ui/Tabs.java: -------------------------------------------------------------------------------- 1 | package ui; 2 | 3 | import burp.IBurpExtenderCallbacks; 4 | import burp.ITab; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | 9 | 10 | public class Tabs implements ITab { 11 | private final JTabbedPane tabs; 12 | private final String name; 13 | private final RuleConfigPanel ruleConfigPanel; 14 | private final BasicUrlInfoPanel msgInfoPanel; 15 | private final BasicHostInfoPanel hostInfoPanel; 16 | 17 | public Tabs(IBurpExtenderCallbacks callbacks, String name){ 18 | this.name = name; 19 | 20 | // 定义tab标签页 21 | this.tabs = new JTabbedPane(); 22 | 23 | this.hostInfoPanel = BasicHostInfoPanel.getInstance(); 24 | this.tabs.add("聚合面板", this.hostInfoPanel); 25 | 26 | this.msgInfoPanel = BasicUrlInfoPanel.getInstance(); 27 | this.tabs.add("请求详情", this.msgInfoPanel); 28 | 29 | this.ruleConfigPanel = RuleConfigPanel.getInstance(); 30 | this.tabs.add("规则配置", this.ruleConfigPanel); 31 | 32 | // 将整个tab加载到平台即可 33 | callbacks.customizeUiComponent(tabs); 34 | // 将自定义选项卡添加到Burp的UI 35 | callbacks.addSuiteTab(this); 36 | } 37 | 38 | 39 | @Override 40 | public String getTabCaption() { 41 | return this.name; 42 | } 43 | 44 | @Override 45 | public Component getUiComponent() { 46 | return this.tabs; 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/utilbox/ByteArrayUtils.java: -------------------------------------------------------------------------------- 1 | package utilbox; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | public class ByteArrayUtils { 6 | 7 | 8 | /** 9 | * byte[]数组截取 10 | * srcPoC 是原数组的起始位置,length是要截取的长度 11 | */ 12 | public static byte[] subByte(byte[] b, int srcPos, int length) { 13 | byte[] b1 = new byte[length]; 14 | System.arraycopy(b, srcPos, b1, 0, length); 15 | return b1; 16 | } 17 | 18 | 19 | public static String getSystemCharSet() { 20 | return Charset.defaultCharset().toString(); 21 | 22 | //System.out.println(System.getProperty("file.encoding")); 23 | } 24 | 25 | 26 | /** 27 | * 将10进制转换为16进制 28 | * 29 | * @param decimal 10进制 30 | * @return 16进制 31 | */ 32 | public static String decimalToHex(int decimal) { 33 | String hex = Integer.toHexString(decimal); 34 | return hex.toUpperCase(); 35 | } 36 | 37 | 38 | /** 39 | * 拼接多个byte[]数组的方法 40 | * 41 | * @param arrays 42 | * @return 43 | */ 44 | public static byte[] join(byte[]... arrays) { 45 | int len = 0; 46 | for (byte[] arr : arrays) { 47 | len += arr.length;//计算多个数组的长度总和 48 | } 49 | 50 | byte[] result = new byte[len]; 51 | int idx = 0; 52 | 53 | for (byte[] arr : arrays) { 54 | for (byte b : arr) { 55 | result[idx++] = b; 56 | } 57 | } 58 | 59 | return result; 60 | } 61 | 62 | 63 | /** 64 | * https://stackoverflow.com/questions/21341027/find-indexof-a-byte-array-within-another-byte-array 65 | * Search the data byte array for the first occurrence 66 | * of the byte array pattern. 67 | */ 68 | public static int BytesIndexOf(byte[] data, byte[] pattern) { 69 | int[] failure = computeFailure(pattern); 70 | 71 | int j = 0; 72 | 73 | for (int i = 0; i < data.length; i++) { 74 | while (j > 0 && pattern[j] != data[i]) { 75 | j = failure[j - 1]; 76 | } 77 | if (pattern[j] == data[i]) { 78 | j++; 79 | } 80 | if (j == pattern.length) { 81 | return i - pattern.length + 1; 82 | } 83 | } 84 | return -1; 85 | } 86 | 87 | /** 88 | * Computes the failure function using a boot-strapping process, 89 | * where the pattern is matched against itself. 90 | */ 91 | private static int[] computeFailure(byte[] pattern) { 92 | int[] failure = new int[pattern.length]; 93 | 94 | int j = 0; 95 | for (int i = 1; i < pattern.length; i++) { 96 | while (j > 0 && pattern[j] != pattern[i]) { 97 | j = failure[j - 1]; 98 | } 99 | if (pattern[j] == pattern[i]) { 100 | j++; 101 | } 102 | failure[i] = j; 103 | } 104 | 105 | return failure; 106 | } 107 | 108 | public static boolean equals(byte[] a, byte[] b) { 109 | if (a == null || b == null) { 110 | return false; 111 | } 112 | 113 | if (a.length != b.length) { 114 | return false; 115 | } 116 | 117 | for (int i = 0; i < a.length; i++) { 118 | if (a[i] != b[i]) { 119 | return false; 120 | } 121 | } 122 | return true; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/utilbox/CharsetUtils.java: -------------------------------------------------------------------------------- 1 | package utilbox; 2 | 3 | import org.apache.commons.io.input.BOMInputStream; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.IOException; 7 | import java.io.UnsupportedEncodingException; 8 | import java.nio.charset.Charset; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class CharsetUtils { 14 | 15 | public static String getSystemCharSet() { 16 | return Charset.defaultCharset().toString(); 17 | } 18 | 19 | public static boolean isValidCharset(String charsetName) { 20 | return getCharsetNameList().contains(charsetName); 21 | } 22 | 23 | public static List getCharsetNameList() { 24 | Map charsets = Charset.availableCharsets(); 25 | return new ArrayList(charsets.keySet()); 26 | } 27 | 28 | /** 29 | * 消除大小写差异 30 | * @param charsetName 31 | * @return 32 | */ 33 | public static String getCharsetName(String charsetName) { 34 | List charsetNameList = getCharsetNameList(); // 调用一次并保存结果 35 | for (String name : charsetNameList) { 36 | if (name.equalsIgnoreCase(charsetName)) { 37 | return name; 38 | } 39 | } 40 | return null; 41 | } 42 | 43 | /** 44 | * 进行响应包的编码转换。 45 | * @param content 46 | * @return 转换后的格式的byte[] 47 | */ 48 | public static byte[] covertCharSet(byte[] content,String originalCharset,String newCharset){ 49 | if (originalCharset == null) { 50 | originalCharset = detectCharset(content); 51 | } 52 | try { 53 | return new String(content,originalCharset).getBytes(newCharset); 54 | } catch (UnsupportedEncodingException e) { 55 | e.printStackTrace(); 56 | return content; 57 | } 58 | } 59 | 60 | 61 | public static byte[] covertCharSet(byte[] content,String newCharset) throws UnsupportedEncodingException { 62 | return covertCharSet(content,null,newCharset); 63 | } 64 | 65 | 66 | public static String detectCharset(byte[] bytes){ 67 | try { 68 | ByteArrayInputStream bis = new ByteArrayInputStream(bytes); 69 | BOMInputStream bomInputStream = BOMInputStream.builder().setInputStream(bis).get(); 70 | String encoding = bomInputStream.getBOMCharsetName(); 71 | bomInputStream.close(); 72 | return getCharsetName(encoding); 73 | } catch (IOException e) { 74 | e.printStackTrace(); 75 | return null; 76 | } 77 | } 78 | 79 | 80 | public static void main(String[] args) throws Exception { 81 | // Map charsets = Charset.availableCharsets(); 82 | // 83 | // // 打印所有字符编码集的规范名称 84 | // System.out.println("Available Charsets:"); 85 | // for (String name : charsets.keySet()) { 86 | // System.out.println(name); 87 | // } 88 | 89 | System.out.println(detectCharset("中国中文11111".getBytes("UTF-8"))); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/utils/AnalyseUriFilter.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import model.HttpUrlInfo; 4 | 5 | import java.net.MalformedURLException; 6 | import java.net.URL; 7 | import java.util.*; 8 | import java.util.regex.Pattern; 9 | 10 | import static utils.BurpPrintUtils.*; 11 | import static utils.CastUtils.isEmptyObj; 12 | import static utils.ElementUtils.isEqualsOneKey; 13 | 14 | public class AnalyseUriFilter { 15 | private static final Pattern CHINESE_PATTERN = Pattern.compile("[\u4E00-\u9FA5]"); 16 | 17 | /** 18 | * 过滤无用的提取路径 通过判断和指定的路径相等 19 | * @param matchList 20 | * @return 21 | */ 22 | public static List filterPathByEqualUselessPath(List matchList, List blackPathEquals) { 23 | List newList = new ArrayList<>(); 24 | for (String path : matchList){ 25 | if(!isEqualsOneKey(path, blackPathEquals, false)){ 26 | newList.add(path); 27 | } 28 | } 29 | return newList; 30 | } 31 | 32 | /** 33 | * 过滤无用的提取路径 通过判断是否包含无用关键字 34 | * @param matchList 35 | * @return 36 | */ 37 | public static List filterPathByContainUselessKey(List matchList, List blackPathKeys) { 38 | if (isEmptyObj(matchList)) return matchList; 39 | 40 | List newList = new ArrayList<>(); 41 | for (String path : matchList){ 42 | if(!ElementUtils.isContainOneKey(path, blackPathKeys, false)){ 43 | newList.add(path); 44 | } 45 | } 46 | return newList; 47 | } 48 | 49 | /** 50 | * 过滤无用的提取路径 通过判断是否包含中文路径 51 | * @param matchList 52 | * @return 53 | */ 54 | public static List filterPathByContainChinese(List matchList) { 55 | if (isEmptyObj(matchList)) return matchList; 56 | 57 | List newList = new ArrayList<>(); 58 | for (String s : matchList){ 59 | if(!CHINESE_PATTERN.matcher(s).find()){ 60 | newList.add(s); 61 | } 62 | } 63 | return newList; 64 | } 65 | 66 | /** 67 | * 过滤黑名单HOST域名 68 | * @param urls 69 | * @param blackHosts 70 | * @return 71 | */ 72 | public static List filterBlackHosts(List urls, List blackHosts) { 73 | if (isEmptyObj(blackHosts) || isEmptyObj(urls)) return urls; 74 | 75 | List list = new ArrayList<>(); 76 | for (String url : urls) { 77 | HttpUrlInfo urlInfo = new HttpUrlInfo(url); 78 | if (!ElementUtils.isContainOneKey(urlInfo.getRootUrlUsual(), blackHosts, false)) { 79 | list.add(url); 80 | } 81 | } 82 | return list; 83 | } 84 | 85 | /** 86 | * 过滤黑名单后缀名 图片后缀之类的不需要提取请求信息 87 | * @param uris 88 | * @param blackSuffixes 89 | * @return 90 | */ 91 | public static List filterBlackSuffixes(List uris, List blackSuffixes) { 92 | if (isEmptyObj(blackSuffixes)||isEmptyObj(uris)) return uris; 93 | 94 | List list = new ArrayList<>(); 95 | for (String urlStr : uris) { 96 | String suffix = parseUrlExt(urlStr); 97 | if (!isEqualsOneKey(suffix, blackSuffixes, false)) 98 | list.add(urlStr); 99 | } 100 | return list; 101 | } 102 | 103 | /** 104 | * 过滤黑名单路径 /jquery.js 之类的不需要提取信息 105 | * @param urls 106 | * @param blackPaths 107 | * @return 108 | */ 109 | public static List filterBlackPaths(List urls, List blackPaths) { 110 | if (isEmptyObj(urls)) return urls; 111 | 112 | List list = new ArrayList<>(); 113 | for (String urlStr : urls) { 114 | try { 115 | URL url = new URL(urlStr); 116 | String path = url.getPath(); 117 | if (!ElementUtils.isContainOneKey(path, blackPaths, false)) { 118 | list.add(urlStr); 119 | }else { 120 | stdout_println(LOG_DEBUG, String.format("[*] Black Paths Filter %s", urlStr)); 121 | } 122 | } catch (MalformedURLException e) { 123 | stderr_println(LOG_DEBUG, String.format("[!] new URL(%s) -> Error: %s", urlStr, e.getMessage())); 124 | } 125 | } 126 | return list; 127 | } 128 | 129 | /** 130 | * 过滤提取的值 在请求字符串内的项 131 | * @param baseUri 132 | * @param matchUriList 133 | * @return 134 | */ 135 | public static List filterUriBySelfContain(String baseUri, List matchUriList) { 136 | if (isEmptyObj(baseUri) || baseUri == "/" ) return matchUriList; 137 | if (isEmptyObj(matchUriList)) return matchUriList; 138 | 139 | List list = new ArrayList<>(); 140 | for (String uri : matchUriList){ 141 | if (!baseUri.contains(uri)) { 142 | // system_println(String.format("%s 不包含 %s", baseUri, uri)); 143 | list.add(uri);} 144 | } 145 | return list; 146 | } 147 | 148 | /** 149 | * 过滤提取出的URL列表 仅保留自身域名的 150 | * @param basicHost 151 | * @param matchUrlList 152 | * @return 153 | */ 154 | public static List filterUrlByMainHost(String basicHost, List matchUrlList){ 155 | if (isEmptyObj(basicHost) || isEmptyObj(matchUrlList)) return matchUrlList; 156 | 157 | List newUrlList = new ArrayList<>(); 158 | for (String matchUrl : matchUrlList){ 159 | //对比提取出来的URL和请求URL的域名部分是否相同,不相同的一般不是 160 | try { 161 | String newHost = (new URL(matchUrl)).getHost(); 162 | if (!newHost.contains(basicHost)) 163 | continue; 164 | } catch (Exception e) { 165 | stderr_println(LOG_DEBUG, String.format("[!] new URL(%s) -> Error: %s", matchUrl, e.getMessage())); 166 | continue; 167 | } 168 | newUrlList.add(matchUrl); 169 | } 170 | return newUrlList; 171 | } 172 | 173 | /** 174 | * 粗略获取一个URI的后缀 支持PATH 忽略 # 号 175 | * @param uri 176 | * @return 177 | */ 178 | private static String parseUrlExt(String uri) { 179 | String pureUrl = uri.substring(0, uri.contains("?") ? uri.indexOf("?") : uri.length()); 180 | return (pureUrl.lastIndexOf(".") > -1 ? pureUrl.substring(pureUrl.lastIndexOf(".") + 1) : "").toLowerCase(); 181 | } 182 | 183 | 184 | /** 185 | * 格式化所有URL 以HttpUrlInfo 内部为基准 统一带端口或不带默认端口 186 | * @param urls 187 | * @return 188 | */ 189 | public static List formatUrls(List urls) { 190 | if (isEmptyObj(urls)) return urls; 191 | 192 | List list = new ArrayList<>(); 193 | for (String urlStr : urls) { 194 | String url = new HttpUrlInfo(urlStr).getRawUrlUsual(); 195 | list.add(url); 196 | } 197 | return list; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/utils/BurpFileUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import burp.BurpExtender; 4 | import burp.IBurpExtenderCallbacks; 5 | import com.alibaba.fastjson2.JSON; 6 | import com.alibaba.fastjson2.TypeReference; 7 | 8 | import java.io.*; 9 | import java.nio.charset.Charset; 10 | import java.nio.charset.StandardCharsets; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | import static utils.BurpPrintUtils.*; 18 | import static utils.CastUtils.isNotEmptyObj; 19 | 20 | public class BurpFileUtils { 21 | /** 22 | * 检查指定路径的文件是否存在 23 | * @param filePath 文件的路径 24 | * @return 如果文件存在返回true,否则返回false 25 | */ 26 | public static boolean isFileExists(String filePath) { 27 | Path path = Paths.get(filePath); 28 | return Files.exists(path); 29 | } 30 | 31 | /** 32 | * 拼接目录和文件名 33 | * @param directory 目录路径 34 | * @param fileName 文件名 35 | * @return 拼接后的完整路径 36 | */ 37 | public static String concatPath(String directory, String fileName) { 38 | Path path = Paths.get(directory, fileName); 39 | return path.toString(); 40 | } 41 | 42 | /** 43 | * 读取文本文件内容并返回一个字符串 44 | * @param filePath 文本文件的路径 45 | * @return 文件内容字符串,如果发生错误则返回null 46 | */ 47 | public static String readFileToString(String filePath, Charset charsetName) { 48 | StringBuilder content = new StringBuilder(); 49 | try (BufferedReader reader = new BufferedReader( 50 | new InputStreamReader(new FileInputStream(filePath), charsetName))) { 51 | String line; 52 | while ((line = reader.readLine()) != null) { 53 | content.append(line).append("\n"); 54 | } 55 | } catch (IOException e) { 56 | stderr_println(e.getMessage()); 57 | e.printStackTrace(); 58 | return null; 59 | } 60 | return content.toString(); 61 | } 62 | 63 | public static String readFileToString(String filePath) { 64 | return readFileToString(filePath, StandardCharsets.UTF_8); 65 | } 66 | 67 | /** 68 | * 从jar包中读取资源文件内容到字符串 69 | * @param resourceName 资源的路径(例如:"com/example/myfile.txt") 70 | * @param charset 71 | * @return 文件内容字符串,如果发生错误则返回null 72 | */ 73 | public static String readResourceToString(String resourceName, Charset charset) { 74 | StringBuilder content = new StringBuilder(); 75 | try (InputStream inputStream = BurpFileUtils.class.getClassLoader().getResourceAsStream(resourceName); 76 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, charset))) { 77 | 78 | if (inputStream == null) { 79 | stderr_println("无法找到资源: " + resourceName); 80 | return null; 81 | } 82 | 83 | String line; 84 | while ((line = reader.readLine()) != null) { 85 | content.append(line).append("\n"); 86 | } 87 | } catch (IOException e) { 88 | stderr_println(e.getMessage()); 89 | e.printStackTrace(); 90 | return null; 91 | } 92 | return content.toString(); 93 | } 94 | 95 | /** 96 | * 获取-插件运行路径 97 | * 98 | * @return 99 | */ 100 | public static String getPluginPath(IBurpExtenderCallbacks callbacks) { 101 | String path = ""; 102 | Integer lastIndex = callbacks.getExtensionFilename().lastIndexOf(File.separator); 103 | path = callbacks.getExtensionFilename().substring(0, lastIndex) + File.separator; 104 | return path; 105 | } 106 | 107 | /** 108 | * 从 jar文件所在路径或jar文件内部读取配置文件 109 | * @param callbacks 110 | * @param configName 111 | * @param charset 112 | * @return 113 | */ 114 | public static String ReadPluginConfFile(IBurpExtenderCallbacks callbacks, String configName, Charset charset) { 115 | String configJson; 116 | 117 | String extensionPath = getPluginPath(callbacks); 118 | String configPath = concatPath(extensionPath, configName); 119 | 120 | if(isFileExists(configPath)){ 121 | stdout_println(LOG_INFO, String.format("[+] Custom Config File Path: %s", configPath)); 122 | configJson = readFileToString(configPath, charset); 123 | }else { 124 | configName = String.format("conf/%s", configName); 125 | stdout_println(LOG_INFO, String.format("[+] User Jar File Inner Config: %s -> %s", extensionPath, configName)); 126 | configJson = readResourceToString(configName, charset); 127 | } 128 | return configJson; 129 | } 130 | 131 | /** 132 | * 获取插件同级目录下的指定文件 133 | * @param callbacks 134 | * @param fileName 135 | * @return 136 | */ 137 | public static Path getPluginDirFilePath(IBurpExtenderCallbacks callbacks, String fileName) { 138 | Path path = Paths.get(getPluginPath(callbacks), fileName); 139 | return path.toAbsolutePath(); 140 | } 141 | 142 | /** 143 | * 获取插件同级目录下的指定文件 144 | * @param fileName 145 | * @return 146 | */ 147 | public static String getPluginDirFilePath(String fileName) { 148 | Path path = Paths.get(getPluginPath(BurpExtender.getCallbacks()), fileName); 149 | return path.toString(); 150 | } 151 | 152 | /** 153 | * 简单的保存字符串到文件,不处理报错信息 154 | * @param file 155 | * @param content 156 | * @throws IOException 157 | */ 158 | public static void writeToFile(File file, String content) throws IOException { 159 | // 使用UTF-8编码写入文件 160 | OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8); 161 | writer.write(content); 162 | writer.close(); 163 | } 164 | 165 | public static void writeToPluginPathFile(String configName, String content) throws IOException { 166 | // 使用UTF-8编码写入文件 167 | String pluginDirFilePath = getPluginDirFilePath(configName); 168 | File fileToSave = new File(pluginDirFilePath); 169 | writeToFile(fileToSave, content); 170 | } 171 | 172 | public static void writeToPluginPathFileNotEx(String configName, String content) { 173 | try { writeToPluginPathFile(configName, content); } catch (IOException e) { e.printStackTrace(); } 174 | } 175 | 176 | /** 177 | * 从本地缓存文件读取过滤器 178 | */ 179 | public static Map> LoadJsonFromFile(String configPath) { 180 | configPath = getPluginDirFilePath(configPath); 181 | if (isFileExists(configPath)){ 182 | String configJson = readFileToString(configPath); 183 | if (isNotEmptyObj(configJson)){ 184 | TypeReference>> typeRef = new TypeReference>>() {}; 185 | return JSON.parseObject(configJson, typeRef); 186 | } 187 | } 188 | return new HashMap<>(); 189 | } 190 | 191 | //检查插件路径是否存在文件 192 | public static boolean fileIsExistOnPluginDir(IBurpExtenderCallbacks callbacks, String fileName) { 193 | return isFileExists(concatPath(getPluginPath(callbacks), fileName)); 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/utils/BurpPrintUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import burp.BurpExtender; 4 | 5 | import java.io.PrintWriter; 6 | 7 | import static burp.BurpExtender.SHOW_MSG_LEVEL; 8 | 9 | public class BurpPrintUtils { 10 | private static PrintWriter stdout; 11 | private static PrintWriter stderr; 12 | 13 | // 定义日志级别 14 | public static int LOG_ERROR = 0; //重要 15 | public static int LOG_INFO = 1; //一般 16 | public static int LOG_DEBUG = 2; //调试 17 | 18 | public BurpPrintUtils(){ 19 | this.stdout = BurpExtender.getStdout(); 20 | this.stderr = BurpExtender.getStderr(); 21 | } 22 | 23 | public BurpPrintUtils(PrintWriter stdout, PrintWriter stderr){ 24 | this.stdout = stdout; 25 | this.stderr = stderr; 26 | } 27 | 28 | /** 29 | * 根据输出日志级别输出错误消息 SHOW_MSG_LEVEL 越大,输出内容越少 30 | * @param msgLevel 31 | * @param msg 32 | */ 33 | public static void stdout_println(Integer msgLevel, Object msg){ 34 | if(msgLevel <= SHOW_MSG_LEVEL){ 35 | stdout.println(msg); 36 | System.out.println(msg); 37 | } 38 | } 39 | 40 | 41 | /** 42 | * 根据输出日志级别输出错误消息 SHOW_MSG_LEVEL 越大,输出内容越少 43 | * @param msgLevel 44 | * @param msg 45 | */ 46 | public static void stderr_println(Integer msgLevel, Object msg){ 47 | if(msgLevel <= SHOW_MSG_LEVEL){ 48 | stderr.println(msg); 49 | System.out.println(msg); 50 | } 51 | } 52 | 53 | 54 | /** 55 | * 根据输出日志级别输出错误消息 SHOW_MSG_LEVEL 越大,输出内容越少 56 | * @param msgLevel 57 | * @param msg 58 | */ 59 | public static void system_println(Integer msgLevel, Object msg){ 60 | if(msgLevel <= SHOW_MSG_LEVEL) 61 | System.out.println(msg); 62 | } 63 | 64 | public static void stdout_println(Object msg){ 65 | stdout_println(LOG_INFO,msg); 66 | } 67 | 68 | public static void stderr_println(Object msg){ 69 | stderr_println(LOG_ERROR,msg); 70 | } 71 | 72 | public static void system_println(Object msg){ 73 | system_println(LOG_INFO,msg); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/utils/BurpSitemapUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import burp.BurpExtender; 4 | import burp.IHttpRequestResponse; 5 | import burp.IProxyScanner; 6 | import database.PathTreeTable; 7 | import database.RecordUrlTable; 8 | import model.HttpMsgInfo; 9 | 10 | import java.util.Set; 11 | 12 | import static burp.BurpExtender.*; 13 | import static utils.BurpPrintUtils.*; 14 | import static utils.ElementUtils.isContainOneKey; 15 | import static utils.ElementUtils.isEqualsOneKey; 16 | 17 | 18 | 19 | public class BurpSitemapUtils { 20 | /** 21 | * 添加 SiteMap 中 所有有关的URL到 RecordPath 或 RecordUrl 表 22 | */ 23 | public static void addSiteMapUrlsToRecord(boolean isRecordUrl){ 24 | // 1、获取所有有关的 urlPrefix 25 | Set urlPrefixes = PathTreeTable.fetchAllRecordPathRootUrls(); 26 | for (String urlPrefix:urlPrefixes){ 27 | //插入一个标记,表明这个主机已经插入过滤 28 | String insertedFlag = isRecordUrl ? urlPrefix + "/RecordUrl" : urlPrefix + "/RecordPath"; 29 | boolean flagIsNotInsert = RecordUrlTable.insertOrUpdateAccessedUrl(insertedFlag, 999) > 0; 30 | 31 | //忽略导入禁止导入的主机的信息 32 | if (isContainOneKey(urlPrefix, CONF_BLACK_AUTO_RECORD_PATH, false) || isContainOneKey(urlPrefix, CONF_BLACK_ROOT_URL, false )){ 33 | continue; 34 | } 35 | 36 | //获取URL相关的前缀 37 | if (flagIsNotInsert){ 38 | IHttpRequestResponse[] httpRequestResponses = BurpExtender.getCallbacks().getSiteMap(urlPrefix); 39 | if (httpRequestResponses.length>0){ 40 | for (IHttpRequestResponse requestResponse: httpRequestResponses){ 41 | HttpMsgInfo msgInfo = new HttpMsgInfo(requestResponse); 42 | String reqBaseUrl = msgInfo.getUrlInfo().getUrlToFileUsual(); 43 | 44 | try { 45 | if (isRecordUrl){ 46 | //插入 reqBaseUrl 排除黑名单后缀、 忽略参数 47 | if(!isEqualsOneKey(msgInfo.getUrlInfo().getSuffix(), CONF_BLACK_URI_EXT_EQUAL, false)){ 48 | RecordUrlTable.insertOrUpdateAccessedUrl(msgInfo); 49 | } 50 | } else { 51 | //插入路径 仅保留200 403等有效目录 52 | if(isEqualsOneKey(msgInfo.getRespInfo().getStatusCode(), CONF_WHITE_RECORD_PATH_STATUS, false) 53 | && !msgInfo.getUrlInfo().getPathToDir().equals("/") 54 | && !isContainOneKey(msgInfo.getRespInfo().getRespTitle(), CONF_BLACK_RECORD_PATH_TITLE, false) 55 | ){ 56 | // RecordPathTable.insertOrUpdateRecordPath(reqBaseUrl, msgInfo.getRespInfo().getStatusCode()); 57 | // stdout_println(LOG_DEBUG, String.format("Record reqBaseUrl: %s", reqBaseUrl)); 58 | IProxyScanner.enhanceRecordPathFilter(msgInfo, IProxyScanner.dynamicPathFilterIsOpen); 59 | } 60 | } 61 | } catch (Exception e){ 62 | stderr_println(String.format("Record SiteMap Urls (isRecordUrl:%s) req Base Url:%s -> Error: %s", isRecordUrl, reqBaseUrl, e.getMessage())); 63 | } 64 | 65 | } 66 | } 67 | 68 | } 69 | 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/utils/ElementUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.util.*; 4 | import java.util.regex.Pattern; 5 | 6 | import static utils.CastUtils.isEmptyObj; 7 | 8 | public class ElementUtils { 9 | private static List formatElementList(List elements) { 10 | List list = new ArrayList<>(); 11 | for (String element : elements) { 12 | list.add(format(element)); 13 | } 14 | return list; 15 | } 16 | 17 | private static boolean isContainOneKey(String stringFormat, List elementsFormat) { 18 | // for (String element : elementsFormat) { 19 | // if (stringFormat.contains(element)){ 20 | // return true; 21 | // } 22 | // } 23 | // return false; 24 | 25 | // 使用 Stream API anyMatch 检查是否包含任意一個元素 26 | return elementsFormat.stream().anyMatch(stringFormat::contains); 27 | } 28 | 29 | private static boolean isEqualsOneKey(String stringFormat, List elementsFormat) { 30 | // //进行判断 31 | // for (String element : elementsFormat) { 32 | // if (stringFormat.equals(element)) { 33 | // return true; 34 | // } 35 | // } 36 | // return false; 37 | 38 | // 使用 Stream API anyMatch 检查是否 equals 任意一個元素 39 | return elementsFormat.stream().anyMatch(stringFormat::equals); 40 | } 41 | 42 | private static boolean isContainAllKey(String stringFormat, List elementsFormat) { 43 | // for (String element : elementsFormat) { 44 | // if (!stringFormat.contains(element)){ 45 | // return false; 46 | // } 47 | // } 48 | // return true; 49 | 50 | // 使用 Stream API allMatch 检查 stringFormat 是否包含 elementsFormat 中的所有元素 51 | return elementsFormat.stream().allMatch(stringFormat::contains); 52 | } 53 | 54 | /** 55 | * 小写和去两端字符 56 | * @param string 57 | * @return 58 | */ 59 | private static String format(String string){ 60 | return string.toLowerCase(); 61 | } 62 | 63 | /** 64 | * 判断字符串 是否 等于 元素列表中的任意元素 忽略大小写 65 | * 66 | * @param string 单个字符串。 67 | * @param elementsString 允许的字符串,用'|'分隔。 68 | * @param defaultBool 当 elementsString 为空时应该返回的响应码 69 | * @return 如果 string 在 elementsString 范围内则返回 true,否则返回false。 70 | */ 71 | public static boolean isEqualsOneKey(String string, String elementsString, boolean defaultBool) { 72 | //当元素为空时,返回默认值 73 | if (isEmptyObj(string) || isEmptyObj(elementsString)) return defaultBool; 74 | 75 | //预先格式化处理 76 | String stringFormat = format(string); 77 | String[] elementsFormat = format(elementsString).split("\\|"); 78 | 79 | return isEqualsOneKey(stringFormat, Arrays.asList(elementsFormat)); 80 | } 81 | 82 | /** 83 | * 判断字符串 是否 等于 元素列表中的任意元素 忽略大小写 84 | * 85 | * @param stringA 单个字符串。 86 | * @param elements 允许的字符串列表 87 | * @param defaultBool 当 elements 为空时应该返回的响应码 88 | * @return 如果 string 在 elements 范围内则返回 true,否则返回false。 89 | */ 90 | public static boolean isEqualsOneKey(Object stringA, List elements, boolean defaultBool) { 91 | String string = String.valueOf(stringA); 92 | //当元素为空时,返回默认值 93 | if (isEmptyObj(string) || isEmptyObj(elements)) return defaultBool; 94 | 95 | String stringFormat = format(string); 96 | List elementsFormat = formatElementList(elements); 97 | 98 | return isEqualsOneKey(stringFormat, elementsFormat); 99 | } 100 | 101 | /** 102 | * 判断字符串 是否 包含 列表中的任意元素 103 | * 104 | * @param string 单个字符串。 105 | * @param elementsString 允许的字符串,用'|'分隔。 106 | * @param defaultBool 当 elementsString 为空时应该返回的响应码 107 | * @return 如果 elementStrings 的任意子元素 在 string 内 则返回true,否则返回false。 108 | */ 109 | public static boolean isContainOneKey(String string, String elementsString, boolean defaultBool) { 110 | //当元素为空时,返回默认值 111 | if (isEmptyObj(string) || isEmptyObj(elementsString)) return defaultBool; 112 | 113 | //预先格式化处理 114 | String stringFormat = format(string); 115 | String[] elementsFormat = format(elementsString).split("\\|"); 116 | 117 | return isContainOneKey(stringFormat, Arrays.asList(elementsFormat)); 118 | } 119 | 120 | /** 121 | * 判断字符串 是否 包含 列表中的任意元素 122 | * 123 | * @param string 单个字符串。 124 | * @param elements 允许的字符串,用'|'分隔。 125 | * @param defaultBool 当 elementsString 为空时应该返回的响应码 126 | * @return 如果 elementStrings 的任意子元素 在 string 内 则返回true,否则返回false。 127 | */ 128 | public static boolean isContainOneKey(String string, List elements, boolean defaultBool) { 129 | //当元素为空时,返回默认值 130 | if (isEmptyObj(string) || isEmptyObj(elements)) return defaultBool; 131 | 132 | String stringFormat = format(string); 133 | List elementsFormat = formatElementList(elements); 134 | 135 | return isContainOneKey(stringFormat, elementsFormat); 136 | } 137 | 138 | /** 139 | * 判断字符串 是否 包含 列表中的任意元素 140 | * 141 | * @param string 单个字符串。 142 | * @param elements 允许的字符串,用'|'分隔。 143 | * @param defaultBool 当 elementsString 为空时应该返回的响应码 144 | * @return 如果 elementStrings 的任意子元素 在 string 内 则返回true,否则返回false。 145 | */ 146 | public static boolean isContainAllKey(String string, List elements, boolean defaultBool) { 147 | //当元素为空时,返回默认值 148 | if (isEmptyObj(string) || isEmptyObj(elements)) return defaultBool; 149 | 150 | String stringFormat = format(string); 151 | List elementsFormat = formatElementList(elements); 152 | 153 | return isContainAllKey(stringFormat, elementsFormat); 154 | } 155 | 156 | /** 157 | * 判断字符串 是否 包含 列表中的任意元素 158 | * 159 | * @param string 单个字符串。 160 | * @param elementsString 允许的字符串,用'|'分隔。 161 | * @param defaultBool 当 elementsString 为空时应该返回的响应码 162 | * @return 如果 elementStrings 的任意子元素 在 string 内 则返回true,否则返回false。 163 | */ 164 | public static boolean isContainAllKey(String string, String elementsString, boolean defaultBool, String split) { 165 | //当元素为空时,返回默认值 166 | if (isEmptyObj(string) || isEmptyObj(elementsString)) return defaultBool; 167 | 168 | String stringFormat = format(string); 169 | String[] elementsFormat = format(elementsString).split(Pattern.quote(split)); 170 | 171 | return isContainAllKey(stringFormat, Arrays.asList(elementsFormat)); 172 | } 173 | 174 | 175 | public static Set findContainKeys(String string, String elementsString, String split) { 176 | HashSet findElements = new HashSet(); 177 | //当元素为空时,返回默认值 178 | if (isEmptyObj(string) || isEmptyObj(elementsString)) return findElements; 179 | 180 | //预先格式化处理 181 | String stringFormat = format(string); 182 | String[] elementsFormat = format(elementsString).split(Pattern.quote(split)); 183 | 184 | for (String element : elementsFormat) { 185 | element = element.trim(); 186 | if(!element.isEmpty() && stringFormat.contains(element)){ 187 | findElements.add(element); 188 | } 189 | } 190 | return findElements; 191 | } 192 | 193 | public static boolean isContainOneKeys(String string, String elementsString, String split) { 194 | HashSet findElements = new HashSet(); 195 | //当元素为空时,返回默认值 196 | if (isEmptyObj(string) || isEmptyObj(elementsString)) return false; 197 | //预先格式化处理 198 | String[] elementsFormat = format(elementsString).split(Pattern.quote(split)); 199 | for (String element : elementsFormat) { 200 | element = element.trim(); 201 | if(!element.isEmpty() && format(string).contains(element)){ 202 | return true; 203 | } 204 | } 205 | return false; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/main/java/utils/RegularUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.regex.Pattern; 6 | import java.util.regex.PatternSyntaxException; 7 | 8 | import static utils.BurpPrintUtils.*; 9 | 10 | public class RegularUtils { 11 | /** 12 | * 编译提取URI的正则表达式列表 13 | */ 14 | public static List compileUriMatchRegular(List regularList) { 15 | List patternList = new ArrayList<>(); 16 | 17 | if (CastUtils.isNotEmptyObj(regularList)){ 18 | for (String regular : regularList) { 19 | try { 20 | Pattern pattern = Pattern.compile(regular); 21 | patternList.add(pattern); 22 | stdout_println(LOG_DEBUG, String.format("[+] compile regular success: [%s]", regular)); 23 | } catch (PatternSyntaxException e) { 24 | // 处理正则表达式语法错误 25 | stderr_println(LOG_ERROR, String.format("[!] Invalid regular expression: [%s] -> [%s]", regular, e.getMessage())); 26 | } catch (Exception e) { 27 | // 处理其他可能的异常 28 | stderr_println(LOG_ERROR, String.format("Unexpected error occurred while compiling regex: [%s] -> [%s]", regular, e.getMessage()) ); 29 | } 30 | } 31 | } 32 | 33 | if (patternList.isEmpty()){ 34 | Pattern FIND_PATH_PATTERN = Pattern.compile("(?:\"|')(((?:[a-zA-Z]{1,10}://|//)[^\"'/]{1,}\\.[a-zA-Z]{2,}[^\"']{0,})|((?:/|\\.\\./|\\./)[^\"'><,;|*()(%%$^/\\\\\\[\\]][^\"'><,;|()]{1,})|([a-zA-Z0-9_\\-/]{1,}/[a-zA-Z0-9_\\-/]{1,}\\.(?:[a-zA-Z]{1,4}|action)(?:[\\?|/|;][^\"|']{0,}|))|([a-zA-Z0-9_\\-]{1,}\\.(?:php|asp|aspx|jsp|json|action|html|js|txt|xml)(?:\\?[^\"|']{0,}|)))(?:\"|')"); 35 | patternList.add(FIND_PATH_PATTERN); 36 | } 37 | 38 | return patternList; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/utils/RespHashUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.Base64; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | import java.util.zip.CRC32; 8 | 9 | public class RespHashUtils { 10 | 11 | public static String getFaviconHash(byte[] body) { 12 | String base64Favicon = Base64.getEncoder().encodeToString(body); 13 | // 格式化base64字符串 14 | String formattedBase64Favicon = formatBase64(base64Favicon); 15 | // 计算格式化后base64字符串的murmurHash3值 16 | return String.valueOf(murmurHash3_x86_32(formattedBase64Favicon.getBytes(), 0, formattedBase64Favicon.length(), 0)); 17 | } 18 | 19 | private static String formatBase64(String base64) { 20 | Pattern pattern = Pattern.compile(".{76}"); 21 | Matcher matcher = pattern.matcher(base64); 22 | StringBuilder formattedBase64 = new StringBuilder(); 23 | 24 | while (matcher.find()) { 25 | formattedBase64.append(matcher.group()).append("\n"); 26 | } 27 | 28 | int remainder = base64.length() % 76; 29 | if (remainder > 0) { 30 | formattedBase64.append(base64.substring(base64.length() - remainder)).append("\n"); 31 | } 32 | 33 | return formattedBase64.toString(); 34 | } 35 | 36 | public static int murmurHash3_x86_32(final byte[] data, int offset, int len, final int seed) { 37 | final int c1 = 0xcc9e2d51; 38 | final int c2 = 0x1b873593; 39 | 40 | int h1 = seed; 41 | final int roundedEnd = offset + (len & 0xfffffffc); // round down to 4 byte block 42 | 43 | for (int i = offset; i < roundedEnd; i += 4) { 44 | // little endian load order 45 | int k1 = (data[i] & 0xff) | ((data[i + 1] & 0xff) << 8) | ((data[i + 2] & 0xff) << 16) | (data[i + 3] << 24); 46 | k1 *= c1; 47 | k1 = Integer.rotateLeft(k1, 15); 48 | k1 *= c2; 49 | 50 | h1 ^= k1; 51 | h1 = Integer.rotateLeft(h1, 13); 52 | h1 = h1 * 5 + 0xe6546b64; 53 | } 54 | 55 | // handle the last few bytes of the input array 56 | int k1 = 0; 57 | switch (len & 0x03) { 58 | case 3: 59 | k1 = (data[roundedEnd + 2] & 0xff) << 16; 60 | // fall through 61 | case 2: 62 | k1 |= (data[roundedEnd + 1] & 0xff) << 8; 63 | // fall through 64 | case 1: 65 | k1 |= (data[roundedEnd] & 0xff); 66 | k1 *= c1; 67 | k1 = Integer.rotateLeft(k1, 15); 68 | k1 *= c2; 69 | h1 ^= k1; 70 | } 71 | 72 | // finalization 73 | h1 ^= len; 74 | 75 | // fmix 76 | h1 ^= h1 >>> 16; 77 | h1 *= 0x85ebca6b; 78 | h1 ^= h1 >>> 13; 79 | h1 *= 0xc2b2ae35; 80 | h1 ^= h1 >>> 16; 81 | 82 | return h1; 83 | } 84 | 85 | /** 86 | * 字符串转 CRC32 87 | */ 88 | public static String calcCRC32(String string) { 89 | // 使用 UTF-8 编码将字符串转换为字节数组 90 | byte[] inputBytes = string.getBytes(StandardCharsets.UTF_8); 91 | return calcCRC32(inputBytes); 92 | } 93 | 94 | /** 95 | * 字符Byte[]转 CRC32 96 | */ 97 | public static String calcCRC32(byte[] inputBytes) { 98 | // 初始化CRC32对象 99 | CRC32 crc32 = new CRC32(); 100 | // 更新CRC值 101 | crc32.update(inputBytes, 0, inputBytes.length); 102 | // 将计算后的CRC32值转换为十六进制字符串并返回 103 | return Long.toHexString(crc32.getValue()).toLowerCase(); 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/main/java/utils/RespTitleUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import java.util.Arrays; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | public class RespTitleUtils { 9 | /** 10 | * 从HTML文档中提取标签的内容。 11 | */ 12 | public static String parseTextTitle(byte[] bodyBytes) { 13 | String title = null; 14 | if (bodyBytes.length>0){ 15 | byte[] bytesToParse = bodyBytes; 16 | // 如果bodyBytes长度大于10000,仅取前10000字节 17 | if (bodyBytes.length > 10000) { 18 | bytesToParse = Arrays.copyOfRange(bodyBytes, 0, 10000); 19 | } 20 | // 将字节数组转换为字符串 21 | String htmlContent = new String(bytesToParse, StandardCharsets.UTF_8); 22 | // 定义一个正则表达式来匹配<title>标签内的内容 23 | Pattern pattern = Pattern.compile("<title>(.*?)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); 24 | // 创建一个Matcher对象 25 | Matcher matcher = pattern.matcher(htmlContent); 26 | // 检查是否找到了匹配项 27 | if (matcher.find()) { 28 | title = matcher.group(1).trim(); 29 | } 30 | } 31 | 32 | return title; 33 | } 34 | 35 | /** 36 | * 获取 HTML 中 标签的内容,支持分块匹配。 37 | * 38 | * @param responseBody 响应体内容 39 | * @param chunkSize 每次处理的块大小(字符数) 40 | * @return 匹配到的 <title> 标签内容,如果没有匹配到则返回空字符串 41 | */ 42 | public static String getTitle(String responseBody, int chunkSize) { 43 | if (responseBody == null || responseBody.isEmpty()) { 44 | return ""; 45 | } 46 | 47 | // 定义正则表达式,匹配 <title> 标签内容 48 | Pattern pattern = Pattern.compile("<title>(.*?)", Pattern.CASE_INSENSITIVE); 49 | 50 | // 分块处理 responseBody 51 | int length = responseBody.length(); 52 | for (int start = 0; start < length; start += chunkSize) { 53 | // 计算当前块的结束位置 54 | int end = Math.min(start + chunkSize, length); 55 | String chunk = responseBody.substring(start, end); 56 | 57 | // 使用正则表达式匹配当前块 58 | Matcher matcher = pattern.matcher(chunk); 59 | if (matcher.find()) { 60 | return matcher.group(1); // 返回第一个匹配到的 内容 61 | } 62 | } 63 | 64 | // 如果没有匹配到,返回空字符串 65 | return ""; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/utils/RespWebpackJsParser.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | 4 | import java.io.BufferedReader; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.util.HashSet; 8 | import java.util.LinkedHashSet; 9 | import java.util.Set; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | public class RespWebpackJsParser { 14 | 15 | 16 | //aaaaaaa.aaaa.js //0.690fe1e4ceaf45313632.js 17 | //private static final String WEBPACK_JS_PATTERN_CHECK = "\"([\\w-]+)\":\"(\\w+)\"\\}\\[\\w\\]\\+\".js\""; //字符型 18 | private static final String WEBPACK_JS_PATTERN_CHECK = "[\"]?([\\d\\w-]+)[\"]?:\"(\\w+)\"\\}\\[\\w\\]\\+\".js\""; //字符+数字 19 | private static final String WEBPACK_JS_PATTERN_EXTRACT_JS = "([^{^+}]+\\}[\\[\\]\\w\\+\\\"]{5}.js\")"; 20 | //private static final String WEBPACK_JS_PATTERN_EXTRACT_KV = "\"([\\w-]+)\":\"(\\w+)\""; //字符型 21 | private static final String WEBPACK_JS_PATTERN_EXTRACT_KV = "[\"]?([\\d\\w-]+)[\"]?:\"(\\w+)\""; //字符+数字 22 | private static final Pattern patternCheck = Pattern.compile(WEBPACK_JS_PATTERN_CHECK); 23 | private static final Pattern patternExtractJS = Pattern.compile(WEBPACK_JS_PATTERN_EXTRACT_JS); 24 | private static final Pattern patternExtractKV = Pattern.compile(WEBPACK_JS_PATTERN_EXTRACT_KV); 25 | 26 | public static Set<String> parseWebpackSimple(String text) { 27 | Set<String> matches = new LinkedHashSet<>(); 28 | Matcher matcherCheck = patternCheck.matcher(text); 29 | if (matcherCheck.find()) { 30 | Matcher matcherJs = patternExtractJS.matcher(text); 31 | while (matcherJs.find()) { 32 | String extract = matcherJs.group(1); 33 | Matcher matcherKV = patternExtractKV.matcher(extract); 34 | while (matcherKV.find()) { 35 | String key = matcherKV.group(1); 36 | String value = matcherKV.group(2); 37 | if (!value.isEmpty()) { 38 | matches.add(key + "." + value + ".js"); 39 | } 40 | } 41 | } 42 | } 43 | return matches; 44 | } 45 | 46 | /** 47 | * 最新实现的分块正则匹配常规版本 48 | */ 49 | public static Set<String> parseWebpackSimpleChunk(String text, int chunkSize) { 50 | Set<String> matches = new HashSet<>(); 51 | int textLength = text.length(); 52 | for (int start = 0; start < textLength; start += chunkSize) { 53 | int end = Math.min(start + chunkSize, textLength); 54 | String jsChunk = text.substring(start, end); 55 | matches.addAll(parseWebpackSimple(jsChunk)); 56 | } 57 | return matches; 58 | } 59 | 60 | public static void main(String[] args) { 61 | String jsFile = "C:\\Users\\WINDOWS\\Desktop\\testdata\\数字型.js"; 62 | String data = BurpFileUtils.readFileToString(jsFile); 63 | System.out.println(data.length()); 64 | Set<String> results = parseWebpackSimple(data); 65 | for (String result : results) { 66 | System.out.println(result); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/resources/icon/addButtonIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/addButtonIcon.png -------------------------------------------------------------------------------- /src/main/resources/icon/convenientOperationIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/convenientOperationIcon.png -------------------------------------------------------------------------------- /src/main/resources/icon/copyIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/copyIcon.png -------------------------------------------------------------------------------- /src/main/resources/icon/customizeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/customizeIcon.png -------------------------------------------------------------------------------- /src/main/resources/icon/deleteButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/deleteButton.png -------------------------------------------------------------------------------- /src/main/resources/icon/editButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/editButton.png -------------------------------------------------------------------------------- /src/main/resources/icon/exportItem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/exportItem.png -------------------------------------------------------------------------------- /src/main/resources/icon/filterIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/filterIcon.png -------------------------------------------------------------------------------- /src/main/resources/icon/findUrlFromJS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/findUrlFromJS.png -------------------------------------------------------------------------------- /src/main/resources/icon/importItem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/importItem.png -------------------------------------------------------------------------------- /src/main/resources/icon/importantButtonIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/importantButtonIcon.png -------------------------------------------------------------------------------- /src/main/resources/icon/insertNewPathIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/insertNewPathIcon.png -------------------------------------------------------------------------------- /src/main/resources/icon/moreButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/moreButton.png -------------------------------------------------------------------------------- /src/main/resources/icon/noFindUrlFromJS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/noFindUrlFromJS.png -------------------------------------------------------------------------------- /src/main/resources/icon/openButtonIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/openButtonIcon.png -------------------------------------------------------------------------------- /src/main/resources/icon/refreshButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/refreshButton.png -------------------------------------------------------------------------------- /src/main/resources/icon/refreshButton2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/refreshButton2.png -------------------------------------------------------------------------------- /src/main/resources/icon/resetItem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/resetItem.png -------------------------------------------------------------------------------- /src/main/resources/icon/runningButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/runningButton.png -------------------------------------------------------------------------------- /src/main/resources/icon/saveItem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/saveItem.png -------------------------------------------------------------------------------- /src/main/resources/icon/searchButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/searchButton.png -------------------------------------------------------------------------------- /src/main/resources/icon/shutdownButtonIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/shutdownButtonIcon.png -------------------------------------------------------------------------------- /src/main/resources/icon/urlIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winezer0/APIFinderPlus/a5336834bbb87b05427a5c6e5fb460d380d910ca/src/main/resources/icon/urlIcon.png --------------------------------------------------------------------------------