
POC para CVE-2026-39816, que permite que usuários do NiFi sem permissões de execução de código executem scripts arbitrários.
Demonstração de prova de conceito para CVE-2026-39816, uma bypass da
permissão EXECUTE_CODE no Apache NiFi 2.8.0 que permite que um designer
de fluxo que teve os privilégios de execução de código deliberadamente
negados execute Groovy arbitrário na JVM do NiFi por meio do processador
ExecuteGraphQuery do graph bundle.
@Restricted ausente no TinkerpopClientService (CVE-2026-39816, CWE-95)O NiFi restringe processadores que executam código por trás da permissão
EXECUTE_CODE por meio da anotação
@Restricted(requiredPermission = RequiredPermission.EXECUTE_CODE).
Todos os 16 componentes dedicados de script no NiFi —
ExecuteScript, InvokeScriptedProcessor, ExecuteGroovyScript,
ScriptedTransformRecord, etc. — possuem essa anotação, portanto um
designer de fluxo precisa receber a permissão explícita EXECUTE_CODE
antes que a camada de autorização permita criar ou configurar esses
processadores.
O graph bundle (nifi-graph-nar + nifi-other-graph-services-nar)
inclui um terceiro caminho de execução de código que não possui essa
anotação:
// 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); // linha 463 — compila string do atacante
compiledCode.put(s, compiled);
...
compiled.setBinding(bindings);
Object result = compiled.run(); // linha 477 — executa na JVM do NiFi
O parâmetro s é a string de consulta da propriedade "Graph Query" do
processador ExecuteGraphQuery, passada sem alterações. Não há
saneamento, nenhuma lista de permissões e nenhuma sandbox — qualquer
Groovy válido é compilado e executado com todos os privilégios do
processo NiFi.
Nem TinkerpopClientService nem os processadores ExecuteGraphQuery /
ExecuteGraphQueryRecord que o utilizam possuem a anotação
@Restricted. A camada de autorização do NiFi, portanto, os trata
como componentes comuns, e um designer de fluxo que teve o
EXECUTE_CODE explicitamente negado ainda pode criá-los.
Um servidor NiFi 2.8.0 é explorável quando ambas as condições a seguir são verdadeiras:
O graph bundle opcional está instalado. Especificamente o NAR
nifi-other-graph-services-nar — este é o bundle que inclui o
TinkerpopClientService. Servidores sem este NAR não são afetados.
Pelo menos um usuário possui permissões de designer de fluxo
(read/write em um process group e em /controller) sem
EXECUTE_CODE. Este é um formato de política comum: uma organização
deseja que usuários possam construir e modificar pipelines, mas não
executar código arbitrário. A permissão EXECUTE_CODE é precisamente
o limite de segurança que essa política pretende impor.
Se ambas as condições forem atendidas, o usuário pode configurar um
TinkerpopClientService no modo "ByteCode Submission", apontá-lo para
qualquer servidor Gremlin acessível (o servidor Gremlin é usado apenas
para inicialização do serviço — a execução ocorre localmente) e criar
um processador ExecuteGraphQuery com código Groovy na propriedade
"Graph Query". Iniciar o processador compila e executa o Groovy na
JVM do NiFi.
Execução arbitrária de código como a conta de serviço do NiFi. A partir daí:
flow.json.gz, colocando um NAR
no diretório de autoload ou modificando um controller service.O exploit contorna a permissão EXECUTE_CODE, portanto funciona mesmo
quando o operador removeu explicitamente os direitos de execução de
código do usuário — exatamente o cenário que a permissão existe para
prevenir.
Este repositório inclui um POC, cobrindo o gatilho mais direto:
Groovy controlado pelo atacante colocado diretamente na propriedade do
processador Graph Query. Sem conexão upstream, sem Expression Language,
sem necessidade de conteúdo FlowFile — apenas o processador executando
em seu próprio timer.
Existem dois caminhos de código adicionais (conteúdo do corpo do
FlowFile usado como consulta quando Graph Query está vazio, e
interpolação via Expression Language de atributos FlowFile influenciados
pelo atacante no template da consulta). Eles não são demonstrados aqui
porque alcançam o mesmo sink com pré-condições extras; o caminho direto
é suficiente para provar a bypass do EXECUTE_CODE.
setup/ — Ambiente Docker Compose. Inicia uma instância NiFi 2.8.0
(imagem oficial apache/nifi:2.8.0) com os NARs do graph bundle
carregados automaticamente, um servidor LDAP fornecendo dois usuários
de teste (admin e flow_designer) e um servidor Gremlin para o
TinkerpopClientService apontar. O setup.sh inicializa as políticas
para que flow_designer tenha direitos de edição de fluxo, mas
não EXECUTE_CODE, e depois verifica a configuração.
pocs/flow_designer_groovy_rce.py — Exploit autônomo.
Autentica como flow_designer, demonstra que ExecuteScript é negado
(a barreira EXECUTE_CODE está funcionando), depois cria um
+ com um payload Groovy
que inicia um reverse shell bash para um listener que o POC inicia
localmente. Uma vez estabelecida a conexão, o POC eleva o shell para
um bash totalmente interativo com PTY via util-linux , com
ponte stdio em modo raw e redimensionamento de janela em tempo real —
o operador obtém um terminal real dentro do contêiner NiFi.
Pré-requisitos: Docker, Python 3.10+ e uv.
cd setup
./setup.sh
A primeira execução baixa os NARs do graph bundle do Maven Central e puxa as imagens Docker (~2-3 min). Quando o setup termina, ele imprime a invocação do POC pronta para colar.
Execute o 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
O POC coloca o operador em uma sessão bash interativa dentro do
contêiner NiFi executando como a conta de serviço nifi. Digite exit
ou pressione Ctrl-D para desconectar — o POC então limpará o
processador e o controller service que criou.
No Docker Linux (onde host.docker.internal não resolve por padrão),
passe --shell-host <host-bridge-ip> ou adicione
extra_hosts: ["host.docker.internal:host-gateway"] ao serviço nifi
em setup/docker-compose.yml.
Encerramento:
cd setup
./teardown.sh
TinkerpopClientServiceExecuteGraphQueryscript