
Apache Spark 4.2.0 - 높음 - 원격 - 파일 읽기 - History zip이 심어진 심볼릭 링크를 따라감
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-history-zip-symlink
분류: 파일 읽기 도달 범위: 원격
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
CVE-2025-54920으로 History Server의 Jackson이 잠겨 sparkEventFromJson은 SparkListenerEvent 하위 클래스만 생성합니다. 하지만 롤링 zipEventLogFiles는 여전히 모든 listStatus 자식을 FileSystem.open을 통해 덤프합니다. LocalFileSystem은 POSIX 심볼릭 링크를 따라갑니다. eventlog_v2_*에 심어둔 자식이 인증 없는 GET /api/v1/applications/{id}/logs에 압축되어 포함됩니다. zip 멤버 본문은 SHS-UID 대상입니다. 엔트리 이름은 심볼릭 링크의 basename입니다.
이것은 Jackson RCE의 재신고가 아닙니다. Jackson은 닫혀 있습니다. 이것은 호스트 RCE가 아닙니다. 무결성과 가용성은 없습니다.
| ID | 아직 CVE 없음 |
| 분류 | 파일 읽기 (CVE-2025-54920 피해자 모델의 롤링 zip 잔여물) |
| 도달 범위 | 원격 (History 로그 디렉터리 작성자 + zip의 인증 없는 GET) |
| CWE | CWE-59, CWE-22 |
| CVSS | High: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| 제품 | Apache Spark |
| 영향받는 버전 | 4.2.0 태그 v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) |
| 인증 | PR:L = spark.history.fs.logDirectory에 쓰기 (54920과 동일한 피해자). History UI ACL은 기본적으로 꺼져 있음. |
| 라이선스 | GNU Affero GPL v3.0 |
| 랩 | 127.0.0.1:18980 전용 |
spark.history.fs.logDirectory(기본값 file:/tmp/spark-events)에 쓸 수 있는 사람은 History Server가 압축할 롤링 디렉터리를 심을 수 있습니다. 롤링 작성자는 기본적으로 켜져 있습니다. History UI ACL은 기본적으로 꺼져 있습니다. SHS 프로세스 UID가 혼동된 대리인입니다.
| 권한 | 메서드 / 싱크 | 랩 검증 또는 추론 |
|---|---|---|
| 기밀성: SHS UID가 열 수 있는 호스트 파일 읽기 | 유효한 appstatus_*와 events_* JSON(SparkListenerLogStart / SparkListenerApplicationStart)에 더해 로그 디렉터리 밖을 가리키는 심볼릭 링크 자식을 가진 eventlog_v2_<id>/를 심음. 스캔을 기다림. 인증 없는 GET /api/v1/applications/{id}/logs (OneApplicationResource.getEventLogs -> FsHistoryProvider.writeEventLogs -> RollingEventLogFilesFileReader.zipEventLogFiles -> FileSystem.open) | 랩 검증됨. zip 멤버 eventlog_v2_app-2026-0001/planted-secret 본문은 SPARK-HISTORY-ZIP-SYMLINK-WITNESS. events 멤버에는 witness가 없음. 엔트리 이름은 대상 경로가 아니라 심볼릭 링크 basename으로 유지됨. |
기밀성: 공격자가 open할 수 없는(660) 동일 테넌트의 이벤트 로그 | 동일한 zip, 심볼릭 링크 대상은 다른 앱의 로그 | 추론됨. 동일한 FileSystem.open. 검증되지 않음. |
기밀성: SHS가 읽을 수 있다면 keytab, spark-env.sh, /etc/passwd | 동일한 zip | 추론됨. 오라클은 해당 파일들이 아니라 전용 /secret/host-secret임. |
| SHS 또는 호스트 파일의 무결성 | zip은 HTTP 본문으로 읽힘 | 닫힘. 랩 검증 결과 없음. |
| SHS 또는 호스트 파일의 가용성 | zip은 대상을 삭제하거나 잘라내지 않음 | 닫힘. 랩 검증 결과 없음. |
| 호스트 RCE / Jackson gadget construct | JsonProtocol.sparkEventFromJson | 닫힘. SparkListenerEvent가 필요함. 이 랩은 무해한 Event JSON만 심음. |
엔트리 이름의 ../ zip slip | Path.getName() | 닫힘. POSIX 파일명은 /를 포함할 수 없음. 엔트리는 eventlog_v2_<id>/<basename>. |
resolveLogPath의 경로 문자열 이탈 | new Path(logDir, attempt.logPath) | 닫힘. Path 객체는 구성된 로그 디렉터리 아래에 머무름. 이탈은 inode임. |
| 대상의 compaction 재작성 / cleaner 삭제 | spark.history.fs.eventLog.rolling.maxFilesToRetain 기본값 Integer.MAX_VALUE; cleaner 기본값 꺼짐 | 닫힘. 심볼릭 링크에 대한 로컬 fs.delete는 대상이 아니라 링크를 unlink함. |
| 인증 없는 job 제출 / ML 모델 로드 | SECURITY.md FAQ / docs/ml-security.md | 닫힘. 설계상 그렇고, 이 잔여물이 아님. |
master나 4.3 RC가 아니라 apache/spark v4.2.0(32f7299601108917fb01920a54e084595b7b3bf8)을 고정했습니다. 스물여덟 번의 추적. Jackson History RCE는 이 버전에서 닫혀 있습니다(JsonProtocol.scala:965-972). History Server는 여전히 공격자가 쓸 수 있는 로그 디렉터리를 가리키고 있었고, 이는 CVE-2025-54920과 동일한 피해자 모델입니다.
zipEventLogFiles는 이벤트 파일만이 아니라 모든 files를 압축합니다:
override def zipEventLogFiles(zipStream: ZipOutputStream): Unit = {
val dirEntryName = rootPath.getName + "/"
zipStream.putNextEntry(new ZipEntry(dirEntryName))
files.foreach { file =>
addFileAsZipEntry(zipStream, file.getPath, dirEntryName + file.getPath.getName)
}
}
addFileAsZipEntry는 fileSystem.open 후 transferTo입니다. LocalFileSystem은 POSIX 심볼릭 링크를 따라갑니다. getLogDirInfo는 "심볼릭 링크를 적절히 처리한다"고 주석을 달고는 표시 이름을 위해 해석되지 않은 Path 부모를 순회합니다.
이미 기록된 잘못된 방향들: SPARK-52381을 History 전체 이야기로 취급한 것; witness를 events_*에 심은 것(랩이 이를 실패시킴); zip 엔트리가 대상 경로를 담을 것이라 기대한 것; resolveLogPath 접두사 결합을 이탈로 취급한 것; 이 버전에서 HiveConnection Event 클래스를 시도한 것.
python3 spark-history-zip-symlink-Abraxas-Labs.py
또는
cd lab
./run.sh
EventLogFileReaders.scala의 롤링 zip 덤프 gadget이며, 완전한 History Server도 아니고 공식 spark:4.2.0 이미지도 아닙니다. Jackson을 통해 Event JSON을 파싱하지 않습니다. 루프백 127.0.0.1:18980. Compose 프로젝트 spark-history-zip-symlink. 롤링 디렉터리는 유효한 appstatus_* + events_*(witness 없음)와 planted-secret -> /secret/host-secret을 담고 있습니다.
SUCCESS SPARK-HISTORY-ZIP-SYMLINK SPARK-HISTORY-ZIP-SYMLINK-WITNESS
모든 listStatus 이름이 아니라 events_* / appstatus_* 자식만 압축하십시오. no-follow(O_NOFOLLOW / Hadoop no-symlink 옵션)로 열고, realpath가 spark.history.fs.logDirectory 밖에 있는 자식은 거부하십시오. 표시 이름 헬퍼는 realpath 검사가 아닙니다.
32f7299601108917fb01920a54e084595b7b3bf8)EventLogFileReaders.scala (addFileAsZipEntry ~48-56, zipEventLogFiles ~267-272)FsHistoryProvider.scala (writeEventLogs ~831-856, resolveLogPath ~866-896)OneApplicationResource.scala (GET .../logs ~128-165)JsonProtocol.scala (sparkEventFromJson ~965-972, Jackson 잠금)GNU Affero GPL v3.0. LICENSE 참조. 루프백 랩 전용. 보증 없음.