5分钟上手BurpSuite Montoya API:构建自定义Proxy拦截器

发布时间:2026/6/24 22:15:21
5分钟上手BurpSuite Montoya API:构建自定义Proxy拦截器 1. 项目概述为什么我们需要一个自定义的Proxy拦截器如果你是一名Web安全测试人员或者渗透测试工程师BurpSuite这个名字对你来说就像吃饭用的筷子一样熟悉。它强大的代理Proxy功能能让我们轻松拦截、查看和修改所有经过的HTTP/HTTPS流量这是安全测试的基石。但BurpSuite自带的拦截和修改功能在面对一些复杂、重复或需要深度定制的场景时就显得有些力不从心了。比如你想自动给每一个请求的Cookie里追加一个特定的签名或者批量修改请求体中的某个参数值又或者想基于响应内容自动触发下一个测试步骤。手动操作效率太低还容易出错。这就是BurpSuite Montoya API的用武之地。Montoya API是BurpSuite官方推出的、面向开发者的新一代扩展开发接口。它比老旧的Extender API更现代、更强大也更好用。通过它我们可以用Java或任何JVM语言编写自己的插件深度集成到BurpSuite的工作流中。而开发一个自定义的Proxy拦截器正是Montoya API最核心、最实用的应用场景之一。它允许我们编写代码在请求到达目标服务器之前或者在响应返回给浏览器之后精确地“插手”这个过程实现自动化、智能化的流量处理。今天我就带你用5分钟时间基于Montoya API从零开始构建一个功能完整的Proxy拦截器。这个拦截器不仅能演示基础的请求/响应修改还会包含一些实战中才会遇到的细节处理比如处理HTTPS、处理不同编码的请求体、避免循环拦截等。我会附上一个可以直接运行的完整Demo项目你拿到手就能编译、加载到BurpSuite里看到效果。2. 环境准备与项目初始化在开始敲代码之前我们需要把“厨房”收拾好。开发BurpSuite插件本质上就是开发一个Java项目只不过它最终要打包成一个JAR文件并被BurpSuite加载。2.1 核心工具链选择首先你需要安装以下工具Java开发工具包 (JDK)必须使用JDK 11或更高版本。BurpSuite自身基于Java其插件也运行在JVM上。我推荐使用OpenJDK 11或17可以从Adoptium等网站下载。安装后在终端运行java -version和javac -version确认版本。构建工具我强烈推荐使用Maven或Gradle。它们能帮你管理依赖、构建项目比手动管理JAR包方便一万倍。这里我以Maven为例因为它配置简单生态成熟。确保安装了Maven (mvn -version)。集成开发环境 (IDE)IntelliJ IDEA社区版免费或 Eclipse。IDEA对Java和Maven的支持更友好自动补全和依赖提示能极大提升效率。BurpSuite Professional你需要一个BurpSuite专业版。社区版虽然免费但不支持加载自定义扩展插件。这是开发的前提条件。注意网上流传的所谓“破解版”BurpSuite存在巨大安全风险可能内置后门或恶意代码导致你的测试流量、敏感数据泄露。请务必通过官方渠道获取正版授权这是对自己和客户负责。2.2 创建Maven项目与配置依赖打开你的IDE创建一个新的Maven项目。GroupId和ArtifactId可以随意比如com.example和burp-montoya-proxy-demo。项目创建成功后打开根目录下的pom.xml文件这是Maven项目的核心配置文件。我们需要在这里声明对BurpSuite Montoya API的依赖。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdburp-montoya-proxy-demo/artifactId version1.0-SNAPSHOT/version properties maven.compiler.source11/maven.compiler.source maven.compiler.target11/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies !-- BurpSuite Montoya API 依赖 -- !-- 注意这个依赖的scope必须是provided因为API实现在BurpSuite运行时提供 -- dependency groupIdnet.portswigger.burp.extensions/groupId artifactIdmontoya-api/artifactId version2023.1/version !-- 请使用与你BurpSuite版本匹配的API版本 -- scopeprovided/scope /dependency /dependencies build plugins !-- 用于打包所有依赖到一个可执行的JAR中Fat Jar -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-assembly-plugin/artifactId version3.3.0/version configuration descriptorRefs descriptorRefjar-with-dependencies/descriptorRef /descriptorRefs archive manifest !-- 指定主类这是插件的入口 -- mainClasscom.example.BurpExtension/mainClass /manifest /archive /configuration executions execution phasepackage/phase goals goalsingle/goal /goals /execution /executions /plugin /plugins /build /project关键点解析scope设置为provided这是最关键的一步。它告诉Maven“这个库在编译和测试时需要但不用打包进最终的JAR里因为运行环境BurpSuite会提供它。” 如果打包进去可能会导致类冲突插件无法加载。版本匹配montoya-api的版本号应尽量与你使用的BurpSuite专业版主版本号一致。例如BurpSuite 2023.1就对应API 2023.1。不匹配可能导致某些新API无法使用或兼容性问题。Assembly插件我们配置了maven-assembly-plugin来生成一个“胖JAR”Fat Jar即把所有项目自身的依赖虽然本例中没有其他依赖都打包进一个JAR文件。这样最终产物就是一个独立的xxx-jar-with-dependencies.jar文件方便加载。保存pom.xml后IDE通常会开始自动下载依赖。如果没自动开始可以在终端进入项目目录运行mvn compile命令来触发下载和编译。3. 核心代码实现编写你的第一个拦截器环境搭好依赖配齐现在可以开始写核心代码了。BurpSuite插件需要一个入口点并实现特定的接口来注册我们的功能。3.1 创建扩展主类在src/main/java/com/example/目录下com.example对应你的groupId创建一个Java类命名为BurpExtension。这个类必须实现BurpExtension接口。package com.example; import burp.api.montoya.BurpExtension; import burp.api.montoya.MontoyaApi; import burp.api.montoya.logging.Logging; // 实现BurpExtension接口这是所有Montoya插件的入口 public class BurpExtension implements BurpExtension { private MontoyaApi api; private Logging logging; Override public void initialize(MontoyaApi api) { // 保存API实例和日志工具后续会频繁用到 this.api api; this.logging api.logging(); // 设置扩展名称这会在Burp的Extender标签页显示 api.extension().setName(5分钟Proxy拦截器Demo); // 输出日志证明插件已成功加载 logging.logToOutput( 5分钟Proxy拦截器Demo 已加载 ); // 注册我们的HTTP处理器即拦截器 // 这是最关键的一步将我们的逻辑挂载到Burp的代理流量上 api.proxy().registerRequestHandler(new MyProxyRequestHandler()); api.proxy().registerResponseHandler(new MyProxyResponseHandler()); logging.logToOutput(请求/响应处理器注册成功); } }代码解读initialize(MontoyaApi api)方法是插件的入口。当BurpSuite加载这个插件JAR时会自动调用此方法并传入一个MontoyaApi对象。这个对象是我们与BurpSuite所有功能交互的“总入口”非常重要我们把它保存为类变量。api.extension().setName(...)为插件设置一个显示名称。api.logging()获取日志工具我们可以用它向BurpSuite的Output和Errors标签页输出信息这是调试插件的生命线。核心注册api.proxy().registerRequestHandler(...)和registerResponseHandler(...)是本章节的重点。它们分别将我们自定义的请求处理器和响应处理器注册到BurpSuite的代理组件中。从此所有流经Proxy的流量都会先经过我们的代码逻辑。3.2 实现请求处理器 (RequestHandler)请求处理器负责处理从客户端浏览器发往服务器的请求。我们创建一个新类MyProxyRequestHandler来实现ProxyRequestHandler接口。package com.example; import burp.api.montoya.core.ByteArray; import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.proxy.ProxyRequestHandler; import burp.api.montoya.proxy.ProxyRequestReceivedAction; import burp.api.montoya.proxy.ProxyRequestToBeSentAction; import static burp.api.montoya.proxy.ProxyRequestReceivedAction.continueWith; import static burp.api.montoya.proxy.ProxyRequestToBeSentAction.continueWith; public class MyProxyRequestHandler implements ProxyRequestHandler { Override public ProxyRequestReceivedAction handleRequestReceived(ProxyRequestReceivedRequest requestReceivedRequest) { // 阶段1请求刚从客户端到达BurpProxy尚未发往服务器 HttpRequest request requestReceivedRequest.request(); String host request.host(); String method request.method(); // 示例1记录所有请求的URL和方法 BurpExtension.getInstance().logging.logToOutput(String.format([请求接收] %s %s, method, request.url())); // 示例2修改特定请求的User-Agent头 if (host.contains(example.com)) { HttpRequest modifiedRequest request.withHeader(User-Agent, My-Custom-Burp-Scanner/1.0); // 注意withHeader方法会替换已存在的同名头或新增头 return continueWith(modifiedRequest); } // 示例3修改POST请求的请求体例如在所有JSON请求中增加一个字段 if (method.equalsIgnoreCase(POST) request.contentType().contains(application/json)) { String originalBody request.bodyToString(); if (!originalBody.isEmpty()) { // 这是一个非常简单的示例实际中应使用JSON库进行解析和操作 String modifiedBody originalBody.replaceFirst(\\}$, , \burp_injected\: true}); HttpRequest modifiedRequest request.withBody(ByteArray.byteArray(modifiedBody)); return continueWith(modifiedRequest); } } // 如果没有需要修改的就原样放行 return continueWith(request); } Override public ProxyRequestToBeSentAction handleRequestToBeSent(ProxyRequestToBeSentRequest requestToBeSentRequest) { // 阶段2请求即将从BurpProxy发往目标服务器 // 这个阶段可以做一些最终检查或修改比如添加基于服务器信息的头 HttpRequest request requestToBeSentRequest.request(); // 示例为所有请求添加一个自定义的请求头标识经过本插件处理 HttpRequest finalRequest request.withAddedHeader(X-Burp-Extension, MyProxyDemo); BurpExtension.getInstance().logging.logToOutput(String.format([请求发送] 为请求添加了自定义头: %s, request.url())); return continueWith(finalRequest); } }关键方法与阶段解析 Montoya API的请求处理分为两个清晰的阶段这比旧API更合理handleRequestReceived请求接收阶段。此时请求刚从客户端你的浏览器到达BurpSuite的代理你可以基于客户端发来的原始信息进行判断和修改。例如根据请求的域名、路径、原始头部来决定是否干预。这是进行条件性修改的主要阶段。handleRequestToBeSent请求发送阶段。此时请求已经过BurpSuite可能的各种处理包括其他扩展、扫描器等即将被发送到目标服务器。你可以在这里进行一些最终的、无条件的调整。比如统一添加一个跟踪头。操作请求对象requestReceivedRequest.request()获取当前的HTTP请求对象。request.withHeader(name, value)设置覆盖或新增一个请求头。request.withAddedHeader(name, value)添加一个请求头如果已存在同名头可能会添加多个。request.withBody(byteArray)替换请求体。这里有个大坑请求体是ByteArray类型直接操作字符串时要特别注意编码。对于非文本内容如图片、加密数据不要用toString()否则会乱码。上面的JSON修改示例仅适用于明确知道是文本的情况。continueWith(request)这是返回的动作告诉BurpSuite“用这个修改后的或原始的请求继续后续流程”。这是最常用的动作。3.3 实现响应处理器 (ResponseHandler)响应处理器负责处理从服务器返回给客户端的响应。创建MyProxyResponseHandler类实现ProxyResponseHandler接口。package com.example; import burp.api.montoya.core.ByteArray; import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.proxy.ProxyResponseHandler; import burp.api.montoya.proxy.ProxyResponseReceivedAction; import burp.api.montoya.proxy.ProxyResponseToBeSentAction; import static burp.api.montoya.proxy.ProxyResponseReceivedAction.continueWith; import static burp.api.montoya.proxy.ProxyResponseToBeSentAction.continueWith; public class MyProxyResponseHandler implements ProxyResponseHandler { Override public ProxyResponseReceivedAction handleResponseReceived(ProxyResponseReceivedRequest responseReceivedRequest) { // 阶段1响应刚从服务器到达BurpProxy尚未发回客户端 HttpResponse response responseReceivedRequest.response(); int statusCode response.statusCode(); BurpExtension.getInstance().logging.logToOutput(String.format([响应接收] 状态码: %d, URL: %s, statusCode, responseReceivedRequest.requestUrl())); // 示例1修改特定状态的响应体 if (statusCode 404) { // 将404页面的内容替换成自定义提示 String customBody htmlbodyh1Not Found (Modified by Burp Extension)/h1pThe resource you requested was not found, but this message is from your Burp plugin./p/body/html; HttpResponse modifiedResponse response.withBody(ByteArray.byteArray(customBody)); return continueWith(modifiedResponse); } // 示例2在HTML响应中注入一段JavaScript用于演示实战中可用于XSS检测等 String contentType response.contentType(); if (contentType ! null contentType.contains(text/html)) { String originalBody response.bodyToString(); if (!originalBody.contains(/body)) { // 如果没有body标签简单追加 originalBody !-- Injected by Burp --; } else { // 在/body标签前注入脚本 String injection scriptconsole.log(Page loaded through Burp Proxy with custom extension);/script; originalBody originalBody.replace(/body, injection /body); } HttpResponse modifiedResponse response.withBody(ByteArray.byteArray(originalBody)); return continueWith(modifiedResponse); } return continueWith(response); } Override public ProxyResponseToBeSentAction handleResponseToBeSent(ProxyResponseToBeSentRequest responseToBeSentRequest) { // 阶段2响应即将从BurpProxy发回客户端 // 这里可以做最后的修改例如移除或修改某些敏感头部 HttpResponse response responseToBeSentRequest.response(); // 示例移除服务器可能泄露的敏感头 HttpResponse finalResponse response.withRemovedHeader(Server) .withRemovedHeader(X-Powered-By); BurpExtension.getInstance().logging.logToOutput(String.format([响应发送] 已清理敏感头部URL: %s, responseToBeSentRequest.requestUrl())); return continueWith(finalResponse); } }响应处理要点阶段划分与请求处理类似响应处理也分received和toBeSent两个阶段逻辑同理。内容类型判断在修改响应体前务必检查Content-Type。对image/png、application/octet-stream等非文本内容进行toString()和字符串替换会导致二进制数据损坏使图片无法显示或文件下载出错。withRemovedHeader这是一个很实用的方法可以用来隐藏服务器信息使目标应用看起来更“安全”或者在测试中观察客户端对缺少某些头的反应。注意性能如果拦截所有流量并执行复杂的字符串操作如全文搜索替换可能会对BurpSuite的性能产生明显影响。在实际开发中应考虑增加更精确的过滤条件如特定域名、特定URL模式。3.4 完善主类并提供实例访问为了让处理器类能方便地记录日志我们需要在主类BurpExtension中提供一个获取静态实例的方法。修改BurpExtension.javapackage com.example; import burp.api.montoya.BurpExtension; import burp.api.montoya.MontoyaApi; import burp.api.montoya.logging.Logging; public class BurpExtension implements BurpExtension { private static BurpExtension instance; // 静态实例引用 private MontoyaApi api; private Logging logging; Override public void initialize(MontoyaApi api) { instance this; // 初始化时设置静态实例 this.api api; this.logging api.logging(); api.extension().setName(5分钟Proxy拦截器Demo); logging.logToOutput( 5分钟Proxy拦截器Demo 已加载 ); api.proxy().registerRequestHandler(new MyProxyRequestHandler()); api.proxy().registerResponseHandler(new MyProxyResponseHandler()); logging.logToOutput(请求/响应处理器注册成功); } // 提供获取静态实例的公共方法 public static BurpExtension getInstance() { return instance; } // 提供获取日志工具的方法 public Logging logging() { return logging; } }这样在MyProxyRequestHandler和MyProxyResponseHandler中就可以通过BurpExtension.getInstance().logging()来记录日志了。4. 构建、加载与调试代码写完了现在需要把它变成BurpSuite能认的插件。4.1 使用Maven打包项目在项目根目录下打开终端执行Maven打包命令mvn clean compile assembly:single这个命令会依次执行清理旧编译文件 - 编译代码 - 运行assembly插件的single目标即生成胖JAR。如果一切顺利你会在target/目录下看到两个主要的JAR文件burp-montoya-proxy-demo-1.0-SNAPSHOT.jar不包含依赖的普通JAR。burp-montoya-proxy-demo-1.0-SNAPSHOT-jar-with-dependencies.jar这就是我们需要加载到BurpSuite的“胖JAR”。它包含了我们编译的所有类。实操心得如果打包失败通常是因为网络问题无法下载montoya-api依赖。你可以尝试检查Maven的settings.xml配置文件确认仓库镜像。手动下载JAR访问PortSwigger的官方仓库需在BurpSuite官网查找Montoya API的Maven仓库信息下载对应版本的montoya-api-2023.1.jar然后使用mvn install:install-file命令安装到本地仓库。4.2 在BurpSuite中加载插件打开BurpSuite Professional。切换到“Extender”标签页。点击“Extensions”子标签。点击左下角的“Add”按钮。在弹窗中“Extension type”选择“Java”。点击“Select file...”浏览并选择我们刚刚生成的burp-montoya-proxy-demo-1.0-SNAPSHOT-jar-with-dependencies.jar文件。点击“Next”。BurpSuite会开始加载JAR文件。如果加载成功你会在下方的输出区域看到绿色的“Extension loaded successfully”提示同时在我们插件的输出中会看到我们写的日志“ 5分钟Proxy拦截器Demo 已加载 ”。如果加载失败输出区域会显示红色错误信息。这是调试插件最关键的地方。常见错误有NoClassDefFoundError或ClassNotFoundException通常是依赖问题。确保montoya-api的scope是provided并且你的BurpSuite版本与API版本匹配。java.lang.UnsupportedClassVersionError编译用的JDK版本高于BurpSuite运行时使用的JRE版本。确保用JDK 11编译。插件代码中有语法错误或运行时异常。错误信息会指向具体的类和行号结合日志输出仔细排查。4.3 测试你的拦截器插件加载成功后它就已经在后台工作了。为了验证效果确保BurpSuite的Proxy拦截是开启的Proxy - Intercept 标签页Intercept is on。配置你的浏览器或其他HTTP客户端使用BurpSuite作为代理通常是127.0.0.1:8080。访问一个HTTP网站例如http://httpbin.org/headers这个网站会回显你的请求头。查看BurpSuite的Proxy - HTTP history找到你刚发的请求查看其请求头应该能看到我们添加的X-Burp-Extension: MyProxyDemo。访问一个不存在的页面触发404查看返回的响应体应该已经被我们替换成了自定义的HTML内容。观察BurpSuite的Extender标签页下的“Output”和“Errors”子标签这里会显示我们通过logging.logToOutput()打印的所有日志这是验证插件逻辑是否按预期执行的最直接方式。5. 进阶技巧与实战避坑指南一个能跑通的Demo只是开始。要让插件在真实、复杂的测试环境中稳定可靠地工作你需要了解以下进阶知识和避坑技巧。5.1 正确处理HTTPS流量你可能发现访问HTTPS网站https://开头时你的插件似乎没有生效。这是因为BurpSuite对HTTPS流量进行了中间人解密。你需要确保BurpSuite的CA证书已正确安装到你的测试浏览器或系统中。更关键的是你的插件代码处理的是解密后的HTTP请求和响应。Montoya API已经帮你处理了TLS层你拿到的HttpRequest和HttpResponse对象里的内容都是明文的。所以你不需要在插件里关心证书和加密的问题BurpSuite已经搞定了。你只需要像处理HTTP一样处理它们即可。5.2 避免循环拦截与性能陷阱这是一个新手极易踩中的大坑。假设你在handleRequestToBeSent中修改了请求然后这个修改后的请求又流经代理再次触发了handleRequestReceived和handleRequestToBeSent就会形成死循环。如何避免添加标识头在第一次修改时添加一个特殊的、无副作用的请求头如X-Processed-By-MyPlugin: true。在处理器开始处检查这个头如果存在则直接continueWith原请求不再处理。if (request.hasHeader(X-Processed-By-MyPlugin)) { return continueWith(request); // 跳过避免循环 } // ... 你的处理逻辑 ... HttpRequest newRequest request.withAddedHeader(X-Processed-By-MyPlugin, true); return continueWith(newRequest);精确过滤不要盲目处理所有流量。使用request.host()、request.path()、request.contentType()等方法精确限定你的插件只处理你关心的特定域名、路径或类型的请求。这不仅能避免循环风险也是提升插件性能的关键。注意性能字符串操作尤其是bodyToString()和对大响应体的替换是CPU密集型操作。如果插件需要处理大量流量一定要做好条件判断避免不必要的操作。可以考虑将复杂的处理逻辑如正则匹配、JSON解析放在条件判断之后。5.3 深入操作HTTP消息体前面的Demo简单演示了字符串替换但实战中情况复杂得多。处理表单数据 (application/x-www-form-urlencoded)不要用字符串替换来修改keyvaluekey2value2这样的格式容易出错。Montoya API提供了更优雅的方式import burp.api.montoya.http.message.params.HttpParameter; import burp.api.montoya.http.message.params.HttpParameterType; import burp.api.montoya.http.message.requests.HttpRequest; import java.util.List; if (request.contentType().contains(application/x-www-form-urlencoded)) { ListHttpParameter params request.parameters(); // 遍历并修改参数 for (HttpParameter param : params) { if (param.name().equals(username)) { // 修改参数值 params.set(params.indexOf(param), param.withValue(admin)); } } // 根据修改后的参数列表重建请求 HttpRequest modifiedRequest request.withParameters(params); return continueWith(modifiedRequest); }处理JSON数据对于JSON强烈建议使用像Jackson或Gson这样的JSON库来解析和修改。将request.bodyToString()得到的字符串用库解析成对象树修改后再序列化回字符串。绝对不要用简单的字符串替换或正则表达式来处理复杂的JSON这极易导致JSON格式破坏。处理二进制数据对于非文本内容如图片、PDF、加密数据ByteArray对象提供了getBytes()方法。你可以直接操作字节数组但务必小心。除非你知道确切的数据格式例如在PNG文件的特定偏移位置修改元数据否则不要修改二进制体。5.4 利用Montoya API的其他强大功能Montoya API不仅仅是Proxy。你的插件可以与其他BurpSuite组件深度集成api.scanner()可以主动发起扫描任务或者对扫描发现的问题进行自定义处理。api.repeater()可以创建新的Repeater标签页发送自定义请求这对于自动化测试流程非常有用。api.intruder()可以编程式地配置和运行Intruder攻击实现高度定制化的模糊测试。api.userInterface()可以在BurpSuite界面上添加自定义的标签页、上下文菜单项、消息编辑器标签让你的插件拥有图形界面。api.logging()除了输出到Output还可以记录到文件或者根据日志级别进行过滤。5.5 插件开发与调试流程建议迭代开发不要试图一次写完所有功能。从一个最简单的功能开始比如只打印日志确保能成功加载。然后逐步增加小功能每步都测试。日志是你的眼睛善用logging.logToOutput()。在关键决策点、修改前后都打印日志这是定位问题最有效的手段。你可以输出变量值、流程状态等。利用IDE调试虽然BurpSuite插件不能直接以Debug模式运行但你可以通过远程调试来连接。在BurpSuite的启动参数中添加JVM调试参数然后在IDE中配置远程调试连接。这能让你设置断点、单步跟踪极大提升复杂逻辑的调试效率。版本管理使用Git等工具管理你的插件代码。特别是当你为不同版本的BurpSuite对应不同Montoya API版本维护插件时分支管理非常重要。代码结构随着插件功能变多不要把所有逻辑都塞在handleRequestReceived一个方法里。按照功能模块进行拆分比如将修改Header的逻辑、修改Body的逻辑、记录日志的逻辑分别封装成独立的方法或类这样代码更清晰也易于维护和测试。开发BurpSuite Montoya插件是一个将自动化思维融入手动安全测试的过程。这个自定义的Proxy拦截器Demo只是一个起点它为你打开了通往BurpSuite自动化世界的大门。你可以基于这个模式开发出自动添加认证令牌的插件、敏感信息实时打码的插件、接口自动化遍历测试的插件等等。关键在于理解流量在BurpSuite中的生命周期并利用Montoya API提供的丰富钩子在合适的时机插入你的逻辑。多动手多测试多读官方文档你会发现它能成倍提升你的安全测试效率。