
POC per CVE-2026-39816 che consente agli utenti NiFi senza permessi di esecuzione del codice di eseguire script arbitrari
Dimostrazione proof-of-concept per CVE-2026-39816, un bypass del permesso EXECUTE_CODE
in Apache NiFi 2.8.0 che consente a un progettista di flussi a cui sono stati
deliberatamente negati i privilegi di esecuzione del codice di eseguire Groovy arbitrario
nella JVM di NiFi tramite il processore ExecuteGraphQuery del bundle graph.
@Restricted mancante su TinkerpopClientService (CVE-2026-39816, CWE-95)NiFi limita i processori che eseguono codice dietro il permesso EXECUTE_CODE
tramite l'annotazione @Restricted(requiredPermission = RequiredPermission.EXECUTE_CODE).
Tutti i 16 componenti di scripting dedicati in NiFi —
ExecuteScript, InvokeScriptedProcessor, ExecuteGroovyScript,
ScriptedTransformRecord, ecc. — portano questa annotazione, quindi un
progettista di flussi deve ricevere il permesso esplicito EXECUTE_CODE prima
che il livello di autorizzazione gli consenta di creare o configurare questi
processori.
Il bundle graph (nifi-graph-nar + nifi-other-graph-services-nar)
include un terzo percorso di esecuzione del codice a cui manca questa annotazione:
// 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); // riga 463 — compila la stringa dell'attaccante
compiledCode.put(s, compiled);
...
compiled.setBinding(bindings);
Object result = compiled.run(); // riga 477 — esegue nella JVM di NiFi
Il parametro s è la stringa di query dalla proprietà "Graph Query" del
processore ExecuteGraphQuery, passata senza modifiche. Non c'è
sanitizzazione, nessuna lista consentita e nessuna sandbox — qualsiasi Groovy valido viene
compilato ed eseguito con tutti i privilegi del processo NiFi.
Né TinkerpopClientService né i processori ExecuteGraphQuery /
ExecuteGraphQueryRecord che lo utilizzano portano l'annotazione
@Restricted. Il livello di autorizzazione di NiFi li tratta quindi
come componenti ordinari, e un progettista di flussi a cui è stato
esplicitamente negato EXECUTE_CODE può comunque crearli.
Un server NiFi 2.8.0 è sfruttabile quando entrambe le seguenti condizioni sono vere:
Il bundle graph opzionale è installato. Nello specifico la
NAR nifi-other-graph-services-nar — questo è il bundle che include
TinkerpopClientService. I server senza questa NAR non sono interessati.
Almeno un utente ha permessi da progettista di flussi
(read/write su un process group e su /controller) senza
EXECUTE_CODE. Questa è una configurazione di policy comune: un'organizzazione
vuole che gli utenti possano creare e modificare pipeline, ma non eseguire
codice arbitrario. Il permesso EXECUTE_CODE è precisamente il
confine di sicurezza che quella policy intende applicare.
Se entrambe le condizioni sono soddisfatte, l'utente può configurare un
TinkerpopClientService in modalità "ByteCode Submission", puntarlo a qualsiasi
server Gremlin raggiungibile (il server Gremlin viene usato solo per
l'inizializzazione del servizio — l'esecuzione avviene localmente), e creare un
processore ExecuteGraphQuery con codice Groovy nella proprietà "Graph Query".
L'avvio del processore compila ed esegue il Groovy nella JVM di NiFi.
Esecuzione di codice arbitrario come account di servizio NiFi. Da lì:
flow.json.gz, inserendo una NAR
nella directory di autoload o modificando un controller service.Lo sfruttamento bypassa il permesso EXECUTE_CODE, quindi funziona anche
quando l'operatore ha esplicitamente rimosso i diritti di esecuzione del codice
all'utente — esattamente lo scenario che il permesso esiste per prevenire.
Questo repository include un POC, che copre il trigger più diretto:
Groovy controllato dall'attaccante inserito direttamente nella proprietà del
processore Graph Query. Nessuna connessione a monte, nessun Expression Language,
nessun contenuto FlowFile necessario — solo il processore che gira sul suo
timer.
Esistono due percorsi di codice aggiuntivi (contenuto del body FlowFile usato come
query quando Graph Query è vuoto, e interpolazione Expression Language
degli attributi FlowFile influenzati dall'attaccante nel template di query).
Non sono dimostrati qui perché raggiungono
lo stesso sink con precondizioni extra; il percorso diretto è sufficiente
per dimostrare il bypass di EXECUTE_CODE.
setup/ — Ambiente Docker Compose. Avvia un'istanza NiFi 2.8.0
(immagine ufficiale apache/nifi:2.8.0) con le NAR del bundle graph
caricate automaticamente, un server LDAP che fornisce due utenti di test (admin
e flow_designer), e un server Gremlin a cui far puntare il
TinkerpopClientService. setup.sh avvia le
policy in modo che flow_designer abbia diritti di modifica dei flussi ma
non EXECUTE_CODE, poi verifica la configurazione.
pocs/flow_designer_groovy_rce.py — Exploit autonomo.
Autentica come flow_designer, dimostra che ExecuteScript
è negato (il gate EXECUTE_CODE funziona), poi crea un
TinkerpopClientService + con un payload Groovy
che avvia una reverse shell bash verso un listener che il POC
avvia localmente. Una volta stabilita la connessione, il POC
aggiorna la shell a una bash completamente interattiva con PTY tramite
di util-linux, con bridging stdio in modalità raw e
ridimensionamento live della finestra — l'operatore ottiene un terminale reale
dentro il container NiFi.
Prerequisiti: Docker, Python 3.10+ e uv.
cd setup
./setup.sh
La prima esecuzione scarica le NAR del bundle graph da Maven Central e scarica le immagini Docker (~2-3 min). Al termine, il setup stampa l'invocazione del POC pronta da incollare.
Esegui il 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
Il POC porta l'operatore in una sessione bash interattiva dentro il
container NiFi in esecuzione come account di servizio nifi. Digita exit o
premi Ctrl-D per disconnetterti — il POC pulirà quindi il processore
e il controller service che ha creato.
Su Docker Linux (dove host.docker.internal non risolve per
impostazione predefinita), passa --shell-host <host-bridge-ip> o aggiungi
extra_hosts: ["host.docker.internal:host-gateway"] al servizio nifi
in setup/docker-compose.yml.
Smontaggio:
cd setup
./teardown.sh
ExecuteGraphQueryscript