
교육용으로 재작성된 Copy Fail PoC(CVE-2026-31431) — algif_aead 인플레이스 암호화 + splice() 페이지 캐시 쓰기를 통한 Linux 커널 LPE
CVE-2026-31431에 대한 Theori/Xint 개념 증명(PoC)의 교육용 재작성입니다.
이는 Linux 커널의 algif_aead 암호화 소켓 인터페이스의 로컬 권한 상승 취약점입니다.
2017년 7월의 4.14부터 6.18.21까지의 모든 메인라인 커널과 6.19.11까지의 6.19 안정 브랜치에 영향을 미칩니다.
교육 및 승인된 보안 연구용으로만 사용하십시오. 소유하지 않았거나 명시적인 서면 허가를 받지 않은 시스템에서 실행하지 마십시오. 취약한 호스트에서 이를 실행하면 권한이 없는 모든 로컬 사용자가 즉시 루트 액세스 권한을 얻을 수 있습니다. 저자는 오용에 대해 책임을 지지 않습니다.
Linux 커널은 커널 3.2부터 AF_ALG 소켓 패밀리(<linux/if_alg.h>)를 통해 사용자 공간에 인커널 암호화 API를 노출했습니다.
사용자 공간은 SOCK_SEQPACKET 소켓을 열고 알고리즘 템플릿(예: "aead" / "authencesn(hmac(sha256),cbc(aes))")에 바인딩한 후 암호화 또는 복호화를 위해 데이터를 전송합니다.
2017년에 커널이 소스와 대상이 동일한 기본 페이지를 공유한다고 감지하면 AEAD 알고리즘이 데이터를 제자리에서(in-place) 암호화 또는 복호화할 수 있도록 하는 성능 최적화가 병합되었습니다. 이로 인해 각 작업에서 불필요한 메모리 복사가 방지됩니다.
authencesn 템플릿(인증된 암호화에 확장 시퀀스 번호(ESN) 지원을 추가)은 복호화 중 스크래치 쓰기를 포함합니다:
인증 검사가 실행되기 전에 작업의 데이터 버퍼 내 특정 오프셋에 ESN 바이트를 씁니다.
작업의 소스 페이지가 splice()를 통해 일반 파일의 페이지 캐시에서 얻어졌고 제자리 최적화가 활성화된 경우, 이 스크래치 쓰기는 해당 캐시된 페이지로 직접 돌아갑니다.
커널의 페이지 캐시는 공유 매핑이므로 — 동일한 파일을 여는 모든 프로세스는 동일한 물리적 페이지를 봅니다 — 권한이 없는 사용자는 다음을 수행할 수 있습니다:
/usr/bin/su)을 O_RDONLY로 엽니다.AF_ALG 작업 소켓으로 splice합니다.authencesn 복호화를 트리거합니다; ESN 스크래치 쓰기가 공격자가 제어하는 바이트로 제어된 오프셋의 페이지를 손상시킵니다.디스크의 파일 체크섬을 비교하는 호스트 외부 무결성 스캐너(AIDE, Tripwire, IMA/EVM)는 이 공격을 완전히 감지하지 못합니다. 쓰기는 페이지 캐시 축출 또는 재부팅 후 사라지며 디스크에 포렌식 흔적을 남기지 않습니다.
/usr/bin/su(setuid root 바이너리)를 대상으로 하고 PAM 인증 검사를 보호하는 분기를 덮어씀으로써 공격자는 비밀번호 없는 루트 셸을 얻습니다.
버그를 도입한 최적화 커밋은 4.14 개발 주기(2017년 7월)에 포함되었습니다.
위 범위에서 CONFIG_CRYPTO_USER_API_AEAD=y 및 CONFIG_CRYPTO_AUTHENC=y(둘 다 일반적인 기본값)로 컴파일된 모든 커널은 취약합니다.
정확한 권고 상태는 배포판의 보안 추적기를 확인하십시오.
os.splice는 Python 3.10에서 추가됨)/usr/bin/su, setuid이므로 전 세계에서 읽기 가능)# 커널을 건드리지 않고 무엇이 쓰여질지 표시(안전):
python3 copy_fail_exploit.py --dry-run
# /usr/bin/su 페이지 캐시 패치(취약한 커널 필요):
# 경고: 취약한 커널 필요 -- 소유한 시스템에서만 사용하십시오
python3 copy_fail_exploit.py
# 패치 후 즉시 루트로 권한 상승:
python3 copy_fail_exploit.py --spawn-shell
# 사용자 지정 대상 바이너리 및 페이로드 사용:
python3 copy_fail_exploit.py --target /usr/bin/sudo --payload-file ./custom_patch.bin
# 자세한 출력(각 4바이트 쓰기 출력):
python3 copy_fail_exploit.py --verbose --dry-run
기본적으로 --spawn-shell은 꺼져 있습니다. 패치 후 스크립트는 다음을 출력합니다:
[+] Patch applied to page cache of '/usr/bin/su' (disk unchanged).
Run `su` to escalate, or re-run with --spawn-shell.
To restore: echo 3 | sudo tee /proc/sys/vm/drop_caches
셸 실행은 --spawn-shell로 명시적으로 선택해야 합니다. 이는 의도적입니다:
결과를 실행하기 전에 익스플로잇이 무엇을 했는지 이해하도록 강제합니다.
프리미티브 설정.
익스플로잇은 AF_ALG 소켓을 열고 더미 올-제로 키로 authencesn(hmac(sha256),cbc(aes)) 템플릿에 바인딩합니다.
그런 다음 ALG_SET_AEAD_AUTHSIZE를 통해 인증 태그 크기를 4바이트로 구성합니다.
제어 소켓에서 accept()는 개별 복호화 요청을 제출할 수 있는 작업 소켓을 생성합니다.
페이지 캐시 쓰기 트리거.
패치 페이로드의 각 4바이트 청크에 대해 익스플로잇은 MSG_MORE가 설정된 상태로 작업 소켓에서 sendmsg()를 호출하여 8바이트의 연관 데이터(AAD)를 전달합니다: 4바이트 필러 다음에 4바이트 페이로드. MSG_MORE는 더 많은 데이터가 도착할 때까지 커널이 작업을 보류하도록 지시합니다. 그런 다음 파이프를 생성하고 두 개의 splice() 호출 — file_fd → pipe → op_sock — 을 실행하여 파일 자체의 페이지 캐시 페이지를 복호화 작업의 입력으로 제공합니다. 마지막으로 recv()가 호출되면 커널은 authencesn 복호화를 실행합니다. 제자리 최적화가 발동하고(소스와 대상이 동일한 페이지) ESN 스크래치 쓰기가 대상 오프셋의 캐시 페이지에 페이로드 바이트를 복사합니다. 인증 태그를 검증할 수 없으므로 recv()는 EBADMSG를 반환합니다 — 이는 예상된 것이며 무해합니다. 쓰기는 이미 발생했습니다.
바이너리 패치.
내장된 페이로드(Theori PoC에서 그대로 가져옴)는 기본 util-linux 빌드의 /usr/bin/su에서 분기 명령을 덮어써 PAM 인증을 강제하는 조건부 점프를 무조건적 폴스루로 바꿉니다. 패치 후 비밀번호 없이 su를 실행하면 성공하고 루트 셸이 생성됩니다. 디스크의 바이너리는 변경되지 않습니다; 페이지 캐시 항목을 삭제하면(echo 3 > /proc/sys/vm/drop_caches) 변경 사항이 즉시 되돌아갑니다.
더 깊은 기술 분석은 아래 참조 자료, 특히 Xint 블로그 게시물과 Theori PoC 저장소를 참조하십시오.
- rule: AF_ALG socket opened by unprivileged process
desc: >
CVE-2026-31431 — an unprivileged process opened an AF_ALG (family 38)
socket, which is the first step of the Copy Fail exploit.
condition: >
evt.type = socket and
evt.arg.domain = 38 and
not user.uid = 0
output: >
AF_ALG socket opened (user=%user.name uid=%user.uid
pid=%proc.pid comm=%proc.name)
priority: WARNING
tags: [host, network, privilege_escalation, CVE-2026-31431]
rule CopyFail_CVE_2026_31431 {
meta:
description = "Detects Copy Fail exploit script in memory or on disk"
cve = "CVE-2026-31431"
strings:
$template = "authencesn(hmac(sha256),cbc(aes))" ascii
$splice = "os.splice" ascii
$aflag = "AF_ALG" ascii
condition:
all of them
}
/etc/audit/rules.d/cve-2026-31431.rules에 추가:
-a always,exit -F arch=b64 -S socket -F a0=38 -k cve_2026_31431_afalg
그런 다음 로그를 다음으로 확인:
ausearch -k cve_2026_31431_afalg --interpret
1. 커널 패치 적용 (권장) 커널 6.18.22, 6.19.12, 7.0-rc7 이상으로 업그레이드하거나 배포판의 보안 업데이트를 적용하십시오.
2. 빌드 시 CONFIG_CRYPTO_USER_API_AEAD 비활성화 자체 커널을 빌드하는 경우 다음을 설정하십시오:
# CONFIG_CRYPTO_USER_API_AEAD is not set
이렇게 하면 전체 AF_ALG AEAD 인터페이스가 제거되어 공격 표면이 사라집니다. 대부분의 임베디드/강화 배포판에서는 필요하지 않습니다.
3. seccomp — AF_ALG 소켓 패밀리 거부
민감한 프로세스(또는 LSM 정책을 통해 시스템 전체)에 socket(AF_ALG, ...)를 거부하는 seccomp 필터를 추가하십시오:
// Deny socket() when domain == AF_ALG (38)
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 38, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EACCES),
4. 커널 강화를 통한 페이지 캐시 권한 축소
vm.unprivileged_userfaultfd=0 및 유사한 튜너블은 사용자 액세스 가능 커널 메모리 인터페이스의 공격 표면을 줄이지만 이 CVE를 직접 차단하지는 않습니다.
익스플로잇은 메모리 내 페이지 캐시만 수정합니다; 디스크의 바이너리는 절대 변경되지 않습니다. 메모리에서 원래 파일 내용을 복원하려면 커널의 페이지 캐시를 삭제하십시오:
echo 3 | sudo tee /proc/sys/vm/drop_caches
이렇게 하면 캐시에서 모든 클린 페이지가 축출됩니다. /usr/bin/su(또는 패치된 바이너리)의 다음 읽기는 디스크에서 원래 바이트를 다시 로드합니다.
재부팅도 페이지 캐시를 완전히 지웁니다.
MIT — LICENSE 참조.
인정된 OSI/SPDX 식별자로 존재하지 않기 때문에 사용자 지정 "교육용 전용" 라이선스 대신 MIT가 선택되었습니다. MIT는 허용적이고 법적으로 잘 이해되며 책임 있는 공개 저장소와 호환됩니다. 교육적 의도는 모듈 독스트링, 이 README의 면책 조항 섹션 및 --help 출력에 표현되어 있습니다 — 법적으로 모호한 사용자 지정 라이선스 조항이 아닙니다.
| 브랜치 | 최초 취약 버전 | 마지막 취약 버전 | 수정 버전 |
|---|
| mainline | 4.14 | 6.18.21 | 6.18.22 |
| 6.19 stable | 6.19.0 | 6.19.11 | 6.19.12 |
| 7.0-rc | 7.0-rc1 | 7.0-rc6 | 7.0-rc7 |
| 배포판 | 제공 커널(대략) | 패치 여부 |
|---|
| Debian 12 (Bookworm) | 6.1.x | 예 (백포트) |
| Debian 13 (Trixie) | 6.12.x / 6.14.x | 예 (백포트) |
| Ubuntu 24.04 LTS | 6.8.x | 예 (USN-7xxx) |
| Ubuntu 24.10 | 6.11.x | 예 |
| RHEL 9.x | 5.14.x (리베이스) | 예 (RHSA) |
| Alpine 3.20 | 6.6.x | 예 (백포트) |
| Arch Linux | 롤링 (현재 ≥ 6.18.22) | 예 |
| Kali Linux 2026.1 | 6.18.12 | 취약 |
| Kali Linux 2026.2+ | 6.18.22+ | 수정됨 |
| Linux Mint 22 | 6.8.x (Ubuntu 기반) | 예 (Ubuntu USN) |