
CVE-2026-39816에 대한 POC로, 실행 코드 권한이 없는 NiFi 사용자가 임의의 스크립트를 실행할 수 있게 해줍니다.
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); // line 463 — 공격자 문자열 컴파일
compiledCode.put(s, compiled);
...
compiled.setBinding(bindings);
Object result = compiled.run(); // line 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이 없는 서버는 영향을 받지 않습니다.
최소 한 명의 사용자가 EXECUTE_CODE 없이 플로우 디자이너 스타일 권한
(프로세스 그룹 및 /controller에 대한 read/write)을 가진 경우.
이는 일반적인 정책 형태입니다: 조직은 사용자가 파이프라인을 구축하고
수정할 수 있기를 원하지만 임의 코드를 실행할 수는 없기를 원합니다.
EXECUTE_CODE 권한은 바로 그러한 정책이 강제하려는 보안 경계입니다.
두 조건이 모두 충족되면, 사용자는 TinkerpopClientService를
"ByteCode Submission" 모드로 구성하고, 연결 가능한 Gremlin 서버를
가리킬 수 있습니다 (Gremlin 서버는 서비스 초기화에만 사용됨 — 실행은
로컬에서 발생), 그리고 "Graph Query" 속성에 Groovy 코드가 있는
ExecuteGraphQuery 프로세서를 생성할 수 있습니다. 프로세서를 시작하면
NiFi JVM에서 Groovy가 컴파일되고 실행됩니다.
NiFi 서비스 계정으로 임의 코드 실행. 여기서부터:
flow.json.gz에 쓰기, autoload 디렉토리에 NAR 배포, 또는 컨트롤러
서비스 수정을 통해 지속성 확보.이 익스플로잇은 EXECUTE_CODE 권한을 우회하므로, 운영자가 사용자에게서
코드 실행 권한을 명시적으로 제거한 경우에도 작동합니다 — 정확히 이
권한이 존재하는 이유인 시나리오입니다.
이 저장소는 가장 직접적인 트리거를 다루는 하나의 POC를 제공합니다:
공격자가 제어하는 Groovy를 Graph Query 프로세서 속성에 직접 배치.
업스트림 연결, Expression Language, FlowFile 콘텐츠가 필요 없음 —
프로세서가 자체 타이머로 실행되는 것만으로 충분합니다.
두 가지 추가 코드 경로가 존재합니다 (Graph Query가 비어 있을 때
FlowFile 본문 콘텐츠를 쿼리로 사용, 공격자가 영향을 미치는 FlowFile
속성을 쿼리 템플릿에 Expression Language 보간). 추가 사전 조건이
있어 동일한 싱크에 도달하므로 여기서는 시연하지 않습니다. 직접 경로만으로
EXECUTE_CODE 우회를 증명하기에 충분합니다.
setup/ — Docker Compose 환경. 그래프 번들 NAR이 자동 로드된
NiFi 2.8.0 인스턴스(공식 apache/nifi:2.8.0 이미지), 두 명의 테스트
사용자(admin 및 flow_designer)를 제공하는 LDAP 서버, 그리고
TinkerpopClientService가 가리킬 Gremlin 서버를 구성합니다.
setup.sh는 flow_designer가 플로우 편집 권한은 있지만 EXECUTE_CODE는
없도록 정책을 부트스트랩한 다음 구성을 검증합니다.
pocs/flow_designer_groovy_rce.py — 독립 실행형 익스플로잇.
flow_designer로 인증하고, ExecuteScript가 거부됨을
시연한 다음(EXECUTE_CODE 게이트가 작동 중), POC가 로컬에서
시작하는 리스너로 bash 리버스 셸을 생성하는 Groovy 페이로드가 포함된
TinkerpopClientService + 를 생성합니다.
연결이 설정되면 POC는 util-linux 를 통해 셸을 완전한
대화형 PTY 기반 bash로 업그레이드하며, raw 모드 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
ExecuteGraphQueryscript