며칠 전 Arch 쓰는 동료가 슬랙에 "내가 몇 년 전에 올려놓고 잊어버린 AUR 패키지가 누군가한테 인수됐다는 메일이 왔는데, 인수되자마자 악성 커밋이 올라왔다"는 메시지를 던졌다. 처음엔 흔한 스팸인 줄 알았는데, 파보니 408개 이상의 패키지가 엮인 꽤 큰 사건이었다. 정보 탈취기에 eBPF 루트킷까지 끼어 있는 공급망 공격이라 단순 "Arch 사용자 본인 책임이지 뭐"로 넘기기엔 결이 다르다.
이 글은 인프라/DevOps 관점에서 이번 사건을 뜯어본다. 어떻게 침투했고, PKGBUILD가 어떻게 변조됐고, 내 시스템이 감염됐는지 어떻게 확인하고, 앞으로 패키지 신뢰 체계를 어떻게 봐야 하는지까지 실무 명령어와 함께 정리한다.
1. 도입: AUR 신뢰 모델과 이번 사건이 왜 중요한가
AUR(Arch User Repository)을 한 번도 안 써본 사람을 위해 짚자면, AUR은 "패키지 바이너리 저장소"가 아니다. 본질은 PKGBUILD라는 빌드 레시피 스크립트의 git 저장소 모음이다. 공식 저장소(core, extra)와 달리 Arch 팀의 심사를 거치지 않고, 사용자가 직접 올린다. 그래서 위키 첫 화면에 빨간 경고 박스로 "사용자 저장소이며 맹목적으로 신뢰하지 말 것"이라고 박혀 있다.
문제는 여기서 끝이 아니다. AUR에는 "고아(orphan) 패키지 인수"라는 구조가 있다. 메인테이너가 손을 떼서 관리되지 않는 상태(unmaintained)로 표시되면, 누구나 그 패키지를 채택(adopt)하고 PKGBUILD 변경을 제출할 수 있다. 이번 공격자가 정확히 이 지점을 노렸다.
핵심을 요약하면 이렇다.
- 새 maintainer 계정이 신뢰받는 기존 maintainer를 사칭해 408개 이상의 (주로 고아) 패키지를 채택했다.
- 채택 직후 PKGBUILD를 변조해 정보 탈취기 + eBPF 루트킷 페이로드를 삽입했다.
- 대부분 잘 안 쓰이는 오래된 패키지지만, 범위가 408개로 넓고 루트킷까지 포함됐다는 점이 위험하다.
실무에서 이게 왜 무서운가? CI 파이프라인에서 Arch 기반 빌드 이미지를 쓰거나, 개발자 워크스테이션이 Arch라면, AUR 헬퍼(yay, paru)로 무심코 -Syu 한 번 돌린 게 곧 침해로 이어질 수 있기 때문이다. 그리고 루트킷이 들어간 이상, "패키지 지웠으니 끝"이 아니라 시스템 신뢰성 자체를 보장할 수 없는 상황이 된다.
2. 핵심: 공격이 어떻게 동작했나
침투 경로 — 사칭과 고아 패키지 인수
중요한 포인트 하나. 이번 사건은 기존 maintainer 계정이 털린 게 아니다. 원문 분석에 따르면 "알려진 maintainer 계정이 사칭된 것"이다. 즉 비슷한 이름이나 신뢰를 흉내 낸 새 계정이 고아 패키지를 줄줄이 인수했고, 기존 검증 이력과 git 히스토리를 그대로 물려받았다. 사용자 입장에서는 "오래 유지돼 온 멀쩡한 패키지"처럼 보인다는 게 함정이다.
비유하자면, 오래 비어 있던 가게 간판을 누가 슬쩍 떼고 본인 가게로 바꿔 달았는데, 손님은 예전 단골 가게인 줄 알고 그대로 들어가는 셈이다.
페이로드 삽입 — PKGBUILD의 preinstall에 npm/bun 끼워넣기
PKGBUILD는 셸 함수들로 이뤄진 빌드 스크립트다. 그 안에는 prepare(), build(), package() 같은 함수와 더불어, 설치 직전/직후에 실행되는 .install 훅(pre_install, post_install 등)을 걸 수 있다. 이 훅은 보통 root 권한으로 실행된다. 공격자는 여기에 악성 코드를 심었다.
원문에 따르면 초기 감염은 preinstall 단계에서 npm으로 악성 NPM 패키지 atomic-lockfile을 설치하게 만들었다. 이후 변형은 npm 대신 bun으로 js-digest를 설치했다. npm 기반 탐지/우회책을 비웃듯 런타임을 갈아탄 것이다.
실제 변조가 어떤 모양인지, PKGBUILD 관점에서 단순화해 보면 대략 이런 그림이다(개념 설명용 예시):
# PKGBUILD 일부 — 정상이라면 없어야 할 라인
prepare() {
# ... 원래 빌드 준비 코드 ...
npm install atomic-lockfile # ← 빌드와 무관한 외부 패키지 설치
}
# 또는 .install 훅에 숨기는 형태
pre_install() {
bun add js-digest # ← root 권한으로 실행되는 악성 페이로드
}
핵심 위화감은 "빌드와 아무 상관 없는 npm/bun 패키지를 왜 여기서 깔지?"라는 점이다. HN 댓글에서도 지적했듯, 의존성 사슬 깊숙이 숨기지 않고 PKGBUILD에서 대놓고 npm install을 때리는 건 오히려 성의 없는 편이다. 그만큼 사람들이 PKGBUILD를 안 읽는다는 데 베팅한 공격이다.
페이로드의 정체 — 정보 탈취기 + eBPF 루트킷
설치된 악성 패키지는 두 가지를 한다. 첫째는 정보 탈취(자격 증명 등), 둘째가 진짜 골치 아픈 eBPF 루트킷이다. 원문에 명시된 침해 지표(IoC)는 다음과 같다.
js-digest에 내장된 악성 Linux 실행 파일 SHA256:7883bda1ff15425f2dbe622c45a3ae105ddfa6175009bbf0b0cad9bf5c79b316- 의심스러운 eBPF map 이름:
hidden_pids,hidden_names,hidden_inodes - outbound Tor 트래픽으로 C2 통신하는 정황 → 모든 사용자에게 Tor 아웃바운드 차단 권고
eBPF 루트킷이 위험한 이유는, 커널 레벨에서 프로세스/파일/소켓을 숨길 수 있기 때문이다. hidden_pids 같은 map 이름에서 의도가 그대로 드러난다. 평범한 ps, ls로는 안 보이고, 일반적인 파일 스캔으로도 못 잡는다. 그래서 "패키지 지우고 자격 증명 교체하고, 루트킷 가능성 때문에 Arch 재설치를 고려하라"는 강경한 대응 권고가 나온 것이다.
3. 실무 관점: 감염 확인부터 대응까지
① 내가 영향받았는지 먼저 확인하기
제일 먼저 할 일은 "내 시스템에 영향 목록의 패키지가 설치돼 있나" 확인이다. 가장 빠른 방법은 설치된 패키지 목록과 침해 패키지 목록을 comm으로 교집합 내는 것이다. HN에서 공유된 방식이고, 외부 스크립트를 통째로 bash에 파이프하지 않아도 돼서 안전하다.
# 설치된 패키지 이름만 뽑아 정렬
pacman -Qq | sort > /tmp/installed.txt
# (침해 패키지 목록을 신뢰할 수 있는 Gist에서 받아 정렬했다고 가정)
sort affected-packages.txt > /tmp/affected.txt
# 두 목록의 교집합 = 내가 설치한 영향 패키지
comm -12 /tmp/installed.txt /tmp/affected.txt
출력 예시 — 아무것도 안 나오면 (적어도 패키지 이름 기준으로는) 일단 한숨 돌려도 된다.
$ comm -12 /tmp/installed.txt /tmp/affected.txt
$ echo $?
0
만약 걸린다면 이렇게 패키지 이름이 찍힌다.
$ comm -12 /tmp/installed.txt /tmp/affected.txt
jd-gui
toggldesktop
단, 중요한 한계가 있다. 이 방식은 "패키지가 설치됐는지"만 본다. 설치된 버전이 감염 버전인지는 확인하지 못한다. 즉, 몇 달째 yay -Syu를 안 돌렸다면 감염 커밋 이전 버전일 수 있어 운 좋게 비켜갔을 가능성이 있고, 반대로 이름만으로는 안전을 단정할 수 없다. HN의 한 사용자도 정확히 이 지점에서 "침해 2시간 전에 jd-gui-bin을 설치해서 운 좋게 피했다"고 적었다.
② eBPF 루트킷 흔적 직접 점검하기
루트킷 가능성이 있으니, eBPF map을 직접 들여다보자. bpftool로 시스템에 로드된 map 목록을 확인한다.
# bpftool이 없으면 (보통 별도 패키지)
sudo pacman -S bpf # 패키지명은 환경에 따라 확인 필요
# 로드된 eBPF map 목록 조회
sudo bpftool map list
정상이라면 시스템/네트워크 관련 map들만 보인다. 만약 아래처럼 노골적인 이름이 보이면 빨간불이다.
$ sudo bpftool map list
12: hash name hidden_pids flags 0x0
key 4B value 4B max_entries 4096
13: hash name hidden_names flags 0x0
key 256B value 1B max_entries 4096
14: hash name hidden_inodes flags 0x0
key 8B value 1B max_entries 4096
이런 map이 보인다면 시스템은 이미 신뢰할 수 없는 상태로 봐야 한다. 루트킷이 활성화돼 있으면 bpftool 출력 자체를 조작했을 가능성도 배제 못 하므로, 가능하면 외부 라이브 USB로 부팅해 오프라인 상태에서 디스크를 검사하는 게 정석이다.
③ 흔한 함정 — 실제로 마주칠 에러들
함정 1: 로캘 때문에 날짜 기반 탐지가 헛돈다. "9 Jun 이후 설치된 패키지를 찾아라" 식의 스크립트는 pacman이 날짜 로캘을 따르기 때문에, 한국어 로캘 환경에서는 매칭이 안 된다. 영어 로캘에서 짠 스크립트를 그대로 돌리면 아무것도 안 잡혀서 "난 안전하구나" 착각하기 쉽다. pacman -Qi의 Install Date 형식을 본인 환경에서 먼저 확인하자.
$ pacman -Qi jd-gui | grep -i "install"
설치한 날짜 : 2026년 06월 09일 (수) 22시 14분 03초
# ← 영어 "9 Jun 2026" 패턴으로 grep하면 절대 안 걸린다
함정 2: bpftool이 없거나 권한이 없다.
$ bpftool map list
bash: bpftool: command not found
# 설치 후 sudo 없이 돌리면
$ bpftool map list
Error: can't get next map: Operation not permitted
eBPF map 조회는 CAP_BPF/root 권한이 필요하다. 반드시 sudo로 실행해야 한다.
함정 3: 출처 불명 스크립트를 그대로 신뢰. 이번 사건 와중에 커뮤니티에서 탐지 스크립트가 우후죽순 올라왔는데, HN에서도 "이게 공식도 아닌데 어떻게 믿냐, 코드 읽어도 안전한지 판단이 안 된다"는 질문이 나왔다. 핵심 원칙은 변하지 않는다. 절대 curl ... | bash 하지 말고, 받아서 읽고, 무슨 일을 하는지 이해한 다음 실행한다. 침해 대응 스크립트랍시고 2차 페이로드를 심는 사례는 다른 생태계에서도 흔하다.
④ 감염이 확인됐을 때의 대응 순서
- 포렌식을 위해 시스템을 먼저 보존한다. 무작정 지우지 말고 스냅샷/이미지를 떠둔다.
- 네트워크에서 outbound Tor 트래픽을 차단한다(원문 권고). 방화벽 레벨에서 막아 C2 통신을 끊는다.
- 모든 자격 증명을 교체한다. SSH 키, 클라우드 액세스 키, 토큰, 브라우저 저장 비밀번호까지. 정보 탈취기가 이미 긁어갔다고 가정하고 움직인다.
- 루트킷 가능성 때문에 시스템 신뢰성을 보장할 수 없으므로, Arch 재설치를 진지하게 고려한다. HN 사용자도
archinstall로 15분 만에 복구했다고 한다.
⑤ 트레이드오프와 대안
"그럼 AUR을 아예 쓰지 말까?"는 너무 극단적이다. 현실적인 절충안은 이렇다.
- AUR 헬퍼의 diff 검토를 켜고 습관화한다. paru/yay 모두 업데이트 전 PKGBUILD 변경 diff를 보여주는 옵션이 있다. 최근 소유자가 바뀐 패키지는 특히 의심한다.
- 가능하면
-bin대신 소스 빌드, 또는 그 반대를 상황에 맞게. 다만 이번처럼 preinstall 훅이 문제면 빌드 방식과 무관하게 당할 수 있다. 만능은 아니다. - 격리된 환경에서 빌드/설치 테스트. 네트워크 차단한 컨테이너나 VM에서 먼저 돌려보면, preinstall에서 npm/bun이 외부로 나가려다 실패하는 것만으로도 이상을 감지할 수 있다. HN에서도 "네트워크 없는 Docker에 설치해보는 게 답 아니냐"는 의견이 나왔다.
- CI 빌드 이미지는 공식 저장소만 사용. AUR 의존성을 파이프라인에 넣지 않는 게 원칙이다. 굳이 필요하면 특정 커밋 해시에 핀하고, 매 업데이트마다 diff를 사람이 검토한다.
4. 정리: 한 줄 요약과 적용 대상
한 줄 요약: AUR의 "누구나 고아 패키지를 인수할 수 있다"는 구조가 maintainer 사칭과 결합해, PKGBUILD/install 훅에 정보 탈취기와 eBPF 루트킷을 심는 공급망 공격으로 터졌다. 패키지 이름만으로는 안전을 단정할 수 없고, 루트킷 때문에 감염 시 재설치가 정석이다.
지금 당장 확인해야 할 사람:
- 워크스테이션이 Arch이고 yay/paru로 AUR 패키지를 쓰는 모든 개발자 → 위
comm점검과bpftool map list를 오늘 돌려라. - Arch 기반 CI/빌드 이미지를 운영하는 DevOps → AUR 의존성이 파이프라인에 끼어 있는지 감사하고, 빌드 환경의 outbound 트래픽을 점검하라.
안심해도 되는 사람: Arch를 안 쓰면 이번 사건의 직접 영향 대상이 아니다(원문 명시). 다만 npm/bun으로 페이로드를 끌어오는 방식 자체는 PyPI, Cargo, Docker Hub 등 모든 생태계의 공통 문제다. "우린 Arch 안 쓰니까 끝"이 아니라, 자기 스택의 패키지 신뢰 체계를 같은 시선으로 돌아보는 계기로 삼는 게 맞다.
개인적으로 이번 사건의 진짜 교훈은 "고아 패키지 인수를 최종 사용자에게 알리지 않는다"는 구조적 구멍이다. 통제권을 내려놓는 가장 쉬운 방법이 패키지를 고아로 만드는 것이고, 그게 곧 공격 표면이 됐다. AUR 팀이 채택 기능에 제한을 걸기로 한 건 늦었지만 옳은 방향이다. 장기적으로는 SLSA 같은 빌드 출처 증명, 그리고 헬퍼 단에서 "최근 소유자 변경" 경고를 띄우는 정도가 현실적인 보완책으로 보인다.
참고 자료
- AUR 패키지가 정보 탈취기와 루트킷에 감염됨 (GeekNews / 하다 원문)
- Arch Linux 공식 공지: Active AUR malicious packages incident
- Arch Wiki: Arch User Repository (상단 경고 박스 참고)
- aur-malware-check (커뮤니티 통합 점검 유틸리티, 코드 직접 검토 권장)
- Arch Linux Reproducible Builds (공식 패키지 검증 체계, 이번 AUR 사건과는 별개)
- Phoronix: Arch Linux AUR 400+ packages compromised
※ 본문의 PKGBUILD 변조 코드는 동작 원리를 설명하기 위한 개념 예시이며, 실제 악성 커밋과 1:1로 일치하지 않을 수 있다. 정확한 분석은 원문에 연결된 Preliminary analysis 문서를 확인하길 권한다.
