End-to-end reproduction and cross-layer detection of CVE-2026-53576, the unauthenticated RCE in Kestra - taken past the base PoC to show how a common Docker-socket misconfiguration turns container-root into full host compromise.
Kestra 是一个开源工作流编排平台,其发布的认证过滤器通过检查 URL 是否以 /configs 结尾来判断请求是否需要凭据——这一检查本意是暴露一个无害的公共端点。
由于该检查只查看字符串的尾部,任何 URL 恰好以该方式结尾的请求都会完全跳过认证,包括那些创建并运行任意代码的端点。 结果:任何能够通过网络访问存在漏洞的 Kestra 实例的人,都可以在零凭据的情况下以 root 身份运行命令,无需钓鱼、无需猜测密码、无需任何权限提升步骤。
在本次演练中,这一单一缺口被端到端地应用于一个自托管实验实例:
每个阶段都针对防御遥测(网络 IDS、主机运行时安全、Linux 审计和系统日志)进行了捕获。
未修补时的业务影响: 运行 Kestra 的主机被完全攻陷,而不仅仅是应用程序——进而波及从该主机可访问的任何其他内容。
修复: 升级到 Kestra 1.0.45 / 1.3.21 或更高版本(仅补丁即可关闭主要漏洞;提升和持久化阶段需要单独的、独立的修复——不要将 Docker socket 挂载到应用程序容器中,参见第 8 节)。
本报告记录了 CVE-2026-53576,这是一个 CVSS 10.0 的未认证远程代码执行漏洞,存在于 Kestra ≤1.3.20 中,由路径后缀认证绕过(AuthenticationFilter.java,endsWith("/configs"))引起。
攻击者将工作流的命名空间和 ID 命名为 configs,即可在无凭据的情况下,以 root 身份在 Kestra 容器内创建并执行任意 shell 命令。已在 1.0.45 和 1.3.21 中修复。同一漏洞也被独立报告为 CVE-2026-49869,这是与真实野外利用相关的 CISA KEV 列出的编号。两者应一并引用,因为公开来源和扫描器可能引用其中任意一个。
| 受影响 | Kestra ≤ 1.3.20(以及 1.0.x 系列中 1.0.45 之前的版本) |
| 已修复 | 1.0.45 / 1.3.21+ |
| CVE | CVE-2026-53576 |
| 孪生公告 | CVE-2026-49869 - 同一漏洞,CISA KEV 列出(野外利用) |
| 日期 | 事件 |
|---|---|
| 2026-06-02 | Kestra 1.3.21 发布;变更日志提到“认证过滤器中潜在的认证绕过” |
| 2026-06-03 | Kestra 1.0.45 在 1.0.x 系列上发布 |
| 2026-09-02 | CVE-2026-49869(孪生公告)被添加到 CISA KEV 目录 |
| 2026-09-22 至 09-24 | 本次实验复现、检测构建和跨层验证 |
自托管家庭实验室:
- Debian 主机(主机名 docker,内核 6.12.107+deb13-amd64),
- Docker Engine 29.8.1。
- Kestra 以官方 kestra/kestra:v1.3.20 镜像部署,可通过 kestra.int.atlasvec.com 上的 Caddy 反向代理访问。
- 被测遥测栈:Falco(运行时/eBPF,主机级),
- Suricata 8.0.3 IDS(被动 SPAN 镜像),转发至 Splunk。
在接触任何东西之前,我确认了目标确实存在漏洞,并且遥测栈处于活动状态。我还对攻击前状态进行了快照,以便完成后可以还原。
Kestra 运行着存在漏洞的版本:

容器本身已启动并可访问:

Falco 的单元已在主机上加载:

Falco 正在主动发出事件(现代 eBPF 模式):

Suricata 服务处于活动状态:

并且其 eve.json 正在实时写入事件:

这是两个错误叠加在一起,而不是一个。
首先,认证过滤器通过匹配请求路径的末尾来决定请求是否可以跳过认证:```java // AuthenticationFilter.java — the vulnerable check, confirmed against the self-hosted v1.3.20 source if (path.endsWith("/configs")) { // treated as a public, unauthenticated path }
这个检查的存在是为了暴露一个无害的端点,即公开的实例配置。由于 `endsWith` 只读取字符串的尾部,它无法区分一个安全路径和任何其他恰好以相同方式结尾的路径。
其次,Kestra 的路由器仍然会将这些看起来相似的路径分发到它们真正的、敏感的处理器。`/api/v1/main/flows/configs` 会到达 flow-create 处理器。`/api/v1/main/executions/configs/configs` 会到达 execution 处理器。过滤器将两者都作为公开路径放行,而路由器无论如何都会执行它们。将一个 flow 的 namespace 和 ID 都命名为 `configs`,一个可执行代码的端点现在就以 `/configs` 结尾,因此它继承了公开路径的免费通行证。
捕获的请求对清楚地展示了这一点。`GET /api/v1/main/flows/search` 在无凭据的情况下返回 `401`。`POST /api/v1/main/flows/configs` 在相同的空 auth 头下返回 `200` 并存储了该 flow(见 §5.1)。
## 5. 攻击模拟
> 下面的每一步都附有自己的截图。所有这些都在我自己的隔离 homelab 中运行,针对的是反向代理后面的自托管 Kestra 实例。
### 5.1 阶段 1 - 认证绕过 -> Kestra 容器内的 root
**对照:** `GET /api/v1/main/flows/search` 在无凭据的情况下 → `401 Unauthorized`。
确认正常路由上强制执行了认证。

_____
**植入:** 发送 `POST /api/v1/main/flows/configs`,不带 auth 头。请求体是一个 flow YAML,其 `namespace` 和 `flow id` 都是 `configs`,包含一个使用 Process 任务运行器的 Commands 任务。服务器返回 `200 OK` 并存储了该 flow。一个原本受保护的路由上的 `/configs` 后缀正是触发绕过的原因 **(见第 4 节)。**

**监听与等待**:出于测试目的,使用 nc 启动一个简单的监听器,以便我们捕获反向 shell。

**触发 / 执行** `POST /api/v1/main/executions/configs/configs`,不带 auth 头 → `200 OK`,创建了新的执行。

**BaaaaM:** 我们已经弹出了 shell,但请记住——我们是“隔离的”,`root` **但是** 在 Kestra docker 容器内部,所以我们“不应该”能够从这里逃逸。“**好吧,我原本是这么想的**”

该任务的命令作为 Kestra JVM 进程的直接子进程运行,已通过宿主机侧的进程树确认,并且根据 Kestra 自己的执行日志成功完成。
**Kestra 的日志仪表板**

**Kestra 的进程树**

**结果:** 在 Kestra 容器内以 root 身份实现未认证远程代码执行。这是 **CVE-2026-53576** 的完整、自包含证明。
### 5.2 阶段 2 - 通过暴露的 Docker socket 提权(homelab 特有,不属于 CVE 本身)
从阶段 1 的 shell 中,发现 `/var/run/docker.sock` 被挂载到了容器内——这是一种 Docker-outside-of-Docker (DooD) 模式,当 Kestra 自己的 `Docker` 任务运行器被配置时很常见,因为它需要一个守护进程来通信。