The insights-runtime-extractor runs as a privileged DaemonSet (`hostPID: true`) that scans container processes for runtime fingerprinting. W
위협 신호 · CVSS · EPSS · KEV
이론적 심각도 점수
30일 내 악용 확률 예측
실측 악용 기록 없음
별도 긴급 패치 불필요 — 정기 시스템 업그레이드 주기에 맞춰 조치
CVSS 벡터 · 메트릭
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H약점 (CWE)
상세 설명
The insights-runtime-extractor runs as a privileged DaemonSet (hostPID: true)
that scans container processes for runtime fingerprinting. When scanning a
container, the coordinator:
- fork()s a child process
- setns() into the target container's PID and mount namespaces
- fchdir() back to an orphaned FD pointing at the extractor's own root
filesystem (where/crio.sockis hostPath-mounted) - Calls only
seteuid(target_uid)— changing the effective UID but leaving
the real UID and saved-set-UID at 0 and retaining the permitted
capability set - The
check_no_privileged_perms()guard inspects only the Effective capability
set (so it passes), and is skipped entirely when the target runs as uid 0 - The
VersionExecutablefingerprint invokesfpr_kind_executablewith
process.command_line[0]— the target's argv[0], read from
/proc/<pid>/cmdlineand fully controlled by the workload fpr_kind_executablecallsexec.Command(executable, "--version").Output()
— executing the workload's own binary inside the workload's mount
namespace while ruid=0 in the host user namespace
The executed process can call setuid(0) to restore full root with
CAP_SYS_ADMIN, and inherits the orphaned cwd which resolves into the extractor
container's filesystem containing the host CRI-O socket.
Affected Code
1. Insufficient privilege drop (Rust — extractor coordinator)
File: extractor/src/insights_runtime_extractor.rs
1// Lines 139-146 — seteuid-only drop, guard skipped for root containers 2join_process_namespaces(process.pid)?; 3change_dir(current_dir); 4switch_user(*process.uid)?; 5if *process.uid != 0 { 6 perms::check_no_privileged_perms() 7 .expect("Must not have privileged permissions to run fingerprints"); 8} 9fingerprint::run_fingerprints(&config, out_dir, &process); 1// Lines 182-185 — only effective UID changed; ruid/suid stay 0 2fn switch_user(uid: u32) -> Result<(), ScannerError> { 3 debug!("... Becoming user: {uid}"); 4 seteuid(uid.into()).map_err(|e| ScannerError::Errno(e)) 5}2. Execution of untrusted binary (Go — fingerprint)
File: fingerprints/pkg/utils/file.go
1// Lines 75-80 — executes a path taken verbatim from workload's argv[0] 2func GetExecutableVersionOutput(executable string) (string, error) { 3 out, err := exec.Command(executable, "--version").Output() 4 if err != nil { 5 return "", err 6 } 7 return string(out), nil 8}3. Workload-controlled path forwarded (Rust — version_executable)
File: version_executable.rs
1// Lines 29-34 — workload-controlled command_line[0] forwarded to fingerprint 2return Some(vec![ 3 fpr_kind_executable, 4 out_dir.to_string(), 5 String::from(&process.command_line[0]), 6 String::from(&version_executable.runtime_kind_name), 7]);Attack Pattern
Attacker: Any tenant able to schedule a pod (unprivileged, any namespace)
Victim: The node running the insights-runtime-extractor DaemonSet
Trigger: Automatic — the DaemonSet scans all container processes on the node
Attack sequence:
- Attacker creates a pod whose process name matches a configured
versioned-executable pattern (node,python,python3,MainThread,
or containsjava) - The pod's main binary (at the path matching argv[0]) is a malicious
executable that callssetuid(0)to restore root - When the insights operator triggers a scan, the DaemonSet forks, enters the
attacker's mount namespace, and executes the attacker's binary with ruid=0 - The attacker's code regains full root with
CAP_SYS_ADMINin the host user
namespace - The inherited cwd contains the host CRI-O socket → full node compromise
AI 심층 분석
공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.