
# CVE-2026-41651 개념 증명 익스플로잇 CVE-2026-41651에 대한 개념 증명 익스플로잇으로, PackageKit의 TOCTOU 로컬 권한 상승 취약점이며, 기술 분석, 탐지 로직, 그리고 수정 지침을 포함합니다.
분류: 퍼플 팀 평가 산출물
승인된 용도로만 사용하십시오. 이 문서와 첨부된 테스트 스크립트는 서면 승인을 받은 시스템에서의 내부 보안 검증 목적으로만 사용됩니다.

| 필드 | 값 |
|---|---|
| CVE ID | CVE-2026-41651 |
| CWE | CWE-367 — 검사 시점 사용 시점(TOCTOU) 경쟁 조건 |
| 구성 요소 | PackageKit (packagekitd, D-Bus 서비스) |
| 영향 범위 | PackageKit ≤ 1.3.4 |
| 수정 버전 | PackageKit 1.3.5 |
| 영향 | 로컬 권한 상승 → root |
| 공격 벡터 | 로컬 / D-Bus (권한 없는 사용자 세션) |
| 공개일 | 2026-04-22 (Deutsche Telekom Red Team) |
| 권고 | GHSA-f55j-vvr9-69xv |
| 수정 커밋 | 76cfb675fb31acc3ad5595d4380bfff56d2a8697 |
PackageKit은 시스템 패키지 관리를 위한 D-Bus 추상화 계층으로, Debian, Ubuntu, Fedora, RHEL, SUSE, Arch Linux의 GNOME 기반 데스크톱에 기본으로 포함되어 있습니다. 권한 없는 사용자를 대신하여 권한이 필요한 패키지 작업을 중재하기 때문에, 인증 흐름의 결함은 시스템 전체에 root 수준의 영향을 미칩니다.
pk-transaction.c(1.3.5 이전)는 액션 메서드 재호출에 대한 상태 가드를 적용하지 않았습니다. D-Bus 클라이언트는 트랜잭션이 이미 PK_TRANSACTION_STATE_NEW 상태를 벗어난 후에도 동일한 트랜잭션 객체에 대해 InstallFiles(또는 다른 액션 메서드)를 여러 번 호출할 수 있었습니다.
공격자 (권한 없음)
│
├─① CreateTransaction() → PackageKit이 트랜잭션 객체 경로(tid) 반환
│
├─② InstallFiles(tid, FLAG_SIMULATE=4, [dummy.pkg])
│ PackageKit이 dummy.pkg에 대한 polkit 인증 확인을 대기열에 추가.
│ 아직 설치가 발생하지 않음 — SIMULATE는 건식 실행만 의미.
│
├─③ InstallFiles(tid, FLAG_NONE=0, [payload.pkg]) ← TOCTOU 창
│ 인증이 해결되기 전에 동일한 tid에 대해 재호출.
│ 취약한 버전은 대기열에 있던 매개변수를 payload.pkg로 덮어씀.
│
└─④ polkit이 인증 부여 (사용자 승인 또는 자동 인증)
packagekitd가 payload.pkg를 root로 설치.
payload postinst/post 스크립트: install -m 4755 /bin/bash /tmp/.suid_bash
공격자가 /tmp/.suid_bash -p 실행 → root 셸.
②와 ③ 단계는 동일한 연결에서 비차단 비동기 D-Bus 호출로 전송되며 단일 쓰기로 플러시됩니다. 두 메시지는 packagekitd가 ②를 처리하고 상태 머신을 진행시키기 전에 도착하므로 TOCTOU 창이 열린 상태로 남습니다. 1.3.5의 수정은 PK_TRANSACTION_STATE_NEW 이후의 모든 재호출에 대해 PK_TRANSACTION_ERROR_INVALID_STATE를 반환하는 명시적 상태 검사를 추가합니다.
파일: cve-2026-41651-purpleteam.py
언어: Python 3
의존성: python3-gi (GObject introspection / GLib/Gio 바인딩)
준비된 테스트 시스템에서 CVE-2026-41651의 악용 가능성을 입증하며, 다음 목적을 위해 사용됩니다:
| 단계 | 작업 |
|---|---|
| 설정 | /proc/mounts를 조사하여 SUID/실행 가능한 드롭 디렉터리를 선택하고, umask(022) 설정, 모든 빌드 산출물에 명시적 chmod 적용 후 /tmp에 더미 및 페이로드 패키지 빌드 |
| 악용 | 시스템 D-Bus 연결을 열고 PackageKit 트랜잭션을 생성한 후 두 호출 경쟁 조건 실행 |
| 페이로드 | 패키지 사후 설치 스크립트가 /bin/bash를 <drop_dir>/.suid_bash로 모드 04755, 소유자 root로 복사. 드롭 디렉터리는 런타임에 결정됨 (강화된 환경 지원 참조) |
| 권한 상승 | SUID 바이너리를 폴링(90초 제한 시간)한 후 -p 옵션으로 execl하여 root 셸 획득 |
| 정리 | 종료 시(성공 또는 실패) 임시 .deb/.rpm 파일 제거 |
기본적으로 강화된 시스템은 두 가지 독립적인 지점에서 악용을 차단할 수 있습니다. 스크립트는 두 경우 모두 자동으로 처리합니다.
027 / 077)프로세스에서 상속된 umask 027 또는 077은 mkdir()이 750 또는 700 디렉터리를 생성하게 합니다. dpkg-deb와 rpmbuild 모두 전체 빌드 트리를 탐색해야 하며, 디렉터리 중 하나라도 읽을 수 없으면 빌드가 조용히 실패합니다.
적용된 수정: os.umask(0o022)가 파일이나 디렉터리가 생성되기 전에 시작 시 호출됩니다. 또한 빌드 트리의 모든 디렉터리와 파일은 생성 직후 명시적 chmod를 받습니다(디렉터리와 스크립트는 0o755, 데이터 파일은 0o644). 따라서 상속된 umask와 관계없이 올바른 권한이 보장됩니다.
/tmp의 nosuid / noexec 마운트 플래그| 플래그 | 악용에 미치는 영향 |
|---|---|
nosuid | 커널이 해당 파일 시스템에 저장된 모든 파일에서 SUID 비트를 조용히 제거 — 복사된 bash는 root가 되지 않음 |
noexec | SUID 바이너리를 전혀 실행할 수 없음 |
적용된 수정: 시작 시 _find_suid_dir()가 /proc/mounts를 읽고 두 플래그가 모두 설정되지 않은 파일 시스템을 찾을 때까지 우선순위 순서로 후보 디렉터리를 테스트합니다. 결정된 경로는 페이로드의 사후 설치 스크립트에 포함되고 폴링 및 실행에 사용됩니다.
| 우선순위 | 후보 | 일반적인 강화 수준 |
|---|---|---|
| 1 | /var/tmp | nosuid/noexec가 거의 없음; 재부팅 후에도 유지됨 |
| 2 | /dev/shm | tmpfs, 일반적으로 허용적; 재부팅 시 지워짐 |
| 3 | /tmp | CIS/STIG 시스템에서 자주 강화됨 |
| 4 | $HOME | 최후의 수단; 사용자가 항상 쓸 수 있음 |
모든 후보가 차단되면 스크립트는 조용히 실패하는 대신 명확한 오류와 함께 종료됩니다.
| 배포판 계열 | 패키지 도구 | 테스트됨 |
|---|---|---|
| Debian / Ubuntu | dpkg-deb | ✓ |
| RHEL / Fedora | rpmbuild | ✓ |
| SUSE / openSUSE | rpmbuild | ✓ |
IOC는 일반/인프라 수준에서 스크립트 특정 산출물 순으로 나열됩니다. 일반 지표를 기반으로 구축된 탐지는 사용된 PoC 변형과 관계없이 이 CVE를 탐지합니다.
| IOC | 경로 / 패턴 | 참고 |
|---|---|---|
| 전 세계 쓰기 가능 디렉터리의 SUID 바이너리 | <drop_dir>/.suid_bash — 드롭 디렉터리는 마운트 플래그에 따라 /var/tmp, /dev/shm, /tmp 또는 $HOME | 스크립트는 nosuid/noexec가 없는 첫 번째 디렉터리를 선택; 네 후보 모두를 포함하도록 탐지 범위 확장 |
/tmp의 임시 패키지 파일 | /tmp/*.deb, /tmp/*.rpm | 권한 없는 사용자가 빌드; 합법적인 패키지 작업은 /var/cache 또는 다운로드 경로 사용 |
| dpkg 빌드 트리 | /tmp/pkbuild_*, /tmp/build_* | dpkg-deb -b용 스테이징 디렉터리 |
/tmp의 rpmbuild 트리 | /tmp/rpmbuild_* | ~/rpmbuild 외부에서 호출된 rpmbuild용 스테이징 디렉터리 |
/tmp의 postinst / %post 스크립트 | /tmp/*/DEBIAN/postinst, /tmp/*/SPECS/*.spec | 시스템 바이너리를 복사하거나 chmod하는 스크립트는 신뢰도가 높음 |
| IOC | 세부 사항 |
|---|---|
비-root, 비-apt 프로세스가 생성한 dpkg-deb | 상위 프로세스는 사용자 Python/셸 프로세스이며 apt, dpkg 또는 unattended-upgrades가 아님 |
권한 없는 사용자가 생성한 rpmbuild | 패키징 관련 상위 프로세스(mock, koji, CI 러너)가 없으면 비정상 |
packagekitd가 /tmp 경로에서 /bin/sh 또는 /bin/bash 생성 | packagekitd는 postinst 스크립트를 실행; /tmp의 소스 경로가 비정상 |
UID ≠ EUID인 bash 프로세스 | SUID 실행; EUID=0, UID=<공격자> |
bash -p 호출 | -p 플래그는 SUID가 설정된 경우 권한 모드를 활성화 — 합법적으로 거의 사용되지 않음 |
수명이 짧은 .suid_bash 프로세스 계보 | 조부모는 packagekitd, 부모는 공격자의 셸 |
| IOC | 세부 사항 |
|---|---|
동일한 객체 경로에 org.freedesktop.PackageKit.Transaction.InstallFiles가 두 번 호출됨 | 두 호출 모두 수 밀리초 내에 도착; 합법적인 클라이언트는 트랜잭션당 한 번만 호출 |
동일한 tid에 FLAG_SIMULATE (4) 직후 FLAG_NONE (0) | 이 시퀀스는 합법적인 사용 사례가 없음 |
CreateTransaction → 빠른 이중 InstallFiles 패턴 | D-Bus 모니터(dbus-monitor --system) 또는 감사 dbus 규칙으로 탐지 가능 |
/tmp 아래의 파일 경로가 있는 InstallFiles | 합법적인 GUI 패키지 설치 프로그램(GNOME Software, Discover)은 $HOME 또는 /var/cache의 경로 사용 |
활성화:
auditctl -a always,exit -F arch=b64 -S all -F path=/usr/bin/packagekitd
또는 아래 규칙을/etc/audit/rules.d/에 사용: