先说结论给BurpSuite插件加一个“未授权访问请求”功能本质上就是把[[手动测试中重复了无数次的“直接请求目标接口看它到底要不要身份验证”这个动作变成一个一键触发、自动审计的能力。我在给某内部安全工具写扩展的时候最深的感触是未授权访问这个漏洞形态单看危害评级可能不算高但一旦目标系统存在大量内部接口、管理后台或者调试路由它的实际风险会呈指数级上升。而且测试这类漏洞的方法论其实特别固定——无非就是去掉Cookie、去掉Authorization头、改成直接访问静态资源路径或API端点然后观察响应差异。这个逻辑完全适合固化到插件里。这篇文章不打算从头教你怎么搭插件工程而是围绕“添加未授权访问请求功能”这件事把需求拆解、API选型、代码实现、调试技巧和踩坑记录完整走一遍。如果你已经在用Java写Burp插件或者正打算把你的手工流程脚本化这篇应该能给你一份可以直接抄作业的参考。1. 未授权访问检测到底在测什么先说清楚这个功能的检测目标不然代码写到一半容易失控。未授权访问通俗讲就是一个接口本来应该要有登录态才能访问但由于配置疏忽、鉴权中间件漏配、或者接口设计得太多太杂导致白名单覆盖过广结果攻击者不带任何凭证直接打过去服务器居然正常返回了数据。典型的场景包括管理后台路径直接可访问比如/admin、/system、/consoleAPI接口不校验身份比如/api/v1/user/info直接返回用户详情内部服务暴露到公网比如/actuator、/druid、/swagger-ui.html越权场景变种带低权限的Cookie访问高权限接口这个严格说叫越权但很多扫描器会把这类响应差异也归类到未授权。测试手法上最基础也最有效的就是“剥壳法”——把请求里跟身份认证相关的部分全部剥掉再重新发一次看响应码、响应体长度、关键返回字段是否跟原请求一致。这里有个细节值得注意如果未授权请求返回了401或302重定向那说明鉴权是生效的不算漏洞如果返回200或者只做了很弱的拦截比如前端跳转但后端数据照样返回那就要深入看了。我在给某跨平台系统做测试的时候遇到过一种情况前端路由做了登录拦截但后端接口并没有校验直接访问API路径返回了完整的业务数据。这种问题用浏览器手工测很容易漏因为你在浏览器里访问接口时浏览器会自动带上当前站点的Cookie你根本意识不到“哦原来不加Cookie也能通”。所以把这个测试动作放到插件里自动执行价值就在这里——它能剥掉所有环境自带的认证因素纯粹地探测一次。另外要说清楚未授权访问检测跟普通的漏洞扫描不一样它的重点是“响应对比”不是“漏洞特征匹配”。普通的扫描器是拿着已知漏洞的签名去匹配响应内容而未授权检测更像是一种行为审计——看的是接口在不同认证状态下的表现差异。2. BurpSuite扩展API选型IScannerCheck还是自定义右键菜单做Burp插件开发最核心的就是选对扩展点。加未授权访问请求功能无非两条路一条是主动扫描挂到主动扫描器里另一条是手动触发通过右键菜单/自定义UI。2.1 主动扫描适合批量资产检测如果你希望这个功能在爬取到大量请求之后自动跑一遍那应该实现IScannerCheck接口。这样Burp在主动扫描阶段就会把每个请求交给你注册的Scanner插件开发者可以在doPassiveScan或doActiveScan里做检测逻辑。主动扫描的优势是覆盖面广爬虫抓到的所有请求都能过一遍筛子。但问题也很明显——误报率会比较高。因为爬虫抓到的请求五花八门有很多压根就不是业务接口比如静态资源、图片、第三方回调地址这些路径不存在鉴权需求你不带Cookie访问返回200是正常的拿这个报漏洞那就闹笑话了。所以如果要走IScannerCheck路线必须加过滤规则只对包含API特征、内含参数、返回JSON或XML的请求做检测静态资源和纯文件下载路径直接跳过。2.2 右键菜单适合定向验证和渗透场景我最终选择的是右键菜单方式也就是实现IContextMenuFactory接口把“未授权访问请求”做成一个右键菜单项选中某个请求后点击触发。这个选择的原因很实际渗透测试场景下你关注的目标通常已经缩小到一个或几个站点没必要全站批量扫右键菜单能保留当前请求的所有上下文信息URL、参数、Headers方便做精确的“剥壳”处理单个请求的检测结果可以直接展示在弹窗或Output里调试起来直观得多。插入点理解起来也很简单BurpSuite的任何位置Proxy、Repeater、HTTP History右键菜单里都有Extensions这个子菜单你实现IContextMenuFactory之后返回的JMenuItem会自动挂到这个子菜单里。用户操作路径就是右键 → Extensions → 你定义的菜单项 → 触发检测。2.3 两个接口都实现其实也不冲突如果想把两个能力都占了完全可以在同一个插件里同时实现IScannerCheck和IContextMenuFactory。我的做法是右键菜单里放“单个请求未授权检测”用于手工定向测试主动扫描的doActiveScan里放“批量对比检测”但加了一个开关选项默认不开启避免误报刷屏。3. 代码结构设计与核心实现这个插件的工程结构我建议这样组织后面扩展起来会省很多事src/ ├── BurpExtender.java // 入口注册各类扩展点 ├── core/ │ ├── UnauthorizedAccessChecker.java // 核心检测逻辑 │ ├── HttpRequestBuilder.java // 请求构造器剥壳/重放 │ └── ResponseComparator.java // 响应对比器 └── ui/ └── ContextMenuProvider.java // 右键菜单实现3.1 入口类注册扩展点在BurpExtender里只需要做一件事把菜单工厂和扫描器插件注册进去。package burp; public class BurpExtender implements IBurpExtender, IContextMenuFactory, IScannerCheck { private static final String EXTENSION_NAME Unauthorized Access Checker; private IExtensionHelpers helpers; private IBurpExtenderCallbacks callbacks; Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { this.callbacks callbacks; this.helpers callbacks.getHelpers(); callbacks.setExtensionName(EXTENSION_NAME); // 注册右键菜单 callbacks.registerContextMenuFactory(this); // 注册主动扫描器 callbacks.registerScannerCheck(this); callbacks.printOutput([*] Unauthorized Access Checker loaded.); } Override public ListJMenuItem createMenuItems(IContextMenuInvocation invocation) { // 只在有请求数据时显示菜单 IHttpRequestResponse[] messages invocation.getSelectedMessages(); if (messages null || messages.length 0) { return null; } JMenuItem menuItem new JMenuItem(Send Unauthorized Request); menuItem.addActionListener(e - { for (IHttpRequestResponse message : messages) { performUnauthorizedCheck(message); } }); ListJMenuItem menuItems new ArrayList(); menuItems.add(menuItem); return menuItems; } private void performUnauthorizedCheck(IHttpRequestResponse message) { // 交给核心检测器去执行 UnauthorizedAccessChecker checker new UnauthorizedAccessChecker(callbacks, helpers); checker.check(message); } // IScannerCheck 相关方法省略后面单独讲 }这段代码有几个值得注意的地方。IContextMenuInvocation.getSelectedMessages()返回的是当前选中的请求信息可能是一个也可能是多个。这里我做了循环处理意味着你可以在HTTP History里多选几条请求一次性批量检测。这个交互细节很实用手工测试时经常想对某个接口的几个变体请求同时验证。3.2 请求构造器如何干净地“剥掉”身份信息未授权访问检测的第一步是把原始请求里的身份凭证全部去掉。身份凭证存在的形式主要有四种Cookie头最常见Authorization头Bearer Token、Basic Auth自定义认证头如 X-Token、Token、session-id 等请求体中携带的认证参数相对少见但存在public class HttpRequestBuilder { private IExtensionHelpers helpers; // 需要剥离的敏感头列表 private static final String[] SENSITIVE_HEADERS { Cookie, Authorization, X-Token, X-Api-Key, Token, Session-ID, X-Requested-With // 某些框架会用它做CSRF判断未授权访问检测时也可以去掉 }; public IHttpRequestResponse buildUnauthorizedRequest(IHttpRequestResponse originalRequest) { IHttpRequestResponse unauthorizedResponse new MockHttpRequestResponse(); // 1. 解析原始请求 byte[] requestBytes originalRequest.getRequest(); IRequestInfo requestInfo helpers.analyzeRequest(originalRequest); // 2. 获取原始header列表 ListString headers requestInfo.getHeaders(); // 3. 过滤掉敏感header ListString sanitizedHeaders new ArrayList(); for (String header : headers) { String headerName header.split(:)[0].trim(); if (!isSensitiveHeader(headerName)) { // 过滤掉cookie等身份标识 sanitizedHeaders.add(header); } } // 4. 如果有请求体保留请求体POST参数一般不剥因为可能是业务参数 byte[] body Arrays.copyOfRange(requestBytes, requestInfo.getBodyOffset(), requestBytes.length); // 5. 构建新请求 byte[] unauthRequest helpers.buildHttpMessage(sanitizedHeaders, body); // 6. 发送请求 IHttpService httpService originalRequest.getHttpService(); return originalRequest.getHttpService() ! null ? callbacks.makeHttpRequest(httpService, unauthRequest) : null; } private boolean isSensitiveHeader(String headerName) { for (String sensitive : SENSITIVE_HEADERS) { if (sensitive.equalsIgnoreCase(headerName)) { return true; } } return false; } }这里有个很重要的设计决策请求体里的业务参数要不要剥我的答案是不要剥。原因是很多API的请求体参数本身就属于业务逻辑的一部分比如登录接口的username/password字段、查询接口的keyword字段这些参数是接口正常工作需要的输入不属于身份认证范畴。如果你把请求体整个去掉那测出来的就不是“未授权访问”而是“缺少参数的异常请求”响应码大概率是400或422全无参考价值。但是有一种情况例外如果请求体里出现了token、sessionId之类的字段且你能明确判断它是用于身份校验的那也可以剥掉。这个我建议做成可配置项默认关掉避免误伤业务参数。3.3 核心检测器响应对比与漏洞判定请求发出去了下一步就是对比响应。核心思路是如果去掉身份凭证后的请求跟原始请求的响应基本一致那就大概率存在未授权访问问题。public class UnauthorizedAccessChecker { private IBurpExtenderCallbacks callbacks; private IExtensionHelpers helpers; private static final int THRESHOLD_SCORE 80; // 相似度阈值 public void check(IHttpRequestResponse originalMessage) { // 1. 构建未授权请求并发送 HttpRequestBuilder builder new HttpRequestBuilder(helpers, callbacks); IHttpRequestResponse unauthorizedResponse builder.buildUnauthorizedRequest(originalMessage); if (unauthorizedResponse null) { callbacks.printOutput([!] Request failed!); return; } // 2. 分别解析两个响应 IResponseInfo originalResp helpers.analyzeResponse(originalMessage.getResponse()); IResponseInfo unauthorizedResp helpers.analyzeResponse(unauthorizedResponse.getResponse()); int originalStatus originalResp.getStatusCode(); int unauthorizedStatus unauthorizedResp.getStatusCode(); byte[] originalBody getResponseBody(originalMessage.getResponse()); byte[] unauthorizedBody getResponseBody(unauthorizedResponse.getResponse()); // 3. 判定逻辑 boolean isVulnerable false; String reason ; // 情形一未授权请求返回200或业务状态码且响应体相似度高 if (unauthorizedStatus 200 || unauthorizedStatus 201) { double similarity ResponseComparator.computeSimilarity(originalBody, unauthorizedBody); if (similarity THRESHOLD_SCORE) { isVulnerable true; reason String.format(Unauthorized request returns %d, response similarity: %.2f%%, unauthorizedStatus, similarity); } } // 情形二两边响应码不同但未授权响应暴露了敏感信息 // 比如原始请求200未授权请求302重定向到登录页这种情况不算漏洞 // 但未授权请求返回403却带着管理页面内容这种要人工确认 // 4. 输出结果 if (isVulnerable) { callbacks.printOutput([] Potential Unauthorized Access Found!); callbacks.printOutput( URL: getURL(originalMessage)); callbacks.printOutput( Reason: reason); } else { callbacks.printOutput([-] No unauthorized access detected.); } } }3.4 响应相似度计算别用Hash对比在写响应对比器时最容易犯的错就是用哈希值做严格对比。实际上未授权请求跟原始请求的响应不可能一模一样差别主要来自这么几个地方未授权请求没有Cookie服务器可能不会返回个性化的用户信息某些动态页面包含时间戳、随机数、CSRF Token两次请求内容天然不同有的服务器对未认证请求会返回一个简化版的响应头比如去掉Set-Cookie。所以相似度算法要保留一定的弹性空间。我实际用的是“归一化后的文本相似度”方案提取响应体去掉所有HTML标签、空白字符、时间戳类动态字段然后按行做包含度匹配。public class ResponseComparator { // 计算响应体的相似度归一化后做文本包含匹配 public static double computeSimilarity(byte[] originalBody, byte[] unauthorizedBody) { if (originalBody null || unauthorizedBody null) { return 0.0; } String originalStr normalize(new String(originalBody)); String unauthorizedStr normalize(new String(unauthorizedBody)); if (originalStr.isEmpty() || unauthorizedStr.isEmpty()) { return 0.0; } // 如果长度差异过大直接认为不相似 if (originalStr.length() unauthorizedStr.length() * 3) { return 0.0; } // 统计原始响应中出现在未授权响应中的行数 String[] originalLines originalStr.split(\n); int matchedLines 0; int totalLines Math.max(originalLines.length, 1); for (String line : originalLines) { line line.trim(); if (line.length() 20) continue; // 忽略过短文本行减少干扰 if (unauthorizedStr.contains(line)) { matchedLines; } } return (double) matchedLines / totalLines * 100; } private static String normalize(String input) { // 1. 去HTML标签 String noHtml input.replaceAll([^]*, ); // 2. 去空白字符 String noWhitespace noHtml.replaceAll(\\s, \n); // 3. 去常见动态字段可选djb哈希等 return noWhitespace.trim(); } }这个算法的思路跟人工判断逻辑是一致的看主要内容在不在里面而不是看两个字符串等不等。在测试某内部系统时原始请求返回了用户的订单列表未授权请求直接访问接口返回了同样的订单列表但少了右上角的用户名。因为大部分订单内容都是一样的相似度算出来92%超过了阈值漏洞就标记出来了。如果我用哈希对比这直接就漏报了。3.5 IScannerCheck的接入可选扩展把功能挂到主动扫描里需要实现IScannerCheck的四个方法。Override public ListIScanIssue doPassiveScan(IHttpRequestResponse baseRequestResponse) { // 被动扫描不主动发包 // 这里可以检测响应中是否暴露了敏感信息但不适合做未授权访问检测 return null; } Override public ListIScanIssue doActiveScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) { // 主动扫描对于每个插入点构造未授权请求 ListIScanIssue issues new ArrayList(); // 只在URL插入点执行避免对参数做过多变体 if (insertionPoint.getInsertionPointName().equals(URL)) { UnauthorizedAccessChecker checker new UnauthorizedAccessChecker(callbacks, helpers); boolean vulnerable checker.checkForIssue(baseRequestResponse); if (vulnerable) { issues.add(new UnauthorizedScanIssue(...)); } } return issues; } Override public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue) { // 如果URL相同认为是重复issue if (existingIssue.getUrl().equals(newIssue.getUrl())) { return -1; } return 0; }主动扫描的地狱级坑点是性能。每个请求、每个插入点都会触发doActiveScan如果不加过滤一次扫描会发出海量重复请求。我的建议是加两个限制只对包含API特征的路径执行检测/api/、/v1/、/admin/等每个请求只测一次不要对每个参数插入点都执行完整检测逻辑。4. 交互体验优化结果展示与UI设计功能做完了只是第一步难的是怎么让结果直观可读。我见过不少插件功能逻辑没问题但输出信息极简只往Output窗口打一行“Possible vulnerability”完全没法用。我自己在UI上做了这几个优化。4.1 结果面板把对比信息完整展示不要只打印结论把对比的细节也打出来。方便在报告里引用也方便人工复核callbacks.printOutput(); callbacks.printOutput([] Unauthorized Access Detected); callbacks.printOutput( URL : url); callbacks.printOutput( Method : method); callbacks.printOutput( Original Status : originalStatus); callbacks.printOutput( Unauthorized Status: unauthorizedStatus); callbacks.printOutput( Original Length : originalRespLength); callbacks.printOutput( Unauthorized Length: unauthRespLength); callbacks.printOutput( Similarity Score : similarityScore %); callbacks.printOutput( Sensitive Headers Removed: Cookie, Authorization); callbacks.printOutput();打印这些信息看起来简单但真到复盘的时候能省大量时间。比如你发现某条请求未授权访问返回了200但响应体是空的相似度只有10%这类大概率是假阳性。如果只打印“Vulnerable”你根本不知道误报在哪。4.2 右键菜单异步执行别卡UI还有个容易被忽略的体验问题网络请求是阻塞操作如果在EDTEvent Dispatch ThreadSwing的事件分发线程里同步执行UI会直接卡死。尤其是批量检测多个请求时界面会变成“未响应”状态。解决方式是用SwingWorker做异步处理menuItem.addActionListener(e - { SwingWorkerVoid, String worker new SwingWorker() { Override protected Void doInBackground() throws Exception { for (IHttpRequestResponse message : messages) { performUnauthorizedCheck(message); publish(Checked: getURL(message)); } return null; } Override protected void process(ListString chunks) { for (String chunk : chunks) { callbacks.printOutput(chunk); } } }; worker.execute(); });4.3 彩色Console输出Burp的Console输出是纯文本不原生支持颜色但可以通过ANSI转义码实现private static final String ANSI_RESET \u001B[0m; private static final String ANSI_RED \u001B[31m; private static final String ANSI_GREEN \u001B[32m; private static final String ANSI_YELLOW \u001B[33m;实测下来Burp的Console是支持ANSI的基本颜色的。红色标记漏洞绿色标记正常黄色标记待确认一眼就能扫到重点信息。5. 常见问题、误报分析与排查技巧这部分全是实际操作中踩出来的经验。功能逻辑看着简单但用起来才知道坑在哪。5.1 误报场景一静态路径被误判为未授权访问最常见也最头疼的误报来源。前端项目基本都会引用一堆JS、CSS、图片资源这些资源大多不需要鉴权去掉Cookie访问照样返回200响应体跟原始请求几乎一致。然后插件就报“未授权访问”了。排查思路看响应Content-TypeJS、CSS、PNG、JPG这几种MIME类型直接跳过。HTML页面也不一定都是业务页面纯静态展示页同样会误报。private boolean isStaticResource(IHttpRequestResponse message) { IResponseInfo respInfo helpers.analyzeResponse(message.getResponse()); String contentType ; for (String header : respInfo.getHeaders()) { if (header.toLowerCase().startsWith(content-type)) { contentType header.split(:)[1].trim(); break; } } return contentType.contains(javascript) || contentType.contains(css) || contentType.contains(image) || contentType.contains(font) || contentType.contains(video); }5.2 误报场景二302跳转被当成漏洞还有一种情况是未授权请求返回302跳转到统一登录页。这个其实说明鉴权是生效的不应该报漏洞。但有些系统设计得比较激进前端做了302跳转后端仍然返回部分业务数据——这种情况才需要标记。处理方式未授权请求返回302时默认不做判定。但如果启用了“跟随重定向后再对比”的选项可以进一步分析重定向后的响应。不过实测这个方法不太可靠因为Burp的makeHttpRequest不会默认跟随重定向你需要自己递归请求Location地址逻辑会变得复杂。我建议这个功能做成手动确认项别放进自动流程。5.3 误报场景三请求本身就带业务必填参数有些接口必须在请求体里带有效业务参数否则直接报400。如果你把Cookie剥掉了但保留了业务参数服务器返回400这显然不是未授权访问。处理方式判定时对400、422这类客户端错误码做特殊处理。除非响应体里出现了明显的敏感数据否则400一律视为“参数校验失败”不报漏洞。if (unauthorizedStatus 400 || unauthorizedStatus 422) { callbacks.printOutput([i] Request rejected due to missing parameters, not an auth issue.); return; }5.4 真实漏洞却漏报的常见情况误报是烦但漏报才是真正的灾难。我在调试中发现最容易被漏报的场景是“前后端分离”的系统。典型情况前端先调/user/current判断用户登录状态后端如果检测到未登录返回401。但业务接口/api/orders/list却没有做同样的检测直接返回数据。如果插件只对比了状态码原请求200、未授权请求401那相似度再高也会被状态码不一致给拦下来导致漏报。优化方案不要只看状态码是否一致而是看“未授权请求的状态码是否属于正常业务范畴”。如果未授权请求返回了200、201、301非登录页跳转且响应体里包含了业务特征字段如status:success、data、list、total等就应该标记为可疑。// 状态码兜底逻辑 boolean isBusinessStatusCode unauthorizedStatus 200 || unauthorizedStatus 201 || unauthorizedStatus 202; // 响应体业务特征 String body new String(unauthorizedBody); boolean hasBusinessData body.contains(\status\) || body.contains(\data\) || body.contains(\success\) || body.contains(\list\) || body.contains(\total\); if (isBusinessStatusCode hasBusinessData) { // 进一步判定 isVulnerable true; }5.5 目标限制别把自己封了做未授权检测要频繁发送篡改过的请求目标系统的WAF或风控模块可能识别出这种异常行为并封IP。实测中踩过这个坑对一个生产环境系统跑了20多个请求触发风控直接被限制访问。经验默认并发数控制在1不要同时发多个请求每次请求前加一个可配置的延时默认300ms对同一Host不要连续发起超过N个检测请求N可配置如果目标处于生产环境建议先在测试环境验证插件逻辑再切到真实目标。5.6 请求头丢失问题Referer和Origin在剥离敏感头时有个细节容易被忽略Referer和Origin头。有些系统的鉴权逻辑依赖这两个头做来源判断比如CSRF防护里校验OriginSSO登录流程里校验Referer。虽然它们不直接携带凭证但剥离后可能导致请求失败从而产生误报。我的策略是这两个头保留。因为它们不属于身份凭证而且去掉之后很容易破坏业务逻辑导致响应异常反而干扰判断。6. 功能扩展思路从单点检测到链式利用基础功能写完如果你还想继续深化有几个扩展方向我认为价值很高。6.1 从“检测”到“利用”自动访问发现接口未授权访问检测的价值如果只停留在“发现接口有问题”其实只发挥了一半。更进阶的玩法是检测到未授权可访问的接口后自动爬取该接口暴露的关联资源比如JSON里引用的/api/v1/user/{id}继续对子路径做二次未授权检测。这样从“单个接口未授权”变成“整条接口链未授权”漏洞报告的说服力会强很多。实现方案是解析响应JSON中的URL模式用正则提取然后递归检测。6.2 支持自定义鉴权头配置不同系统的鉴权方式差异很大有的用X-Access-Token有的用X-User-SessionId有的甚至用自定义加密头。把敏感头列表做成可配置是最实际的需求。可以用Burp插件自带的配置UI框架比如ITab接口画一个简单的面板让用户自己输入需要剥离的Header名称。也可以直接读配置文件在扩展目录下放一个unauth_headers.conf启动时解析。private ListString loadCustomSensitiveHeaders() { Path configPath Paths.get(System.getProperty(user.home), .burp-unauth-checker, headers.conf); if (!Files.exists(configPath)) { return Collections.emptyList(); } try { return Files.readAllLines(configPath).stream() .map(String::trim) .filter(line - !line.isEmpty() !line.startsWith(#)) .collect(Collectors.toList()); } catch (IOException e) { callbacks.printError(Failed to load custom headers: e.getMessage()); return Collections.emptyList(); } }6.3 联动响应编辑器一键发送到Repeater很多安全测试流程里用插件检测到可疑请求后下一步操作是手动打开Repeater人工做精细化验证。可以加一个联动按钮检测到漏洞后直接打开Repeater并把未授权请求加载进去。Burp的API里IBurpExtenderCallbacks.sendToRepeater就是干这个的callbacks.sendToRepeater(httpService.getHost(), httpService.getPort(), httpService.getProtocol().equals(https), unauthorizedRequest, Unauthorized- index);这样从“检测发现”到“人工验证”的闭环就打通了操作效率高了不少。6.4 生成未授权访问报告最后很多人写插件忽略报告输出环节。用了一段时间之后检测记录散落在Console里难回溯、难统计、难写进渗透测试报告。我加了一个简单的CSV导出功能点击自定义Tab上的“Export Report”把当前所有检测结果按URL、Method、原始状态码、未授权状态码、相似度、判定结果、时间戳这些字段导出成CSV。实测对后期整理交付报告帮助很大。private void exportReport(File outputFile) { try (PrintWriter writer new PrintWriter(new FileWriter(outputFile))) { writer.println(URL,Method,OriginalStatus,UnauthorizedStatus,Similarity,Result,Timestamp); for (CheckRecord record : checkRecords) { writer.printf(%s,%s,%d,%d,%.2f,%s,%s%n, record.getUrl(), record.getMethod(), record.getOriginalStatus(), record.getUnauthorizedStatus(), record.getSimilarity(), record.getResult(), record.getTimestamp()); } } catch (IOException e) { callbacks.printError(Failed to export report: e.getMessage()); } }7. 测试与调试方法总结最后简单整理一下我验证插件逻辑的测试流程。插件开发最怕的就是在Burp界面和IDE之间来回切换效率极低。我总结出一套相对顺畅的调试流程。7.1 准备本地测试环境用本地环境做靶场是最快的。起一个简单的Spring Boot应用写几个测试接口/api/public/info无需鉴权正常返回/api/private/userinfo需要Interceptor校验Token/api/private/admin需要校验Token且要求admin角色/static/app.js静态资源。这样就能覆盖所有的检测分支正常漏洞、已拦截请求、静态资源过滤。7.2 Burp加载扩展在Burp的Extender选项卡里Add → Java → 选择编译好的Jar包。加载成功后看Console输出确认扩展注册成功。调试时建议打开“Run in background”的日志开关让Burp把插件内部的printOutput内容全部打印到Console。每次改动代码后要重新编译然后重新加载Jar。7.3 手动验证逻辑分支按这个顺序逐项验证正常鉴权接口未授权请求返回401插件不报警真正未授权接口未授权请求返回200且响应相似度高插件报警静态资源去掉Cookie请求CSS插件不应报警参数不完整请求返回400插件不报警302跳转接口插件提供待确认标记不直接报告漏洞。把这些分支全部跑通功能才算达到可用状态。我在实际调试时发现第四种和第五种分支最容易出错建议多调几轮。标题命名上建议右键菜单的名称不要起得太“攻击性”像“Check Unauthorized Access”这种中性描述就足够。插件开发这件事逻辑本身往往不复杂复杂的是把边缘情况想清楚。在写这篇实战笔记的过程中我最大的一个体会是未授权访问检测的本质不是“找一个Bug”而是“理解这个系统的鉴权信任边界在哪里”。只有想清楚哪些部分是系统默认信任的、哪些是需要主动验证的才能把检测规则设计得既减少误报又能覆盖真实风险。