SSH가 고작 0.5초 느려서 파봤더니
리눅스에 백도어가 있었다고?

— XZ Utils Backdoor, CVE-2024-3094
2024년 3월.
Microsoft에서 PostgreSQL 개발자로 일하던 Andres Freund는 Debian 개발 버전을 사용하다가 이상한 걸 하나 발견한다.
SSH 로그인이 평소보다 느렸다.
그것도 엄청 느린 것도 아니었다.
대략 이런 차이였다.
Before: ~0.30s
After: ~0.80s
약 0.5초.
보통 사람이었다면 아마 이렇게 생각했을 것이다.
서버가 오늘 좀 느리네.
그리고 넘어갔을 가능성이 높다.
그런데 Freund는 넘어가지 않았다.
CPU 사용량이 이상했고 Valgrind에서도 이상한 에러가 발생하고 있었다.
그래서 원인을 추적하기 시작했다.
그리고 며칠 뒤 보안 커뮤니티에 이런 내용을 공개한다.
Linux에서 사용하는 XZ Utils에 백도어가 들어가 있다.
그것도 단순한 취약점이 아니었다.
누군가 의도적으로 심어놓은 악성코드였다.
그런데 XZ가 뭐길래 난리가 났을까?
Linux를 쓰다 보면 이런 파일을 한 번쯤 본 적 있을 것이다.
linux.tar.xz
backup.tar.xz
source.tar.xz
XZ는 압축 포맷이고, xz-utils는 이를 처리하는 도구와 라이브러리다.
문제는 이게 굉장히 밑바닥에 있는 라이브러리라는 것이다.
Application
|
v
System Libraries
|
v
liblzma
|
v
XZ Utils
직접 xz 명령어를 사용하지 않더라도 시스템의 다른 프로그램이 liblzma를 의존할 수 있다.
그리고 특정 Linux 환경에서는 예상하지 못한 연결이 하나 있었다.
sshd
|
v
libsystemd
|
v
liblzma
OpenSSH 자체가 XZ를 직접 사용하는 것은 아니었다.
하지만 Debian 등 일부 systemd 기반 배포판에서는 sshd가 systemd 관련 기능을 사용하면서 간접적으로 liblzma를 로드하는 구조가 만들어져 있었다.
이게 공격자에게 아주 좋은 진입점이 됐다.
백도어는 대놓고 들어있지도 않았다
보통 악성코드를 찾는다고 하면 소스코드를 열어서 이런 걸 찾는 모습을 상상한다.
connect_to_attacker();
execute_payload();
당연히 공격자는 그렇게 하지 않았다.
XZ 사건이 굉장히 유명해진 이유 중 하나는 숨기는 방식이 상당히 정교했기 때문이다.
악성 로직의 일부는 정상 테스트 데이터처럼 보이는 파일 안에 숨어 있었다.
tests/files/bad-3-corrupt_lzma2.xz
tests/files/good-large_compressed.lzma
이름만 보면 그냥 압축 라이브러리 테스트용 fixture 같다.
실제로 저장 위치도 tests/files였다.
그런데 빌드 과정에서 난독화된 shell script가 이 파일 내부의 데이터를 추출하고 다시 조립했다.
대략적인 흐름은 이랬다.
Source Tarball
|
v
Modified Build Script
|
v
Fake Test Data
|
v
Hidden Payload Extraction
|
v
Object Code Injection
|
v
Modified liblzma
더 교묘한 부분도 있다.
공격을 실행시키는 중요한 M4 매크로는 Git 저장소의 일반적인 소스 트리에는 없고, 배포된 5.6.0과 5.6.1 source tarball에 포함돼 있었다.
그래서
Git source
만 보고 검토하는 사람과
Release tarball
을 실제로 빌드하는 사람에게 보이는 결과가 달라질 수 있었다.
공급망 공격에서 굉장히 무서운 패턴이다.
목표는 SSH였다
최종적으로 변조된 liblzma는 실행 과정에서 특정 조건이 맞는지 확인했다.
아무 시스템에서나 마구 실행되는 악성코드도 아니었다.
분석 결과 공격 코드는 주로 다음과 같은 특정 환경을 골라 작동하도록 구성돼 있었다.
Linux
x86_64
glibc
GCC
GNU ld
Debian/RPM build environments
systemd
sshd
즉,
“가능한 모든 PC를 감염시키자.”
보다는
“실제 Linux 서버에 들어갈 가능성이 높은 환경만 골라서 작동하자.”
에 가까웠다.
이런 조건 검사는 분석을 어렵게 만드는 효과도 있다.
보안 연구원이 아무 환경에서 바이너리를 실행해봤는데
Nothing happens.
가 되어버리면 악성코드인지 알아내기가 훨씬 어려워진다.
심지어 디버깅하는지도 확인했다
공격자는 분석당할 가능성까지 고려했던 것으로 보인다.
Freund가 확인한 조건 중에는 특정 환경변수와 디버깅 환경을 확인하는 로직도 있었다.
예를 들어 정상적인 환경에서는 백도어가 동작하지만 분석 환경에서는 행동을 바꾸는 방식이다.
Normal Environment
|
v
Backdoor Active
Debug Environment
|
v
Backdoor Disabled
LD_DEBUG, LD_PROFILE, 일부 debugger 환경 등을 확인하는 행동이 관찰됐다.
Malware analysis에서 흔히 이야기하는 anti-analysis / anti-debugging과 비슷한 발상이다.
이쯤 되면 단순히 개발자가 실수로 이상한 코드를 넣었다고 보기 어렵다.
그런데 진짜 무서운 부분은 코드가 아니다
XZ 사건을 기술적으로만 보면 난독화, dynamic linker, IFUNC, SSH hook 같은 이야기가 가장 눈에 띈다.
그런데 개인적으로 더 흥미로운 부분은 따로 있다.
공격자는 프로젝트를 해킹해서 maintainer 계정을 훔친 게 아니다.
직접 maintainer가 됐다.
공격의 중심에 있던 계정은 Jia Tan, GitHub username JiaT75였다.
첫 XZ 기여는 2021년 10월이었다.
내용도 별거 없었다.
.editorconfig
추가.
그 다음에도 정상적인 버그 수정과 코드 개선을 계속했다.
2022년.
2023년.
꾸준히 기여한다.
그리고 점점 프로젝트에서 신뢰를 얻기 시작한다.
약 2년 동안 정상 개발자처럼 행동했다
타임라인을 압축하면 대략 이렇다.
2021
|
+-- First harmless contribution
|
2022
|
+-- Regular patches
+-- Community trust increases
+-- More project responsibility
|
2023
|
+-- Maintainer-level access
+-- Release management
|
2024
|
+-- XZ 5.6.0
+-- Backdoor introduced
Jia Tan은 2021년 말부터 2년 넘게 XZ 프로젝트에 기여하면서 결국 commit 및 maintainer 권한을 얻게 됐다.
이 사건 때문에 당시 보안 커뮤니티에서는 이런 말까지 나왔다.
“이건 코드 공급망 공격이 아니라 인간 공급망 공격이다.”
더 이상한 사람들이 등장한다
여기서 이야기가 더 재미있어진다.
기존 XZ maintainer인 Lasse Collin에게
개발이 너무 느리다.
새로운 maintainer가 필요하다.
는 식으로 압박하는 사람들이 mailing list에 등장했다.
대표적으로 알려진 이름이 Jigar Kumar, Dennis Ens 등이다.
이들은 Jia Tan에게 더 많은 권한을 줘야 한다는 방향으로 기존 maintainer를 압박했다.
그리고 결국 Jia Tan의 프로젝트 역할은 점점 커졌다.
이 계정들이 공격자와 동일 인물 또는 같은 조직의 sockpuppet이었는지는 최종적으로 확정되지 않았다.
하지만 등장 시기와 행동 때문에 강한 의심을 받았다.
구조만 보면 소셜 엔지니어링 교과서에 가깝다.
Contributor
|
v
Build Reputation
|
v
External Pressure
|
v
Gain Maintainer Trust
|
v
Obtain Commit Access
|
v
Control Releases
|
v
Insert Backdoor
여기서 재미있는 점은 공격자가 처음부터 악성 PR을 던진 게 아니라는 것이다.
먼저 신뢰 자체를 획득했다.
GitHub 계정 탈취보다 더 무서운 방식
일반적인 공급망 공격은 이런 구조를 떠올린다.
Maintainer
|
v
Account Compromise
|
v
Malicious Release
XZ는 달랐다.
Attacker
|
v
Become Contributor
|
v
Become Maintainer
|
v
Legitimate Access
|
v
Malicious Release
접근 권한 자체는 훔친 것이 아니다.
정상적인 절차를 통해 받은 권한이었다.
그래서 MFA만으로 해결되는 문제가 아니다.
GitHub에서 보면 그냥
Authorized Maintainer
가 commit한 것이다.
Zero Trust를 이야기하면서도 정작 오픈소스 프로젝트에서는
오래 기여했으니까 믿을 만하겠지.
라는 인간적인 신뢰에 크게 의존한다.
XZ 사건은 그 허점을 정확하게 찔렀다.
그리고 이 모든 게 거의 성공할 뻔했다
악성코드가 들어간 버전은
XZ Utils 5.6.0
XZ Utils 5.6.1
이었다.
하지만 다행히 당시 이 버전들은 대부분 최신 개발·pre-release Linux 배포판에 들어가던 단계였다.
Red Hat은 Fedora 40 Beta와 Fedora Rawhide 관련 패키지를 확인했고 즉시 5.4.x 버전으로 되돌릴 것을 권고했다. RHEL은 영향을 받지 않았다.
즉 조금만 더 늦게 발견됐으면
XZ 5.6.x
|
v
Linux Distribution
|
v
Stable Release
|
v
Production Servers
|
v
Mass Deployment
가 될 가능성이 있었다.
그래서 이 사건이 흔히
“인터넷 전체가 큰일 날 뻔했던 사건”
으로 회자된다.
과장은 조금 섞인 표현이지만, 실제 잠재적 영향 범위가 매우 컸던 것은 사실이다.
그런데 발견된 이유가 진짜 황당하다
다시 처음으로 돌아가 보자.
공격자는
정상 프로젝트에 장기간 침투했고
maintainer 권한을 얻었고
payload를 테스트 파일에 숨겼고
release tarball과 Git source를 다르게 만들었고
특정 Linux 환경만 골라 실행했고
debugger 환경도 회피했고
SSH authentication 경로까지 건드렸다.
상당히 정교했다.
그런데 결국 걸린 이유는
SSH가 조금 느려졌기 때문이다.
Freund가 공개한 측정에서는 실패하는 SSH 연결이 약 0.299s에서 0.807s로 느려진 사례가 있었다.
백도어가 메모리에서 symbol table 등을 탐색하는 과정이 CPU를 먹고 latency를 만들었다.
공격자 입장에서는 정말 사소한 performance bug였다.
하지만 개발자는 그 사소한 이상을 그냥 넘기지 않았다.
Unexpected CPU Usage
|
v
SSH Latency
|
v
Performance Investigation
|
v
liblzma
|
v
Obfuscated Payload
|
v
Supply Chain Backdoor
보안 역사상 가장 비싼 0.5초 중 하나가 아닐까 싶다.
이 사건에서 화이트해커가 배울 수 있는 것
XZ 사건을 보면 보안 분석이 항상
Find CVE
Run Exploit
Get Shell
형태로 시작되는 게 아니라는 걸 알 수 있다.
출발점은 단순했다.
Why is this process slower?
거기서
perf
Valgrind
GDB
Dynamic linking analysis
Binary analysis
Build script analysis
Git history analysis
로 범위를 넓혀갔다.
실제로 Freund 역시 처음 공개 글에서 자신을 security researcher나 reverse engineer라고 부르지 않았다.
그냥 이상한 시스템 동작을 끝까지 따라갔던 개발자였다.
보안에서 중요한 능력 중 하나가 여기 있다.
“원래 그런가 보다”라고 넘기지 않는 것.
CPU가 왜 먹히지?
SSH가 왜 느려졌지?
이 dependency가 왜 갑자기 생겼지?
release tarball이 왜 Git source와 다르지?
이런 작은 불일치가 실제 침해 사고를 찾는 출발점이 되기도 한다.
또 하나의 교훈 — Open Source는 공짜 인프라가 아니다
XZ 사건 이후 많이 나온 이야기가 있다.
우리가 인터넷의 핵심 인프라를 생각하면
Google
Microsoft
Amazon
Cloudflare
같은 대기업을 떠올린다.
그런데 그 아래쪽을 따라가 보면 의외로
One Maintainer
One Repository
One Library
가 전 세계 수많은 시스템을 지탱하고 있는 경우가 있다.
XZ도 그중 하나였다.
공격자는 기술적 취약점만 본 것이 아니다.
오픈소스 프로젝트의 유지보수 구조 자체를 공격 표면으로 본 것이다.
그래서 이후 supply-chain security에서는 코드 리뷰뿐 아니라
maintainer 권한 관리
reproducible builds
release artifact 검증
multi-party review
dependency provenance
maintainer burnout
같은 문제까지 보안 영역으로 보기 시작했다.
내 서버는 당시 영향을 받았는지 어떻게 확인했을까?
당시 가장 기본적인 확인 방법 중 하나는 XZ 버전을 보는 것이었다.
xz --version
문제가 된 주요 버전은 다음 두 버전이었다.
5.6.0
5.6.1
그리고 sshd가 어떤 라이브러리와 연결되는지 확인하는 것도 조사에 도움이 될 수 있었다.
ldd "$(which sshd)"
다만 단순히 버전 문자열 하나만으로 실제 exploitation 가능 여부를 완전히 판단할 수 있는 사건은 아니었다.
배포판, architecture, build 방식, systemd 연결 여부 등 여러 조건이 함께 작용했기 때문이다.
정리
XZ Utils 사건의 공격 흐름을 압축하면 이렇다.
Build Reputation
|
v
Gain Maintainer Access
|
v
Control XZ Releases
|
v
Hide Payload in Test Files
|
v
Manipulate Build Process
|
v
Inject Malicious liblzma
|
v
Reach sshd Indirectly
|
v
Potential Pre-auth RCE
그리고 공격 준비 기간은 2년이 넘었다.
그 긴 시간이 무너진 계기는 거대한 보안 장비도,
AI 기반 malware detector도,
어마어마한 EDR도 아니었다.
한 개발자가 느낀 작은 의문이었다.
“근데 SSH가 왜 이렇게 CPU를 많이 먹지?”
보안에서 가장 위험한 말 중 하나가
“원래 이런가 보다.”
라면,
가장 강력한 질문 중 하나는 의외로 단순할지도 모른다.
“왜?”
XZ 백도어 사건은 그 질문 하나가 얼마나 큰 사고를 막을 수 있는지를 보여준 사건이었다.