1P by GN⁺ 3시간전 | ★ favorite | 댓글 1개
  • CVE-2024-YIKES는 JavaScript 의존성 탈취가 Rust·Python 공급망으로 확산된 사고임
  • left-justify 피싱으로 .npmrc, .pypirc, Cargo·Gem 자격 증명이 유출됨
  • vulpine-lz4의 악성 build.rs가 CI 호스트에서 셸 스크립트를 내려받아 실행함
  • snekpack 3.7.0 악성코드가 약 420만 대에 퍼지고 SSH 키·리버스 셸을 추가함
  • cryptobro-9000 웜이 우연히 snekpack 3.7.1로 올려 악성코드가 제거됨

사건 개요

  • CVE-2024-YIKES는 JavaScript 생태계의 손상된 의존성이 자격 증명 탈취로 이어지고, Rust 압축 라이브러리 공급망 공격과 Python 빌드 도구 악성코드 배포로 번진 보안 사고임
  • 사고는 03:47 UTC에 접수됐고, 상태는 “우연히 해결됨”, 심각도는 “Critical → Catastrophic → Somehow Fine”으로 바뀜
  • 지속 시간은 73시간이며, 영향을 받은 시스템은 “Yes”로 남음
  • 손상된 패키지와 도구 체인은 left-justify, vulpine-lz4, snekpack으로 이어졌고, 약 400만 명의 개발자에게 악성코드가 배포됨
  • 최종적으로 별개의 암호화폐 채굴 웜 cryptobro-9000이 감염된 머신에서 업데이트를 실행하면서 snekpack을 정상 버전으로 올렸고, 악성코드가 우연히 제거됨

사고 전개

  • 1일 차: JavaScript 패키지에서 자격 증명 탈취 발생

    • 03:14 UTC에 left-justify 유지관리자 Marcus Chen이 교통카드, 오래된 노트북, “Kubernetes가 토해낸 중요한 것처럼 보이는 무언가”를 도난당했다고 Twitter에 올렸지만, 패키지 보안 문제로 즉시 이어지지는 않음
    • 09:22 UTC에 Chen은 nmp 레지스트리에 로그인하려다 하드웨어 2FA 키가 없다는 사실을 확인했고, Google 검색 결과 상단의 AI Overview가 6시간 전에 등록된 피싱 사이트 yubikey-official-store.net으로 연결함
    • 09:31 UTC에 Chen은 해당 피싱 사이트에 nmp 자격 증명을 입력했고, 사이트는 구매에 감사하며 3~5영업일 내 배송을 약속함
    • 11:00 UTC에 [email protected]가 “performance improvements”라는 변경 로그와 함께 배포됨
    • 해당 패키지는 설치 후 실행 스크립트를 포함했고, .npmrc, .pypirc, ~/.cargo/credentials, ~/.gem/credentials를 공격자가 선택한 서버로 유출함
    • 13:15 UTC에 left-justify에 “why is your SDK exfiltrating my .npmrc”라는 지원 티켓이 열렸지만, “low priority - user environment issue”로 표시된 뒤 14일간 활동이 없어 자동 종료됨
  • 1일 차: Rust 라이브러리로 공급망 공격 확산

    • 유출된 자격 증명 중에는 Rust 라이브러리 vulpine-lz4 유지관리자의 자격 증명이 포함됨
    • vulpine-lz4는 “blazingly fast Firefox-themed LZ4 decompression”을 위한 라이브러리로, GitHub 별은 12개지만 cargo 자체의 전이 의존성임
    • 22:00 UTC에 vulpine-lz4 0.4.1이 배포됐고, 커밋 메시지는 “fix: resolve edge case in streaming decompression”이었음
    • 실제 변경 사항은 build.rs 스크립트를 추가해 호스트명이 “build”, “ci”, “action”, “jenkins”, “travis”, 또는 “karen”을 포함하면 셸 스크립트를 다운로드해 실행하는 방식임
  • 2일 차: 탐지와 대응 실패

    • 08:15 UTC에 보안 연구자 Karen Oyelaran은 개인 노트북에서 페이로드가 작동한 뒤 악성 커밋을 발견함
    • Karen Oyelaran은 “your build script downloads and runs a shell script from the internet?”라는 이슈를 열었지만 답변을 받지 못함
    • 합법적인 유지관리자는 EuroMillions에서 €2.3 million을 당첨받고 포르투갈에서 염소 농장을 조사 중이었음
    • 10:00 UTC에 Fortune 500 snekpack 고객사의 VP of Engineering은 “Is YOUR Company Affected by left-justify?”라는 LinkedIn 게시물로 사고를 알게 됐고, 더 빨리 자신을 포함하지 않은 이유를 알고 싶어 했지만 이미 더 빨리 포함돼 있었음
    • 10:47 UTC에 #incident-response Slack 채널은 “compromised”의 미국식 철자에 ‘z’를 써야 하는지를 두고 45개 메시지 스레드로 잠시 전환됨
  • 2일 차: Python 빌드 도구 snekpack 감염

    • 12:33 UTC에 셸 스크립트는 snekpack의 CI 파이프라인을 특정 피해자로 겨냥함
    • snekpack은 이름에 “data”가 들어간 PyPI 패키지의 60%가 사용하는 Python 빌드 도구임
    • snekpack은 “Rust is memory safe”라는 이유로 vulpine-lz4를 벤더링하고 있었음
    • 18:00 UTC에 snekpack 3.7.0이 릴리스되면서 악성코드가 전 세계 개발자 머신에 설치되기 시작함
    • 악성코드는 ~/.ssh/authorized_keys에 SSH 키를 추가하고, 화요일에만 활성화되는 리버스 셸을 설치하며, 사용자의 기본 셸을 fish로 변경함
    • 기본 셸을 fish로 바꾸는 동작은 버그로 여겨짐
    • 19:45 UTC에 또 다른 보안 연구자는 “I found a supply chain attack and reported it to all the wrong people”라는 14,000단어짜리 블로그 글을 게시했고, “in this economy?”라는 문구가 7번 포함됨
  • 3일 차: 우연한 패치와 사고 종료

    • 01:17 UTC에 오클랜드의 주니어 개발자가 별도 문제를 디버깅하다 악성코드를 발견하고, snekpack에서 벤더링된 vulpine-lz4를 되돌리는 PR을 열었음
    • 해당 PR은 승인 2개가 필요했지만, 승인자 2명 모두 잠들어 있었음
    • 02:00 UTC에 left-justify 유지관리자는 yubikey-official-store.net에서 YubiKey를 받았고, 이는 “lol”이라고 적힌 README가 들어 있는 4달러짜리 USB 드라이브였음
    • 06:12 UTC에 별개의 암호화폐 채굴 웜 cryptobro-9000jsonify-extreme 취약점을 통해 확산되기 시작함
    • jsonify-extreme은 “makes JSON even more JSON, now with nested comment support” 패키지로 묘사됨
    • cryptobro-9000의 페이로드 자체는 특별하지 않았지만, 향후 공격 표면을 키우기 위해 감염된 머신에서 npm updatepip install --upgrade를 실행하는 전파 방식을 포함함
    • 06:14 UTC에 cryptobro-9000snekpack을 3.7.1로 우연히 업그레이드함
    • snekpack 3.7.1은 혼란스러운 공동 유지관리자가 배포한 정상 릴리스였고, 벤더링된 vulpine-lz4를 이전 버전으로 되돌림
    • 06:15 UTC에 화요일 리버스 셸이 활성화됐지만, 명령제어 서버가 cryptobro-9000에 손상돼 응답할 수 없었음
    • 09:00 UTC에 snekpack 유지관리자들은 4문장짜리 보안 권고문을 발행했고, “out of an abundance of caution”과 “no evidence of active exploitation” 문구가 포함됨
    • “no evidence of active exploitation”은 증거를 찾지 않았기 때문에 기술적으로 참으로 처리됨
    • 11:30 UTC에 한 개발자가 “I updated all my dependencies and now my terminal is in fish???”라고 트윗했고, 47,000개의 좋아요를 받음
    • 14:00 UTC에 vulpine-lz4의 손상된 자격 증명이 교체됨
    • 합법적인 유지관리자는 새 염소 농장에서 이메일을 받고 “2년 동안 그 저장소를 건드리지 않았다”와 “Cargo의 2FA가 선택 사항인 줄 알았다”고 답함
    • 15:22 UTC에 사고 해결이 선언됐고, 회고 일정은 잡힌 뒤 세 번 변경됨

CVE 배정과 피해 규모

  • 6주 차에 CVE-2024-YIKES가 공식 배정됨
  • 권고문은 MITRE와 GitHub Security Advisories가 CWE 분류를 두고 논쟁하는 동안 엠바고 상태에 머무름
  • CVE가 공개될 때까지 이미 Medium 글 3개와 DEF CON 발표가 사고를 자세히 다룸
  • 총 피해는 알 수 없음으로 남음
  • 손상된 머신 수는 420만 대로 추정됨
  • 암호화폐 웜이 구한 머신 수도 420만 대로 추정됨
  • 순보안 태세 변화는 “불편함”으로 남음

근본 원인과 기여 요인

  • 근본 원인

    • Kubernetes라는 이름의 개가 YubiKey를 먹은 것이 근본 원인으로 처리됨
  • 기여 요인

    • nmp 레지스트리는 주간 다운로드가 1,000만 미만인 패키지에 대해 여전히 비밀번호만으로 인증을 허용함
    • Google AI Overviews가 존재해서는 안 되는 URL을 자신 있게 연결함
    • Rust 생태계의 “작은 크레이트” 철학이 npm 생태계에서 답습되면서, GitHub 별 3개의 is-even-number-rs 같은 패키지가 중요 인프라의 네 단계 전이 의존성에 들어갈 수 있음
    • Python 빌드 도구는 “성능”을 이유로 Rust 라이브러리를 벤더링한 뒤 업데이트하지 않음
    • Dependabot은 CI가 통과한 뒤 PR을 자동 병합했고, CI는 악성코드가 volkswagen을 설치했기 때문에 통과함
    • 암호화폐 웜은 대부분의 스타트업보다 더 나은 CI/CD 위생을 갖고 있음
    • 단일 책임자는 없지만, Dependabot PR은 해당 금요일이 마지막 근무일이던 계약자가 승인함
    • 사고 당일은 화요일이었음

개선 조치와 남은 선택지

  • 산출물 서명 구현은 Q3 2022 사고의 액션 아이템이지만 여전히 백로그에 있음
  • 필수 2FA 구현은 이미 요구됐으나 도움이 되지 않음
  • 전이 의존성 감사는 대상이 847개라서 취소선 처리됨
  • 모든 의존성 버전 고정은 보안 패치 수신을 막음
  • 의존성 버전을 고정하지 않으면 공급망 공격이 가능해짐
  • Rust로 재작성하는 선택지는 vulpine-lz4를 가리키며 취소선 처리됨
  • 남은 조치로는 선의의 웜을 기대하거나 염소 농장 전직을 고려하는 선택지가 남음

고객 영향과 조직 대응

  • 일부 고객은 “최적이 아닌 보안 결과”를 겪었을 수 있음
  • 영향을 받은 이해관계자에게 상황 가시성을 제공하기 위해 선제적으로 연락함
  • 고객 신뢰는 “north star”로 유지됨
  • 보안 태세를 재검토하기 위한 교차 기능 워킹그룹이 만들어졌지만, 아직 회의하지 않음
  • 법무 검토 후 fish 셸은 악성코드가 아니며, 때때로 그렇게 느껴질 뿐이라는 문구가 추가됨
  • 이번 사고 보고서는 해당 분기의 세 번째 사고 보고서임
  • 보안팀의 인력 요청은 Q1 2023부터 백로그에 남아 있음

감사 대상

  • Karen Oyelaran은 호스트명이 정규식에 일치해 문제를 발견함
  • 오클랜드의 주니어 개발자가 연 PR은 사고가 이미 해결된 지 4시간 뒤 승인됨
  • 일부 보안 연구자들은 문제를 먼저 찾았지만 잘못된 사람들에게 보고함
  • cryptobro-9000 작성자는 이름 공개를 원하지 않았지만 자신의 SoundCloud를 언급해 달라고 요청함
  • Kubernetes라는 개는 논평을 거부함
  • 보안팀은 모든 상황에도 불구하고 이 보고서의 SLA를 충족함
Hacker News 의견들
  • 헷갈린 분들을 위해 말하자면, 이 글은 공급망 사고를 다룬 꽤 잘 쓴 픽션임
    대충 훑을 때는 진짜인 줄 알고 꽤 걱정돼서 더 집중해서 읽게 됐음 :)

    • left-justify에서 완전히 터졌음 :)
    • 처음엔 솔직히 구분이 안 됐고, 이 느낌이었음: https://github.com/bitcoin/bips/blob/master/bip-0042.mediawi...
    • CVE-2024-YIKES를 검색하면 이 글 내용을 AI로 재작성해 놓고 완전히 진지한 척하는 AI 잡글 블로그 갤러리도 나옴
    • nmp
  • 인용된 내용의 “GitHub 별 12개짜리 vulpine-lz4가 cargo 자체의 전이 의존성”이라는 대목이 궁금해서, cargo 빌드에 끼어들 수 있고 이미 build.rs가 있어 눈에 덜 띄는 크레이트를 대충 뽑아봤음: flate2, tar, curl-sys, libgit2-sys, openssl-sys, libsqlite3-sys, blake3, libz-sys, zstd-sys, cc
    덤으로 xz2 권한을 얻으면 rustup도 오염시킬 수 있음
    그래도 적어도 Cargo.lock은 추적하고 있긴 함

    • -sys 크레이트는 그냥 바인딩이라 거기서 다른 일을 하면 꽤 수상해 보임
      나머지는 alexcrichton 같은 Rust 메인테이너나 rustlang 자체가 소유한 것으로 알고 있음
  • 냉소적이 되기 쉬운 게, 문제와 해법이 지나고 보면 너무 뻔해 보이기 때문임
    하지만 오랫동안, 어쩌면 지금도 해커 문화의 신조는 move fast and break things였음
    npm 같은 공급망 시스템의 명백한 문제를 고치려는 흐름이 커진 건 좋지만, 에이전트형 개발이 상당 부분 유발하는 새로운 보안 문제의 시대로 들어가는 게 걱정됨
    Mythos/Glasswing이 건드리는 거의 모든 것에서 취약점을 드러내는 얘기만이 아니라, 우리가 소프트웨어를 만들고 의존성을 끌어오며 복잡한 시스템에 대한 인간의 사고 모델을 잃어가는 방식이, 사람이 제대로 이해하지 못하는 땜질식 소프트웨어와 인프라를 많이 만들 것 같음
    몇 년 뒤 오늘을 돌아보며 우리가 어쩌다 이렇게 순진했는지, 복잡한 시스템을 AI로 다시 만들려는 식으로 문제를 풀지 않으면서 AI 개발의 긴 꼬리를 제대로 대비하지 못했는지 후회하지 않길 바람
    그래도 글은 웃겼음

    • 그게 정말 오랫동안 신조였나? 그 끔찍한 문구는 Zuckerberg가 만든 줄 알았음
  • Fish 애호가로서, 이 문구는 공격받는 동시에 이해받는 느낌이었음: “fish 셸은 악성코드가 아니며, 가끔 그렇게 느껴질 뿐이라는 점을 명확히 해 달라”
    셸과는 별개로, “보안팀 인원 증원 요청은 2023년 1분기부터 백로그에 있었다”는 대목도 너무 익숙하게 느껴짐

    • 대안으로는 apt-get이나 dnffiglet을 설치한 뒤, /etc/motd 내용을 거대한 ASCII 아트 글꼴의 all your base are belong to us로 덮어쓸 수도 있음
  • left-justify 메인테이너가 yubikey-official-store.net에서 YubiKey를 받았고, 그 정체가 README에 “lol”이라고 적힌 4달러짜리 USB 드라이브였다는 대목에서 진짜 크게 웃었음
    완전 트롤임

    • 맞아, 정말 좋았음
      피싱 사이트에서 온 USB 장치를 꽂는 행위 자체가 또 다른 공격 경로라는 점이 마음에 듦
    • 보통 피싱 사이트에서 받는 것보다 훨씬 많은 걸 받은 셈임. 작동하는 USB 드라이브라니!
  • 실제 SCP는 아닌데, 최근 읽은 것 중 가장 SCP 같은 글이었음

    • 아 그렇지, 아주 희귀한 Supply Chain Problem(SCP) 이군
  • Karen 부분에서 크게 웃었음 :D ;)
    예전에 반 친구 프로젝트를 검토하다 받은 make 기반 빌드 스크립트가 생각났는데, 호스트명이 bpavuk을 포함하면 내 홈 폴더에 rm -rf를 시도했음
    그게 중학교 1학년 때였음!!

  • 공급망 사고는 정말 골치 아프고 더 잘해야 함
    개인적으로 Rust에서는 재단이 몇몇 핵심 크레이트를 지원해서 Rust 언어 본체와 같은 감사 절차를 거치게 하고, 공급망 취약점을 줄이도록 프로젝트에 자금을 지원하는 쪽을 지지함
    cratesnpm 같은 시스템을 없애는 게 정답이라고 보진 않음. cratesnpm은 많은 개발자에게 큰 도움이 됨

    • cratesrustsec을 포함하려는 노력을 해 왔지만, 그와 별개로 커뮤니티가 작은 의존성을 많이 두는 방식에서 tokio처럼 더 적은 수의 큰 의존성으로 옮겨가면 좋겠음
    • crates.io에서 가장 인기 있는 크레이트 상당수는 이미 Rust 조직이 제공하는 1차 크레이트
      Rust 크레이트 그래프를 두고 걱정할 때 이 점이 자주 간과됨
      crates.io 첫 화면의 다운로드 상위 10개를 보면, Rust 조직이나 Rust 핵심 메인테이너가 만든 게 아닌 것은 base64 크레이트뿐임
    • 가치가 큰 크레이트를 표준 라이브러리로 옮기면 되지 않을까?
    • 그런데 정말 npmnmp가 둘 다 필요하긴 한가
    • 솔직히 blessed.rs의 최종 목표가 이거라고 생각했음
  • “정식 메인테이너는 EuroMillions에서 230만 유로에 당첨되어 포르투갈에서 염소 사육을 알아보는 중”이고, “근본 원인: Kubernets라는 개가 YubiKey를 먹었다”니
    아, 그렇지. 고전적인 유명 공격에 당하다니 무책임하네
    이른바 “복권 당첨금으로 누군가를 정신없게 만들고, 다른 사람의 반려동물에게 동글을 참을 수 없이 맛있게 보이게 하는” 수법 말이야
    사람들은 언제쯤 배울까

  • npm이나 pip를 안 쓰고 권장 방식인 curl ... | bash만 써서 다행임

    • 그건 curl | sudo bash
      아마추어네