Java插件化漏洞扫描器Artillery:架构设计与一键Getshell实现
1. 项目概述与核心价值
最近在整理自己的安全工具箱,发现很多扫描器要么太重,要么太老,要么就是纯命令行对新手不太友好。特别是想快速验证一些常见中间件、框架的漏洞时,往往需要翻好几个工具,或者写一堆脚本,效率很低。直到我动手把 Artillery 这个项目重新捡起来并深度定制了一番,才算是找到了一个比较趁手的“瑞士军刀”。Artillery 本质上是一个用 Java 写的、支持插件化的漏洞扫描器,它的图形界面(GUI)基于 JavaFX 开发,目前已经集成了针对 Weblogic、Tomcat、Shiro、Spring 等主流组件的漏洞检测能力,最关键的是,它设计的目标很明确:一键验证,甚至一键 getshell。
对于渗透测试人员、安全运维甚至是刚入门的安全爱好者来说,这样一个工具的价值在于它极大地简化了从漏洞发现到验证的流程。你不需要再去记忆复杂的命令参数,或者在不同的终端窗口之间切换。通过一个统一的图形界面,选择目标,点击扫描,结果直观展示,对于确认存在的漏洞,往往一个按钮就能完成利用,拿到 shell。这不仅仅是效率的提升,更是降低了安全操作的门槛,让验证工作变得更加标准化和可重复。当然,工具是死的,人是活的,如何合规、合法地使用它,是每个使用者必须坚守的底线。
2. Artillery 的整体架构与设计思路
2.1 为什么选择 Java 和插件化架构?
Artillery 选择 Java 作为开发语言,在我看来,首要考虑的是跨平台性和生态成熟度。Java “一次编写,到处运行”的特性,使得编译好的 Artillery JAR 包可以在 Windows、Linux、macOS 上无缝运行,无需为每个平台单独编译,这对于需要在不同环境中穿梭的安全人员来说非常友好。其次,Java 拥有庞大而稳定的网络编程、多线程、反射等核心库,非常适合实现扫描器所需的并发请求、协议处理、动态加载等功能。
而插件化架构,则是 Artillery 的灵魂所在,也是它能保持生命力和扩展性的关键。传统的单体扫描器,每增加一个漏洞检测模块(POC),都需要修改核心代码、重新编译、发布整个程序,过程繁琐且容易引入错误。Artillery 的插件化设计,将扫描引擎和漏洞检测逻辑彻底解耦。
核心引擎只负责最通用的任务:管理任务队列、调度并发线程、发送 HTTP/HTTPS 等基础网络请求、处理原始响应、提供日志和结果存储接口。它不关心具体要检测什么漏洞。
漏洞检测插件(POC 模块)则被实现为独立的 JAR 文件或类文件。每个插件都遵循一个统一的接口规范。这个规范通常会定义几个核心方法,例如:
check(String target):验证漏洞是否存在,返回布尔值或漏洞详情。exploit(String target):利用漏洞,执行命令或上传 webshell。getVulnInfo():返回漏洞的 CVE 编号、名称、风险等级等信息。
当 Artillery 启动时,它会扫描指定的插件目录,利用 Java 的类加载机制(如 URLClassLoader)动态加载所有符合规范的插件类。用户在前端界面上看到的漏洞列表,其实就是这些被成功加载的插件。当用户点击扫描时,引擎会调用对应插件的check方法;点击利用时,则调用exploit方法。
这种设计带来了巨大的优势:
- 热插拔:新增漏洞检测能力时,只需编写新的插件 JAR,放入插件目录,重启 Artillery 即可生效,核心程序无需改动。
- 社区贡献友好:安全研究人员可以专注于编写漏洞验证代码,而不必理解整个扫描器的复杂逻辑,降低了贡献门槛。
- 安全隔离:即使某个插件存在 bug 或恶意代码,理论上也更易于被限制在插件沙箱内(虽然 Artillery 默认可能没有强沙箱,但架构为这种增强提供了可能),不会导致整个程序崩溃。
2.2 JavaFX GUI 的优势与挑战
图形界面选用 JavaFX 而非更古老的 Swing 或需要额外运行时的 Eclipse RCP 等,是一个符合现代技术趋势的选择。JavaFX 提供了比 Swing 更丰富、更现代化的 UI 控件,支持 CSS 样式美化,动画效果也更流畅,能够构建出体验更好的桌面应用。
在 Artillery 中,GUI 层主要承担几个职责:
- 项目管理:创建、打开、保存扫描任务,管理目标列表(支持导入 IP/域名文件)。
- 扫描配置:设置并发线程数、请求超时、代理(如 Burp Suite)、请求头等。
- 状态监控:实时显示扫描进度、当前正在检测的插件和目标。
- 结果展示:以表格、树形等清晰的形式展示漏洞结果,并对高危漏洞进行突出显示。
- 交互控制:提供“一键验证”、“一键利用”的按钮,并将利用结果(如命令执行回显、Webshell 地址)反馈给用户。
然而,JavaFX 的挑战在于其打包和分发。为了让用户无需复杂配置就能运行,我们需要将 Artillery 及其所有依赖(包括 JavaFX 运行时)打包成一个可独立执行的“胖 JAR”(Fat JAR)或者使用jlink创建自定义的 JRE 镜像。目前社区常用的工具是Maven Shade Plugin或Gradle Shadow Plugin,它们能将所有依赖库合并到一个 JAR 文件中。对于包含 JavaFX 的情况,还需要特别注意模块化路径和原生库的处理,否则很容易在非开发环境出现JavaFX runtime components are missing的错误。
实操心得:在打包 Artillery 这类 JavaFX 应用时,我强烈推荐使用
javapackager(或后续版本中的jpackage)工具。它可以生成真正意义上的原生安装包(如 Windows 的.exe/.msi, macOS 的.dmg/.pkg, Linux 的.deb/.rpm)。用户安装后就像使用一个普通软件一样,完全感知不到 Java 环境的存在,体验提升巨大。虽然配置过程稍显复杂,但一劳永逸。
3. 核心 POC 插件解析与实现要点
Artillery 的威力完全体现在其集成的 POC 插件上。我们以它目前支持的 Weblogic、Tomcat、Shiro、Spring 为例,拆解一下这些插件的典型实现逻辑和注意事项。
3.1 Weblogic 反序列化漏洞插件
Weblogic 的反序列化漏洞(如 CVE-2017-10271, CVE-2019-2725 等)是它的“重灾区”。这类插件的核心逻辑是向特定的 URI(如/wls-wsat/CoordinatorPortType)发送一个精心构造的、包含恶意序列化对象的 SOAP/XML 请求。
实现要点:
- 漏洞指纹识别:在攻击前,插件通常会先发送一个普通请求,根据返回的 Server 头、错误页面特征等,确认目标是否为 Weblogic 以及大致版本,避免盲目攻击。
- Payload 生成:利用公开的漏洞利用工具(如 ysoserial)生成对应的序列化 Payload。这里需要在插件中集成或调用 ysoserial 的功能。一种做法是将 ysoserial 的源码作为库编译进来;另一种是在插件初始化时,动态生成常用的 Gadget Chain(如 CommonsCollections1, Jdk7u21)的字节码。
- 请求构造:将 Payload 进行 Base64 编码,嵌入到特定的 SOAP XML 模板中。请求头需设置
Content-Type: text/xml。 - 结果判断:
- 延时检测:如果利用的是执行命令的 Payload,可能会让目标执行
sleep 5,通过判断响应时间是否显著延长来确认漏洞。这种方式相对隐蔽。 - 回显检测:更直接的方式是使用能回显命令结果的 Gadget(如通过修改 Tomcat 的 Response 对象),在响应体中直接查找命令执行结果。
- DNSLog 外带:最安全、最通用的方式是让目标执行一条解析特定 DNS 记录的指令(如
ping或curl),然后监控自己的 DNSLog 平台是否有收到请求,从而确认漏洞存在且可利用。
- 延时检测:如果利用的是执行命令的 Payload,可能会让目标执行
注意事项:反序列化 Payload 的生成和利用高度依赖目标的 JDK 版本和应用的 ClassPath。一个在测试环境成功的 Payload,在生产环境可能因为缺少某个依赖库而失败。因此,一个健壮的插件应该准备多种 Gadget Chain 进行尝试,并具备良好的错误处理和日志输出,方便使用者排查原因。
3.2 Tomcat AJP 文件包含/读取漏洞插件
这里主要指 CVE-2020-1938 (GhostCat)。该漏洞源于 Tomcat AJP 协议设计缺陷,允许攻击者读取或包含 Web 应用目录下的任意文件。
实现要点:
- 协议连接:插件需要实现 AJP 协议(一个二进制协议)的客户端,与 Tomcat 默认的 8009 端口建立 TCP 连接。这比 HTTP 协议处理要复杂一些。
- 构造恶意 AJP 请求:按照 AJP 协议格式,构造一个
SC_REQ_ATTRIBUTE为javax.servlet.include.request_uri的请求,并将要读取的文件路径设置到javax.servlet.include.path_info属性中。 - 发送与解析:将构造好的二进制数据包发送出去,并解析返回的 AJP 响应消息,提取出响应体(即文件内容)。
- 结果判断:如果成功读取到
/WEB-INF/web.xml等敏感文件内容,即可判定漏洞存在。对于文件包含,可以尝试包含一个已知内容的文件,在响应中搜索该内容。
关键代码片段示意(概念性):
// 建立到目标 8009 端口的 Socket 连接 Socket socket = new Socket(targetIp, 8009); OutputStream out = socket.getOutputStream(); InputStream in = socket.getInputStream(); // 构造 AJP Forward Request 数据包 byte[] ajpRequest = constructAjpRequestForFileRead("/WEB-INF/web.xml"); // 发送请求 out.write(ajpRequest); out.flush(); // 读取并解析 AJP 响应 byte[] response = readAjpResponse(in); String fileContent = parseResponseBody(response); if (fileContent.contains("<web-app>")) { // 漏洞存在 saveVulnResult(target, fileContent); }3.3 Shiro 反序列化漏洞插件
Apache Shiro 的反序列化漏洞(CVE-2016-4437 等)利用方式比较特殊,关键在于 Shiro 使用了硬编码的 AES 加密密钥对用户身份信息(RememberMe Cookie)进行序列化、加密和传输。
实现要点:
- 密钥检测:漏洞利用的前提是攻击者知道了目标系统使用的 AES 密钥。Shiro 历史上存在多个版本的默认硬编码密钥,社区也积累了非常庞大的常见密钥字典。插件的首要任务就是“撞库”。
- 利用流程:
- 生成一个恶意序列化对象(同样使用 ysoserial)。
- 使用一个候选 AES 密钥,按照 Shiro 的规则(AES-CBC,PKCS5Padding)对序列化后的字节进行加密。
- 将加密结果进行 Base64 编码,设置为 Cookie 头中
rememberMe字段的值。 - 发送携带此 Cookie 的请求到目标。
- 结果判断:与 Weblogic 类似,采用延时、DNSLog 或回显的方式判断命令是否执行成功。如果成功,则不仅证明了漏洞存在,还揭示了当前系统使用的加密密钥。
实操心得:编写 Shiro 漏洞插件时,性能优化很重要。因为密钥字典可能非常大(数万条),如果对每个密钥都重新生成完整的 Payload 并加密,会非常慢。一个优化技巧是:先使用一个简单的、无害的序列化对象(比如只包含一个字符串)作为“探针”,用所有密钥加密并发送。通过响应特征(如特定的错误信息、响应时间差异)快速筛选出几个最可能的密钥候选,然后再用真正的恶意 Payload 针对这几个候选密钥进行尝试,能极大提升检测效率。
3.4 Spring 相关漏洞插件
Spring 框架的漏洞类型多样,例如:
- Spring Cloud Function SpEL 注入(CVE-2022-22963):向特定路径发送包含恶意 SpEL 表达式的 POST 请求。
- Spring Framework RCE(CVE-2022-22965):通过构造特殊的请求参数,在 JDK 9+ 环境下利用数据绑定机制实现 RCE。
- Spring Security 绕过:某些配置不当导致的安全绕过。
实现要点:这类插件的编写更需要“因地制宜”。
- 精准指纹:Spring 应用的特征可能不那么明显,需要结合路径(如
/actuator、/functionRouter)、特定的错误信息、Cookie 中的JSESSIONID命名模式等综合判断。 - Payload 适配:不同漏洞的利用 Payload 构造方式截然不同。例如 CVE-2022-22965 的 Payload 是一系列特殊的 HTTP 请求参数,而 SpEL 注入的 Payload 则是
T(java.lang.Runtime).getRuntime().exec(\"calc\")这样的表达式。 - 利用链探测:Spring 漏洞的利用可能依赖特定的 ClassPath(如 tomcat-embed-core, spring-webmvc)。插件可能需要尝试不同的内存马注入方式(如 Interceptor, Controller, Servlet)来适配目标环境。
4. 从扫描到 Getshell:一键利用的实现
“一键 getshell”是 Artillery 宣传的亮点,也是其工具价值的集中体现。但这背后并非一个魔法按钮,而是一系列标准化、自动化的操作流程。
4.1 利用流程标准化
对于一个被验证存在的漏洞,其利用过程可以被抽象为:
- 环境确认:根据漏洞类型,确认利用所需的前置条件(如是否需要特定端口开放、是否需要特定权限)。
- Payload 投递:将恶意代码(如 WebShell、内存马)通过漏洞通道投递到目标服务器。这可能是上传一个文件,也可能是通过反序列化直接注入到内存中。
- 通道建立:确保投递的 Payload 能够成功创建一个可供远程控制的持久化或半持久化通道。
- 交互测试:尝试与建立的通道进行通信,验证其可用性。
在 Artillery 中,点击“一键利用”按钮后,对应的插件会自动化执行上述流程。例如,对于一个 Tomcat 上传漏洞,插件会:
- 自动生成一个冰蝎(Behinder)或哥斯拉(Godzilla)的 JSP WebShell。
- 利用漏洞的上传功能将 WebShell 文件写到 web 目录下。
- 尝试访问这个 WebShell 的 URL,如果返回特定标识(如连接密码验证前的固定字符串),则判定利用成功。
- 在 GUI 上高亮显示 WebShell 的访问地址和连接密码。
4.2 内存马与文件 WebShell 的选择
这是利用阶段的一个关键决策点。
- 文件 WebShell:传统方式,在网站目录下写入一个
.jsp或.php文件。优点是稳定、兼容性好,各种管理客户端支持完善。缺点是非常容易被安全软件或运维人员通过文件监控发现并清除,留下明显的攻击痕迹。 - 内存马:无文件落地技术。利用漏洞将恶意代码直接注入到目标应用的运行时内存中,例如注册一个恶意的 Filter、Servlet 或 Controller。优点是隐蔽性极强,没有文件,重启应用前难以清除。缺点是技术实现复杂,依赖特定的中间件和框架版本,且应用重启后即失效。
一个成熟的 Artillery 插件,应该根据目标环境智能选择利用方式。例如,检测到是 Tomcat 9 + Spring Boot,可以优先尝试注入 Filter 内存马;如果是老版本的 Tomcat,则可能回退到上传 JSP WebShell。
4.3 利用后的“清理”与持久化思考
严格来说,作为攻击方模拟工具,在取得 shell 后还应考虑后续的持久化操作。但 Artillery 作为扫描/验证工具,通常不涉及这么深。不过,在插件设计时,可以提供一个“清理”功能选项,用于在测试结束后,自动删除上传的 WebShell 文件或卸载注入的内存马,这是一个很好的实践,体现了负责任的安全测试态度。
5. 开发与使用中的常见问题与排查
5.1 插件开发常见坑点
类加载冲突:这是插件化架构最常见的问题。插件 A 和插件 B 可能依赖了同一个库的不同版本(如 httpclient 4.5 和 4.8),或者插件依赖的库与 Artillery 核心引擎依赖的库冲突。这会导致
NoSuchMethodError,ClassNotFoundException等错误。- 解决方案:使用 Maven Shade Plugin 对插件进行重打包(Relocation),修改其依赖库的包路径。例如,将插件中
org.apache.http下的所有类重命名到com.artillery.plugin.xxx.shaded.org.apache.http下,实现隔离。
- 解决方案:使用 Maven Shade Plugin 对插件进行重打包(Relocation),修改其依赖库的包路径。例如,将插件中
网络环境适配:插件中的 HTTP 请求代码需要处理好代理、超时、重定向、SSL 证书验证等问题。特别是在内网复杂环境下,可能需要支持 SOCKS 代理或 NTLM 认证。
- 解决方案:插件的网络请求最好复用 Artillery 核心引擎提供的统一 HTTP 客户端,该客户端应已集成这些配置。如果插件必须自己实现,则需要提供完善的配置项。
并发安全:插件的方法(如
check)可能会被多个线程同时调用。必须确保插件内的操作是线程安全的,避免使用可变的共享状态。- 解决方案:将插件类设计为无状态的(Stateless),所有所需信息通过方法参数传入。如果必须有状态,则使用
ThreadLocal或每次调用创建新实例。
- 解决方案:将插件类设计为无状态的(Stateless),所有所需信息通过方法参数传入。如果必须有状态,则使用
5.2 工具使用排查指南
| 问题现象 | 可能原因 | 排查步骤 |
|---|---|---|
启动时报JavaFX runtime components are missing | 运行环境缺少 JavaFX 模块或打包不正确。 | 1. 确认使用java --module-path <path-to-javafx-lib> --add-modules javafx.controls,javafx.fxml -jar artillery.jar命令启动。2. 或使用打包好的原生安装包。 |
| 扫描过程中程序卡死或无响应 | GUI 线程被耗时操作阻塞;并发数设置过高导致资源耗尽。 | 1. 检查所有耗时的插件操作(如网络请求)是否在后台线程执行。 2. 降低扫描并发线程数。 3. 检查是否有插件陷入死循环。 |
| 某个特定漏洞插件总是失败 | 插件逻辑有 Bug;目标环境与插件预期不符;网络策略限制。 | 1. 打开 Artillery 的详细日志模式,查看该插件执行时的具体请求和响应。 2. 手动使用其他工具(如 curl, burp)验证漏洞是否存在。 3. 检查插件依赖的库是否完整。 |
| “一键利用”成功但连接不上 WebShell | 防火墙/安全组拦截;WebShell 路径不对;连接密码错误。 | 1. 在目标服务器上验证 WebShell 文件是否确实存在且内容正确。 2. 尝试从目标服务器内部访问该 WebShell URL,看是否可达。 3. 核对生成的 WebShell 类型与连接客户端是否匹配。 |
| 扫描结果漏报 | 插件指纹识别不准确;目标有 WAF/IPS 拦截;网络不稳定导致请求超时。 | 1. 确认目标服务的真实版本和组件。 2. 尝试在 Burp Suite 中重放插件的请求,观察是否被 WAF 拦截。 3. 适当增加请求超时时间。 |
5.3 性能优化与稳定性建议
- 连接池复用:在核心引擎中为每个扫描目标维护一个 HTTP 连接池,避免每次请求都建立新的 TCP 连接,可以大幅提升扫描速度。
- 超时与重试策略:为网络请求设置合理的连接超时、读取超时时间,并实现指数退避的重试机制,以应对不稳定的网络环境。
- 资源限制:在 GUI 中提供选项,限制最大并发任务数、每秒请求数(RPS),避免对目标造成过大压力,也防止程序自身资源耗尽。
- 结果去重与聚合:同一个漏洞可能被多个插件以不同方式检测到,引擎需要对结果进行智能去重和聚合,避免报告界面混乱。
最后,我想强调的是,像 Artillery 这样的工具,其价值在于提高安全工作的效率和标准化程度,但它永远替代不了人的判断。自动化扫描的结果必然存在误报和漏报,每一个高危漏洞的确认,都需要人工进行审慎的复核。工具让我们跑得更快,但方向和道路,仍需我们自己来把握。在合规的授权范围内,善用工具,持续学习,才是安全从业者的立身之本。