针对 CVE-2026-39816 的概念验证演示,这是 Apache NiFi 2.8.0 中的一个 EXECUTE_CODE
权限绕过漏洞,允许已被明确禁止代码执行权限的流程设计者通过图包中的 ExecuteGraphQuery
处理器在 NiFi JVM 中运行任意 Groovy 代码。
@Restricted 注解(CVE-2026-39816,CWE-95)NiFi 通过 @Restricted(requiredPermission = RequiredPermission.EXECUTE_CODE)
注解将执行代码的处理器限制在 EXECUTE_CODE 权限之后。
NiFi 中所有 16 个专用脚本组件——
ExecuteScript、InvokeScriptedProcessor、ExecuteGroovyScript、
ScriptedTransformRecord 等——都带有此注解,因此流程设计者必须被授予显式的
EXECUTE_CODE 权限,授权层才会允许他们创建或配置这些处理器。
图包(nifi-graph-nar + nifi-other-graph-services-nar)
附带了一条缺少此注解的第三条代码执行路径:
// nifi-extension-bundles/nifi-graph-bundle/nifi-other-graph-services/
// src/main/java/org/apache/nifi/graph/TinkerpopClientService.java:451
protected Map<String, String> bytecodeSubmission(
String s, Map<String, Object> map, GraphQueryResultCallback cb) {
...
compiled = groovyShell.parse(s); // 第 463 行 — 编译攻击者字符串
compiledCode.put(s, compiled);
...
compiled.setBinding(bindings);
Object result = compiled.run(); // 第 477 行 — 在 NiFi JVM 中执行
s 参数是来自 ExecuteGraphQuery 处理器“Graph Query”属性的查询字符串,原样传递。
没有净化、没有白名单、也没有沙箱——任何有效的 Groovy 代码都会被编译并以 NiFi
进程的完整权限执行。
TinkerpopClientService 以及驱动它的 ExecuteGraphQuery /
ExecuteGraphQueryRecord 处理器都不带有 @Restricted 注解。因此 NiFi 的授权层
将它们视为普通组件,被明确拒绝 EXECUTE_CODE 权限的流程设计者仍然可以创建它们。
当以下两个条件同时满足时,NiFi 2.8.0 服务器可被利用:
已安装可选的图包。 具体来说是 nifi-other-graph-services-nar NAR——
这是包含 TinkerpopClientService 的包。未安装此 NAR 的服务器不受影响。
至少有一个用户拥有流程设计者风格的权限(对流程组和 /controller 具有
read/write 权限)但没有 EXECUTE_CODE。 这是一种常见的策略形态:
组织希望用户能够构建和修改管道,但不能运行任意代码。EXECUTE_CODE 权限
正是该策略旨在强制执行的安全边界。
如果两个条件都满足,用户可以在“ByteCode Submission”模式下配置一个
TinkerpopClientService,将其指向任何可达的 Gremlin 服务器(Gremlin 服务器仅用于
服务初始化——执行在本地进行),并创建一个在“Graph Query”属性中包含 Groovy 代码的
ExecuteGraphQuery 处理器。启动处理器会在 NiFi JVM 中编译并运行该 Groovy 代码。
以 NiFi 服务账户身份执行任意代码。由此可以:
flow.json.gz、将 NAR 放入自动加载目录或修改控制器服务来建立持久性。该漏洞绕过了 EXECUTE_CODE 权限,因此即使操作员已明确移除用户的代码执行权限,
此漏洞仍然有效——这正是该权限存在所要防止的场景。
本仓库附带一个 POC,覆盖最直接的触发方式:攻击者控制的 Groovy 代码直接放置在
Graph Query 处理器属性中。无需上游连接、无需表达式语言、无需 FlowFile 内容——
只需处理器在其自身的定时器上运行。
还存在另外两条代码路径(当 Graph Query 为空时使用 FlowFile 主体内容作为查询,
以及将攻击者影响的 FlowFile 属性通过表达式语言插值到查询模板中)。此处未演示它们,
因为它们需要额外的前置条件才能到达相同的汇点;直接路径足以证明 EXECUTE_CODE 绕过。
setup/ — Docker Compose 环境。启动一个 NiFi 2.8.0 实例(官方
apache/nifi:2.8.0 镜像),自动加载图包 NAR,一个提供两个测试用户(admin
和 flow_designer)的 LDAP 服务器,以及一个供 TinkerpopClientService
指向的 Gremlin 服务器。setup.sh 引导策略,使 flow_designer 拥有流程编辑
权限但不拥有 EXECUTE_CODE,然后验证配置。
pocs/flow_designer_groovy_rce.py — 自包含的漏洞利用脚本。
以 flow_designer 身份进行身份验证,演示 ExecuteScript 被拒绝
(EXECUTE_CODE 门禁正常工作),然后创建一个 TinkerpopClientService +
ExecuteGraphQuery,其中包含一个 Groovy 载荷,该载荷会向 POC 在本地启动的
监听器生成 bash 反向 shell。连接建立后,POC 通过 util-linux
将 shell 升级为完全交互式的 PTY 支持的 bash,具有原始模式 stdio 桥接和实时
窗口大小调整——操作员可以在 NiFi 容器内获得一个真正的终端。
前置条件:Docker、Python 3.10+ 和 uv。
cd setup
./setup.sh
首次运行会从 Maven Central 下载图包 NAR 并拉取 Docker 镜像(约 2-3 分钟)。 设置完成后会打印可直接粘贴的 POC 调用命令。
运行 POC:
uv run --no-project --with requests \
pocs/flow_designer_groovy_rce.py \
--base-url https://localhost:8443 \
--username flow_designer \
--password 'flowDesigner123!' \
--gremlin-host gremlin-server
POC 会将操作员带入 NiFi 容器内以 nifi 服务账户身份运行的交互式 bash 会话。
输入 exit 或按 Ctrl-D 断开连接——POC 随后会清理它创建的处理器和控制器服务。
在 Linux Docker 上(host.docker.internal 默认无法解析),传递
--shell-host <host-bridge-ip> 或在 setup/docker-compose.yml 中的 nifi
服务中添加 extra_hosts: ["host.docker.internal:host-gateway"]。
拆除环境:
cd setup
./teardown.sh
script