- 2024년 3월 발견된
xz백도어는 영향받는sshd환경에서 원격 코드 실행으로 이어질 수 있었고, NixOS 사례는 재현 가능한 빌드가 공급망 변조를 기계적으로 드러낼 수 있음을 보여줌 - 악성 요소는
xzGit 저장소 전체가 아니라5.6.0·5.6.1의 관리자 제공 릴리스 tarball에 숨어 있었고, 빌드 중 가짜.xz테스트 파일에서 셸 스크립트와 오브젝트 파일을 만들어냄 - 공격은
glibc의 ifunc를 이용해liblzma로딩 시 코드를 실행하고,sshd가libsystemd를 거쳐liblzma에 전이 의존하는 Debian·Fedora 계열 환경을 노림 - NixOS는 보통 GitHub 자동 생성 소스 아카이브를 선호하지만,
xz가nixpkgsbootstrap 단계에 있어autoconf의존성 순환을 피하려면 관리자 tarball을 신뢰해야 했음 - 제안된 방어법은 bootstrap 이후 GitHub 소스로
xz를 다시 빌드해 기존 tarball 산출물과 비교하는 방식이며, 조건을 활성화하자liblzma.so크기 차이와_get_cpuid심볼 추가가 드러남
xz 백도어가 숨어든 방식
xz는 Linux 배포판의 핵심 경로에서 소스 tarball을 풀 때 자주 쓰이는 압축·해제 소프트웨어임- 2024년 3월
xz에서 백도어가 발견됐고, 악성 관리자인 Jia Tan은 약 3년에 걸쳐 신뢰를 얻고 저장소 push 권한을 확보한 뒤 합법적인 기여 사이에 난독화된 코드를 조금씩 넣었음 - Andres Freund는 여러 Debian unstable 머신에서 겪은
ssh의 500ms 성능 저하를 조사하다가liblzma까지 추적했고, 백도어를 식별해 문서화함 - 백도어의 목표는
ssh를 하이재킹해 특정 RSA 키로 로그인할 때 공격자가 피해자 머신에서 임의 명령을 실행할 수 있게 하는 것이었음
악성 tarball과 빌드 중 생성되는 오브젝트
- 악성 구성요소는
xzGit 저장소 코드 전체에 직접 들어 있지 않았고, Jia Tan이 빌드·서명해 배포한xz5.6.0 및 5.6.1 릴리스 tarball에 포함됨 - Git 저장소 안의 일부 테스트용
.xz파일에는 머신 코드가 숨겨져 있었고, 릴리스 tarball에는 이를 꺼내도록 위장된 변경이 추가됨 - 특히
m4/build-to-host.m4변경은 겉보기에는 무해하고 주석도 달려 있었지만, 실제로는 여러 가짜.xz테스트 파일을 복호화·난독 해제하는 명령 체인을 숨김 - 이 과정의 결과물은 두 가지임
xz빌드 중 실행되는 셸 스크립트- 악성 바이너리 오브젝트 파일
- 빌드 중 실행되는 셸 스크립트는 두 역할을 맡음
- 특정 Linux 배포판,
glibc기능,ssh설치 여부 등 백도어 실행 조건을 확인함 - 정상 오브젝트인
liblzma_la-crc64_fast.o가 백도어 오브젝트 파일의_get_cpuid심볼을 쓰도록 수정함
- 특정 Linux 배포판,
- Russ Cox의
xz스크립트 분석은 빌드 중 악성 리소스가 만들어지는 과정을 자세히 다룸
sshd로 이어지는 ifunc 하이재킹
- 동적 링크 프로그램은 실행 시점에 동적 로더가 공유 라이브러리를 메모리에 로드하고, 심볼 주소를 Global Offset Table(GOT) 에 채움
glibc의 ifunc는 동적 로딩 시점에 같은 함수의 여러 구현 중 하나를 선택하기 위한 기능이며,xz백도어는 이를 악성 코드 실행 경로로 사용함- Debian·Fedora 등 일부 배포판의
ssh는systemd알림 지원을 위해libsystemd와 링크되고,libsystemd는 다시liblzma와 링크됨- 이 환경에서는
sshd가liblzma에 전이 의존함
- 이 환경에서는
sshd실행 시 동적 로더가libsystemd와liblzma를 로드하고, 백도어가 설치된 경우liblzma로딩 중 악성 코드가 실행됨- 백도어는 ifunc 리졸버가 악성
_get_cpuid심볼을 호출하게 만들고, 아직 읽기 전용이 아닌 GOT를 조작해RSA_public_decrypt주소를 악성 함수로 바꿈 - Securelist의 상세 분석과 공격 벡터 및 완화책을 정리한 연구 글을 참고 자료로 볼 수 있음
신뢰할 수 있는 소스와 NixOS의 bootstrap 제약
- 많은 배포판이 Git forge의 원본 소스 대신 관리자 제공 tarball로
xz를 빌드했기 때문에 이 공격이 효과를 가질 수 있었음 - tarball 워크플로에는 역사적·실용적 이유가 있음
git보다 오래된 초기 Linux 배포판의 방식임- 릴리스 시점의 소스 상태를 자체 포함 아카이브로 보존함
- manpage, configure 스크립트 같은 중간 산출물을 포함해 빌드 부담을 줄일 수 있음
- 압축을 통해 저장 공간 효율을 얻을 수 있음
- 보안 관점에서는 기술적으로 가능할 때 가장 신뢰할 수 있는 주체가 인증한 소스에서 빌드해야 함
- GitHub에서 개발되는 프로젝트라면 GitHub가 릴리스마다 자동 생성하는 아카이브를 사용할 수 있음
- Codeberg, SourceHut, GitLab 같은 신뢰할 수 있는 제3자 플랫폼에도 같은 논리를 적용할 수 있음
- NixOS는 함수형 패키지 관리 모델을 쓰며, 패키지는 Nix 표현식으로 빌드 레시피를 정의함
- NixOS 관리자는 가능한 경우
fetchFromGitHub로 GitHub 자동 생성 소스 아카이브를 쓰는 문화가 있지만,xz패키지는fetchurl로 수동 업로드된 관리자 tarball을 가져오고 있었음 - 이유는
xz가nixpkgs의 bootstrap 단계에 포함되어 있기 때문임- bootstrap은 작은 seed binary 집합에서
nixpkgs패키지 전체를 다시 빌드할 수 있게 하는 과정임 stdenv는 다른 패키지가 쓰는 기본 컴파일 환경임stdenv는 런타임에xz에 의존하고,coreutils같은 패키지는.tar.xz소스 아카이브를 풀기 위해xz가 필요함
- bootstrap은 작은 seed binary 집합에서
- GitHub 소스에서
xz를 빌드하려면 configure 스크립트를 만들기 위한autoconf가 필요하지만,autoconf도xz에 의존함- 관리자 제공 tarball에는 configure 스크립트가 이미 생성되어 있어 이 의존성 순환을 끊을 수 있음
- 따라서
nixpkgs그래프에서xz가 빌드되는 지점에서는 GitHub 소스 아카이브를 쓰기 어렵고 관리자 tarball을 신뢰해야 했음
소스 비교보다 산출물 수렴 비교
- 관리자 tarball과 GitHub 소스 tarball이 동일한지 비교하는 방식은 자연스러워 보이지만, 실무에서는 잘 맞지 않음
- Daniel Stenberg은 릴리스 tarball이 소스와 다른 것이 기능이라고 설명함
- manpage, configure 스크립트 같은 중간 산출물을 tarball에 포함할 수 있음
- 배포판이
autoconf의존성을 피하려는 경우 특히 유용함
- 공급망 보안 관점에서는 이런 유연성이 관리자를 정직하게 신뢰해야 하는 부담으로 바뀜
- 재현 가능한 빌드는 같은 조건에서 두 번 빌드했을 때 비트 단위로 동일한 산출물이 나오는 성질임
- reproducible-builds 그룹은 가능한 많은 패키지를 재현 가능하게 만드는 것을 목표로 하며, Reproducible Builds: Increasing the Integrity of Software Supply Chains는 바이너리 산출물 배포 신뢰를 높이는 속성으로 이를 다룸
- tarball 생성 자체를 재현 가능하게 만드는 접근은 PostgreSQL 프로젝트가 채택했음
- 사용자가 관리자 tarball이 원본 소스에서 정직하게 생성됐는지 독립적으로 검증할 수 있음
- 다만 upstream 프로젝트 관리자가 구현해야 하므로 FOSS 커뮤니티에서 채택 속도가 느릴 수 있음
NixOS에서 제안된 검출 절차
xz가 비트 단위로 재현 가능하고 관리자 tarball이 빌드 과정에 영향을 주는 변경을 담지 않았다면, GitHub tarball 빌드와 관리자 tarball 빌드는 같은 산출물을 내야 함- 제안된 방법은
xz를 bootstrap 이후 한 번 더 빌드하되, 이때는fetchFromGitHub로 GitHub 소스 아카이브를 사용하고 기존 bootstrap 단계의xz산출물과 비교하는 방식임 - 구현 예시는
pkgs/tools/compression/xz아래에after-bootstrap.nix를 두고xz5.6.1을 GitHub 소스에서 빌드함- 관리자 tarball에는 configure 스크립트가 이미 들어 있지만, GitHub 소스 빌드에서는
autoconf,libtool,automake등을 사용해./autogen.sh를 실행해야 함
- 관리자 tarball에는 configure 스크립트가 이미 들어 있지만, GitHub 소스 빌드에서는
- 비교 단계로
compareArtifacts를 추가해 빌드 마지막에liblzma.so를 비교함- 예시에서는 백도어가 수정한 파일인
liblzma.so만 비교했지만, 모든 바이너리와 라이브러리 산출물로 일반화할 수 있음
- 예시에서는 백도어가 수정한 파일인
- 정상 master 리비전에서는
xz-after-bootstrap빌드가 성공했고, 두 산출물이 동일했음 - 백도어가 포함된 리비전
c53bbe3에서는 처음에는 동일하게 보였음- NixOS에서는 백도어가 활성화되지 않았고, 그 이유 중 하나는 설치 스크립트가
RPM_ARCH변수 설정을 확인했기 때문임 env.RPM_ARCH = true;를 설정해 조건을 활성화하자liblzma.so바이너리 차이가 드러남
- NixOS에서는 백도어가 활성화되지 않았고, 그 이유 중 하나는 설치 스크립트가
- 두
liblzma.so를 저장해 비교하자 백도어 포함 산출물은 258KB, bootstrap 이후 GitHub 소스 빌드 산출물은 210KB였고 약 48KB 차이가 있었음 nm으로 심볼을 비교하자_cpuid,_get_cpuid,__tls_get_addr@GLIBC_2.3가 추가되어 있었고,_get_cpuid는 여러xz백도어 기술 보고서에서 문서화된 심볼임
적용 방안과 한계
- 제안된 보호 장치는 bootstrap 단계에서 빌드되며 신뢰된 소스 아카이브를 쓰지 않는 모든 패키지에 적용하는 방식임
*-after-bootstrap패키지를 channel blocker에 추가하면, 빌드 실패 시 관리자의 개입이 필요한 큰 경보로 만들 수 있음xz사례에 대한 개념 증명으로nixpkgs저장소에 pull request가 열림- 이 방법은 대상 패키지가 비트 단위 재현 가능하다는 가정이 필요함
- 2017년부터 2023년까지
nixpkgs-unstable리비전 17개를 표본으로 삼아stdenv의 non-fixed-output derivation을nix-build --check로 재빌드함 - 모든 리비전에서
xz는 비트 단위로 재현 가능했음 - 17개 중 12개 리비전에서는 빌드 가능하지만 재현 불가능한 패키지가 1~2개 있었고, 예를 들어
gcc는 2017~2021년,bash는 2019년까지 일관되게 재현 불가능했음
- 2017년부터 2023년까지
- 모든
stdenv패키지에 이 방법을 적용할 수는 없지만, 장기적으로 재현성이 좋은 패키지에는 선택적으로 활성화할 수 있음 - Ken Thompson의 trusting trust 문제는 이 방식의 이론적 한계로 남음
- bootstrap 단계의 신뢰할 수 없는
xz가xz-after-bootstrap빌드를 간접 오염시켜 산출물이 동일해 보이게 만들 수 없다는 가정이 필요함 - 이런 공격은 매우 복잡할 수 있지만, 방법 자체는 이 가정을 전제로 함
- bootstrap 단계의 신뢰할 수 없는
- 이 검출법은 tarball 변경이 최종 산출물에 영향을 줄 때만 탐지함
- NixOS 실행 파일에는 백도어가 포함되지 않았기 때문에, 조건을 따로 활성화하지 않았다면 이 사례의 백도어를 발견하지 못했을 것임