사고 보고서: CVE-2024-YIKES

3 hours ago 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-9000이 jsonify-extreme 취약점을 통해 확산되기 시작함
    • jsonify-extreme은 “makes JSON even more JSON, now with nested comment support” 패키지로 묘사됨
    • cryptobro-9000의 페이로드 자체는 특별하지 않았지만, 향후 공격 표면을 키우기 위해 감염된 머신에서 npm update와 pip install --upgrade를 실행하는 전파 방식을 포함함
    • 06:14 UTC에 cryptobro-9000은 snekpack을 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를 충족함
Read Entire Article