
리눅스 LPE - 신뢰할 수 있는 Jail/컨테이너 탈출
CentOS / RHEL 10용 비특권 컨테이너 / jail 탈출 proof of concept입니다.
현재는 수정된 __ip6_append_data()의 IPv6 단편화 버그(상류 커밋 38becddc로 패치됨, CVE 없음)를 이용하며, 패킷 자체의 head 객체 끝에 있는 skb_shared_info로의 in-slab 선형 오버플로(in-slab linear overflow)입니다. 이 README는 익스플로잇 체인만 다룹니다. 트리거는 다루지 않습니다.
6.12.0-242.el10).이것은 proof of concept으로, 의도적으로 바로 사용 가능한 무기(turnkey weapon)가 아닙니다. 신뢰성 확보 장치는 의도적으로 빠져 있습니다: per-CPU PCP 정리도 없고, cross-cache SLUB 아머링도 없으며, skb head는 조용하지 않은 공유 캐시에 배치됩니다. 배포되는 코드는 체인을 증명할 만큼은 자주 동작하지만, 그 이상은 아닙니다. CentOS / RHEL 10 전용입니다.
CONFIG_INIT_ON_ALLOC_DEFAULT_ON이 꺼져 있어야 합니다(RHEL / CentOS 기본값). 이 PoC는 초기화되지 않은 slab 바이트에 오래된 포인터를 심습니다. init_on_alloc=1이면 해당 슬롯이 0으로 채워져 이 특정 기법은 커널을 크래시만 시킬 뿐입니다(NULL 역참조). 이는 버그의 한계가 아니라 기법의 한계입니다. 다른 기법으로 해당 커널까지 커버하는 전체 익스플로잇은 수정 패치와 CVE가 공개된 뒤에 공개될 예정입니다./sys/kernel/btf/vmlinux가 존재하고 세계적으로 읽기 가능해야 합니다(RHEL / CentOS에서는 기본 제공).| 배포판 | 커널 | 결과 |
|---|---|---|
| CentOS Stream 10 | 6.12.0-242.el10 | root, 컨테이너 탈출 |
| RHEL 10 | 6.12.0-228.el10 | httpd_t 컨텍스트 탈출 |
In-slab 오버플로를 통한 self-UAF. 이 버그로 skb_shared_info의 단일 nr_frags 바이트를 제어할 수 있습니다. 해제 경로(skb_release_data())는 frags[0 .. nr_frags)를 순회하며 각 항목에 put_page()를 호출하는데, frags[]는 절대 초기화되지 않습니다. 우리는 제어된 캐시 재사용을 통해 pipe 버퍼 페이지의 struct page *를 해당 slab 슬롯에 미리 심은 다음 nr_frags를 1로 설정하여, 해제 과정에서 우리가 여전히 소유한 페이지에 대해 참조를 하나 감소시킵니다. 선형 오버플로가 페이지 use-after-free가 되는 것입니다. 오버플로는 frags[0]을 건드리지 않으므로 스프레이와 타이밍을 맞출 필요가 없으며, 이것이 이 기법을 관대하게 만드는 대부분의 이유입니다.
페이지 UAF를 통한 Dirty-Pagetable. 해제된 pipe 페이지는 새 익명 매핑에 페이지 폴트를 발생시켜 최하위 페이지 테이블로 재할당됩니다. 이제 하나의 물리 페이지는 동시에 살아있는 리프 페이지 테이블이자 pipe가 여전히 읽고 쓰는 페이지입니다. pipe에 8바이트를 쓰면 위조된 PTE가 설치되고, pipe를 읽으면 테이블이 다시 읽힙니다. 이것은 테이블당 약 460개의 PTE 윈도우 규모의 제한된 임의 물리 읽기/쓰기입니다.
KASLR 무력화. 물리 읽기를 통해 KASLR이 절대 재배치하지 않는 고정 저메모리 SMP 트램폴린 페이지 테이블을 스캔합니다. 커널 절반 항목이 커널의 물리 베이스인 level4_kernel_pgt를 가리킵니다. 거기서부터 init_top_pgt(자기 참조 항목 511로 식별됨)는 보편적인 가상-물리 변환기 역할을 하여 커널 가상 베이스를 복구하고 우리의 주소 공간을 물리 메모리에 묶습니다.
제한된 읽기/쓰기를 무제한으로 확장. 리프 테이블에서 테이블 자체의 물리 주소를 가리키는 PTE 하나를 위조합니다. 그러면 해당 가상 윈도우가 페이지 테이블 자체를 별칭(alias)하게 되어 PTE가 평범한 메모리가 됩니다: 파이프 없이 무제한의 임의 접근 커널 읽기/쓰기가 가능해집니다. 링 3 TLB 일관성은 과도한 크기의 mprotect()로 전체 mm 플러시를 강제하여 처리합니다.
오프셋 해석 및 자격 증명 탈취. 구조체 멤버 오프셋은 런타임에 /sys/kernel/btf/vmlinux에서 읽어오며 하드코딩하지 않습니다. vmemmap의 struct page 워크와 mm_struct.owner를 통해 자신의 task_struct를 찾은 다음, 자격 증명 ID를 0으로 만들고 cred와 real_cred의 모든 capability 세트를 채웁니다. 심볼 주소는 임의 읽기를 통해 커널 자체의 kallsyms 테이블을 파싱하는 내부 리졸버에서 얻습니다. 이것이 실패하는 경우에만 cred.user_ns를 init_user_ns로 지정하여(초기 네임스페이스에서 CAP_SYSLOG가 유지됨) 실제 주소를 /proc/kallsyms에서 읽는 폴백을 사용합니다.
enforcing을 바꾸지 않고 SELinux 비활성화. avc_denied()의 프롤로그를 xor eax, eax ; ret 스텁으로 덮어씁니다(IBT에서 endbr64는 건너뜁니다). 이제 모든 거부가 프로세스 전체에서 허용으로 반환되며, getenforce는 여전히 Enforcing을 보고합니다. 이것은 탈출의 선행 조건이기도 합니다: 코어 덤프 핸들러의 도메인 간 재실행(cross domain re-exec)이 그렇지 않으면 거부되기 때문입니다.
core_pattern을 통한 탈출. 전역 core_pattern을 | 접두사가 붙은 핸들러로 덮어쓰고, 핸들러가 /proc/<PID>/root(크래시한 태스크의 컨테이너 chroot)를 통해 우리 자신의 바이너리를 가리키게 한 다음, 자식 프로세스를 크래시시킵니다. 커널은 이 핸들러를 초기 네임스페이스의 루트인 usermodehelper로 실행하며, 우리는 컨테이너 내부에 바인딩된 Unix 소켓을 통해 대화형 root 셸을 중계합니다. 핸들러는 init 네임스페이스 안에서 생성되므로, 제자리 네임스페이스 수술도 필요 없고 PID 네임스페이스와 관련한 위험 요소도 없습니다.