CVE-2026-92717:Covenant 认证缺失导致操作员 API 越权访问

漏洞从匿名创建监听器开始

Covenant 的 /covenanthub 接口允许客户端调用服务端方法,但对应的 CovenantHub 类缺少认证保护。攻击者可匿名创建 HTTP 监听器,并从返回对象中取得服务端为监听器签发的身份令牌。由于操作员 API 的共享授权策略只检查有效身份,没有限制角色,这个监听器令牌进一步成为访问管理功能的凭据。漏洞属于关键功能缺少认证检查(CWE-306),同时暴露了监听器身份与操作员权限之间的授权缺口。

CVE 记录列出的受影响范围是 0.6 及更早版本;完整攻击链的验证覆盖 v0.6,较早版本未逐一测试或进行源码对照。攻击者需要能够访问受影响 Covenant 的 HTTPS 入口,包括 /covenanthub,无需已有账户、凭据、特权角色或用户交互,首次管理员注册也不是攻击前提。

服务端如何把匿名调用变成有效凭据

SignalR 是支持客户端与服务端通信、调用服务端方法的框架;Hub 是承载这些可调用方法的类。Covenant 的启动配置将 /covenanthub 映射到 CovenantHub。其他五个 Hub 类带有授权属性,但这个类没有用于要求认证的 [Authorize] 属性。在 v0.6 中,CreateHttpListener 是该类唯一未被注释的 Hub 方法,因此匿名客户端能够触发监听器创建逻辑。

已验证的调用链使用 HTTPS 和 LongPolling 传输。LongPolling 即长轮询,客户端通过持续发起 HTTP 请求接收服务端消息。客户端先向 /covenanthub/negotiate 发送 POST 请求取得连接标识,再通过 /covenanthub 完成 SignalR 的 JSON 握手。这些步骤建立通信连接,并未要求客户端证明操作员身份。

随后,攻击者调用 CreateHttpListener,控制监听器元数据、GUID 标识和状态。其中,Status=0 对应 ListenerStatus.Uninitialized,即未初始化状态。选择该状态仍会创建监听器并将其保存到数据库,但不会启动监听服务;只有状态为 Active 时,服务端才会调用 StartInitialHttpListener 并绑定端口。因此,攻击者取得凭据不依赖于成功启动一个活动监听器。

关键副作用发生在 CovenantService.CreateHttpListener 内部。处理函数先通过 CreateUser 创建一个使用生成用户名的账户,再用 GetRoleByName 找到 Listener 角色,并通过 CreateUserRole 将该角色赋给账户。接着,服务端调用 Utilities.GenerateJwtToken,把生成的令牌写入监听器对象的 CovenantToken 字段,最后通过 GetHttpListener(listener.Id) 返回该对象。匿名客户端从长轮询得到的调用完成响应中,便可读取 result.covenantToken。

JWT(JSON Web Token)是一种可携带身份和角色信息、由签名保护的令牌。这里的令牌携带 Listener 角色,由服务端使用配置中的 JwtKey 签名,有效期为 2000 天。攻击者无需知道签名密钥,也无需伪造签名:缺少认证的创建流程直接为其签发了真实有效的凭据。

为什么 Listener 令牌能通过操作员 API 的检查

取得令牌后,攻击者将其放入请求的 Authorization: Bearer 头中。Bearer 表示请求方通过持有令牌来证明身份,因此签名有效的令牌能否访问某个接口,还取决于该接口是否检查相应权限。

Covenant 的 17 个 API 控制器类都使用 Authorize(Policy = RequireJwtBearer)。这个共享策略组合了 RequireAuthenticatedUser() 和 JwtBearer 认证方案,却没有角色要求。于是,服务端虽然确认了请求来自一个有效的 Listener 身份,却没有在类级检查中判断该身份是否应当访问操作员功能。长期令牌的签发与这一授权缺口相连,形成了从匿名请求到操作员 API 访问的完整链条。

已验证的结果包括:GET /api/users 接受该令牌并返回操作员用户名单;GET /api/grunts 也成功响应。Grunt 是 Covenant 管理的植入端。测试实例没有活动 Grunt,后一个接口返回为空,因此该结果证明了接口可达,尚不能证明已经读取到植入端数据或成功下发任务。

共享策略也不意味着所有接口都可访问。在 CovenantUserApiController 中,DeleteUser、CreateUserRole 和 DeleteUserRole 还要求 RequireAdministratorRole,即额外的管理员角色检查。源码显示,只有 Listener 角色的令牌无法满足这三项操作的要求;它们实际拒绝请求的行为未经过测试。

其他潜在能力包括读取已存储凭据、下载文件、访问事件、向植入端下发任务,以及生成用于启动植入端的启动器。这些判断来自接口路由与共享授权策略的源码分析,未逐项验证;实际数据泄露和植入端相关影响取决于目标实例保存的数据及其植入端状态。

文件写入的路径与边界

同一令牌还可用于调用 POST /api/listeners/{id}/hostedfiles,提交监听器标识、攻击者选择的文件名和文件内容。这里的 hosted files 是监听器用于托管文件的功能。ListenerApiController.CreateHostedFile 随后调用 HttpListener.HostFile,最终通过 File.WriteAllBytes 将内容写入所选监听器自己的 Static 目录。攻击者能够选择监听器 GUID 和文件名,因此可以确定该目录内的目标位置。

文件写入已有验证,但范围受到路径约束:监听器 GUID 只允许字母和数字,托管文件路径也会检查目录穿越,即试图通过路径跳出指定目录的行为。已证实的能力限于向所选监听器的 Static 目录写入攻击者提供的内容,直接利用该写入能力执行主机代码尚未得到证实,可用性影响也未测试。

修复需要同时处理入口与角色权限

上游修复版本尚未确认,因而没有可据此指定的已知修复版本。针对根因,工程上的修复方向有两处:为 CovenantHub 及监听器创建操作强制实施认证和操作权限检查;为操作员 API 明确设置角色或权限要求,防止仅具备 Listener 身份的令牌通过共享策略获得操作员访问权。这些是针对已知失效边界的修复建议,不代表已发布补丁的实际行为。

修复方案还应处理此前签发的长期凭据。补上匿名入口的认证检查可以阻止继续匿名领取令牌,但这一改动本身并不使既有令牌失效;操作员 API 的权限收紧与既有令牌的失效处理需要一并考虑。