
End-to-end reproduction and cross-layer detection of CVE-2026-53576, the unauthenticated RCE in Kestra - taken past the base PoC to show how a common Docker-socket misconfiguration turns container-root into full host compromise.
Kestra는 오픈소스 워크플로 오케스트레이션 플랫폼으로, 요청에 자격 증명이 필요한지 여부를 URL이 /configs로 끝나는지 확인하여 결정하는 인증 필터를 배포했습니다. 이 검사는 무해한 공개 엔드포인트 하나를 노출하기 위한 것이었습니다.
이 검사가 문자열의 끝부분만 확인했기 때문에, URL이 그렇게 끝나는 모든 요청은 인증을 완전히 건너뛰었으며, 여기에는 임의의 코드를 생성하고 실행하는 엔드포인트도 포함되었습니다. 그 결과: 네트워크를 통해 취약한 Kestra 인스턴스에 접근할 수 있는 사람은 누구나 자격 증명 없이, 피싱 없이, 비밀번호 추측 없이, 권한 상승 단계 없이 root로 명령을 실행할 수 있습니다.
이 실습에서는 그 단일 결함을 자체 호스팅 랩 인스턴스에 대해 처음부터 끝까지 실행했습니다:
모든 단계는 방어 텔레메트리 (네트워크 IDS, 호스트 런타임 보안, Linux 감사, 시스템 로그)에 대해 캡처되었습니다.
패치하지 않을 경우의 비즈니스 영향: Kestra를 실행하는 호스트의 완전한 장악이며, 애플리케이션만이 아니라 그 호스트에서 접근 가능한 다른 모든 것까지 확장됩니다.
수정: Kestra 1.0.45 / 1.3.21 이상으로 업그레이드하십시오 (패치만으로 기본 취약점은 해결되지만, 상승 및 지속성 단계는 별도의 독립적인 수정이 필요합니다 - 애플리케이션 컨테이너에 Docker 소켓을 마운트하지 마십시오, 섹션 8 참조).
이 보고서는 Kestra ≤1.3.20의 CVSS 10.0 인증되지 않은 원격 코드 실행 취약점인 CVE-2026-53576을 문서화하며, 이는 경로 접미사 인증 우회 (AuthenticationFilter.java, endsWith("/configs"))로 인해 발생합니다.
워크플로의 네임스페이스와 ID를 configs로 지정하는 공격자는 자격 증명 없이 Kestra 컨테이너 내부에서 root로 임의의 셸 명령을 생성하고 실행할 수 있습니다. 1.0.45 및 1.3.21에서 수정되었습니다. 동일한 버그는 CVE-2026-49869로도 독립적으로 보고되었으며, 이는 실제 야생에서의 악용과 관련된 CISA KEV 등재 번호입니다. 공개 소스와 스캐너가 둘 중 하나를 참조할 수 있으므로 둘 다 함께 인용해야 합니다.
| 취약 | Kestra ≤ 1.3.20 (및 1.0.x 라인의 1.0.45 이전) |
| 수정 | 1.0.45 / 1.3.21+ |
| CVE | CVE-2026-53576 |
| 쌍둥이 권고 | CVE-2026-49869 - 동일한 버그, CISA KEV 등재 (야생에서) |
| 날짜 | 이벤트 |
|---|---|
| 2026-06-02 | Kestra 1.3.21 릴리스; 변경 로그에 "인증 필터의 잠재적 인증 우회" 언급 |
| 2026-06-03 | Kestra 1.0.45가 1.0.x 라인에 릴리스 |
| 2026-09-02 | CVE-2026-49869 (쌍둥이 권고)가 CISA KEV 카탈로그에 추가됨 |
| 2026-09-22 ~ 09-24 | 이 랩 재현, 탐지 구축, 교차 계층 검증 |
자체 호스팅 홈랩:
- Debian 호스트 (호스트명 docker, 커널 6.12.107+deb13-amd64),
- Docker Engine 29.8.1.
- Kestra는 공식 kestra/kestra:v1.3.20 이미지로 배포되었으며, kestra.int.atlasvec.com의 Caddy 리버스 프록시를 통해 접근 가능.
- 테스트 중인 텔레메트리 스택: Falco (런타임/eBPF, 호스트 수준),
- Suricata 8.0.3 IDS (수동 SPAN 미러), Splunk로 전달.
무엇이든 건드리기 전에 대상이 실제로 취약한지, 텔레메트리 스택이 살아 있는지 확인했습니다. 또한 작업이 끝난 후 되돌릴 수 있도록 공격 전 상태의 스냅샷을 찍었습니다.
취약한 버전을 실행 중인 Kestra:

컨테이너 자체, 실행 중이며 접근 가능:

호스트에 로드된 Falco의 유닛:

Falco가 활발히 이벤트를 내보내는 중 (최신 eBPF 모드):

Suricata 서비스 활성:

그리고 eve.json이 실시간 이벤트를 기록 중:

이것은 하나가 아니라 두 가지 실수가 겹친 것입니다.
첫째, 인증 필터는 요청 경로의 끝을 매칭하여 요청이 인증을 건너뛸 수 있는지 결정합니다:```java // AuthenticationFilter.java — the vulnerable check, confirmed against the self-hosted v1.3.20 source if (path.endsWith("/configs")) { // treated as a public, unauthenticated path }
이 검사는 단일하고 무해한 엔드포인트인 공개 인스턴스 설정을 노출하기 위해 존재합니다. `endsWith`는 문자열의 끝부분만 읽기 때문에, 하나의 안전한 경로를 같은 방식으로 끝나는 다른 경로와 구별할 수 없습니다.
둘째, Kestra의 라우터는 여전히 그렇게 닮은 경로들을 실제의 민감한 핸들러로 디스패치합니다. `/api/v1/main/flows/configs`는 flow-create 핸들러에 도달합니다. `/api/v1/main/executions/configs/configs`는 execution 핸들러에 도달합니다. 필터는 둘 다 공개로 통과시키고, 라우터는 어쨌든 그것들을 실행합니다. flow의 namespace와 ID를 `configs`로 지정하면, 코드를 실행하는 엔드포인트가 이제 `/configs`로 끝나므로, 공개 경로의 무료 통과권을 상속받습니다.
캡처된 요청 쌍은 이를 명확히 보여줍니다. 자격 증명 없이 `GET /api/v1/main/flows/search`는 `401`을 반환합니다. 동일한 빈 auth 헤더로 `POST /api/v1/main/flows/configs`는 `200`을 반환하고 flow를 저장합니다(§5.1 참조).
## 5. 공격 시뮬레이션
> 아래의 모든 단계는 각각의 스크린샷과 함께 캡처됩니다. 모든 것은 리버스 프록시 뒤의 자체 호스팅 Kestra 인스턴스에 대해 제 격리된 홈랩 내부에서 실행됩니다.
### 5.1 1단계 - 인증 우회 -> Kestra 컨테이너 내부의 root
**대조 :** 자격 증명 없이 `GET /api/v1/main/flows/search` → `401 Unauthorized`.
정상 경로에서 인증이 강제됨을 확인합니다.

_____
**심기 :** auth 헤더 없이 `POST /api/v1/main/flows/configs`를 전송합니다. 본문은 `namespace`와 `flow id`가 모두 `configs`인 flow YAML이며, Process task runner를 사용하는 Commands task를 포함합니다. 서버는 `200 OK`를 반환하고 flow를 저장합니다. 그 외에는 보호되는 경로에 붙은 `/configs` 접미사가 우회를 유발하는 것입니다 **(섹션 4 참조).**

**청취 및 대기** : 테스트 목적으로 nc를 사용하여 간단한 리스너를 시작하여 리버스 셸을 잡습니다.

**트리거 / 실행** `POST /api/v1/main/executions/configs/configs`, auth 헤더 없음 → `200 OK`, 새 execution 생성됨.

**BaaaaM :** 셸을 획득했습니다. 하지만 기억하세요 - 우리는 "격리된" 상태이고, `root`이지만 Kestra docker 컨테이너 **내부**에 있으므로 여기서 탈출할 "수 없어야" 합니다. "**음, 그게 내가 생각한 거였는데**"

task의 명령은 Kestra JVM 프로세스의 직접 자식으로 실행되며, 호스트 측 프로세스 트리를 통해 확인되고, Kestra 자체의 실행 로그에 따라 성공적으로 완료됩니다.