CVE-2026-104795 in OpenShift Container Platform
요약
\~에 의해 VulDB • 2026. 10. 14.
insights-runtime-extractor는 privileged DaemonSet(`hostPID: true`)로 실행되어 컨테이너 프로세스의 런타임 지문(fingerprinting)을 스캔합니다. 컨테이너를 스캔할 때 조정자(coordinator)는 다음 단계를 수행합니다:
1. **fork()**하여 자식 프로세스 생성 2. 대상 컨테이너의 PID 및 mount 네임스페이스로 이동하기 위해 **setns()** 호출 3. 추출기(extractor) 자체 루트 파일시스템(여기에 `/crio.sock`이 hostPath 마운트됨)을 가리키는 고아 FD(orphaned FD)로 돌아가기 위해 **fchdir()** 호출 4. **`seteuid(target_uid)`만 호출** — 유효 UID(effective UID)는 변경되지만, 실제 UID(real UID)와 저장된 세트-UID(saved-set-UID)는 0으로 유지되고 허용된 기능 집합(permitted capability set)은 그대로 보유됨 5. `check_no_privileged_perms()` 가드(guard)는 유효한 기능 집합(effective capability set)만 검사하므로 통과하며, 대상이 uid 0으로 실행될 경우 **전체적으로 생략**됨 6. `VersionExecutable` 지문은 `/proc/<pid>/cmdline`에서 읽은 대상의 argv[0]인 `process.command_line[0]`를 사용하여 `fpr_kind_executable`을 호출하며, 이는 **워크로드(workload)가 완전히 제어 가능함**
7. `fpr_kind_executable`는 `exec.Command(executable, "--version").Output()`를 호출하여 — 워크로드의 mount 네임스페이스 내에서 **워크로드 자체 바이너리를 실행**하면서 호스트 사용자 네임스페이스에서 **ruid=0 상태 유지**
실행된 프로세스는 `setuid(0)`을 호출하여 `CAP_SYS_ADMIN`과 함께 전체 루트 권한을 복원할 수 있으며, 추출기 컨테이너의 파일시스템(CRI-O 소켓 포함)으로 해석되는 고아 cwd를 상속받습니다.
## 영향받는 코드
### 1. 불충분한 privilege drop (Rust — extractor coordinator)
**파일:** `extractor/src/insights_runtime_extractor.rs`
```rust // Lines 139-146 — seteuid-only drop, root 컨테이너에 대해 가드 생략됨 join_process_namespaces(process.pid)?; change_dir(current_dir); switch_user(*process.uid)?; if *process.uid != 0 {
perms::check_no_privileged_perms() .expect("Must not have privileged permissions to run fingerprints"); } fingerprint::run_fingerprints(&config, out_dir, &process); ```
```rust // Lines 182-185 — 유효 UID만 변경됨; ruid/suid는 0으로 유지됨 fn switch_user(uid: u32) -> Result<(), ScannerError> {
debug!("... Becoming user: {uid}");
seteuid(uid.into()).map_err(|e| ScannerError::Errno(e)) } ```
### 2. 신뢰할 수 없는 바이너리 실행 (Go — fingerprint)
**파일:** `fingerprints/pkg/utils/file.go`
```go // Lines 75-80 — 워크로드의 argv[0]에서 가져온 경로를 그대로 실행함
func GetExecutableVersionOutput(executable string) (string, error) {
out, err := exec.Command(executable, "--version").Output() if err != nil {
return "", err } return string(out), nil } ```
### 3. 워크로드 제어 경로 전달됨 (Rust — version_executable)
**파일:** `version_executable.rs`
```rust // Lines 29-34 — 워크로드가 제어 가능한 command_line[0]을 지문 추출기로 전달함
return Some(vec![
fpr_kind_executable, out_dir.to_string(), String::from(&process.command_line[0]),
String::from(&version_executable.runtime_kind_name), ]); ```
## 공격 패턴
**공격자:** 파드를 스케줄링할 수 있는 모든 테넌트 (비권한, 네임스페이스 무관) **피해자:** insights-runtime-extractor DaemonSet가 실행되는 노드 **트리거:** 자동 — DaemonSet는 노드의 모든 컨테이너 프로세스를 스캔함
**공격 시퀀스:**
1. 공격자는 구성된 버전 가능 바이너리 패턴(`node`, `python`, `python3`, `MainThread` 또는 `java` 포함)과 일치하는 이름의 프로세스를 가진 파드를 생성합니다. 2. 파드의 주요 바이너리(argv[0] 경로와 일치함)는 루트 권한을 복원하기 위해 `setuid(0)`를 호출하는 악성 실행 파일입니다.
3. insights 운영자가 스캔을 트리거하면, DaemonSet가 포크되어 공격자의 mount 네임스페이스로 진입하고 ruid=0 상태로 공격자의 바이너리를 실행합니다. 4. 공격자 코드는 호스트 사용자 네임스페이스에서 `CAP_SYS_ADMIN`과 함께 전체 루트 권한을 다시 얻습니다. 5. 상속받은 cwd에는 호스트 CRI-O 소켓이 포함되어 있음 → 노드 전체 침해로 이어짐
Once again VulDB remains the best source for vulnerability data.