# NPM에 8만6천 회 이상 다운로드된 악성 패키지 대량 유포

> Clean Markdown view of GeekNews topic #24053. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=24053](https://news.hada.io/topic?id=24053)
- GeekNews Markdown: [https://news.hada.io/topic/24053.md](https://news.hada.io/topic/24053.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-10-31T23:34:02+09:00
- Updated: 2025-10-31T23:34:02+09:00
- Original source: [arstechnica.com](https://arstechnica.com/security/2025/10/npm-flooded-with-malicious-packages-downloaded-more-than-86000-times/)
- Points: 3
- Comments: 2

## Topic Body

- NPM 저장소에서 **100개 이상 자격 증명 탈취용 악성 패키지**가 8월 이후 탐지되지 않은 채 업로드되어, 총 8만6천 회 이상 다운로드된 사실이 확인됨  
- 보안업체 **Koi**는 ‘**PhantomRaven**’으로 명명한 공격 캠페인이 NPM의 **Remote Dynamic Dependencies(RDD)** 기능을 악용해 126개의 악성 패키지를 배포했다고 보고  
- RDD는 패키지가 **신뢰되지 않은 도메인에서 동적으로 의존성 코드를 내려받을 수 있게 하는 구조**로, 정적 분석 도구에서는 탐지되지 않음  
- 공격자는 이 기능을 이용해 **HTTP 연결을 통한 악성 코드 다운로드**를 수행했으며, 패키지 메타데이터에는 “0 Dependencies”로 표시되어 개발자와 보안 스캐너가 인식하지 못함  
- 이러한 구조적 취약점은 **NPM 생태계의 보안 관리 한계와 자동 설치 메커니즘의 위험성**을 드러냄  
  
---  
  
### NPM 저장소 내 악성 패키지 확산  
- 공격자들이 NPM 코드 저장소의 **구조적 약점**을 이용해 8월 이후 100개 이상의 **자격 증명 탈취용 패키지**를 업로드  
  - 대부분의 패키지가 탐지되지 않은 채 배포되었으며, 누적 다운로드 수는 **86,000회 이상**  
- 보안업체 **Koi**는 이 공격을 **PhantomRaven 캠페인**으로 명명하고, NPM의 특정 기능이 악용되었다고 분석  
  - Koi에 따르면 126개의 악성 패키지 중 약 80개가 기사 작성 시점에도 여전히 NPM에 남아 있었음  
  
### Remote Dynamic Dependencies(RDD)의 취약 구조  
- RDD는 패키지가 **외부 웹사이트에서 의존성 코드를 동적으로 다운로드**하도록 허용하는 기능  
  - 일반적으로 의존성은 NPM의 **신뢰된 인프라**에서 내려받지만, RDD는 **HTTP 등 암호화되지 않은 연결**을 통한 다운로드도 허용  
- PhantomRaven 공격자는 이 기능을 이용해 악성 URL(예: `http://packages.storeartifact.com/npm/unused-imports`)에서 코드를 내려받도록 설정  
  - 이러한 의존성은 **개발자와 보안 스캐너에 보이지 않으며**, 패키지 정보에는 “0 Dependencies”로 표시됨  
- NPM의 자동 설치 기능으로 인해 이러한 **‘보이지 않는’ 의존성 코드가 자동 실행**되는 구조  
  
### 보안 도구의 탐지 한계  
- Koi의 **Oren Yomtov**는 “PhantomRaven은 기존 보안 도구의 **사각지대를 정교하게 악용한 사례**”라고 언급  
  - RDD는 **정적 분석 도구에서 탐지되지 않음**  
- 이로 인해 공격자는 **보안 검증을 우회**하며 악성 코드를 배포할 수 있었음  
  
### 추가적 취약 요인  
- Koi는 RDD로 내려받은 의존성이 **매 설치 시마다 공격자 서버에서 새로 다운로드**된다고 설명  
  - 캐시나 버전 관리가 없어, 동일 패키지라도 설치 시점마다 **다른 악성 코드가 주입될 가능성** 존재  
- 이러한 동적 다운로드 구조는 **패키지 무결성 검증을 어렵게 함**  
  
### NPM의 구조 및 배경  
- NPM은 **JavaScript용 패키지 관리자**로, GitHub 자회사인 **npm, Inc.** 가 관리  
  - Node.js의 기본 패키지 관리자이며, 명령줄 클라이언트와 **npm registry**로 구성  
  - registry에는 공개 및 유료 비공개 패키지가 저장되며, 웹사이트를 통해 검색 가능  
- 이번 사건은 **NPM의 자동 의존성 관리 구조가 공격에 악용될 수 있음을 보여주는 사례**로 지적됨  
  
### 기타 언급  
- 기사 말미에서는 **불필요한 JavaScript 실행을 차단해야 한다는 의견**이 언급됨  
  - 그러나 이번 공격은 **필수 JavaScript 코드조차 악용된 사례**로 지적됨

## Comments



### Comment 46767

- Author: developerjhp
- Created: 2025-11-25T14:10:53+09:00
- Points: 1

실시간 스캐너 스크립트를 만들어보았어요.  
  
의심되는 리포지토리의 path에서   
npx sha1-hulud-scanner  
  입력하시면됩니다.  
  
소스코드 : https://github.com/developerjhp/sha1-hulud-scanner

### Comment 45714

- Author: neo
- Created: 2025-10-31T23:34:03+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=45755027) 
- 요즘 나는 `npm` 명령어를 **Docker 컨테이너 안에서 실행**하도록 alias를 걸어둠  
  이렇게 하면 내 환경 변수를 노출하지 않고, 현재 디렉토리 외부 파일에도 접근하지 않으며, `.bashrc` 같은 설정 파일에도 접근하지 못하게 됨  
  참고: [Run tools inside Docker](https://ashishb.net/programming/run-tools-inside-docker/)
  - 그건 너무 과한 샌드박싱 같음. 어차피 `npm`은 바로 실행될 **임의 코드**를 다운로드하니까  
    대신 `pnpm`을 추천함. 기본적으로 lifecycle 스크립트를 실행하지 않고, 허용할 스크립트를 **화이트리스트**로 지정할 수 있음
  - post-install 스크립트를 악마화하는 건 **잘못된 안전 환상**을 줄 뿐임  
    진짜 보호를 원한다면 설치뿐 아니라 실행 전체를 샌드박스 안에서 돌려야 함  
    지금처럼 post-install만 막는 건 절반짜리 대책에 불과함. 공급망 공격은 점점 더 위험해지고 있음
  - 공격 벡터는 너무 많음. 악의적인 의도를 가진다면 인기 있는 **플러그인이나 LSP** 이름을 오타 스쿼팅해서 에디터 실행 시 자동으로 코드가 실행되게 만들 수 있음  
    `neovim`이나 `vscode`가 감염되면 이미 사용자 권한으로 충분히 위험한 일들을 할 수 있음
  - 나는 [sandbox-run](https://github.com/sandbox-utils/sandbox-run)을 사용함  
    단순 alias는 `node/npm`에는 통하지만, 다른 프로그램들에는 적용하기 어려움. 컨테이너에 필요한 리소스를 마운트해야 하니까
  - 그래도 결국 **악성 패키지**를 다운로드할 수 있지 않음? 의존성 자체가 감염되어 있을 수도 있음

- 예전부터 궁금했음. 왜 사람들은 아무렇지 않게 `npm`을 시스템에서 돌리는지  
  `make`처럼 **재현 가능한 빌드**에 익숙한 입장에서는, `npm`이 매번 다른 걸 다운로드하고 다른 결과를 내는 게 충격이었음  
  CSS 생성조차 npm 의존성으로 묶는 게 이상했음. 그래서 Docker 안에 npm 환경을 통째로 **고정(freeze)** 시켜봤지만, 결국 지는 싸움 같음
  - 요즘 모든 패키지 매니저가 그런 식으로 동작함. `maven`, `nuget`, `pip`, `npm` 모두 마찬가지임  
    과거처럼 배포판 패키지 매니저에 의존하면 지금 같은 **빠른 생태계**는 불가능했을 것임  
    다만 보안이 강화된 새 패키지 매니저들이 등장하고 있음. 이유를 이해하지 못한 채 수단만 비난하는 건 옳지 않음
  - 프론트엔드 개발은 마치 **“믿어줘 브로”식 와일드 웨스트** 같음. 브라우저 진화 과정상 어쩔 수 없이 덕트테이프처럼 이어붙인 느낌임
  - `npm`을 Docker로 고정했다면, 매번 의존성 업데이트 후 그 환경을 **검증**했는지 묻고 싶음  
    사실 `npm`과 `pnpm`은 이미 기본적으로 **lock 파일**로 의존성을 고정함
  - “`npm install thing`”이 너무 쉽고 싸서 생긴 문제임  
    많은 오픈소스가 **품질보다 이력서용 코드**로 채워지고, 결국 거대 기업의 광고 추적기나 지갑 앱 같은 걸 만드는 데 쓰임

- `npm install`은 단순히 패키지를 다운로드하는 게 아니라 **코드를 실행**함  
  `package.json`의 preinstall, install, postinstall 훅이 실제로 실행됨  
  합법적으로 설치 과정에서 임의 명령을 실행해야 할 이유가 뭘까?  
  관련 보고서: [PhantomRaven npm malware](https://www.koi.ai/blog/phantomraven-npm-malware-hidden-in-invisible-dependencies)  
  또 다른 사례: [Socket.dev 블로그](https://socket.dev/blog/10-npm-typosquatted-packages-deploy-credential-harvester)
  - 사실 이런 구조는 오래된 패키지 매니저(DEB, RPM)에도 있었음  
    예를 들어 리눅스 커널 패키지는 설치 후 **initramfs 재생성, GRUB 업데이트** 등을 위해 post-install 스크립트를 실행함  
    대부분의 DEB/RPM 패키지에 이런 스크립트가 포함되어 있음. 즉, 설계 자체의 문제임
  - 문제는 `npm`은 **아무나 패키지를 올릴 수 있다는 점**임  
    리눅스 배포판은 신뢰할 수 있는 **메인테이너 체계**가 있고, PGP 기반의 루트 트러스트를 직접 구축하기도 함  
    반면 `npm`, `pip`, `rubygems`, `cargo` 등은 사실상 “`curl | bash`”의 세련된 버전일 뿐임
  - 예를 들어 **Mediasoup** 프로젝트는 C++로 작성된 스트리밍 라이브러리인데, 설치 시 소스를 직접 컴파일함  
    유지보수 부담을 줄이기 위해 이런 post-install 빌드가 필요했음
  - **Swift Package Manager**도 `Package.swift` 파일을 실제로 실행함  
    다만 강하게 **샌드박싱**되어 있어서 악용은 어렵다고 들음  
    참고: [SwiftPM 문서](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/), [PackageDescription](https://developer.apple.com/documentation/packagedescription)
  - 참고로 `pnpm v10`은 기본적으로 모든 lifecycle 스크립트를 비활성화하고, 사용자가 직접 허용해야 함  
    [관련 논의](https://github.com/orgs/pnpm/discussions/8945)

- 최근 `npm` 공격들을 보면, 이제는 `npm`으로 개발하는 게 **안전한가** 싶음  
  React 프로젝트를 시작할 때마다 수백 개의 패키지가 깔리는데, 뭘 하는지도 모름  
  백엔드에서는 필요한 패키지만 명시적으로 설치하지만, 프론트엔드는 **취약점의 판도라 상자** 같음
  - 사실 모든 언어 생태계가 비슷함. 단지 `npm`이 제일 크고 뉴스에 많이 나올 뿐임
  - Rust의 `jj`를 설치했더니 470개, Python의 `wan2gp`는 211개 패키지가 깔렸음. 다 거기서 거기임
  - JavaScript 생태계는 구조적으로 공격에 취약함  
    `xz` 사건처럼, 각 의존성이 **무작위 개인**에게 달려 있고, 그들이 사회공학 공격에 당하지 않을 거라 믿어야 함
  - 의존성이 적을수록 좋음. **0개면 완벽**임. 그게 진짜 승리임
  - 참고로 `PyPI`도 안전하지 않음. GitHub Actions 해킹으로 **정상 패키지에 악성 코드가 삽입된 사례**도 있음

- Angular, Vue 같은 프레임워크로 개발할 때마다 불안함  
  `node_modules` 안의 수천 개 의존성을 보면 **재앙의 예고**처럼 느껴짐  
  오픈소스 개발자 한 명이 피싱당하면 바로 감염될 수도 있음  
  JavaScript 생태계는 **근본적으로 깨져 있음**. 오타 한 번으로 공급망 공격에 노출됨  
  NuGet이나 Maven도 가능은 하지만, 그쪽은 **표준 라이브러리**가 커서 의존성이 적고 통제감이 있음
  - Go는 패키지 이름 대신 **repo URL**을 사용해서 오타 스쿼팅을 줄임  
    완벽하진 않지만 그래도 한 단계 나음
  - Deno는 이런 문제를 해결함. **Node.js / npm의 구조적 문제**임

- 86,000회 다운로드 중 대부분은 실제 사용자가 아니라 **자동화된 스캐너나 봇**일 가능성이 큼  
  새 버전을 올리면 하루이틀 만에 수백 번 다운로드되지만, 실제 사람은 아닐 수도 있음  
  즉, 감염된 사용자는 거의 없을 수도 있음
  - 나도 라이브러리를 올렸을 때 초반엔 주당 300회, 이후엔 100회 정도 다운로드됐음  
    AI 챗봇이 **환각으로 만들어낸 패키지 이름**을 노린 공격도 많음. 단순한 통계 이상임
  - 혹은 누군가의 **좀비 CI**가 계속 다운로드하는 걸 수도 있음
  - 하지만 LLM이 만들어낸 가짜 패키지 이름을 노린 공격이라면, 실제로 많은 개발자가 감염됐을 수도 있음

- 더 자세한 공격 설명은 [BleepingComputer 기사](https://www.bleepingcomputer.com/news/security/phantomraven-attack-floods-npm-with-credential-stealing-packages/) 참고

- `npm install` 중 **HTTP URL**을 의존성으로 사용하는 패키지를 탐지하거나 필터링할 방법이 있을까 궁금함  
  요청자별로 다른 페이로드를 보낼 수 있어서 일반적인 스캐너로는 탐지가 어려움

- 취미 개발자로서 이런 **공급망 공격에 대비**하려면 어떻게 해야 할지 고민임  
  유명한 튜토리얼을 따라가며 의존성을 설치하다 보면, 어느새 보안에 무심해짐  
  내 홈랩에도 여러 서비스를 돌리는데, 혹시라도 봇이 침투할까 걱정임. 어디서부터 시작해야 할까?
  - 컨테이너나 VM 안에서 서비스를 분리해 돌리면 피해를 **격리**할 수 있음  
    완벽한 보장은 아니지만, 전체 서버가 뚫리는 것보단 훨씬 낫음
  - 모든 의존성을 **잠재적 보안 리스크**로 보고, 꼭 필요할 때만 사용해야 함  
    필요한 코드만 직접 복사해 쓰는 것도 좋은 학습이자 안전한 방법임
  - **인기 있고 1년 이상 된 릴리스**를 사용하는 게 안전함. 문제가 있었다면 이미 발견됐을 확률이 높음
  - FreeBSD처럼 시스템 패키지 매니저를 사용하는 OS도 있음  
    이런 구조라면 수백만 사용자가 직접 검증할 필요 없이 **배포판 수준에서 신뢰성 확보**가 가능함
  - 주간 다운로드 100만 회 이상, **의존성이 없는 패키지**를 선호함  
    예: [Hono](https://npmgraph.js.org/?q=hono), [Zod](https://npmgraph.js.org/?q=zod)  
    나는 최근 **Bun**으로 전환했는데, 기본적으로 DB 드라이버나 S3 클라이언트 같은 게 내장되어 있어 추가 다운로드가 줄어듦

- lifecycle 훅에서 의존성을 가져오는 구조는 언제든 **공격 전환점**이 될 수 있음  
  지금은 정상이라도, 나중에 소유자가 해킹당하거나 마음을 바꾸면 악성 코드로 바뀔 수 있음  
  이런 형태의 설치 훅은 결국 **지속 불가능한 설계**임
