Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-8933 — CVE-2026-8933에 대한 C 기반 개념 증명(PoC)으로, cgroup 동결, 심볼릭 링크 경쟁 조건, udev 규칙 주입을 이용해 root 권한으로 명령을 실행하는 snap-confine 로컬 권한 상승 취약점입니다. | Kitploit
도구/GitHubGitHub/yifengchen-cc/cve-2026-8933
Privilege EscalationContainer SecurityVulnerability AnalysisExploitation
GitHubyifengchen-cc/cve-2026-8933

CVE-2026-8933

CVE-2026-8933에 대한 C 기반 개념 증명(PoC)으로, cgroup 동결, 심볼릭 링크 경쟁 조건, udev 규칙 주입을 이용해 root 권한으로 명령을 실행하는 snap-confine 로컬 권한 상승 취약점입니다.

저장소 보기
14일 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-8933 — snap-confine set-capabilities 로컬 권한 상승 PoC

Qualys 공지 《set-capabilities 버전의 snap-confine에서의 로컬 권한 상승》(https://cdn2.qualys.com/advisory/2026/07/21/snap-confine-set-capabilities.txt)을 재현합니다.

로컬 머신(Ubuntu 25.10 / snapd 2.73+ubuntu25.10.1 / 커널 6.17)에서 실측: uid 1000에서 출발하여 최종적으로 systemd-udevd가 root로 명령을 실행하고, /tmp/pwned가 uid=0(root) gid=0(root) groups=0(root)를 얻었으며, 공지의 마지막 시연 효과와 일치한다.

재현 과정에서 겪은 시행착오 기록, 그리고 첫 평가에서 왜 "악용 불가"로 오판했는지는 **NOTES.md**를 참조.

파일

파일설명
poc.c전체 PoC(단일 파일)
Makefile빌드(시스템에 설치된 libfuse3.so.3.17.4 링크)
include/libfuse3 3.17.4 헤더 파일(시스템 런타임 라이브러리와 일치)
NOTES.md시행착오 기록 + 도달 가능성 분석 방법론 회고

취약점 원리

cmd/snap-confine/mount-support.c(snapd 2.73):

sc_bootstrap_mount_namespace()      // 第 475 行起
  479:  mkdtemp("/tmp/snap.rootfs_XXXXXX")          <- 目录先归调用者所有
  491:  sc_do_mount(scratch, scratch, NULL, MS_BIND)
  498:  sc_do_mount("none", scratch, NULL, MS_UNBINDABLE)
  503:  sc_do_mount("none", scratch, "tmpfs", ...)
  504:  sc_replicate_base_rootfs(scratch, rootfs_dir, mounts)

sc_replicate_base_rootfs()
  330:  chown(scratch, 0, 0)
  418:  fd = open(full_path, O_CREAT | O_TRUNC, 0644)   <- 缺少 O_NOFOLLOW
  422:  fchown(fd, 0, 0)

행 번호는 본机 snapd 2.73의 소스 코드(cmd/snap-confine/mount-support.c)에서 가져온 것입니다. Qualys 공지는 자체 보관한 파일 사본을 인용한 것으로, 대응하는 행 번호는 511 / 523 / 530 / 535 / 536 / 362 / 450 / 454 입니다 —— 문장은 일대일로 대응하며, 단지 행 번호만 다릅니다.

이 기기의 /usr/lib/snapd/snap-confine는 mode=755 + 파일 capability(즉 setuid-root가 아님)이므로, 공격자의 euid로 실행되면서도 거의 root에 가까운 capability를 보유합니다. 따라서 이 프로그램이 생성하는 디렉터리와 파일은 잠시 동안 공격자 소유가 되며, 418번째 줄의 open()은 심볼릭 링크를 따라갑니다.

디스어셈블리로 확인: 본机 open@plt의 플래그는 0x240(O_CREAT|O_TRUNC, O_NOFOLLOW 없음)입니다. 수정된 2.77.1의 동일 지점은 0x20240입니다.

PoC의 여섯 단계

  1. snap-confine을 자신의 cgroup에 넣기: systemd-run --user --scope --unit=snap.<snap>.<app>. 이 scope는 현재 사용자에 속하며 cgroup.freeze가 쓰기 가능하므로, 시스템 콜 단위로 snap-confine을 통과시킬 수 있습니다.
  2. 올바른 위치에서 동결: SNAPD_DEBUG=1일 때 snap-confine은 associated snap application process ... with device cgroup을 출력합니다. 이 줄은 장치 cgroup의 BPF 프로그램 attach 이후, mkdtemp() 이전에 나타납니다. 더 일찍 동결할 수 없습니다: freeze trap이 존재하면 BPF 검증기가 EAGAIN을 반환하고 snap-confine이 즉시 종료됩니다. stderr는 pty(파이프가 아님)에 연결됩니다: 그렇지 않으면 stdio가 블록 버퍼링되어 marker가 종종 mkdtemp()와 bind mount 이후에야 flush됩니다. 또한 scope는 CPUQuota=1%로 시작됩니다: marker에서 mkdtemp()까지는 수백 마이크로초의 CPU 시간뿐으로, 캡처 프로세스의 깨어남 지연과 같은 규모입니다; 스로틀링은 이 창을 실제 시간에서 밀리초 수준으로 늘리며, 동결이 확인되면 즉시 cpu.max를 100%로 복원합니다.
  3. scratch 디렉터리를 우리 디렉터리를 가리키는 심볼릭 링크로 교체: mount(2)는 대상 경로 내의 심볼릭 링크를 따라가므로, snap-confine의 bind/unbindable/tmpfs가 모두 <scratch>.dA에 위치하게 되고, 복사 루프는 여전히 scratch 디렉터리라는 문자열을 사용합니다.
  4. tmpfs가 마운트되기를 기다린 후(ns_mount_count(sc, .dA) >= 2), 심볼릭 링크를 <scratch>.dB로 다시 지정하고, dB 안에 base rootfs 최상위 일반 파일과 같은 이름의 심볼릭 링크를 넣어 /run/udev/rules.d/97-cve-2026-8933-<pid>-<rand>.rules를 가리키게 합니다. 이때 복사 루프의 경로 해석은 dB —— 우리가 완전히 제어할 수 있는 일반 디렉터리 —— 로 떨어집니다. tmpfs는 dA에 남아 더 이상 방해하지 않습니다. 주의: .dA에 마운트가 1개뿐일 때는 bind mount입니다(/tmp 자체가 tmpfs이고, bind 후에도 fstype은 여전히 tmpfs). 반드시 두 번째 마운트(진짜 tmpfs)가 나타난 후에 다시 지정해야 하며, 그렇지 않으면 snap-confine의 mount --make-unbindable이 경로가 더 이상 마운트 루트가 아니어서 EINVAL로 실패합니다.
  5. open()/fchown() 사이의 창을 획득: 복사 루프는 첫 번째 일반 파일에 대해 open(O_CREAT|O_TRUNC)(심볼릭 링크를 따라가며, root 권한으로 /run/udev/rules.d 아래에 파일 생성)를 수행한 뒤 fchown(fd,0,0)을 수행합니다. 파일은 이 순간 여전히 우리 소유입니다. PoC는 동시에 두 가지 수단을 사용합니다:
    • 주 경로: 계속 freezer로 매번 약 하나의 시스템 콜만 통과시키며, stat()이 파일을 보고 uid가 여전히 자신임을 확인하면 즉시 chmod 0666 + udev 규칙 쓰기(snap-confine이 동결되어 창이 "벌어진" 상태);
    • 폴백: 여러 spinner 프로세스가 chmod() 핫 루프로 같은 창을 선점합니다. spinner가 선점한 후 파일은 root 소유이지만 모드는 0666 —— 모드 비트는 fchown(0,0) 이후에도 유지되므로 주 경로도 동일하게 파일을 다 쓸 수 있습니다(코드에서 판단하는 것은 "쓸 수 있는가"이지 "소유자인가"가 아닙니다).
  6. udev 트리거: snap-confine의 AppArmor profile은 /run/udev/** rw를 허용합니다. FUSE 파일 시스템을 마운트/언마운트하면 커널이 bdi 서브시스템에 대해 add uevent를 발생시키고, systemd-udevd가 즉시 root 권한으로 규칙의 PROGRAM=을 실행합니다.

사용법

make
./poc --diagnose            # 只读自检,不改动任何系统状态
./poc                       #  exploit,默认最多 5 次尝试
./poc --attempts 8          # 增加尝试次数
./poc --file default256.png # 只用 base rootfs 里的这一个普通文件
sudo ./poc --cleanup        # 清理(仅在异常中断留下残留时需要)

参数:

参数설명
--snap NAME어떤 snap의 rootfs를 사용할지 (기본값 firefox, 환경 변수 CVE8933_SNAP로도 지정 가능)
--app NAME해당 snap의 app (기본값은 meta/snap.yaml의 apps: 아래 첫 번째 항목)
--file NAMEbase rootfs 안의 이 일반 파일 하나만 사용; 일치하는 항목이 없으면 오류와 함께 종료
--attempts N시도 횟수 (기본값 5)
--step US / --step2 US두 단계의 조각당 CPU 시간(마이크로초), 디버깅용
--keep<scratch>.dA / .dB 디렉터리와 심볼릭 링크 유지 (디버깅용)
--diagnose읽기 전용 자체 점검
--cleanup과거 잔여물 정리 (root 필요)

로그는 /tmp/cve8933.log에 기록되고, snap-confine의 디버그 출력은 /tmp/cve8933-debug.txt에 기록됩니다.

CVE8933_PAYLOAD_CMD='<shell 명령>'로 payload 본문을 대체할 수 있습니다(다른 root 명령 실행 시연용). 이 경우 자동 정리가 수행되지 않으며, 사용자 정의 명령도 여전히 /tmp/pwned에 기록해야 합니다. 그렇지 않으면 PoC는 이번 시도가 성공하지 못한 것으로 간주합니다.

실측 결과

$ ./poc
[+] WIN: uid=0(root) gid=0(root) groups=0(root)
[+] exploit succeeded, evidence in /tmp/pwned

/tmp/cve8933.log 에 해당하는 성공 사례(2026-10-23, 변경 후 재테스트):

[    0.256] === attempt 1/5 ===
[ 1062.158] marker seen after 750.5 ms
[ 1067.530] scratch dir /tmp/snap.rootfs_oFBraI replaced by a symlink to ...dA
[ 1100.887] bind mount + tmpfs on ...dA after 417 slices
[ 1100.974] scratch directory now points to ...dB with 4 symlinks -> ...rules
[ 1108.782] took the file over while snap-confine was stopped (242 slices)
[ 1108.820] rule written after 797.1 ms
[ 1108.832] target: uid=0 gid=0 mode=0666 size=73 content="ACTION=="add", ..."

(이전 8회 시도 실행에서는 2번째에 성공했습니다: 1번째의 freeze 확인이 너무 늦어서 snap-confine이 같은 타임 슬라이스 안에서 bind mount를 지나쳐 버렸습니다.)

/run/udev/rules.d/에 잠시 나타났던 내용:

-rw-rw-rw- 1 root root 73 ... 97-cve-2026-8933-85431-485b.rules

내용은 ACTION=="add", SUBSYSTEM=="bdi", PROGRAM="/tmp/cve-2026-8933-payload.sh"이며, udevd 로그에서 로드되었음이 확인되었고("marked world-writable ... Proceeding anyway"), payload는 root로 실행되었습니다.

성공률에 관하여

5단계의 윈도우(open()와 fchown() 사이)는 시스템 콜 하나 또는 두 개 폭밖에 되지 않는다. 주 경로(freezer 단일 단계 + 동결 시점 인수)는 대부분의 시도에서 성공한다. 간혹 동결 확인이 늦어지면 snap-confine이 같은 타임 슬라이스 안에서 bind mount를 지나쳐 버리고, 해당 시도는 곧바로 실패하여 재시도한다. 기본 5회 시도이며, 실측으로 연속 세 번 실행에서 각각 2, 1, 2회 시도 내에 성공했다. --attempts를 늘리면 된다.

정리와 복원

정상 실행 시 PoC는 스스로 정리한다: payload가 root 권한으로 snap-confine이 전파한 모든 /tmp/snap.rootfs_* 마운트를 언마운트하고, 해당 디렉터리를 삭제하며, /run/udev/rules.d/97-cve-2026-8933*.rules와 두 스크립트를 삭제한다. 테스트 전에 먼저 베이스라인을 기록하고, 실행 후 항목별로 대조하며, 실측으로 매번 성공 실행 후:

mounts under /tmp/snap.rootfs_*: 0
leftover directories:            0
/run/udev/rules.d:               只有 90-netplan.rules / 99-netplan-ens33.rules
/etc/passwd md5:                 24c3aa8f7b7bdf6fa770b46601abc1a0(未改变)
user scopes:                     0

유일하게 의도적으로 남겨둔 것은 /tmp/pwned(root 소유, 즉 공지 데모의 증거 파일)입니다. 이를 삭제하려면 root 권한이 필요합니다: sudo ./poc --cleanup.

여러 번 시도가 모두 실패하면(예: 시스템 부하가 매우 높은 경우) 여러 /tmp/snap.rootfs_* 마운트와 디렉터리가 남을 수 있습니다. sudo ./poc --cleanup이 이를 함께 정리합니다.

보안 설명

이 PoC는 취약점 연구 및 검증에만 사용됩니다:

  • payload는 기본적으로 id > /tmp/pwned만 실행하고 자체 정리하며, 어떤 자격 증명 파일도 수정하지 않습니다 (/etc/passwd, /etc/shadow 모두 건드리지 않으며, md5로 확인 가능);
  • 호스트 이름, 네트워크, proxy를 변경하지 않고, 소프트웨어 패키지를 설치/제거하지 않으며, 재부팅하지 않습니다;
  • 매 실행 시 자체적으로 생성한 마운트를 자동으로 언마운트하고 임시 파일을 삭제합니다.
도구 다운로드