Logic in Code,
Freedom in Travel.

인생 뭐 있나 사람 사는거 다 똑같지

실무 경험/실무 개발 & 협업

CVE-2026-31431 "Copy Fail": 732바이트 파이썬으로 리눅스 루트 탈취 이슈

귀찮은 개발자 2026. 5. 4. 14:29 계산 중...
목차 (Table of Contents)

TL;DR: 2017년에 심어진 커널 크립토 최적화 코드 한 줄이 로컬 권한 탈취(LPE)로 이어지는 취약점이 터졌다. PoC는 이미 공개됐고, 732바이트짜리 Python 스크립트 하나로 Ubuntu/RHEL/Amazon Linux/SUSE 전부 루트 탈취가 가능하다.

1. 배경과 문제: "계정 비밀번호 백도어, 커널 업데이트는 리스크가 크다."

우리 서버는 OVH 베어메탈 위에서 동작하는 온프레미스 환경이다. 어느 날 모니터링 채널에 CVE 하나가 올라왔다.

CVE-2026-31431, 일명 "Copy Fail" 이며 영향받는 배포판은 Ubuntu/Amazon Linux/RHEL/SUSE 인데 사실상 웬만한 서버에 설치되어 있는 리눅스 전부이다.

문제는 커널 업데이트를 바로 실행하기에는 애매한 상황이었다. 커널 버전이 올라가면 네트워크 관련 툴(ip, nftables 등)의 의존성이 함께 올라가면서 방화벽 규칙 / 라우팅 설정 등 생각하지 못한 부분에서 이슈가 발생할 수 있다. 일부 서비스들의 경우 서비스 특성상 다운타임은 곧 장애로 이어지며 수많은 고객사에도 장애가 발생하기 때문에 커널 버전을 바로 올리기는 부담스러웠다.

그래서 우선 "우리 서버가 영향 범위 안에 들어가는가?" 부터 기도하면서 확인을 해봤다.

2. 트러블슈팅 / 원인: "2017년에 추가된 이슈"

2-1. 취약점 구조

이 취약점의 핵심을 한 줄로 요약하면 'AF_ALG 소켓 + splice() + authencesn = 아무 파일의 페이지 캐시에 4바이트 통제 쓰기' 이다. 

구체적으로 설명하자면 

  1. 2015년 : 커널에 algif_aead.c가 추가되면서 AF_ALG 소켓을 통한 AEAD 암호화 인터페이스가 생겼다. 이 시점에서는 req->src(입력)와 req->dst(출력) 스캐터리스트가 분리되어 있었다. splice()로 페이지 캐시 페이지를 넣어도 읽기용 src에만 들어가므로 안전했다.
  2. 2017년 (커밋 72548b093ee3) : 누군가 성능 최적화 목적으로 in-place 연산을 추가했다. "어차피 같은 데이터 쓰는데 복사 한 번 줄이면 빠르지 않나?" 라는 합리적인 아이디어였다. 그런데 이 최적화가 req->src = req->dst로 세팅하면서 — splice()로 들어온 페이지 캐시 페이지(원래 읽기 전용)가 쓰기 가능한 목적지 스캐터리스트에 체이닝되는 상황이 생겼다.
  3. authencesn의 스크래치 쓰기: IPsec ESN(Extended Sequence Number)용 래퍼인 authencesn에는 오래된 버그가 있다. 출력 버퍼 끝 경계를 4바이트 넘어서 스크래치 패드로 쓰는 코드다. 원래는 별다른 문제가 없었는데, 복사 한번의 최적화로 인해 이 4바이트가 공격자가 선택한 읽기 가능 파일의 페이지 캐시 내부에 떨어지게 되었다. 
  4. 결론으로 HMAC 검증은 실패해서 recvmsg()는 에러를 리턴하지만, 쓰기는 이미 일어난 뒤다. setuid 바이너리(예: /usr/bin/passwd)의 페이지 캐시를 조작하면 로컬 권한 탈취가 가능하다.

2-2. 영향 범위 확인

Copy Fail 의 영향 범위를 확인하기 위해 배포된 파이썬 스크립트가 있다. 해당 스크립트들 실행해보니 결과는 다음과 같았다.

스크립트에서는 "6.12/6.17/6.18 라인보다 낮아서 트리거가 안 될 수도 있다"는 메세지를 출력해줬지만, 전제 조건(AF_ALG + authencesn 로드 가능)은 충족되어 VULNERABLE 판정이 났다.

해당 커널(6.8.0-53-generic)에는 2017년 in-place 최적화 커밋(72548b093ee3)이 포함되어 있고, algif_aead 모듈이 로드 가능한 상태라는 뜻이다. 아직 패치 커밋(a664bf3d603d)이 백포팅되지 않은 상태인 것이다. 

3. 해결책 : 임시 조치

3-1. 임시 조치: algif_aead 모듈 비활성화

커널 업데이트 전에 바로 적용이 가능하고 간단한 방법이다. algif_aead 커널 모듈이 올라오지 못하도록 하는 것이다.

# algif_aead 모듈 블랙리스트 등록
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf

# 현재 로드된 모듈 제거 (에러 나도 무시)
rmmod algif_aead 2>/dev/null || true

/bin/false로 install 명령을 덮어쓰면, modprobe algif_aead를 시도해도 실패하게 되며 재부팅을 하더라도 모듈이 올라오지 않는다.

algif_aead 을 막으면 기존의 서비스에 문제가 생기지 않을까라는 생각이 들어 확인해봤지만, dm-crypt, LUKS, IPsec, TLS, SSH, OpenSSL/GnuTLS 와 같이 많이 사용되는 것들은 커널 내부 crypto API를 직접 사용하고 AF_ALG 소켓을 경유하지 않는다. AF_ALG는 유저스페이스에서 커널 암호화 기능을 직접 사용하는 특수한 케이스에서만 필요하다.

3-2. 컨테이너 / 멀티 테넌트 환경 추가 조치

컨테이너를 운용 중이라면 seccomp 정책으로 AF_ALG 소켓 생성 자체를 틀어막는 게 더 깔끔할 수 있다. 

{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": ["socket"],
      "action": "SCMP_ACT_ERRNO",
      "args": [
        {
          "index": 0,
          "value": 38,
          "op": "SCMP_CMP_EQ"
        }
      ]
    }
  ]
}

3-3. 영구 해결: 커널 패치

임시 조치는 말 그대로 임시다. 정식 해결은 업스트림 픽스가 포함된 커널 패키지로 업데이트하는 것이다.

픽스 커밋: a664bf3d603d — algif_aead.c의 in-place 최적화를 되돌리고, 소스/목적지 스캐터리스트를 완전히 분리하여 페이지 캐시 페이지가 쓰기 가능한 목적지로 체이닝되는 경로를 원천 차단한다.

# Ubuntu / Debian 계열
sudo apt update && sudo apt upgrade linux-image-$(uname -r)

# RHEL / CentOS / Amazon Linux 계열
sudo yum update kernel
# 또는
sudo dnf update kernel

업데이트 후 재부팅 필수이다. 커널은 디스크에 반영만 해선 안 되고 실제로 부팅해서 올려야 한다. 당연한 얘기지만 가끔 재부팅 안 하고 "업데이트를 했는데 왜 적용이 안될까" 하는 경우도 있다. 

3-4. 취약 여부 재검증

조치 후 검출 스크립트를 다시 돌려서 algif_aead 로드 불가 확인 또는 패치 커밋 반영 여부를 검증한다.

# 모듈 비활성화 확인
modprobe algif_aead 2>&1
# 아래 이미지처럼 출력이 된다면 문제가 없는거다.

# 또는 modinfo로 아예 없는지 확인
modinfo algif_aead

4. 마지막 요약

항목 내용

CVE CVE-2026-31431 "Copy Fail"
심각도 CVSS 7.8 (HIGH) — 로컬 권한 탈취(LPE)
원인 2017년 algif_aead in-place 최적화로 읽기 전용 페이지 캐시가 쓰기 가능 스캐터리스트에 체이닝됨
영향 범위 2017년 이후 대부분의 리눅스 배포판 (Ubuntu / RHEL / SUSE / Amazon Linux)
PoC 공개됨 (732바이트 Python, 루트 획득)
임시 조치 algif_aead 모듈 블랙리스트 (/etc/modprobe.d/disable-algif.conf)
영구 해결 커널 업데이트 (픽스 커밋: a664bf3d603d)
  • 알고 있어야 할 것: 732바이트 Python PoC로 로컬 루트 가능, 2017년부터 지금까지 사실상 모든 리눅스가 영향 받음
  • 지금 당장 할 것:
    • echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
    • rmmod algif_aead 2>/dev/null || true
  • 앞으로 해야 할 것: 커널 패치 + 재부팅

참고 자료