├── settings.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── BappManifest.bmf ├── API_Sword └── src │ └── main │ └── java │ └── burp │ ├── SuperHttpReqAndRes.java │ ├── Extension.java │ ├── MyTableModel.java │ ├── MyHttpHandler.java │ ├── SwordMain.form │ └── SwordMain.java ├── BappDescription.html ├── gradlew.bat ├── old-README.md ├── README.md ├── [EN]-README.md └── gradlew /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "NSFOCUS-API_Sword" -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PortSwigger/api-sword/main/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: eaed4c35d8354d19917365f17c4df661 2 | ExtensionType: 1 3 | Name: API Sword 4 | RepoName: api-sword 5 | ScreenVersion: 1.0.6 6 | SerialVersion: 2 7 | MinPlatformVersion: 19 8 | ProOnly: True 9 | Author: Sugobet 10 | ShortDescription: Automatically collects API endpoints from any HTTP response, extracts specified API and JS files, performs GET/POST requests, and displays results. 11 | EntryPoint: NSFOCUS-API_Sword.jar 12 | BuildCommand: ./gradlew build 13 | SupportedProducts: Pro 14 | -------------------------------------------------------------------------------- /API_Sword/src/main/java/burp/SuperHttpReqAndRes.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import burp.api.montoya.http.handler.HttpResponseReceived; 4 | import burp.api.montoya.http.message.HttpRequestResponse; 5 | 6 | public class SuperHttpReqAndRes { 7 | private final HttpRequestResponse req_res; 8 | private final HttpResponseReceived res; 9 | 10 | SuperHttpReqAndRes(HttpRequestResponse req_res, HttpResponseReceived res) 11 | { 12 | this.req_res = req_res; 13 | this.res = res; 14 | } 15 | 16 | public HttpRequestResponse getReq_res() 17 | { 18 | return req_res; 19 | } 20 | 21 | public HttpResponseReceived getRes() 22 | { 23 | return res; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BappDescription.html: -------------------------------------------------------------------------------- 1 |

2 | API Sword automatically extracts and recursively discovers API endpoints from HTTP responses. It eliminates the 3 | manual work of searching through JavaScript files to find interfaces, paths, and parameters, streamlining API 4 | discovery during security testing. 5 |

6 | 7 |

Features

8 | 17 | 18 |

Usage

19 |
    20 |
  1. Navigate to the API Sword tab and configure your scope in the Scope section (URL, domain, or IP address)
  2. 21 |
  3. Review settings in the Settings tab, including whether to use original headers and request rate limits
  4. 22 |
  5. Browse the target application normally with your browser proxied through Burp Suite
  6. 23 |
  7. API Sword will automatically capture traffic and begin discovering endpoints
  8. 24 |
  9. Review discovered APIs in the API Sword Sitemap tab, where results are displayed with their source files
  10. 25 |
  11. Send discovered requests to other Burp tools using Ctrl+R for further testing 26 |
  12. 27 |
-------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /old-README.md: -------------------------------------------------------------------------------- 1 | [\[EN-ReadMe\]](https://github.com/Sugobet/API_Sword/blob/main/%5BEN%5D-README.md) 2 | # 微信公众号:APT250 3 | 4 | # 秉着开源至上、交流学习的原则,API剑将于两周后(9月7日后)开源并同时上架Burp官方插件商店BApp Store,方便大家日后更新和使用 5 | 6 | # [burp新经典插件] API剑 - 全自动深度 收集各种响应中的API接口 7 | 8 | 开始之前,很抱歉我推迟了联合锻剑计划的时间,工作后的时间比较有限,我也在寻找一个时机,尽量早日推出。 9 | 10 | API剑这个插件灵感来源于实习中,API剑处于测试中,近期不打算开源,后面功能完善后会考虑开源交流学习 11 | 12 | [API剑]项目地址:https://github.com/Sugobet/API_Sword 13 | 14 | jar包在release 15 | ## 前言 16 | 17 | 这个插件结合了我近期的工作内容和此前我的4万美刀赏金微软账户漏洞api的部分经验 18 | 19 | “API剑”这个burp插件耗时一周加文章发布日前一晚的一个通宵,连夜做出了测试版,我从java完全零基础 + 极度讨厌java,到翻看burp插件官方实例 + 不断的debug,凭借我已久的通用编程思想和编码经验,还是把它做了出来,目前一千行代码左右,比较简陋,望见谅,望包涵。 20 | 21 | ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/388ed286aff845ce8863640b37d4636e.png) 22 | 23 | 与众多JS Finder、URLFinder等比较火热的相关js、api挖掘工具类似,它们是非常优秀的工具,**而API剑凭借burp的特点而获得能力和优势。** 24 | 25 | 插件主页面截图: 26 | 27 | ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c23a7d7924924224810dc777c0e4e1bc.png) 28 | 29 | ## API剑的主要功能 30 | 31 | API剑 全自动防环路,从各种响应里提取范围内的api和js文件,然后深度提取api,主动请求api、js等有价值文件 32 | 33 | api结果所见即所得,右边的窗口显示api的来源js,可以立刻从js里面获得api的参数信息,然后burp再ctrl + r一键过去测 34 | 35 | 它没有想象的那么复杂,API剑做的事情更多是为我们**减少了大量重复耗时且无趣的js、api、api参数搜寻工作。** 36 | 37 | 1. API剑捕获经过burp的范围内的流量,并从**http响应中提取绝大多数link** 38 | 2. API剑将对上一步提取的任意链接、路径进行清洗,并由**API剑判断后对API、JS等主动发起GET、POST请求** 39 | 3. API剑对上一步主动请求的响应进一步的处理,继续从响应中提取信息,并重复上一步的动作,**API剑具有防环路功能,无需担心死循环请求问题** 40 | 4. API剑对所有符合条件的API请求、响应,以及该API接口来源的js文件响应,全部推送到API剑的burp GUI中 41 | 5. API剑自动将所有相关请求添加至burp的target sitemap中,**您可在target的sitemap的分析等功能中尽情享受API剑带来的果实** 42 | 43 | 用户只需要启用API剑并设置一个“合理的范围”,接着在浏览器中继续点击web系统的各种功能,让所有流量经过burp,最终交给API剑做分析处理,API剑将会向您返回您想要的恶魔果实。 44 | 45 | **考虑到opsec等操作安全风险,目前API剑不会主动fuzz参数,如果后续有需求再额外添加作为可选功能。** 46 | 47 | ## API剑的设置 48 | 49 | 在Scope选项卡中,我们可以设置范围 50 | 51 | ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d99fe948bccf4783b1a04ea10fed64be.png) 52 | 53 | 这个范围特别重要,建议谨慎考虑,否则容易扫到外太空去。 54 | 55 | 设置好范围后我们再看Setting选项卡 56 | 57 | image 58 | 59 | 1. 允许主动对API请求 60 | 61 | 这个选项默认开,不建议关,否则API剑无法更深层提取数据 62 | 63 | 2. 是否使用原headers 64 | 65 | 默认开,如果想专门测试未授权api接口,可以把这个选项关掉,关掉后不会携带任何cookie或session等信息 66 | 67 | 3. 立即停止发送所有请求 68 | 69 | 默认关,避免遇到突发情况想暂停,用来刹车的,建议搭配第一个选项一起使用 70 | 71 | 4. 清除当前SiteMap所有数据 72 | 73 | 这个按钮用于清除API剑的Site Map中的所有站点数据 74 | 75 | ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d7e8eca0e2994a65b9bae2abb8554e69.png) 76 | 77 | 其它设置待开发和完善,如有任何想法建议和问题,可通过github上提issue反馈 78 | 79 | ## 致谢 80 | 81 | 感谢 我的`绿盟导师` 82 | 83 | 感谢 ` mil1ln` 84 | 85 | 感谢以上所有人为API剑提供的一切支持! 86 | 87 | ## TODO 88 | 89 | 1. 收集一件梅花K的polo衫 ⬛️ 90 | 2. 添加可选的base url路径fuzz ✅ 91 | 3. 添加自定义响应码过滤 ✅ 92 | 4. 添加API剑主动请求时,添加自定义base路径的选项 ✅ 93 | 5. 优化了匹配策略,解锁API剑性能80% ✅ 94 | 6. API剑主动请求优化,避免访问危险api ✅ 95 | 7. 解决burp默认header不携带CT字段的问题 ✅ 96 | 8. 优化响应table的tags宽度 ✅ 97 | 9. 修复sitemap的ui闪烁问题 ✅ 98 | 10. 添加自定义请求头可选功能 ✅ 99 | 11. 添加响应列表的tags自动排序 ✅ 100 | 12. 优化匹配策略 ✅ 101 | 13. 优化代码块 ⬛️ 102 | 14. 注册burp卸载处理 ✅ 103 | 15. 全代码添加中英双语可阅读代码注释 ⬛️ 104 | 16. gui添加中英双语切换功能 ⬛️ 105 | 17. 优化ui造成burp卡顿、渲染问题 ✅ 106 | 18. 添加保存范围和配置的功能 ✅ 107 | 19. 添加主动http请求速率功能 ⬛️ 108 | -------------------------------------------------------------------------------- /API_Sword/src/main/java/burp/Extension.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | 4 | import burp.api.montoya.BurpExtension; 5 | import burp.api.montoya.MontoyaApi; 6 | import burp.api.montoya.extension.ExtensionUnloadingHandler; 7 | import burp.api.montoya.ui.contextmenu.ContextMenuItemsProvider; 8 | import burp.api.montoya.ui.contextmenu.ContextMenuEvent; 9 | import burp.api.montoya.ui.contextmenu.MessageEditorHttpRequestResponse; 10 | 11 | import javax.swing.*; 12 | import java.awt.*; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.concurrent.*; 17 | 18 | 19 | // import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY; 20 | 21 | // import burp.ui; 22 | 23 | 24 | public class Extension implements BurpExtension { 25 | private MontoyaApi api; 26 | private SwordMain sM; 27 | private MyTableModel tableModel; 28 | private ThreadPoolExecutor executor; 29 | 30 | @Override 31 | public void initialize(MontoyaApi montoyaApi) { 32 | this.api = montoyaApi; 33 | 34 | montoyaApi.extension().setName("NSFOCUS-API_Sword"); 35 | montoyaApi.logging().logToOutput("[Main]: NSFOCUS API_Sword v1.0.6 loaded!"); 36 | montoyaApi.logging().logToOutput("[Main]: author:NSFOCUS/APT250 冯家鸣(M1n9K1n9)"); 37 | montoyaApi.logging().logToOutput("[Main]: github: https://github.com/Sugobet/API_Sword"); 38 | 39 | // 注册上下文菜单 40 | montoyaApi.userInterface().registerContextMenuItemsProvider(new ContextMenuItemsProvider() 41 | { 42 | @Override 43 | public List provideMenuItems(ContextMenuEvent event) 44 | { 45 | // if (event.isFromTool(ToolType.TARGET)) { 46 | 47 | JMenuItem menuItem = new JMenuItem("API Scan"); 48 | menuItem.addActionListener(l -> { 49 | montoyaApi.logging().logToOutput("[Context Menu]: API Scan"); 50 | 51 | apiScan(event.messageEditorRequestResponse()); 52 | }); 53 | return List.of(menuItem); 54 | } 55 | }); 56 | 57 | // 注册选项卡 58 | tableModel = new MyTableModel(); 59 | sM = new SwordMain(); 60 | api.userInterface().registerSuiteTab("API Sword", sM.InitRootComponent(api, tableModel)); 61 | 62 | // 注册http监听 63 | executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); 64 | api.http().registerHttpHandler(new MyHttpHandler(tableModel, sM.getScopeList(), api, sM, executor)); 65 | 66 | // 注册卸载插件处理 67 | api.extension().registerUnloadingHandler(new ExtensionUnloadingHandler() { 68 | @Override 69 | public void extensionUnloaded() { 70 | sM.isStop.setSelected(true); 71 | sM.isReqAPI.setSelected(false); 72 | tableModel.removeAll(); 73 | } 74 | }); 75 | } 76 | 77 | void apiScan(Optional hrrList) 78 | { 79 | JTextArea scopeList = sM.getScopeList(); 80 | String scp = scopeList.getText(); 81 | 82 | // 自动添加scope 83 | var hrr = hrrList.get().requestResponse(); 84 | 85 | String hurl = hrr.request().httpService().host(); 86 | 87 | if (!scp.contains(hurl)) 88 | { 89 | scopeList.setText(scp + "\n" + hurl); 90 | } 91 | 92 | try 93 | { 94 | executor.submit(() -> api.http().sendRequest(hrr.request())); 95 | } catch (Exception e){ 96 | api.logging().logToError(e); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [\[EN-ReadMe\]](https://github.com/Sugobet/API_Sword/blob/main/%5BEN%5D-README.md) <------ please read this 2 | # 微信公众号:APT250 3 | 4 | ## 秉着开源至上、交流学习的原则,API剑将于两周后(9月7日后)开源并同时上架Burp官方插件商店BApp Store,方便大家日后更新和使用,目前正在又官方进行代码审核中,相信不久后便能上线BApp Store 5 | 6 | # [burp新经典插件] API剑 - 全自动深度 收集各种响应中的API接口 7 | 8 | jar包在release,之后上架burp官方插件商店后也可从商店下载 9 | 10 | ## 前言 11 | 12 | 这个插件结合了我近期的工作内容和此前我的4万美刀赏金微软账户漏洞api的部分经验, 13 | 14 | API剑开发者利用API剑已多次在项目上获得成果及通用0day,拥有此工具后,我再也没有手动从任何js里痛苦的查找任何接口、路径及参数。 15 | 16 | ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/388ed286aff845ce8863640b37d4636e.png) 17 | 18 | 与众多JS Finder、URLFinder等比较火热的相关js、api挖掘工具类似,它们是非常优秀的工具,**而API剑凭借burp的特点而获得能力和优势。** 19 | 20 | 插件主页面截图: 21 | 22 | image 23 | 24 | ## API剑的主要功能 25 | 26 | API剑 全自动防环路,从各种响应里提取指定范围内的api和js文件,然后递归深度提取api,主动请求api、js等有价值文件 27 | 28 | api结果所见即所得,右边的窗口显示api的来源js,可以立刻从js里面获得api的参数信息,然后burp再ctrl + r一键过去测 29 | 30 | 它没有想象的那么复杂,API剑做的事情更多是为我们**减少了大量重复耗时且无趣的js、api、api参数搜寻工作。** 31 | 32 | 1. API剑捕获经过burp的范围内的流量,并从**http响应中提取绝大多数link** 33 | 2. API剑将对上一步提取的任意链接、路径进行清洗,并由**API剑判断后对API、JS等主动发起GET、POST请求** 34 | 3. API剑对上一步主动请求的响应进一步的处理,继续从响应中提取信息,并重复上一步的动作,**API剑具有防环路功能,无需担心死循环请求问题** 35 | 4. API剑对所有符合条件的API请求、响应,以及该API接口来源的js文件响应,全部推送到API剑的burp GUI中 36 | 5. API剑自动将所有相关请求添加至burp的target sitemap中,**您可在target的sitemap的分析等功能中尽情享受API剑带来的果实** 37 | 38 | 用户只需要启用API剑并设置一个“合理的范围”,接着在浏览器中继续点击web系统的各种功能,让所有流量经过burp,最终交给API剑做分析处理,API剑将会向您返回您想要的恶魔果实。 39 | 40 | **考虑到opsec等操作安全风险,目前API剑不会主动fuzz参数,如果后续有需求再额外添加作为可选功能。** 41 | 42 | ## 如何使用? 43 | 44 | `注意:插件需要运行在2024.7版本以上的burpsuite;(对于低于2024.7的版本,则需要手动在插件的settings页面将“是否使用原headers”功能关闭)` 45 | 46 | API剑的使用非常的简单, 47 | 48 | 1. 将插件安装至burp 2024以后的版本,确保插件无任何报错 49 | 2. 为插件设置Scope 50 | 3. 打开浏览器确保浏览器的流量会通过burp 51 | 4. 进入目标网站,点击和测试任何在网站中看到的一切 52 | 5. 过一段时间后,从API剑的Sitemap检查果实 53 | 54 | ## API剑的设置 55 | 56 | 在Scope选项卡中,我们可以设置范围,范围可以是url、域名、ip 57 | 58 | image 59 | 60 | 这个范围特别重要,建议谨慎考虑,否则容易扫到外太空去。 61 | 62 | 设置好范围后我们再看Setting选项卡 63 | 64 | image 65 | 66 | 1. 允许主动对API请求 67 | 68 | 这个选项默认开,不建议关,否则API剑无法更深层提取数据 69 | 70 | 2. 是否使用原headers 71 | 72 | 默认开,如果想专门测试未授权api接口,可以把这个选项关掉,关掉后不会携带任何cookie或session等信息 73 | 74 | 3. 立即停止发送所有请求 75 | 76 | 默认关,避免遇到突发情况想暂停,用来刹车的,建议搭配第一个选项一起使用 77 | 78 | 4. 清除当前SiteMap所有数据 79 | 80 | 这个按钮用于清除API剑的Site Map中的所有站点数据 81 | 82 | ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d7e8eca0e2994a65b9bae2abb8554e69.png) 83 | 84 | 5. 启用主动http请求速率 85 | 86 | 限制每个请求的间隔时间 87 | 88 | 6. 是否在主动请求时额外添加自定义路径请求 89 | 90 | 启用该选项后,API剑会在拼接前为主URL添加指定的自定义路径后再进行拼接 91 | 92 | 7. 过滤掉非200的自定义响应码 93 | 94 | 8. 允许API剑主动从响应中寻找baseURL并主动对baseURL进行路径拼接 95 | 96 | 9. 添加自定义header字段:(自动覆盖已有的header字段) 97 | 98 | 10. 启用绕过危险接口访问(接口包含字符串则跳过) 99 | 100 | 11. 保存范围及所有设置 101 | 102 | 12. 是否在API接口后、参数前额外添加自定义路径 103 | 104 | 13. 线程数量控制 105 | 106 | 其它设置待开发和完善,如有任何想法建议和问题,可通过github上提issue反馈 107 | 108 | ## 致谢 109 | 110 | 感谢 `Microsoft` 111 | 112 | 感谢 我的`绿盟导师` 113 | 114 | 感谢 ` mil1ln` 115 | 116 | 感谢 `探姬` 117 | 118 | 感谢 所有在测试阶段为API剑提供宝贵意见和反馈的所有人 119 | 120 | 感谢以上所有人为API剑提供的一切支持! 121 | 122 | ## TODO 123 | 124 | 1. 收集一件梅花K的polo衫 ⬛️ 125 | 2. 添加可选的base url路径fuzz ✅ 126 | 3. 添加自定义响应码过滤 ✅ 127 | 4. 添加API剑主动请求时,添加自定义base路径的选项 ✅ 128 | 5. 优化了匹配策略,解锁API剑性能80% ✅ 129 | 6. API剑主动请求优化,避免访问危险api ✅ 130 | 7. 解决burp默认header不携带CT字段的问题 ✅ 131 | 8. 优化响应table的tags宽度 ✅ 132 | 9. 修复sitemap的ui闪烁问题 ✅ 133 | 10. 添加自定义请求头可选功能 ✅ 134 | 11. 添加响应列表的tags自动排序 ✅ 135 | 12. 优化匹配策略 ✅ 136 | 13. 优化代码块 ⬛️ 137 | 14. 注册burp卸载处理 ✅ 138 | 15. 全代码添加中英双语可阅读代码注释 ⬛️ 139 | 16. gui添加中英双语切换功能 ✅ 140 | 17. 优化ui造成burp卡顿、渲染问题 ✅ 141 | 18. 添加保存范围和配置的功能 ✅ 142 | 19. 添加主动http请求速率功能 ✅ 143 | 20. 添加多线程功能 ✅ 144 | 21. 添加接口后、参数前的自定义路径功能 ✅ 145 | 22. 紧急修复因多线程导致防环逻辑失效问题 ✅ 146 | 23. 修复自动排序存在显示出错问题 ✅ 147 | 24. 优化API列表的UI,插入数据时,方向键不再会被打断施法 ✅ 148 | 25. 彻底修复因多线程导致防环逻辑依然失效的bug ✅ 149 | 26. 添加过滤器 ✅ 150 | 27. 添加展开节点、收起所有节点 ✅ 151 | 28. 清空site map功能优化 ✅ 152 | 29. 优化cdn|跨站点中的js拼接逻辑,使其通过referer作为baseUrl ✅ 153 | 30. 添加手动扫描功能,手动扫描会自动添加host到范围列表:burp中对某个请求体进行鼠标右键 -> 扩展 -> API Sword -> API Scan ✅ 154 | -------------------------------------------------------------------------------- /API_Sword/src/main/java/burp/MyTableModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. PortSwigger Ltd. All rights reserved. 3 | * 4 | * This code may be used to extend the functionality of Burp Suite Community Edition 5 | * and Burp Suite Professional, provided that this usage does not violate the 6 | * license terms for those products. 7 | */ 8 | 9 | package burp; 10 | 11 | import burp.api.montoya.http.handler.HttpResponseReceived; 12 | import burp.api.montoya.http.message.HttpRequestResponse; 13 | 14 | import javax.swing.table.AbstractTableModel; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public class MyTableModel extends AbstractTableModel 19 | { 20 | private final List resLog; 21 | private final List _resLog; 22 | private final List orgLog; 23 | 24 | public MyTableModel() 25 | { 26 | this.resLog = new ArrayList<>(); 27 | this._resLog = new ArrayList<>(); 28 | this.orgLog = new ArrayList<>(); 29 | } 30 | 31 | @Override 32 | public synchronized int getRowCount() 33 | { 34 | return resLog.size(); 35 | } 36 | 37 | @Override 38 | public int getColumnCount() 39 | { 40 | return 5; 41 | } 42 | 43 | @Override 44 | public String getColumnName(int column) 45 | { 46 | return switch (column) 47 | { 48 | case 0 -> "Status Code"; 49 | case 1 -> "URL"; 50 | case 2 -> "Query"; 51 | case 3 -> "Length"; 52 | case 4 -> "Comment"; 53 | default -> ""; 54 | }; 55 | } 56 | 57 | @Override 58 | public synchronized Object getValueAt(int rowIndex, int columnIndex) 59 | { 60 | HttpRequestResponse responseReceived = resLog.get(rowIndex); 61 | 62 | return switch (columnIndex) 63 | { 64 | case 0 -> responseReceived.response().statusCode(); 65 | case 1 -> responseReceived.request().url(); 66 | case 2 -> responseReceived.request().query(); 67 | case 3 -> responseReceived.response().body().length(); 68 | case 4 -> responseReceived.annotations().notes(); 69 | default -> ""; 70 | }; 71 | } 72 | 73 | private synchronized void add(HttpRequestResponse responseReceived) 74 | { 75 | // int index = resLog.size(); 76 | resLog.add(responseReceived); 77 | // fireTableRowsInserted(index, index); 78 | } 79 | 80 | public synchronized void add1(SuperHttpReqAndRes responseReceived) 81 | { 82 | // int index = _resLog.size(); 83 | _resLog.add(responseReceived); 84 | // fireTableRowsInserted(index, index); 85 | } 86 | 87 | public synchronized HttpRequestResponse getRes(int rowIndex) 88 | { 89 | return resLog.get(rowIndex); 90 | } 91 | 92 | public synchronized void orgAdd(HttpResponseReceived responseReceived) { 93 | // int index = orgLog.size(); 94 | orgLog.add(responseReceived); 95 | // fireTableRowsInserted(index, index); 96 | } 97 | 98 | public synchronized HttpResponseReceived getOrgRes(int rowIndex) 99 | { 100 | return orgLog.get(rowIndex); 101 | } 102 | 103 | public synchronized void pushDisplayData(List data) 104 | { 105 | this.resLog.clear(); 106 | this.orgLog.clear(); 107 | 108 | for (SuperHttpReqAndRes hrr : data) 109 | { 110 | add(hrr.getReq_res()); 111 | orgAdd(hrr.getRes()); 112 | } 113 | 114 | fireTableDataChanged(); 115 | } 116 | 117 | public synchronized void pushDisplayData1(SuperHttpReqAndRes data) 118 | { 119 | int index = resLog.size(); 120 | 121 | add(data.getReq_res()); 122 | orgAdd(data.getRes()); 123 | 124 | fireTableRowsInserted(index, index); 125 | } 126 | 127 | public synchronized List getResList() 128 | { 129 | return this._resLog; 130 | } 131 | 132 | public synchronized List getResLogList() 133 | { 134 | return this.resLog; 135 | } 136 | 137 | public void removeAll() 138 | { 139 | this.resLog.clear(); 140 | this.orgLog.clear(); 141 | this._resLog.clear(); 142 | fireTableDataChanged(); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /[EN]-README.md: -------------------------------------------------------------------------------- 1 | # WeChat Official Account: APT250 2 | 3 | ## Adhering to the principles of open source and communication and learning, API Sword will be open sourced in two weeks (after September 7th) and will also be available on the Burp official plugin store (BApp Store) for easy updates and use. The code is currently under official review and I believe it will be available on the BApp Store soon. 4 | 5 | # [Burp New Classic Plugin] API Sword - Fully automatic deep analysis of API interfaces in various responses 6 | 7 | The JAR package is being released and will be available for download from the Burp official plugin store after it is available. 8 | 9 | ## Preface 10 | 11 | This plugin combines my recent work and some of my previous experience with a $40,000 bounty for Microsoft account vulnerability APIs. 12 | 13 | API Sword developers have used it to achieve numerous project successes and discover common 0-days. With this tool, I no longer have to manually search for any interface, path, or parameter in any JavaScript code. 14 | 15 | image 16 | 17 | Similar to many popular JavaScript and API mining tools like JS Finder and URLFinder, they are excellent tools. **API Sword, however, leverages Burp's capabilities and advantages.** 18 | 19 | Screenshot of the plugin's main page: 20 | 21 | image 22 | 23 | ## API Sword's Main Features 24 | 25 | API Sword automatically prevents loops, extracting API and JS files within a specified range from various responses. It then recursively extracts APIs deep within the API, proactively requesting valuable API, JS, and other files. 26 | 27 | API results are WYSIWYG. The window on the right displays the source JS file for the API. You can immediately retrieve API parameter information from the JS file and then use Burp to test it with a single click of Ctrl+R. 28 | 29 | It's not as complex as you might think. **API Sword primarily reduces the repetitive, time-consuming, and tedious task of searching for JS files, APIs, and API parameters.** 30 | 31 | 1. API Sword captures traffic within the Burp scope and extracts the majority of links from the HTTP responses. 32 | 2. API Sword cleans any links and paths extracted in the previous step and, after verification, initiates GET and POST requests to APIs, JavaScript, and other applications. 33 | 3. API Sword further processes the response to the previous active request, extracting information from the response and repeating the previous step. **API Sword has anti-loop functionality, eliminating the need to worry about infinite request loops** 34 | 4. API Sword pushes all matching API requests and responses, as well as the JavaScript file responses from the API interface, to the API Sword's Burp GUI. 35 | 5. API Sword automatically adds all relevant requests to Burp's target sitemap. **You can fully benefit from API Sword's benefits through features like analysis in the target sitemap.** 36 | 37 | Users simply enable API Sword and set a "reasonable scope." Then, continue clicking various web system functions in their browser. All traffic will flow through Burp and ultimately be analyzed and processed by API Sword, which will then return the desired Devil Fruit. 38 | 39 | **Due to operational security risks such as OPSEC, API Sword currently does not actively fuzz parameters. This feature will be added as an optional feature if needed.** 40 | 41 | ## How to use it? 42 | 43 | `Note: The plugin needs to run on burpsuite version 2024.7 or later; (for versions lower than 2024.7, you need to manually turn off the "Use original headers" function on the plugin settings page)` 44 | 45 | Using API Sword is very simple. 46 | 47 | 1. Install the plugin in Burp version 2024 or later and ensure the plugin is running correctly. 48 | 2. Set the scope for the plugin. 49 | 3. Open a browser and ensure that browser traffic is flowing through Burp. 50 | 4. Visit the target website and click and test everything you see on the website. 51 | 5. After a while, check the results in the API Sword sitemap. 52 | 53 | ## API Sword Settings 54 | 55 | In the Scope tab, we can set the scope. The scope can be a URL, domain name, or IP address. 56 | 57 | image 58 | 59 | This scope is extremely important and should be carefully considered, otherwise it could easily lead to scanning into outer space. 60 | 61 | After setting the scope, let's look at the Settings tab. 62 | 63 | image 64 | 65 | 1. Allow active API requests 66 | 67 | This option is on by default and is not recommended, as it will prevent API Sword from extracting deeper data. 68 | 69 | 2. Use original headers 70 | 71 | This is on by default. If you want to specifically test unauthorized API endpoints, you can turn this option off. After turning it off, no cookies or session information will be carried. 72 | 73 | 3. Stop all requests immediately 74 | 75 | This is off by default to prevent unexpected pauses. It is recommended to use this option in conjunction with the first option. 76 | 77 | 4. Clear all data in the current SiteMap 78 | 79 | This button clears all data in the API Sword site. All site data in the Map 80 | 81 | ![Insert image description here](https://i-blog.csdnimg.cn/direct/d7e8eca0e2994a65b9bae2abb8554e69.png) 82 | 83 | 5. Enable active HTTP request rate 84 | 85 | Limit the time between each request 86 | 87 | 6. Whether to add a custom path request to active requests 88 | 89 | If this option is enabled, API Sword will add the specified custom path to the main URL before concatenation. 90 | 91 | 7. Filter out custom response codes other than 200 92 | 93 | 8. Allow API Sword to actively find the baseURL from the response and concatenate the path to the baseURL 94 | 95 | 9. Add custom header fields: (automatically overwrite existing header fields) 96 | 97 | 10. Enable bypassing dangerous interface access (skip interfaces containing strings) 98 | 99 | 11. Save scope and all settings 100 | 101 | 12. Add a custom path after the API interface and before the parameters 102 | 103 | 13. Control the number of threads 104 | 105 | Other settings are under development and improvement. If you have any suggestions or questions, please submit an issue on GitHub. 106 | 107 | ## Acknowledgements 108 | 109 | Thanks `Microsoft` 110 | 111 | Thanks `My NSFOCUS mentor` 112 | 113 | Thanks `mil1ln` 114 | 115 | Thanks `everyone who provided valuable feedback and suggestions during the beta phase of API Sword.` 116 | 117 | Thanks everyone for all your support! 118 | 119 | ## TODO 120 | 121 | 1. Collect a NSFOCUS M-KING polo shirt ⬛️ 122 | 2. Add optional base URL path fuzzing ✅ 123 | 3. Add custom response code filtering ✅ 124 | 4. Add a custom base path option when adding API Sword active requests ✅ 125 | 5. Optimized matching strategy, unlocking 80% of API Sword performance ✅ 126 | 6. Optimized API Sword active requests to prevent access to dangerous APIs ✅ 127 | 7. Fixed the issue where Burp default headers do not include the CT field ✅ 128 | 8. Optimized the width of tags in the response table ✅ 129 | 9. Fixed flickering UI issue in sitemap ✅ 130 | 10. Add optional custom request headers ✅ 131 | 11. Add automatic tag sorting in the response list ✅ 132 | 12. Optimize matching strategy ✅ 133 | 13. Optimize code blocks ⬛️ 134 | 14. Register Burp uninstall handler ✅ 135 | 15. Add readable code comments in both Chinese and English throughout the code ⬛️ 136 | 16. Add Chinese and English language switching to the GUI ✅ 137 | 17. Optimized the UI to address Burp lag and rendering issues ✅ 138 | 18. Added the ability to save scopes and configurations ✅ 139 | 19. Added the ability to control the active HTTP request rate ✅ 140 | 20. Added multi-threading functionality ✅ 141 | 21. Added custom path functionality after the interface and before the parameters ✅ 142 | 22. Urgently fixed the issue of anti-dead loop logic failure caused by multithreading ✅ 143 | 23. Fixed a display error issue with automatic sorting ✅ 144 | 24. Optimized the API list UI so that arrow keys will no longer interrupt spell casting when inserting data ✅ 145 | 25. Completely fix the bug that caused the anti-loop logic to still fail due to multithreading ✅ 146 | 26. Added a filter ✅ 147 | 27. Added expand nodes and collapse all nodes ✅ 148 | 28. Optimized the clear site map function ✅ 149 | 29. Optimized the JavaScript concatenation logic in CDN/Cross-Site Scripting to use the referrer as the base URL. ✅ 150 | 30. Added manual scanning functionality. Manual scanning will automatically add hosts to the scope list: Right-click on a request body in Burp Suite -> Extension -> API Sword -> API Scan. ✅ 151 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /API_Sword/src/main/java/burp/MyHttpHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023. PortSwigger Ltd. All rights reserved. 3 | * 4 | * This code may be used to extend the functionality of Burp Suite Community Edition 5 | * and Burp Suite Professional, provided that this usage does not violate the 6 | * license terms for those products. 7 | */ 8 | 9 | package burp; 10 | 11 | import burp.api.montoya.MontoyaApi; 12 | import burp.api.montoya.http.handler.*; 13 | import burp.api.montoya.http.message.HttpHeader; 14 | import burp.api.montoya.http.message.HttpRequestResponse; 15 | import burp.api.montoya.http.message.requests.HttpRequest; 16 | 17 | import javax.swing.*; 18 | import javax.swing.tree.DefaultMutableTreeNode; 19 | import javax.swing.tree.DefaultTreeModel; 20 | import java.awt.event.ActionEvent; 21 | import java.awt.event.ActionListener; 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.Collections; 25 | import java.util.List; 26 | import java.util.concurrent.*; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | import java.util.regex.Matcher; 29 | import java.util.regex.Pattern; 30 | 31 | 32 | public class MyHttpHandler implements HttpHandler 33 | { 34 | private final MyTableModel tableModel; 35 | private final JTextArea scopeList; 36 | private final MontoyaApi api; 37 | private final List ur_list; 38 | private final AtomicInteger _sign; 39 | private final Pattern pattern = Pattern.compile("(?:\"|'|`)(((?:[a-zA-Z]{1,10}://|//)[^\"'`/]{1,}\\.[a-zA-Z]{2,}[^\"'`]{0,})|((?:/|\\.\\./|\\./)[^\"'`><,;|*()%^/\\\\\\[\\]][^\"'`><,;|()]{1,})|([a-zA-Z0-9_\\-/]{1,}/[a-zA-Z0-9_\\-/]{1,}\\.(?:[a-zA-Z]{1,4}|action)(?:[\\?|#][^\"|'|`]{0,}|))|([a-zA-Z0-9_\\-/]{1,}/[a-zA-Z0-9_\\-/]{3,}(?:[\\?|#][^\"|'|`]{0,}|))|([a-zA-Z0-9_\\-]{1,}\\.(?:\\w)(?:[\\?|#][^\"|'|`]{0,}|)))(?:\"|'|`)"); 40 | 41 | private final SwordMain sM; 42 | private final DefaultMutableTreeNode TreeRoot; 43 | 44 | private final ThreadPoolExecutor executorService; 45 | 46 | public MyHttpHandler(MyTableModel tableModel, JTextArea scopeList, MontoyaApi api, SwordMain sM, ThreadPoolExecutor executorService) 47 | { 48 | 49 | this.tableModel = tableModel; 50 | this.scopeList = scopeList; 51 | this.api = api; 52 | this.ur_list = Collections.synchronizedList(new ArrayList<>()); 53 | this._sign = new AtomicInteger(0); 54 | 55 | this.sM = sM; 56 | this.TreeRoot = sM.getTreeRoot(); 57 | 58 | this.executorService = executorService; 59 | 60 | sM.useTPool.addActionListener(new ActionListener() { 61 | @Override 62 | public void actionPerformed(ActionEvent e) { 63 | // 自定义进程数 64 | setExecutorService(Integer.parseInt(sM.threadNum.getText())); 65 | JOptionPane.showMessageDialog(sM.useTPool.getRootPane(), "ok!", "Tip", JOptionPane.INFORMATION_MESSAGE); 66 | } 67 | }); 68 | } 69 | 70 | public void setExecutorService(int n) 71 | { 72 | executorService.setMaximumPoolSize(n); 73 | executorService.setCorePoolSize(n); 74 | } 75 | 76 | @Override 77 | public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) 78 | { 79 | return RequestToBeSentAction.continueWith(requestToBeSent); 80 | } 81 | 82 | @Override 83 | public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) 84 | { 85 | try { 86 | executorService.submit(() -> 87 | { 88 | _sign.incrementAndGet(); 89 | try { 90 | this.get(responseReceived); 91 | } finally 92 | { 93 | if (_sign.decrementAndGet() <= 0) 94 | { 95 | // 判断队列归零,清空 96 | if (executorService.getQueue().isEmpty()) 97 | { 98 | ur_list.clear(); 99 | } 100 | } 101 | } 102 | }); 103 | // this.get(responseReceived); 104 | } catch (Exception e) { 105 | api.logging().logToError(e.toString()); 106 | } 107 | 108 | return ResponseReceivedAction.continueWith(responseReceived); 109 | } 110 | 111 | // 处理函数 112 | private void get(HttpResponseReceived responseReceived) 113 | { 114 | if (sM.isStop.isSelected()) 115 | { 116 | return; 117 | } 118 | // api.logging().logToOutput(scope.toString()); 119 | 120 | String url = responseReceived.initiatingRequest().url().strip(); 121 | 122 | // 判断范围 123 | // api.logging().logToOutput(url); 124 | boolean sign = isInScope(url); 125 | 126 | if (!sign) 127 | { 128 | return; 129 | } 130 | 131 | // 处理http 132 | // String path = responseReceived.initiatingRequest().pathWithoutQuery(); 133 | // // api.logging().logToOutput(path); 134 | // var pathList = path.split("/"); 135 | // // api.logging().logToOutput(Arrays.toString(pathList)); 136 | // if (pathList.length <= 1) 137 | // { 138 | // return; 139 | // } 140 | // 141 | // String url1 = Arrays.stream(pathList).toList().getLast(); 142 | 143 | // 过滤资源文件 144 | if (isResource(url)) 145 | { 146 | return; 147 | } 148 | 149 | // 匹配js、html、json、xml,html还要单独从响应体判断 150 | // 记录访问过的文件,防环 151 | 152 | List baseURLList = new ArrayList<>(); 153 | 154 | // 提取api 155 | List apiList = new ArrayList<>(); 156 | if (isJss(url) || responseReceived.body().toString().contains(" extractedLinks = new ArrayList<>(); 170 | try { 171 | Matcher matcher = pattern.matcher(responseReceived.bodyToString()); 172 | 173 | while (matcher.find()) { 174 | for (int i = 2; i <= matcher.groupCount(); i++) { 175 | if (matcher.group(i) != null) { 176 | String link = matcher.group(i).strip(); 177 | // 提取api 178 | extractedLinks.add(link); 179 | // api.logging().logToOutput(link); 180 | break; 181 | } 182 | } 183 | } 184 | } catch (Exception e) { 185 | return; 186 | } 187 | 188 | // api.logging().logToOutput(extractedLinks.toString()); 189 | // 清洗api 190 | if (!extractedLinks.isEmpty()) 191 | { 192 | for (String lnk : extractedLinks) { 193 | // 过滤资源文件 194 | if (isResource(lnk)) 195 | { 196 | continue; 197 | } 198 | 199 | // 如果提取的lnk符合要求 进apiList 200 | if (lnk.matches("^(?:https?:\\/\\/|\\/\\/)([a-zA-Z0-9.-]++(?::\\d+)?\\/)(?!\\s*$)[^\\s?#]+(?:[?#][^\\s]*)?$")) 201 | { 202 | // 二次范围判断 203 | boolean sign11 = isInScope(lnk); 204 | if (!sign11) {continue;} 205 | // api.logging().logToOutput(lnk); 206 | apiList.add(lnk); 207 | 208 | // 是否从响应提取的url中添加为baseurl,判断lnk是否纯协议+domain/ip+port/+路径,无? 无参数 209 | if (sM.isBaseURLFind.isSelected()) 210 | { 211 | if (lnk.matches("^(?:https?:\\/\\/|\\/\\/)([a-zA-Z0-9.-]+(?::\\d+)?\\/)(?!\\s*$)[^\\s?#]*\\/$")) 212 | { 213 | baseURLList.add(lnk); 214 | } 215 | } 216 | continue; 217 | } 218 | 219 | // 匹配正常路径(可选参数) 220 | // 拼接api 221 | // api.logging().logToOutput(lnk); 222 | if (lnk.matches("^(?:/{0,}[a-zA-Z][a-zA-Z0-9\\._/-]*)?(?:\\?[^\\s\"']*)?$")) { 223 | 224 | // url只要协议域名端口/ 225 | String regex2 = "((?:[^/]*/){3}).*"; 226 | String result1 = url.replaceFirst(regex2, "$1"); 227 | if (lnk.startsWith("/")) 228 | { 229 | lnk = lnk.replaceFirst("/", ""); 230 | } 231 | apiList.add(result1 + lnk); 232 | 233 | // 判断自定义fuzz路径 234 | if (isPathFuzzing()) 235 | { 236 | for (String pf : sM.getPathFuzzingList()) 237 | { 238 | apiList.add(result1 + pf.strip() + lnk); 239 | }; 240 | } 241 | 242 | // 判断baseURLList,给它拼接上一起fuzz 243 | if (sM.isBaseURLFind.isSelected()) 244 | { 245 | for (String _url : baseURLList) 246 | { 247 | apiList.add(_url + lnk); 248 | // 判断自定义fuzz路径 249 | if (isPathFuzzing()) 250 | { 251 | for (String pf : sM.getPathFuzzingList()) 252 | { 253 | apiList.add(_url + pf.strip() + lnk); 254 | }; 255 | } 256 | } 257 | } 258 | 259 | // 判断是否在参数前添加自定义路径 260 | if (sM.isBackCustomPath.isSelected()) 261 | { 262 | String[] _lnk1 = lnk.split("\\?", 2); 263 | if (_lnk1.length == 2) 264 | { 265 | for (String pf : sM.getBackCustomPathList()) 266 | { 267 | apiList.add(result1 + _lnk1[0] + pf.strip() + "?" + _lnk1[1]); 268 | } 269 | } else 270 | { 271 | for (String pf : sM.getBackCustomPathList()) 272 | { 273 | apiList.add(result1 + _lnk1[0] + pf.strip()); 274 | } 275 | } 276 | 277 | } 278 | } 279 | } 280 | } 281 | 282 | // api.logging().logToOutput(apiList.toString()); 283 | } 284 | 285 | // 请求速率限制 286 | long rate = -1; 287 | if (sM.isReqAPI.isSelected()) 288 | { 289 | rate = Long.parseLong(sM.sleepTime.getText()); 290 | } 291 | 292 | // 向api发起请求 293 | var model = ((DefaultTreeModel)sM.getSiteMapTreeRoot().getModel()); 294 | String[] wrnList = sM.getWarnList(); 295 | for (String apii : apiList) 296 | { 297 | if (!sM.isReqAPI.isSelected()) {break;} 298 | // 二次范围判断 299 | boolean sign1 = isInScope(apii.strip()); 300 | 301 | if (!sign1) 302 | { 303 | continue; 304 | } 305 | 306 | // 防环 307 | if (ur_list.contains(apii.strip())) 308 | { 309 | continue; 310 | } 311 | ur_list.add(apii); 312 | 313 | // 判断危险接口跳过 314 | if (sM.isBypassWarn.isSelected()) 315 | { 316 | boolean _sign0 = false; 317 | for (String ws : wrnList) 318 | { 319 | if (apii.contains(ws)) { 320 | _sign0 = true; 321 | break; 322 | } 323 | } 324 | if (_sign0) 325 | { 326 | api.logging().logToOutput("[Log]: 从 " + responseReceived.initiatingRequest().url() + " 获取 " + apii.strip() + " [危险接口已跳过]"); 327 | continue; 328 | } 329 | } 330 | 331 | api.logging().logToOutput("[Log]: 从 " + responseReceived.initiatingRequest().url() + " 获取 " + apii.strip()); 332 | 333 | if (rate > 0) 334 | { 335 | try { 336 | Thread.sleep(rate); 337 | } catch (InterruptedException ignored) { 338 | } 339 | } 340 | 341 | HttpRequestResponse res = null; 342 | HttpRequest hr; 343 | try { 344 | hr = HttpRequest.httpRequestFromUrl(apii.strip()); 345 | } catch (Exception e) { 346 | continue; 347 | } 348 | HttpHeader hd = hr.header("Host"); 349 | 350 | // 携带原headers 351 | if (sM.isUseHeader.isSelected()) 352 | { 353 | hr = hr.withRemovedHeaders(responseReceived.initiatingRequest().headers()); 354 | hr = hr.withAddedHeaders(responseReceived.initiatingRequest().headers()); 355 | hr = hr.withUpdatedHeader(hd); 356 | } 357 | hr = hr.withRemovedHeader("Content-Length"); 358 | hr = hr.withAddedHeader("Content-Length", "0"); 359 | 360 | // 自定义headers 361 | if (sM.isSetHeaders.isSelected()) 362 | { 363 | for (String kv : sM.getSetHeaderList()) 364 | { 365 | var kvl = kv.strip().split(":"); 366 | String k = kvl[0]; 367 | String v = kvl[1]; 368 | if (v.startsWith(" ")) 369 | { 370 | v = v.replaceFirst(" ", ""); 371 | } 372 | hr = hr.withUpdatedHeader(k, v); 373 | } 374 | } 375 | 376 | try { 377 | if (!sM.isReqAPI.isSelected()) {break;} 378 | res = api.http().sendRequest(hr); 379 | } catch (Exception ignored) { 380 | continue; 381 | } 382 | if (res == null || !res.hasResponse()){continue;} 383 | // 如果访问不到,则尝试POST 384 | if (res.response().statusCode() != 200) 385 | { 386 | try { 387 | res = api.http().sendRequest(hr.withMethod("POST")); 388 | } catch (Exception ignored) { 389 | continue; 390 | } 391 | } 392 | 393 | boolean _sign1 = false; 394 | try { 395 | for (String sc : sM.getStatusCodeFilterList()) 396 | { 397 | if (res.response().statusCode() == Integer.parseInt(sc.strip())) {_sign1 = true;} 398 | } 399 | } catch (NumberFormatException e) { 400 | continue; 401 | } 402 | if (_sign1) {continue;} 403 | 404 | api.siteMap().add(res); 405 | // 非资源文件才添加进table model并进行下一步tree list操作 406 | if (isResource(apii) || isJss(apii)) 407 | { 408 | continue; 409 | } 410 | // tableModel.orgAdd1(responseReceived); 411 | 412 | SuperHttpReqAndRes shrr = new SuperHttpReqAndRes(res, responseReceived); 413 | tableModel.add1(shrr); 414 | 415 | // 添加到tree list ui 416 | // api.logging().logToOutput(String.valueOf(TreeRoot.getChildCount())); 417 | DefaultMutableTreeNode urlNameNode = null; 418 | // 获取url,判断url node是否存在,再添加新node 419 | // url只要协议域名端口/ 420 | String regex2 = "((?:[^/]*/){3}).*"; 421 | String result1 = hr.url().replaceFirst(regex2, "$1"); 422 | for (int i = 0; i < TreeRoot.getChildCount(); i++) 423 | { 424 | var uNode = (DefaultMutableTreeNode)TreeRoot.getChildAt(i); 425 | if (uNode.getUserObject().equals(result1)) 426 | { 427 | urlNameNode = uNode; 428 | break; 429 | } 430 | } 431 | 432 | 433 | // api.logging().logToOutput(TreeRoot.getChildAt(i).toString()); 434 | if (urlNameNode == null) 435 | { 436 | // 添加url node 437 | DefaultMutableTreeNode site1 = new DefaultMutableTreeNode(result1); 438 | // TreeRoot.add(site1); 439 | 440 | // 更新site map, 与add二选一 441 | SwingUtilities.invokeLater(() -> model.insertNodeInto(site1, TreeRoot, TreeRoot.getChildCount())); 442 | 443 | urlNameNode = site1; 444 | } 445 | 446 | // 添加路径node到list 447 | // 循环判断路径node是否存在 448 | var pl = hr.pathWithoutQuery().split("/"); 449 | // var smtr = (DefaultTreeModel)SiteMapTreeRoot.getModel(); 450 | for (int i1 = 0; i1 < pl.length - 1; i1++) { 451 | pl[i1] = "/" + pl[i1]; 452 | 453 | boolean nodeExists = false; 454 | DefaultMutableTreeNode nextParentNode = null; 455 | for (int j = 0; j < urlNameNode.getChildCount(); j++) 456 | { 457 | DefaultMutableTreeNode child = (DefaultMutableTreeNode) urlNameNode.getChildAt(j); 458 | if (child.getUserObject().equals(pl[i1])) { 459 | nodeExists = true; 460 | nextParentNode = child; 461 | break; 462 | } 463 | } 464 | 465 | // api.logging().logToOutput(pl[i1]); 466 | // 如果不存在,创建新节点并添加 467 | if (!nodeExists) 468 | { 469 | DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(pl[i1]); 470 | // urlNameNode.add(newNode); 471 | 472 | // 更新site map, 与add二选一 473 | DefaultMutableTreeNode finalUrlNameNode = urlNameNode; 474 | DefaultMutableTreeNode finalUrlNameNode1 = urlNameNode; 475 | SwingUtilities.invokeLater(() -> model.insertNodeInto(newNode, finalUrlNameNode, finalUrlNameNode1.getChildCount())); 476 | 477 | nextParentNode = newNode; 478 | 479 | } 480 | urlNameNode = nextParentNode; 481 | 482 | } 483 | // 通知事件 484 | SwingUtilities.invokeLater(() -> sM.nodeChanged(tableModel, shrr)); 485 | } 486 | 487 | // if (this._sign.get() <= 1) 488 | // { 489 | // ur_list.clear(); 490 | // } 491 | } 492 | 493 | boolean isJss(String str) 494 | { 495 | return str.matches(".*\\.(?:js|mjs|json|xml|html|htm|shtml|map)(?:\\?[^#\\s]*)?(?:#[^\\s]*)?$"); 496 | } 497 | 498 | boolean isJss1(String str) 499 | { 500 | return str.matches(".*\\.(?:js|mjs|map)(?:\\?[^#\\s]*)?(?:#[^\\s]*)?$"); 501 | } 502 | 503 | boolean isResource(String str) 504 | { 505 | return str.matches(".*\\.(?:jpe?g|png|gif|svg|webp|ico|bmp|avif|css|mjs|swf|flv|woff2?|ttf|eot|mov|webm)(?:\\?[^#\\s]*)?(?:#[^\\s]*)?$"); 506 | } 507 | 508 | boolean isInScope(String url) 509 | { 510 | var scope = scopeList.getText().lines().toList(); 511 | for (String s : scope) { 512 | if (s.strip().equals("*")) { 513 | return true; 514 | } 515 | if (url.contains(s.strip())) { 516 | return true; 517 | } 518 | } 519 | return false; 520 | } 521 | 522 | boolean isPathFuzzing() 523 | { 524 | return sM.isAddPathFuzzing.isSelected(); 525 | } 526 | } 527 | -------------------------------------------------------------------------------- /API_Sword/src/main/java/burp/SwordMain.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 |
434 | -------------------------------------------------------------------------------- /API_Sword/src/main/java/burp/SwordMain.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | import burp.api.montoya.MontoyaApi; 4 | import burp.api.montoya.http.handler.HttpResponseReceived; 5 | import burp.api.montoya.http.message.HttpRequestResponse; 6 | import burp.api.montoya.persistence.PersistedObject; 7 | import com.intellij.uiDesigner.core.GridConstraints; 8 | import com.intellij.uiDesigner.core.GridLayoutManager; 9 | import com.intellij.uiDesigner.core.Spacer; 10 | import burp.api.montoya.ui.UserInterface; 11 | import burp.api.montoya.ui.editor.HttpRequestEditor; 12 | import burp.api.montoya.ui.editor.HttpResponseEditor; 13 | 14 | import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY; 15 | 16 | import javax.swing.*; 17 | import javax.swing.event.DocumentEvent; 18 | import javax.swing.event.DocumentListener; 19 | import javax.swing.plaf.metal.MetalTreeUI; 20 | import javax.swing.table.TableModel; 21 | import javax.swing.table.TableRowSorter; 22 | import javax.swing.text.JTextComponent; 23 | import javax.swing.tree.DefaultMutableTreeNode; 24 | import javax.swing.tree.DefaultTreeModel; 25 | import javax.swing.tree.TreePath; 26 | import java.awt.*; 27 | import java.awt.event.*; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | import java.util.Locale; 31 | 32 | public class SwordMain { 33 | private JTabbedPane tabbedPane1; 34 | private JPanel panel1; 35 | private JPanel apiMap; 36 | private JPanel swordSetting; 37 | private JPanel scope; 38 | private JSplitPane httpview; 39 | private JTextArea scopeList; 40 | private JTextPane note; 41 | public JCheckBox isReqAPI; 42 | public JCheckBox isUseHeader; 43 | private JCheckBox isTiming; 44 | public JTextField sleepTime; 45 | private JScrollPane siteMapPane; 46 | private JLabel title; 47 | public JCheckBox isStop; 48 | public JCheckBox isAddPathFuzzing; 49 | private JTextArea pathFuzzingList; 50 | private JTextField statusCodeFilterList; 51 | public JCheckBox isBaseURLFind; 52 | public JCheckBox isSetHeaders; 53 | private JTextArea setHeaderList; 54 | private JTextField warnList; 55 | public JCheckBox isBypassWarn; 56 | private JButton toSaveSettings; 57 | private JButton saveSiteMap; 58 | private JButton clearSiteMap; 59 | private JComboBox Lang; 60 | private JLabel fscLabel1; 61 | private JTextArea backCustomPathList; 62 | public JCheckBox isBackCustomPath; 63 | public JTextField threadNum; 64 | public JButton useTPool; 65 | private JLabel threadNumLabel; 66 | private DefaultMutableTreeNode TreeRoot; 67 | // Create the JTree with the root node 68 | private JTree SiteMapTreeRoot; 69 | private JLabel filterLabel; 70 | public JTextField filterContext; 71 | public JComboBox filterOptions; 72 | private TableRowSorter sorter; 73 | private JButton expandAll; 74 | private JButton collapseAll; 75 | private JButton clearAll; 76 | 77 | private String up1 = "/"; 78 | 79 | public SwordMain() { 80 | 81 | $$$setupUI$$$(); 82 | isTiming.addActionListener(new ActionListener() { 83 | @Override 84 | public void actionPerformed(ActionEvent e) { 85 | sleepTime.setEditable(isTiming.isSelected()); 86 | } 87 | }); 88 | 89 | Lang.addItemListener(new ItemListener() { 90 | @Override 91 | public void itemStateChanged(ItemEvent e) { 92 | // 切换语言 93 | setLang(e.getItem().toString()); 94 | } 95 | }); 96 | } 97 | 98 | /** 99 | * Method generated by IntelliJ IDEA GUI Designer 100 | * >>> IMPORTANT!! <<< 101 | * DO NOT edit this method OR call it in your code! 102 | * 103 | * @noinspection ALL 104 | */ 105 | private void $$$setupUI$$$() { 106 | panel1 = new JPanel(); 107 | panel1.setLayout(new GridLayoutManager(2, 1, new Insets(0, 0, 0, 0), -1, -1)); 108 | tabbedPane1 = new JTabbedPane(); 109 | panel1.add(tabbedPane1, new GridConstraints(1, 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, new Dimension(200, 200), null, 0, false)); 110 | apiMap = new JPanel(); 111 | apiMap.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1, true, true)); 112 | tabbedPane1.addTab("Site Map", apiMap); 113 | httpview = new JSplitPane(); 114 | httpview.setDividerLocation(300); 115 | httpview.setDividerSize(5); 116 | apiMap.add(httpview, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_GROW, null, new Dimension(200, 200), null, 0, false)); 117 | siteMapPane = new JScrollPane(); 118 | siteMapPane.setEnabled(true); 119 | httpview.setLeftComponent(siteMapPane); 120 | scope = new JPanel(); 121 | scope.setLayout(new GridLayoutManager(4, 3, new Insets(0, 0, 0, 0), -1, -1)); 122 | tabbedPane1.addTab("Scope", scope); 123 | final JScrollPane scrollPane1 = new JScrollPane(); 124 | scope.add(scrollPane1, new GridConstraints(0, 0, 2, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 125 | scopeList = new JTextArea(); 126 | scopeList.setText(""); 127 | scrollPane1.setViewportView(scopeList); 128 | final Spacer spacer1 = new Spacer(); 129 | scope.add(spacer1, new GridConstraints(2, 1, 2, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 130 | note = new JTextPane(); 131 | note.setEditable(false); 132 | note.setText(""); 133 | scope.add(note, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(150, 50), null, 0, false)); 134 | final Spacer spacer2 = new Spacer(); 135 | scope.add(spacer2, new GridConstraints(2, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); 136 | swordSetting = new JPanel(); 137 | swordSetting.setLayout(new GridLayoutManager(21, 9, new Insets(0, 0, 0, 0), -1, -1)); 138 | tabbedPane1.addTab("Settings", swordSetting); 139 | isReqAPI = new JCheckBox(); 140 | isReqAPI.setSelected(true); 141 | isReqAPI.setText("允许主动对API请求"); 142 | swordSetting.add(isReqAPI, new GridConstraints(0, 0, 1, 6, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, 1, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 143 | isUseHeader = new JCheckBox(); 144 | isUseHeader.setSelected(true); 145 | isUseHeader.setText("是否使用原header"); 146 | swordSetting.add(isUseHeader, new GridConstraints(1, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, 1, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 147 | isTiming = new JCheckBox(); 148 | isTiming.setContentAreaFilled(true); 149 | isTiming.setEnabled(true); 150 | isTiming.setFocusPainted(true); 151 | isTiming.setFocusable(true); 152 | isTiming.setRequestFocusEnabled(true); 153 | isTiming.setRolloverEnabled(false); 154 | isTiming.setSelected(false); 155 | isTiming.setText("启用主动http请求速率"); 156 | swordSetting.add(isTiming, new GridConstraints(4, 0, 1, 2, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, 1, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 157 | final Spacer spacer3 = new Spacer(); 158 | swordSetting.add(spacer3, new GridConstraints(5, 5, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); 159 | isStop = new JCheckBox(); 160 | isStop.setActionCommand(""); 161 | isStop.setText("立即停止发送所有请求(急刹车)"); 162 | swordSetting.add(isStop, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 163 | final JScrollPane scrollPane2 = new JScrollPane(); 164 | swordSetting.add(scrollPane2, new GridConstraints(1, 5, 1, 2, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 165 | pathFuzzingList = new JTextArea(); 166 | scrollPane2.setViewportView(pathFuzzingList); 167 | isAddPathFuzzing = new JCheckBox(); 168 | isAddPathFuzzing.setActionCommand(""); 169 | isAddPathFuzzing.setText("是否在主动请求时额外添加自定义路径请求(一行一个,非/开头)"); 170 | swordSetting.add(isAddPathFuzzing, new GridConstraints(0, 6, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 171 | final Spacer spacer4 = new Spacer(); 172 | swordSetting.add(spacer4, new GridConstraints(20, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 173 | final Spacer spacer5 = new Spacer(); 174 | swordSetting.add(spacer5, new GridConstraints(19, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 175 | final Spacer spacer6 = new Spacer(); 176 | swordSetting.add(spacer6, new GridConstraints(18, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 177 | final Spacer spacer7 = new Spacer(); 178 | swordSetting.add(spacer7, new GridConstraints(17, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 179 | final Spacer spacer8 = new Spacer(); 180 | swordSetting.add(spacer8, new GridConstraints(16, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 181 | final Spacer spacer9 = new Spacer(); 182 | swordSetting.add(spacer9, new GridConstraints(15, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 183 | final Spacer spacer10 = new Spacer(); 184 | swordSetting.add(spacer10, new GridConstraints(14, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 185 | final Spacer spacer11 = new Spacer(); 186 | swordSetting.add(spacer11, new GridConstraints(13, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 187 | final Spacer spacer12 = new Spacer(); 188 | swordSetting.add(spacer12, new GridConstraints(12, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 189 | final Spacer spacer13 = new Spacer(); 190 | swordSetting.add(spacer13, new GridConstraints(11, 3, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 191 | fscLabel1 = new JLabel(); 192 | fscLabel1.setText("过滤掉非200的自定义响应码:(英文逗号隔开)"); 193 | swordSetting.add(fscLabel1, new GridConstraints(2, 6, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 194 | statusCodeFilterList = new JTextField(); 195 | statusCodeFilterList.setText("404,301,302,0"); 196 | swordSetting.add(statusCodeFilterList, new GridConstraints(3, 6, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); 197 | sleepTime = new JTextField(); 198 | sleepTime.setEditable(false); 199 | sleepTime.setText("300"); 200 | swordSetting.add(sleepTime, new GridConstraints(5, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_HORIZONTAL, 1, 1, null, new Dimension(150, -1), null, 0, false)); 201 | final JLabel label1 = new JLabel(); 202 | label1.setAlignmentX(0.0f); 203 | label1.setAlignmentY(0.5f); 204 | label1.setAutoscrolls(false); 205 | label1.setHorizontalAlignment(2); 206 | label1.setHorizontalTextPosition(2); 207 | label1.setOpaque(false); 208 | label1.setText("ms"); 209 | label1.setToolTipText(""); 210 | label1.putClientProperty("html.disable", Boolean.FALSE); 211 | swordSetting.add(label1, new GridConstraints(5, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, 1, 1, null, null, null, 0, false)); 212 | isBaseURLFind = new JCheckBox(); 213 | isBaseURLFind.setText("允许API剑主动从响应中寻找baseURL并主动对baseURL进行路径拼接"); 214 | swordSetting.add(isBaseURLFind, new GridConstraints(4, 6, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 215 | isSetHeaders = new JCheckBox(); 216 | isSetHeaders.setText("添加自定义header字段:(自动覆盖已有的header字段)"); 217 | swordSetting.add(isSetHeaders, new GridConstraints(6, 6, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 218 | final JScrollPane scrollPane3 = new JScrollPane(); 219 | swordSetting.add(scrollPane3, new GridConstraints(7, 6, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 220 | setHeaderList = new JTextArea(); 221 | setHeaderList.setText("User-Agent: APT250&NSF/API_Sword"); 222 | scrollPane3.setViewportView(setHeaderList); 223 | final Spacer spacer14 = new Spacer(); 224 | swordSetting.add(spacer14, new GridConstraints(0, 7, 13, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 225 | final Spacer spacer15 = new Spacer(); 226 | swordSetting.add(spacer15, new GridConstraints(5, 8, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false)); 227 | isBypassWarn = new JCheckBox(); 228 | isBypassWarn.setText("启用绕过危险接口访问(接口包含字符串则跳过):逗号隔开"); 229 | swordSetting.add(isBypassWarn, new GridConstraints(6, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, 1, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 230 | warnList = new JTextField(); 231 | warnList.setText("/delete,/update,/del,del"); 232 | swordSetting.add(warnList, new GridConstraints(7, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_HORIZONTAL, 1, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); 233 | saveSiteMap = new JButton(); 234 | saveSiteMap.setText("保存SiteMap数据(未实现)"); 235 | swordSetting.add(saveSiteMap, new GridConstraints(8, 0, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, 1, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 236 | clearSiteMap = new JButton(); 237 | clearSiteMap.setText("清空SiteMap的保存数据(未实现)"); 238 | swordSetting.add(clearSiteMap, new GridConstraints(9, 0, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, 1, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 239 | Lang = new JComboBox<>(); 240 | final DefaultComboBoxModel defaultComboBoxModel1 = new DefaultComboBoxModel<>(); 241 | defaultComboBoxModel1.addElement("EN"); 242 | defaultComboBoxModel1.addElement("CN"); 243 | Lang.setModel(defaultComboBoxModel1); 244 | swordSetting.add(Lang, new GridConstraints(0, 8, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, 1, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 245 | final JScrollPane scrollPane4 = new JScrollPane(); 246 | swordSetting.add(scrollPane4, new GridConstraints(12, 6, 4, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false)); 247 | backCustomPathList = new JTextArea(); 248 | backCustomPathList.setText(""); 249 | scrollPane4.setViewportView(backCustomPathList); 250 | isBackCustomPath = new JCheckBox(); 251 | isBackCustomPath.setActionCommand(""); 252 | isBackCustomPath.setText("是否在API接口后、参数前额外添加自定义路径"); 253 | swordSetting.add(isBackCustomPath, new GridConstraints(11, 6, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 254 | toSaveSettings = new JButton(); 255 | toSaveSettings.setText("保存范围及所有设置"); 256 | swordSetting.add(toSaveSettings, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, 1, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 257 | threadNumLabel = new JLabel(); 258 | threadNumLabel.setText("线程数:"); 259 | swordSetting.add(threadNumLabel, new GridConstraints(10, 0, 1, 1, GridConstraints.ANCHOR_SOUTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 260 | threadNum = new JTextField(); 261 | threadNum.setText("1"); 262 | swordSetting.add(threadNum, new GridConstraints(11, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_HORIZONTAL, 1, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(150, -1), null, 0, false)); 263 | useTPool = new JButton(); 264 | useTPool.setText("确定"); 265 | swordSetting.add(useTPool, new GridConstraints(11, 1, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, 1, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); 266 | title = new JLabel(); 267 | title.setText(""); 268 | panel1.add(title, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, 1, 1, null, null, null, 1, false)); 269 | } 270 | 271 | /** 272 | * @noinspection ALL 273 | */ 274 | public JComponent $$$getRootComponent$$$() { 275 | return panel1; 276 | } 277 | 278 | public JComponent InitRootComponent(MontoyaApi api, MyTableModel tableModel) { 279 | JComponent panel = $$$getRootComponent$$$(); 280 | 281 | filterLabel = new JLabel(); 282 | filterContext = new JTextField(); 283 | filterContext.getDocument().addDocumentListener(new DocumentListener() { 284 | @Override 285 | public void insertUpdate(DocumentEvent e) { 286 | filterAction(); 287 | } 288 | 289 | @Override 290 | public void removeUpdate(DocumentEvent e) { 291 | filterAction(); 292 | } 293 | 294 | @Override 295 | public void changedUpdate(DocumentEvent e) { 296 | filterAction(); 297 | } 298 | }); 299 | 300 | expandAll = new JButton(); 301 | collapseAll = new JButton(); 302 | clearAll = new JButton(); 303 | 304 | pathFuzzingList.setText("api/\n" + 305 | "user/v1/"); 306 | 307 | backCustomPathList.setText(";/\n" + 308 | "/;\n" + 309 | "/\n" + 310 | "..;\n" + 311 | "/..;\n" + 312 | ";.js\n" + 313 | "/;.js"); 314 | 315 | // scope视图 316 | scopeList.setText("nsfocus.com.cn/\nnsfocus.com.cn:8888/api/v1\n0.0.0.0"); 317 | 318 | // 设置默认环境语言 319 | String _lang = Locale.getDefault().getCountry(); 320 | setLang(_lang); 321 | 322 | // 加载范围和设置 323 | loadConfig(api); 324 | 325 | // 保存范围和设置 326 | toSaveSettings.addActionListener(new ActionListener() { 327 | @Override 328 | public void actionPerformed(ActionEvent e) { 329 | // 保存范围 330 | api.persistence().extensionData().setString("Scope", scopeList.getText()); 331 | // 保存设置 332 | api.persistence().extensionData().setBoolean("IsReqAPI", isReqAPI.isSelected()); 333 | api.persistence().extensionData().setBoolean("IsUseHeader", isUseHeader.isSelected()); 334 | api.persistence().extensionData().setBoolean("IsStop", isStop.isSelected()); 335 | 336 | api.persistence().extensionData().setBoolean("IsBypassWarn", isBypassWarn.isSelected()); 337 | api.persistence().extensionData().setString("WarnList", warnList.getText()); 338 | 339 | api.persistence().extensionData().setBoolean("IsAddPathFuzzing", isAddPathFuzzing.isSelected()); 340 | api.persistence().extensionData().setString("PathFuzzingList", pathFuzzingList.getText()); 341 | 342 | api.persistence().extensionData().setString("StatusCodeFilterList", statusCodeFilterList.getText()); 343 | 344 | api.persistence().extensionData().setBoolean("IsBaseURLFind", isBaseURLFind.isSelected()); 345 | 346 | api.persistence().extensionData().setBoolean("IsSetHeaders", isSetHeaders.isSelected()); 347 | api.persistence().extensionData().setString("SetHeaderList", setHeaderList.getText()); 348 | 349 | JOptionPane.showMessageDialog(toSaveSettings.getRootPane(), "ok!", "Tip", JOptionPane.INFORMATION_MESSAGE); 350 | } 351 | }); 352 | 353 | // site map视图 354 | JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 355 | splitPane.setDividerLocation(360); 356 | 357 | JSplitPane tabs = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); 358 | tabs.setDividerLocation(600); 359 | 360 | UserInterface userInterface = api.userInterface(); 361 | 362 | HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY); 363 | HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY); 364 | HttpResponseEditor orgResponseViewer = userInterface.createHttpResponseEditor(READ_ONLY); 365 | 366 | JTabbedPane tabs1 = new JTabbedPane(); 367 | JTabbedPane tabs2 = new JTabbedPane(); 368 | 369 | tabs1.addTab("Request", requestViewer.uiComponent()); 370 | tabs2.addTab("Response", responseViewer.uiComponent()); 371 | 372 | 373 | tabs.setLeftComponent(tabs1); 374 | tabs.setRightComponent(tabs2); 375 | // tabs.updateUI(); 376 | 377 | // req和res上面的视图 378 | // 左边显示list 379 | 380 | // table of log entries 381 | JTable table = new JTable(tableModel) { 382 | @Override 383 | public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) { 384 | // show the log entry for the selected row 385 | int viewRow = convertRowIndexToModel(rowIndex); 386 | HttpRequestResponse responseReceived = tableModel.getRes(viewRow); 387 | HttpResponseReceived orgRes = tableModel.getOrgRes(viewRow); 388 | 389 | var req = responseReceived.request(); 390 | orgResponseViewer.setResponse(orgRes); 391 | orgResponseViewer.setSearchExpression(req.pathWithoutQuery()); 392 | requestViewer.setRequest(req); 393 | responseViewer.setResponse(responseReceived.response()); 394 | 395 | super.changeSelection(rowIndex, columnIndex, toggle, extend); 396 | } 397 | }; 398 | // 自动排序 399 | table.setAutoCreateRowSorter(true); 400 | sorter = (TableRowSorter) table.getRowSorter(); 401 | 402 | var tc = table.getColumnModel(); 403 | tc.getColumn(0).setPreferredWidth(5); 404 | tc.getColumn(1).setPreferredWidth(200); 405 | tc.getColumn(2).setPreferredWidth(50); 406 | tc.getColumn(3).setPreferredWidth(7); 407 | tc.getColumn(4).setPreferredWidth(60); 408 | 409 | 410 | // 右边显示来源 左边显示列表 411 | JSplitPane orgP = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); 412 | orgP.setDividerLocation(780); 413 | 414 | JScrollPane listP = new JScrollPane(table); 415 | orgP.setLeftComponent(listP); 416 | orgP.setRightComponent(orgResponseViewer.uiComponent()); 417 | 418 | splitPane.setTopComponent(orgP); 419 | 420 | splitPane.setBottomComponent(tabs); 421 | httpview.setRightComponent(splitPane); 422 | 423 | 424 | // root 425 | TreeRoot = new DefaultMutableTreeNode("Site Map"); 426 | // Create the JTree with the root node 427 | SiteMapTreeRoot = new JTree(TreeRoot); 428 | // SiteMapTreeRoot.setBorder(new EmptyBorder(0, 10, 0, 0)); 429 | SiteMapTreeRoot.setShowsRootHandles(true); 430 | SiteMapTreeRoot.setUI(new MetalTreeUI()); 431 | 432 | // 鼠标点击list事件 433 | SiteMapTreeRoot.addMouseListener(new MouseAdapter() { 434 | @Override 435 | public void mouseClicked(MouseEvent e) { 436 | // api.logging().logToOutput("2"); 437 | TreePath selPath = SiteMapTreeRoot.getPathForLocation(e.getX(), e.getY()); 438 | // api.logging().logToOutput(selPath.toString()); 439 | if (selPath != null && selPath.getLastPathComponent().toString().matches("/.*[^/]?$")) { 440 | var list1 = tableModel.getResList(); 441 | 442 | StringBuilder url1 = new StringBuilder(); 443 | var ulp = selPath.getPath(); 444 | for (int i = 1; i < ulp.length; i++) { 445 | if (i == 1) { 446 | String _u = ulp[i].toString(); 447 | if (_u.endsWith("/")) { 448 | url1.append(_u.substring(0, _u.length() - 1)); 449 | continue; 450 | } 451 | 452 | } 453 | if (i == 2) { 454 | continue; 455 | } 456 | url1.append(ulp[i]); 457 | } 458 | 459 | String url2 = url1.toString(); 460 | 461 | List data = new ArrayList<>(); 462 | for (SuperHttpReqAndRes hrr : list1) { 463 | // 判断点击的list的路径是否在响应的请求中 464 | if (hrr.getReq_res().request().url().contains(url2)) { 465 | data.add(hrr); 466 | } 467 | } 468 | 469 | up1 = url2; 470 | 471 | // 将数据压入list显示 472 | tableModel.pushDisplayData(data); 473 | 474 | 475 | // api.logging().logToOutput("Clicked API Path: " + selPath); 476 | 477 | // DefaultMutableTreeNode clickedNode = (DefaultMutableTreeNode) selPath.getLastPathComponent(); 478 | // // api.logging().logToOutput(clickedNode.toString()); 479 | // if (clickedNode != null && clickedNode.toString().matches("/.*[^/]$")) { 480 | // api.logging().logToOutput("Clicked API Path: " + clickedNode.getUserObject().toString()); 481 | // // 从Model找到api的请求,添加进list 482 | // } 483 | } 484 | } 485 | }); 486 | 487 | // 过滤器 and sitemap数据清除, 放在sitemap上面 488 | JPanel filter = new JPanel(new FlowLayout(FlowLayout.LEFT)); 489 | 490 | filterOptions = new JComboBox<>(); 491 | final DefaultComboBoxModel defaultComboBoxModel1 = new DefaultComboBoxModel<>(); 492 | defaultComboBoxModel1.addElement("Status Code"); 493 | defaultComboBoxModel1.addElement("URL"); 494 | defaultComboBoxModel1.addElement("Query"); 495 | defaultComboBoxModel1.addElement("Length"); 496 | defaultComboBoxModel1.addElement("Comment"); 497 | filterOptions.setModel(defaultComboBoxModel1); 498 | filter.add(filterLabel); 499 | filter.add(filterOptions); 500 | filterContext.setPreferredSize(new Dimension(120, 25)); 501 | filter.add(filterContext); 502 | 503 | // 展开所有节点|收起所有节点|清空sitemap数据, 放在sitemap下面 504 | JPanel buttonMap = new JPanel(new FlowLayout(FlowLayout.LEFT)); 505 | 506 | clearAll.addActionListener(new ActionListener() { 507 | @Override 508 | public void actionPerformed(ActionEvent e) { 509 | // 清除site map数据 510 | TreeRoot.removeAllChildren(); 511 | tableModel.removeAll(); 512 | ((DefaultTreeModel) SiteMapTreeRoot.getModel()).reload(); 513 | 514 | JOptionPane.showMessageDialog(toSaveSettings.getRootPane(), "ok!", "Tip", JOptionPane.INFORMATION_MESSAGE); 515 | } 516 | }); 517 | 518 | expandAll.addActionListener(new ActionListener() { 519 | @Override 520 | public void actionPerformed(ActionEvent e) { 521 | int rowCount = SiteMapTreeRoot.getRowCount(); 522 | for (int i = 0; i < rowCount; i++) { 523 | SiteMapTreeRoot.expandRow(i); 524 | } 525 | } 526 | }); 527 | 528 | collapseAll.addActionListener(new ActionListener() { 529 | @Override 530 | public void actionPerformed(ActionEvent e) { 531 | int rowCount = SiteMapTreeRoot.getRowCount(); 532 | for (int i = rowCount - 1; i >= 0; i--) { 533 | SiteMapTreeRoot.collapseRow(i); 534 | } 535 | } 536 | }); 537 | 538 | buttonMap.add(expandAll); 539 | buttonMap.add(collapseAll); 540 | buttonMap.add(clearAll); 541 | 542 | JPanel leftPanel = new JPanel(new BorderLayout()); 543 | leftPanel.add(filter, BorderLayout.NORTH); 544 | leftPanel.add(buttonMap, BorderLayout.SOUTH); 545 | 546 | JScrollPane smtrJS = new JScrollPane(SiteMapTreeRoot); 547 | leftPanel.add(smtrJS, BorderLayout.CENTER); 548 | siteMapPane.setViewportView(leftPanel); 549 | 550 | return panel; 551 | } 552 | 553 | void filterAction() { 554 | String filText = filterContext.getText(); 555 | if (filText.isEmpty()) { 556 | sorter.setRowFilter(null); 557 | return; 558 | } 559 | 560 | String columnName = filterOptions.getSelectedItem().toString(); 561 | int columnIndex = filterOptions.getSelectedIndex(); 562 | 563 | switch (columnName) { 564 | case "Status Code": 565 | case "Length": 566 | int numberValue = Integer.parseInt(filText); 567 | sorter.setRowFilter(RowFilter.numberFilter(RowFilter.ComparisonType.EQUAL, numberValue, columnIndex)); 568 | break; 569 | default: 570 | sorter.setRowFilter(RowFilter.regexFilter("(?i)" + filText, columnIndex)); 571 | } 572 | } 573 | 574 | public JTextArea getScopeList() { 575 | return scopeList; 576 | } 577 | 578 | public DefaultMutableTreeNode getTreeRoot() { 579 | return this.TreeRoot; 580 | } 581 | 582 | // public void setSiteMapPaneView() { 583 | // // var old = SiteMapTreeRoot.getUI(); 584 | // // SiteMapTreeRoot.updateUI(); 585 | // // SiteMapTreeRoot.setUI(old); 586 | // // ((DefaultTreeModel)SiteMapTreeRoot.getModel()).reload(tn); 587 | // 588 | // siteMapPane.setViewportView(SiteMapTreeRoot); 589 | // } 590 | 591 | public JTree getSiteMapTreeRoot() { 592 | return SiteMapTreeRoot; 593 | } 594 | 595 | public synchronized void nodeChanged(MyTableModel tableModel, SuperHttpReqAndRes shrr) { 596 | var list1 = tableModel.getResLogList(); 597 | 598 | // 避免未点击tree时不更新 599 | if (up1.equals("/") && shrr.getReq_res().request().url().contains(up1)) { 600 | tableModel.pushDisplayData1(shrr); 601 | return; 602 | } 603 | 604 | // 判断点击的list的路径是否在响应的请求中 605 | if (shrr.getReq_res().request().url().contains(up1) && !list1.contains(shrr.getReq_res())) { 606 | // 将数据压入list显示 607 | tableModel.pushDisplayData1(shrr); 608 | } 609 | } 610 | 611 | public List getPathFuzzingList() { 612 | return pathFuzzingList.getText().lines().toList(); 613 | } 614 | 615 | public List getBackCustomPathList() { 616 | return backCustomPathList.getText().lines().toList(); 617 | } 618 | 619 | public String[] getStatusCodeFilterList() { 620 | return statusCodeFilterList.getText().split(","); 621 | } 622 | 623 | public List getSetHeaderList() { 624 | return setHeaderList.getText().lines().toList(); 625 | } 626 | 627 | public String[] getWarnList() { 628 | return warnList.getText().split(","); 629 | } 630 | 631 | void loadConfig(MontoyaApi api) { 632 | PersistedObject persistedData = api.persistence().extensionData(); 633 | // 加载范围 634 | String _scope = persistedData.getString("Scope"); 635 | setStrConf(_scope, scopeList); 636 | 637 | // 加载设置 638 | Boolean _IsReqAPI = persistedData.getBoolean("IsReqAPI"); 639 | setBoolConf(_IsReqAPI, isReqAPI); 640 | 641 | Boolean _IsUseHeader = persistedData.getBoolean("IsUseHeader"); 642 | setBoolConf(_IsUseHeader, isUseHeader); 643 | 644 | Boolean _IsStop = persistedData.getBoolean("IsStop"); 645 | setBoolConf(_IsStop, isStop); 646 | 647 | Boolean _IsBypassWarn = persistedData.getBoolean("IsBypassWarn"); 648 | setBoolConf(_IsBypassWarn, isBypassWarn); 649 | String _WarnList = persistedData.getString("WarnList"); 650 | setStrConf(_WarnList, warnList); 651 | 652 | Boolean _IsAddPathFuzzing = persistedData.getBoolean("IsAddPathFuzzing"); 653 | setBoolConf(_IsAddPathFuzzing, isAddPathFuzzing); 654 | String _PathFuzzingList = persistedData.getString("PathFuzzingList"); 655 | setStrConf(_PathFuzzingList, pathFuzzingList); 656 | 657 | String _StatusCodeFilterList = persistedData.getString("StatusCodeFilterList"); 658 | setStrConf(_StatusCodeFilterList, statusCodeFilterList); 659 | 660 | Boolean _IsBaseURLFind = persistedData.getBoolean("IsBaseURLFind"); 661 | setBoolConf(_IsBaseURLFind, isBaseURLFind); 662 | 663 | Boolean _IsSetHeaders = persistedData.getBoolean("IsSetHeaders"); 664 | setBoolConf(_IsSetHeaders, isSetHeaders); 665 | String _SetHeaderList = persistedData.getString("SetHeaderList"); 666 | setStrConf(_SetHeaderList, setHeaderList); 667 | } 668 | 669 | void setBoolConf(Boolean key, JCheckBox jCheckBox) { 670 | if (key != null) { 671 | jCheckBox.setSelected(key); 672 | } 673 | } 674 | 675 | void setStrConf(String key, JTextComponent jTextComponent) { 676 | if (key != null) { 677 | jTextComponent.setText(key); 678 | } 679 | } 680 | 681 | void setLang(String lang) { 682 | if (lang.equals("CN")) { 683 | Lang.setSelectedItem("CN"); 684 | title.setText("API剑 v1.0.6 by NSFOCUS & APT250 --- M1n9K1n9"); 685 | 686 | isReqAPI.setText("允许主动对API请求"); 687 | isUseHeader.setText("是否使用原header"); 688 | isTiming.setText("启用主动http请求速率"); 689 | isStop.setText("立即停止发送所有请求(急刹车)"); 690 | isAddPathFuzzing.setText("是否在主动请求时额外添加自定义路径请求(一行一个,非/开头)"); 691 | fscLabel1.setText("过滤掉非200的自定义响应码:(英文逗号隔开)"); 692 | isBaseURLFind.setText("允许API剑主动从响应中寻找baseURL并主动对baseURL进行路径拼接"); 693 | isSetHeaders.setText("添加自定义header字段:(自动覆盖已有的header字段)"); 694 | isBypassWarn.setText("启用绕过危险接口访问(接口包含字符串则跳过):逗号隔开"); 695 | saveSiteMap.setText("保存SiteMap数据(未实现)"); 696 | clearSiteMap.setText("清空SiteMap的保存数据(未实现)"); 697 | toSaveSettings.setText("保存范围及所有设置"); 698 | note.setText("强烈不建议单独一行使用*,否则扫出银河系被外星生命捕获信号 或扫进FBI、CIA等敏感组织资产信息 引发核战争或任何反人类罪行,作者概不承担责任\n\n" + 699 | "限定url范围,字符串包含模式,暂不支持正则,一行一个\n" + 700 | "例如:范围:nsfocus.com,其任何子域都将匹配,伪代码:url.contains(你设置的范围)\n" + 701 | "单独一行*默认全部都在范围内,清空范围列表代表全部不在范围内\n\n" + 702 | "API剑官方GitHub仓库:https://github.com/Sugobet/API_Sword\n" + 703 | "如有任何问题或建议,请提交issue,作者将会第一时间处理,万般感谢\n" + 704 | "NSF也可通过企业微信联系M1n9K1n9"); 705 | isBackCustomPath.setText("是否在API接口后、参数前额外添加自定义路径"); 706 | useTPool.setText("确定"); 707 | threadNumLabel.setText("线程数:"); 708 | filterLabel.setText("过滤器:"); 709 | expandAll.setText("展开节点"); 710 | collapseAll.setText("收起节点"); 711 | clearAll.setText("清空数据"); 712 | } else { 713 | Lang.setSelectedItem("EN"); 714 | title.setText("API Sword v1.0.6 by NSFOCUS & APT250 --- M1n9K1n9"); 715 | 716 | isReqAPI.setText("Allow active API requests"); 717 | isUseHeader.setText("Use the original header"); 718 | isTiming.setText("Enable active HTTP request rate"); 719 | isStop.setText("Immediately stop sending all requests"); 720 | isAddPathFuzzing.setText("Add a custom path for requests (not starting with /)"); 721 | fscLabel1.setText("Filter custom response codes other than 200:"); 722 | isBaseURLFind.setText("Allow active search for the baseURL in the response and concatenate the path to the baseURL"); 723 | isSetHeaders.setText("Add custom header fields: (automatically overwrite existing header fields)"); 724 | isBypassWarn.setText("Enable bypassing dangerous interface access (skip if the interface contains a string):"); 725 | saveSiteMap.setText("Save SiteMap data (not implemented)"); 726 | clearSiteMap.setText("Clear saved SiteMap data (not implemented)"); 727 | toSaveSettings.setText("Save scope and all settings"); 728 | note.setText("Using * on a single line is strongly discouraged. Doing so could result in signals being captured by alien life forms in the galaxy, or sensitive information from organizations like the FBI and CIA being captured, potentially leading to nuclear war or any crimes against humanity. The author assumes no responsibility.\n\n" + 729 | "To limit URL ranges, use a string containing a pattern. Regular expressions are not currently supported. Use one per line.\n" + 730 | "For example, if the range is: nsfocus.com, any subdomain will be matched. In pseudocode: url.contains(your set range)\n" + 731 | "A single * on a single line defaults to matching all domains within the range. Clearing the range list excludes all domains.\n\n" + 732 | "API Sword Official GitHub Repository: https://github.com/Sugobet/API_Sword\n" + 733 | "If you have any questions or suggestions, please submit an issue. The author will address it promptly. Thank you very much." 734 | ); 735 | isBackCustomPath.setText("Add a custom path after the API interface and before the parameter"); 736 | useTPool.setText("Use"); 737 | threadNumLabel.setText("Thread number:"); 738 | filterLabel.setText("filter:"); 739 | expandAll.setText("Expand all"); 740 | collapseAll.setText("Collapse all"); 741 | clearAll.setText("Clear all"); 742 | } 743 | } 744 | } 745 | --------------------------------------------------------------------------------