본문 바로가기
CVE 취약점

CVE-2026-49869 분석: 문자열 하나로 Kestra 서버를 root로 장악하는 인증 우회

by WhiteGuidance 2026. 10. 12.
반응형

개요

항목 내용
CVE ID CVE-2026-49869
영향 제품 Kestra OSS (오픈소스 워크플로/오케스트레이션 플랫폼)
영향 버전 1.0.45 미만, 그리고 1.1.0 이상 ~ 1.3.21 미만
패치 버전 1.0.45, 1.3.21
CVSS 3.1 10.0 (Critical) — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
취약점 유형 CWE-287(인증 미흡), CWE-184(불완전한 차단 목록), CWE-78(OS 커맨드 인젝션), CWE-918(SSRF)
인증 요구 여부 불필요
비고 CISA KEV 등재 (2026-09-02), 실제 악용(크립토마이너 설치 등) 사례 확인됨

 

Kestra는 워크플로를 정의하고 스케줄링·실행하는 오픈소스 오케스트레이션 플랫폼입니다. 로그인 없이도 접근 가능해야 하는 엔드포인트가 딱 하나 있는데, 바로 초기 설치 화면이 로그인 전에 읽어야 하는 공개 설정 조회 API입니다. 이 단 하나의 예외를 만들기 위해 작성한 조건문 한 줄이, Kestra 전체 인증 체계를 무력화하는 구멍이 되었습니다.

 

원인은 간단합니다. "이 경로가 설정 조회 API인가?"를 판단해야 할 자리에, "이 경로가 configs라는 문자열로 끝나는가?"를 판단하는 코드가 들어가 있었습니다. 둘은 의도한 단 하나의 경로에 대해서는 같은 답을 내놓지만, 그 외의 모든 경로에 대해서는 전혀 다른 답을 내놓습니다. 그리고 Kestra의 REST API는 흐름(flow) 이름처럼 공격자가 직접 정할 수 있는 값을 경로에 그대로 포함시키는 구조라서, 그 차이를 공격자가 손쉽게 파고들 수 있었습니다.

 

결과적으로 누구든 흐름의 이름을 configs로 짓기만 하면, 그 흐름을 생성·실행·삭제하는 모든 API가 인증 없이 열립니다. 그리고 Kestra는 셸·파이썬·Node 스크립트를 실행하는 플러그인을 기본으로 켜둔 채 배포되고, 그 실행은 워커 컨테이너 안에서 root 권한으로 이뤄집니다. 인증 우회 하나가 바로 무인증 원격 코드 실행(RCE)으로 직결되는 이유입니다.


취약점이 숨어있던 자리 — AuthenticationFilter

Kestra OSS의 기본 인증 방식은 Basic Auth이며, 모든 API 요청은 AuthenticationFilter라는 단일 필터를 거칩니다. 이 필터는 요청이 들어올 때마다 "이 경로는 인증 없이 통과시켜도 되는가"를 먼저 판단합니다. 로그인 화면이 뜨기도 전에 서버의 기본 설정(로그인 방식, 기능 플래그 등)을 읽어야 하므로, 설정 조회 API(GET /api/v1/configs, GET /api/v1/{tenant}/configs)만큼은 예외로 두어야 했습니다.

 

문제가 된 코드는 이 예외 처리 한 줄입니다.


단계별 소스코드 분석

 

1단계 — "정확히 이 경로인가"가 아니라 "이 문자열로 끝나는가"

endsWith("/configs")가 묻는 질문은 "이 요청 경로의 마지막 부분이 문자열 /configs와 일치하는가"입니다. 반면 원래 필요했던 질문은 "이 요청이 바로 그 공개 설정 조회 API인가"입니다. 두 질문은 전혀 다릅니다.

 

Kestra의 REST API는 리소스를 /flows/{namespace}/{flowId}처럼 사용자가 정한 이름(flowId)으로 식별합니다. 즉 flowId를 configs로 설정하는 것만으로, 그 흐름과 관련된 거의 모든 경로의 마지막 조각이 /configs가 되어버립니다. endsWith()는 그 경로가 설정 조회 API인지 흐름 관리 API인지 전혀 구분하지 못한 채로 똑같이 통과시킵니다.

 

실제로 이 결함으로 인증을 건너뛸 수 있었던 경로는 한둘이 아니었습니다.

컨트롤러 우회되는 패턴 영향
FlowController PUT /api/v1/{tenant}/flows/{namespace}/configs 흐름 생성·덮어쓰기
ExecutionController POST /api/v1/{tenant}/executions/{namespace}/configs 흐름 실행
KVController PUT /api/v1/{tenant}/namespaces/{namespace}/kv/configs 임의 키-값 저장소 쓰기
FlowController DELETE /api/v1/{tenant}/flows/{namespace}/configs 흐름 삭제
DashboardController DELETE /api/v1/{tenant}/dashboards/configs 대시보드 삭제
LogController DELETE /api/v1/{tenant}/logs/{namespace}/configs 감사 로그 삭제

이 중 FlowController와 ExecutionController 조합이 바로 RCE로 이어지는 체인의 핵심입니다.

 

2단계 — 인증 없이 셸 명령을 담은 흐름 생성

공격자는 자격 증명 없이 PUT 요청 하나로 configs라는 이름의 흐름을 만듭니다. 이 흐름의 작업(task) 타입을 io.kestra.plugin.scripts.shell.Commands로 지정하면, 실행될 때 셸 명령어 목록을 그대로 실행합니다.

 

 

요청 경로가 /api/v1/main/flows/tutorial/configs로 /configs로 끝나기 때문에, AuthenticationFilter는 이 요청을 설정 조회 API와 동일하게 취급해 그대로 통과시킵니다.

 

3단계 — 인증 없이 해당 흐름 실행

생성만으로는 아직 코드가 실행되지 않습니다. 실행을 트리거하는 두 번째 요청도 같은 패턴의 경로를 사용하므로 역시 인증이 우회됩니다.

 

4단계 — 워커가 root로 플러그인을 실행하며 RCE 완성

이 시점에서 Kestra 워커는 실행 요청을 받아 shell.Commands 작업을 실제로 수행합니다. Kestra는 설치 시 셸·파이썬·Node·PowerShell·Go 등 80개 이상의 스크립트 실행 플러그인을 기본으로 함께 설치하며, 워커 프로세스는 컨테이너 안에서 기본적으로 uid=0(root)로 동작합니다. 인증 우회로 생성한 흐름이 "셸 명령을 실행하라"는 작업 하나만 가지고 있어도, 그 명령은 root 권한으로 그대로 수행됩니다.

[INFO] uid=0(root) gid=0(root) groups=0(root)
[INFO] root:x:0:0:root:/root:/bin/bash
[INFO] daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
[DEBUG] Command succeed with exit code 0
 

파이썬 플러그인으로도 동일하게 동작합니다.

 

 

5단계 — 인증 없이 실행 로그에서 결과 회수

마지막으로, 실행 결과(명령의 표준 출력)는 로그 검색 API를 통해 조회할 수 있는데, 이 경로 역시 /configs로 끝나는 패턴을 포함하므로 인증 없이 열람 가능합니다.

curl "http://<target>/api/v1/main/logs/search?executionId=4eeB6vY7FT3mSj5FGYRrXW"
 

생성 → 실행 → 결과 회수, 세 번의 HTTP 요청 모두에 인증 토큰이나 세션 쿠키가 전혀 포함되지 않습니다. 공격자는 로그인 화면을 단 한 번도 거치지 않고 서버 안에서 임의 명령을 실행하고 그 결과를 그대로 돌려받습니다.

 

 

https://youtu.be/3lKOZi5JtIU

 


부가 공격면 — Pebble 템플릿 엔진을 통한 SSRF

이 인증 우회는 또 다른 취약점과 결합할 수 있습니다. Kestra의 워크플로 템플릿 엔진(Pebble)은 http()라는 함수를 제공하는데, 전달받은 URI에 아무런 검증을 하지 않습니다.

 

연구자는 이를 통해 워커 컨테이너에서 클라우드 메타데이터 엔드포인트(http://169.254.169.254/)에 접근 가능함을 확인했습니다. AWS·GCP·Azure 환경이라면, 이 메타데이터 엔드포인트를 통해 해당 인스턴스에 부여된 IAM 자격 증명이 그대로 유출될 수 있습니다. RCE 자체로 이미 충분히 심각하지만, 이 SSRF 경로는 사고 대응 시 "명령 실행 흔적만 봐서는 놓칠 수 있는" 추가 유출 지점이라는 점에서 별도로 점검할 가치가 있습니다.


패치 — 접미사 비교를 경로 일치 비교로 교체

Kestra 1.0.45와 1.3.21은 endsWith()로 구현되어 있던 비교 로직을 정확한 경로 일치 비교로 교체했습니다. 요청 경로가 공개 설정 조회 API의 실제 경로(/api/v1/configs, /api/v1/{tenant}/configs)와 정확히 같을 때만 인증을 건너뛰도록 바뀌었고, 그 결과 흐름 이름을 configs로 짓는 것만으로는 더 이상 어떤 경로도 인증 예외 대상이 되지 않습니다.

 

이 결함의 교훈은 단순합니다. 인증을 건너뛸 경로를 화이트리스트로 관리할 때는 접미사·포함 여부가 아니라 정확한 경로 일치로 비교해야 하며, 특히 REST API처럼 사용자가 지정한 값이 경로의 일부로 들어가는 구조에서는 endsWith()나 contains() 류의 비교가 인증 로직에 섞여 들어가 있지 않은지 별도로 점검할 필요가 있습니다.


대응 방안

  • 1.0.x 브랜치는 1.0.45로, 1.1.x~1.3.x 브랜치는 1.3.21로 즉시 업데이트합니다.
  • 웹서버 프로세스뿐 아니라 워커 컨테이너도 반드시 재시작해야 합니다. 실제 권한 있는 명령 실행은 워커에서 이뤄지므로, 이미지는 새 버전이어도 워커 프로세스가 교체되지 않으면 취약한 코드 경로가 계속 서비스됩니다.
  • 역방향 프록시를 앞단에 두는 것만으로는 방어되지 않습니다. 취약한 비교 로직이 Kestra 내부 필터 체인에서 실행되기 때문에, 프록시가 요청을 넘긴 뒤에야 문제가 발생합니다. 당장 패치가 어렵다면 프록시에서 /api/v1/configs와 같은 정확한 경로만 허용하는 규칙을 임시로 둘 수는 있으나, 정식 패치를 대체할 수는 없습니다.
  • 패치 적용 이후에도 노출 기간 동안 접근이 있었을 가능성을 전제로 사고 대응에 준하는 점검이 필요합니다. 워커가 접근 가능했던 시크릿·서비스 자격 증명·클라우드 IAM 역할을 교체하고, 흐름/실행 이력에서 알지 못하는 configs라는 이름의 흐름이나 실행이 있었는지 확인합니다.
반응형