Skip to content

ZeroPathAI/nifi-CVE-2026-39816-poc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Apache NiFi CVE-2026-39816 POC

Proof-of-concept demonstration for CVE-2026-39816, an EXECUTE_CODE permission bypass in Apache NiFi 2.8.0 that lets a flow designer who has been deliberately denied code-execution privileges run arbitrary Groovy in the NiFi JVM via the graph bundle's ExecuteGraphQuery processor.

  • Fixed in NiFi 2.9.0
  • Discovered by ZeroPath -- full technical write up available here.

Vulnerability

Missing @Restricted annotation on TinkerpopClientService (CVE-2026-39816, CWE-95)

NiFi gates code-executing processors behind the EXECUTE_CODE permission via the @Restricted(requiredPermission = RequiredPermission.EXECUTE_CODE) annotation. All 16 dedicated scripting components in NiFi — ExecuteScript, InvokeScriptedProcessor, ExecuteGroovyScript, ScriptedTransformRecord, etc. — carry this annotation, so a flow designer must be granted the explicit EXECUTE_CODE permission before the authorization layer will let them create or configure these processors.

The graph bundle (nifi-graph-nar + nifi-other-graph-services-nar) ships a third code-execution path that is missing this annotation:

// 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);     // line 463 — compile attacker string
    compiledCode.put(s, compiled);
    ...
    compiled.setBinding(bindings);
    Object result = compiled.run();      // line 477 — execute in NiFi JVM

The s parameter is the query string from the ExecuteGraphQuery processor's "Graph Query" property, passed through unchanged. There is no sanitization, no allowlist, and no sandbox — any valid Groovy is compiled and executed with the full privileges of the NiFi process.

Neither TinkerpopClientService nor the ExecuteGraphQuery / ExecuteGraphQueryRecord processors that drive it carry the @Restricted annotation. NiFi's authorization layer therefore treats them as ordinary components, and a flow designer who has been explicitly denied EXECUTE_CODE can still create them.

When is a server exploitable?

A NiFi 2.8.0 server is exploitable when both of the following are true:

  1. The optional graph bundle is installed. Specifically the nifi-other-graph-services-nar NAR — this is the bundle that ships TinkerpopClientService. Servers without this NAR are not affected.

  2. At least one user has flow-designer-style permissions (read/write on a process group and on /controller) without EXECUTE_CODE. This is a common policy shape: an organization wants users to be able to build and modify pipelines, but not run arbitrary code. The EXECUTE_CODE permission is precisely the security boundary that policy is meant to enforce.

If both conditions hold, the user can configure a TinkerpopClientService in "ByteCode Submission" mode, point it at any reachable Gremlin server (the Gremlin server is only used for service initialization — execution happens locally), and create an ExecuteGraphQuery processor with Groovy code in the "Graph Query" property. Starting the processor compiles and runs the Groovy in the NiFi JVM.

What can an attacker do?

Arbitrary code execution as the NiFi service account. From there:

  • Read and modify everything on the NiFi host's filesystem, including the keystore, sensitive properties key, and any flow-encrypted secrets.
  • Reach every internal system NiFi can reach. NiFi typically sits next to data-warehouse, message-bus, object-storage, and database credentials in its Parameter Contexts and Controller Services — the Groovy payload can read all of them and pivot.
  • Establish persistence by writing to flow.json.gz, dropping a NAR into the autoload directory, or modifying a controller service.

The exploit bypasses the EXECUTE_CODE permission, so this works even when the operator has explicitly removed code-execution rights from the user — exactly the scenario the permission exists to prevent.

POC scope

This repository ships one POC, covering the most direct trigger: attacker-controlled Groovy placed directly in the Graph Query processor property. No upstream connection, no Expression Language, no FlowFile content needed — just the processor running on its own timer.

Two additional code paths exist (FlowFile body content used as the query when Graph Query is empty, and Expression Language interpolation of attacker-influenced FlowFile attributes into the query template). They are not demonstrated here because they reach the same sink with extra preconditions; the direct path is sufficient to prove the EXECUTE_CODE bypass.

Repository contents

  • setup/ — Docker Compose environment. Brings up a NiFi 2.8.0 instance (official apache/nifi:2.8.0 image) with the graph bundle NARs autoloaded, an LDAP server providing two test users (admin and flow_designer), and a Gremlin server for the TinkerpopClientService to point at. setup.sh bootstraps the policies so that flow_designer has flow-editing rights but not EXECUTE_CODE, then verifies the configuration.

  • pocs/flow_designer_groovy_rce.py — Self-contained exploit. Authenticates as flow_designer, demonstrates that ExecuteScript is denied (the EXECUTE_CODE gate is working), then creates a TinkerpopClientService + ExecuteGraphQuery with a Groovy payload that spawns a bash reverse shell to a listener the POC starts locally. Once the connection is established, the POC upgrades the shell to a fully interactive PTY-backed bash via util-linux script, with raw-mode stdio bridging and live window resizing — operator gets a real terminal inside the NiFi container.

Instructions

Prerequisites: Docker, Python 3.10+, and uv.

cd setup
./setup.sh

The first run downloads the graph-bundle NARs from Maven Central and pulls Docker images (~2-3 min). When setup completes it prints the ready-to-paste POC invocation.

Run the 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

The POC drops the operator into an interactive bash session inside the NiFi container running as the nifi service account. Type exit or press Ctrl-D to disconnect — the POC will then clean up the processor and controller service it created.

On Linux Docker (where host.docker.internal does not resolve by default), pass --shell-host <host-bridge-ip> or add extra_hosts: ["host.docker.internal:host-gateway"] to the nifi service in setup/docker-compose.yml.

Tear down:

cd setup
./teardown.sh

About

POC for CVE-2026-39816 which allows NiFi users without execute code permissions to run arbitrary scripts

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors