COMMENTS (0)
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
PackageKit 1.0.2~1.3.4에 12년간 잠복한 TOCTOU 경합 CVE-2026-41651(Pack2TheRoot)이 무조건 플래그 덮어쓰기·조용한 상태 전이 거절·지연된 플래그 읽기로 polkit 인증을 무력화하는 흐름과 탐지·완화를 분해한다.
댓글은 익명으로 작성되며, 삭제 비밀번호를 설정하면 본인만 삭제할 수 있습니다. 비밀번호를 설정하지 않은 댓글은 누구나 삭제할 수 있습니다.
리눅스 데스크톱과 서버를 한꺼번에 흔든 취약점이 2026년 4월 22일 공개됐다. 도이치 텔레콤(Deutsche Telekom) 레드팀이 PackageKit 1.3.5 릴리스와 함께 CVE-2026-41651, 별칭 "Pack2TheRoot" 를 공개했다. 비밀번호 없이 로컬 일반 사용자를 루트로 만드는 TOCTOU(Time-of-check Time-of-use) 경합이다.
공격 전제는 낮다. 로컬 셸 하나와 D-Bus 호출 권한이면 충분하다.
영향 범위는 넓다. PackageKit 1.0.2 ~ 1.3.4 — 12년 넘는 릴리스 전체가 취약하다. 우분투·데비안·Rocky Linux·페도라 주요 릴리스가 기본 설치 상태에서 재현된다. Cockpit 같은 웹 관리 도구가 노출되면 원격 공격 표면으로 확장된다.
| 항목 | 값 |
|---|---|
| CVSS 3.1 | 8.8 (High) |
| 필요 권한 | 로컬 셸 + D-Bus |
| 사전 인증 | 없음 |
| 공격 복잡도 | 낮음 |
| 취약 범위 | PackageKit 1.0.2 ~ 1.3.4 |
| 검증된 배포판 | 우분투 18.04~26.04 · 데비안 Trixie 13.4 · Rocky Linux 10.1 · 페도라 43 |
CVSS 벡터 상세(AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H)는 NVD 항목 참조.
이 글은 왜 PackageKit이 이런 설계를 갖게 됐는지부터 따라가면서, 세 개의 버그가 어떻게 하나의 경합 창을 만들었는지를 분해한다. 마지막에는 패치 이전 조직이 당장 할 수 있는 완화책과 탐지 지표를 정리한다.
PackageKit은 단순한 "또 하나의 패키지 도구"가 아니다. 그놈 소프트웨어, KDE 디스커버, Cockpit 웹 콘솔 같은 최상위 UI가 뒤에서 호출하는 공용 백엔드다. 사용자가 GUI에서 "업데이트" 버튼 하나를 누를 때 뒤에서 대신 apt, dnf, zypper를 호출해 주는 계층이 바로 packagekitd 데몬이다.
이 계층은 세션 사용자가 보안 방식으로 패키지를 관리할 수 있는 D-Bus 추상화 계층이며, freedesktop.org가 공식 문서에서 명시한 목적이다. 다시 말해 PackageKit은 태생적으로 "권한 없는 세션 사용자"를 위한 것이다. 일반 사용자가 루트 권한 없이 소프트웨어를 설치하도록 만드는 것이 존재 이유이고, 그래서 설계의 핵심은 어떤 사용자가 어떤 조건에서만 권한이 승격되는지를 엄격히 통제하는 것이다.
그 승격 결정은 또 다른 freedesktop 프로젝트인 polkit이 맡는다. PackageKit의 D-Bus 메서드를 부를 때 polkit은 "이 사용자가 이 액션을 할 수 있는가"를 판정하고, 허용되면 인증 프롬프트 없이 바로 통과시키거나, 아니면 관리자 비밀번호를 묻는 방식으로 상승시킨다. 문제는 이 판정이 "지금 이 순간"의 트랜잭션 플래그를 본다는 점이다.
Pack2TheRoot는 바로 이 판정과 실행 사이의 시간차를 공격한다. 판정 시점에는 "신뢰된 패키지만 설치"라는 온순한 플래그를 보여 주고, 실제 실행 시점에는 그 플래그를 끈 상태로 임의 RPM이나 DEB를 루트로 설치한다. 인증은 정당하게 통과했고, 실행된 작업은 정당하지 않다. 시스템 로그에도 정상 설치로 남는다.
12년이라는 잠복 기간도 주목할 만하다. PackageKit 1.0.2는 2014년 릴리스이고, 그 이후 수많은 리팩터링을 거치면서도 이 세 개의 버그는 살아남았다. 한 개의 논리적 결함이 아니라 세 개의 독립 결함이 결합해야 성립하는 경합이라는 점이 이유일 수 있다. 정적 분석 도구로도, 코드 리뷰로도 한 번에 보이지 않는 부류의 취약점이다.
공격을 이해하려면 평범한 pkcon install 호출이 내부에서 어떤 단계를 거치는지부터 봐야 한다.
pkcon은 PackageKit의 커맨드라인 프런트엔드다. pkcon install-local ./foo.rpm 을 치면 pkcon 은 직접 RPM을 다루지 않는다. D-Bus 시스템 버스를 통해 org.freedesktop.PackageKit 서비스로 메서드 호출을 보낸다. 이 서비스의 실행 주체는 루트 권한으로 동작하는 packagekitd 시스템 데몬이다.
데몬은 CreateTransaction 으로 새 트랜잭션 객체를 만들고, 클라이언트에게 트랜잭션 경로(예: /org/freedesktop/PackageKit/Tx_12)를 돌려준다. 그 다음 클라이언트는 그 경로 위에서 SetHints, InstallFiles 같은 메서드를 이어 호출한다. 각 트랜잭션은 유한 상태 기계(finite state machine)로 관리된다. NEW → WAIT_IN_QUEUE → RUNNING → FINISHED 같은 순서다.
이 트랜잭션에는 flags가 붙는다. freedesktop 스펙의 org.freedesktop.PackageKit.Transaction.xml 에서 정의된 트랜잭션 플래그 중 가장 중요한 것이 ONLY_TRUSTED 다. 이 플래그가 TRUE면 데몬은 서명이 확인된 패키지만 설치하고, FALSE면 서명이 없거나 유효하지 않은 패키지도 허용한다. 보안 관점에서 TRUE와 FALSE는 완전히 다른 액션이다. 그래서 polkit 정책도 두 경우를 구분한다. 신뢰된 설치는 보통 프롬프트 없이 통과되지만, 신뢰되지 않은 설치는 관리자 비밀번호를 요구한다.
polkit은 이 판정을 "이 D-Bus 호출이 요청한 플래그 조합"을 기준으로 내린다. 문제는, 클라이언트가 플래그를 보내고 polkit이 읽는 시점, 그리고 데몬이 실제로 백엔드를 돌리기 위해 플래그를 읽는 시점이 서로 다르다는 것이다. 그 사이에 같은 트랜잭션 객체 위에서 새 InstallFiles 가 다시 불리면 플래그는 덮어써진다. 여기에서 경합 창이 생긴다.
Pack2TheRoot의 제일 정확한 요약은 NVD 공식 설명이다.
"The issue stems from three bugs: unconditional flag overwriting, silent state-transition rejection, and delayed flag reading at execution time." — NVD,
세 개의 버그를 하나씩 본다.
InstallFiles() 는 호출될 때마다 전달받은 플래그를 transaction->cached_transaction_flags 에 바로 저장한다. 트랜잭션의 현재 상태를 확인하지 않는다. 이미 인증이 통과된 상태든, 디스패치가 예약된 상태든, 무조건 최신 값으로 덮는다. PackageKit 소스 src/pk-transaction.c 의 관련 라인은 GitHub 커밋 04057883 에서 확인할 수 있다.
이것만으로는 취약점이 아니다. 같은 트랜잭션에서 InstallFiles 를 재호출하는 게 원래 허용되지 않아야 하니까. 그런데 두 번째 버그가 이 보호를 풀어 버린다.
트랜잭션 상태 전이는 pk_transaction_set_state() 를 거친다. 정상 흐름이라면 FINISHED 나 RUNNING 같은 상태에서 뒤로 되돌아가는 전이는 막혀야 한다. 실제로도 이 함수는 잘못된 역방향 전이를 거부한다. 그런데 거부하는 방식이 "오류를 돌려주고 전체를 중단"이 아니라 조용히 드롭이다. 상태는 바뀌지 않지만, 그 상태 변경을 요구했던 스레드는 그대로 계속 진행한다. 디스패치 스레드가 이미 대기 큐에 올라가 있으면, 큐는 자기 차례에 cached_transaction_flags 를 읽어 그대로 실행한다.
결과적으로 공격자는 두 번째 InstallFiles 호출이 "거부됐는지 수용됐는지" 를 외부에서 구분할 필요가 없다. 거부되어도 플래그 덮어쓰기는 일어났고, 디스패치는 정상 흐름을 탔기 때문이다.
백엔드가 실제 패키지를 설치하려고 스케줄러의 idle 콜백이 깨어날 때, 스케줄러는 cached_transaction_flags 를 "그 순간" 다시 읽는다. pk-transaction.c 의 라인 2273~2277 근처에서 벌어지는 동작이다. 인증 시점에 polkit이 보았던 플래그가 아니라, 디스패치 시점의 최신 값이다. 공격자가 그 사이에 플래그를 갈아치웠다면, 스케줄러는 갈아치운 값을 가지고 백엔드에 지시를 내린다.
이 "읽기" 시점이 인증 시점과 묶여 있지 않다는 것이 세 번째 버그이자 TOCTOU의 본질이다. 검사는 과거 플래그로, 사용은 현재 플래그로 한다.
정상 호출과 공격 호출의 차이는 이렇게 요약된다.
이 두 번째 호출은 polkit을 다시 거치지 않는다. 이미 첫 호출로 트랜잭션 자체가 인증됐기 때문이다. polkit의 정책 엔진은 "어떤 플래그가 실행됐는가"는 알지 못한다. 재인증을 트리거하는 것은 메서드 호출이지 플래그의 변화가 아니기 때문이다. 이 디자인적 공백이 세 버그와 결합해 완전한 우회가 된다.
공격자가 얻는 것은 단순한 루트가 아니다. 정식 패키지 관리자를 거친 설치이기 때문에 RPM DB나 dpkg 상태 파일에 정상 기록이 남는다. 많은 HIDS/EDR 룰은 "의심스러운 바이너리 드롭"을 보지만 "패키지 관리자를 통한 정당해 보이는 설치"는 통과시킨다. 결과적으로 공격자는 지속성(persistence)을 얻기 좋은 위치에 들어간다. 악성 postinstall 스크립트가 시스템 서비스로 자리 잡으면, 이후 탐지는 일반 침해 사고 조사에서 가장 어려운 쪽으로 넘어간다.
접근 전제도 실질적이다. Cockpit은 많은 리눅스 서버가 웹 관리용으로 열어 두는 도구다. 도이치 텔레콤은 공식 권고에서 "Cockpit이 노출된 인터넷 서버"를 우선 패치 대상으로 지목했다. Cockpit 세션은 PackageKit의 D-Bus 메서드를 호출할 권한을 갖기 때문에, 정당하게 로그인한 관리자 계정 하나만 탈취되면 내부 공격자의 기준선이 곧바로 루트가 된다.
이 취약점은 공격자가 상태 전이를 뒤로 돌리려다 드롭된 흔적을 남긴다. 데몬의 어설션이 깨지기 때문이다. 공개된 IOC 문자열은 다음과 같이 저널에 남는다.
PackageKit:ERROR:../src/pk-transaction.c:514:pk_transaction_finished_emit: assertion failed
이 문자열은 정상 사용에서는 거의 발생하지 않는다. 리눅스 호스트 집중 로그에서 다음 Sigma 룰로 탐지 신호를 잡을 수 있다.
title: PackageKit CVE-2026-41651 TOCTOU Exploitation Indicator
id: 9b7f1f66-41be-4d2a-9b7a-pack2theroot24
status: experimental
description: Pack2TheRoot 익스플로잇 시 PackageKit 데몬이 남기는 어설션 실패 로그
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-41651
- https://github.com/PackageKit/PackageKit/security/advisories/GHSA-f55j-vvr9-69xv
logsource:
product: linux
service: syslog
detection:
keywords:
- 'pk-transaction.c:514:pk_transaction_finished_emit: assertion failed'
condition: keywords
falsepositives:
- PackageKit 데몬 비정상 종료로 인한 희귀 어설션
level: high
추가 관찰 지점은 D-Bus 호출 감사다. systemd-journald가 켜져 있으면 journalctl --unit packagekit --since "1 hour ago" 로 동일 트랜잭션 경로에 대해 InstallFiles 가 짧은 간격으로 두 번 이상 호출된 이력을 확인할 수 있다. 정상 프런트엔드는 같은 트랜잭션 객체에 같은 메서드를 재호출하지 않는다.
근본 조치는 PackageKit 1.3.5 이상으로의 업데이트다. 배포판별 패치 상태는 Debian, Ubuntu, Fedora, Rocky Linux 모두 공개 이후 순차 릴리스 중이다. 서버 측에서는 packagekit 패키지 버전을 확인한 뒤 배포판 보안 채널에서 최신으로 올리는 것이 첫 단계다.
업데이트가 당장 불가능한 환경에는 polkit 규칙으로 게이트를 강화하는 우회책이 있다. 공식 권고에 따르면 polkit 규칙을 수정해 PackageKit 관련 액션에 대해 일반 사용자에게도 관리자 인증을 강제하면 애초에 첫 번째 InstallFiles 호출 단계에서 비밀번호 프롬프트가 뜬다. 경합 창이 열리기 전 단계에서 막는 것이다. /etc/polkit-1/rules.d/ 아래에 다음 원칙의 룰 파일을 추가한다.
polkit.addRule(function(action, subject) {
if (action.id.indexOf("org.freedesktop.packagekit.") === 0) {
return polkit.Result.AUTH_ADMIN;
}
});
이 규칙은 GUI에서 업데이트 버튼을 누를 때마다 인증 프롬프트를 띄우기 때문에 사용성이 떨어진다. 패치 적용까지의 단기 조치로만 쓰고, 업데이트 완료 후에는 원복하는 것이 맞다.
마지막으로 네트워크 경계 통제 측면에서는 Cockpit의 외부 노출을 차단하고, 반드시 노출해야 하는 경우에는 관리자 계정의 FIDO2/MFA 및 IP 허용 리스트를 최소한 적용한다. PackageKit 취약점 자체가 원격 코드 실행은 아니지만, Cockpit 세션 위에 올라타면 공격자의 발판이 곧 루트가 되는 구조다.
pk-transaction.c:514: assertion failed 로그와 같은 트랜잭션 경로의 InstallFiles 이중 호출이다.AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 인용된 통계와 사례는 참고 자료에 명시된 출처에 근거하며, 설명을 위한 일부 표현은 각색되었습니다.
면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.
KW_PROTECT_0