8P by GN⁺ 6시간전 | ★ favorite | 댓글 2개
  • 널리 사용되는 axios HTTP 클라이언트의 두 악성 버전이 npm에 게시되어, 설치 시 원격 접근 트로이목마(RAT) 를 배포함
  • 공격자는 유지관리자 계정 자격 증명 탈취를 통해 GitHub Actions를 우회하고 수동으로 악성 패키지를 업로드함
  • 악성 버전은 가짜 의존성 plain-crypto-js@4.2.1 을 포함해, postinstall 스크립트로 RAT를 설치하고 흔적을 삭제함
  • RAT는 macOS, Windows, Linux를 모두 감염시키며, C2 서버(sfrclak.com:8000)와 통신해 추가 페이로드를 다운로드함
  • npm과 GitHub이 신속히 악성 버전을 제거했으나, 공급망 보안 강화와 자격 증명 보호의 중요성이 다시 부각됨

axios npm 공급망 공격 개요

  • 2026년 3월 31일, 널리 사용되는 axios HTTP 클라이언트 라이브러리의 두 악성 버전(axios@1.14.1, axios@0.30.4)이 npm에 게시됨
  • 공격자는 axios 주요 유지관리자의 npm 자격 증명을 탈취해 GitHub Actions CI/CD 파이프라인을 우회하고 수동으로 악성 패키지를 게시함
  • 두 버전 모두 plain-crypto-js@4.2.1 이라는 가짜 의존성을 삽입, 이 패키지는 postinstall 스크립트를 통해 원격 접근 트로이목마(RAT) 를 설치함
  • RAT는 macOS, Windows, Linux를 모두 대상으로 하며, C2(Command and Control) 서버(sfrclak.com:8000)와 통신해 2단계 페이로드를 내려받음
  • 설치 후 악성 코드와 흔적을 삭제하고 깨끗한 package.json으로 교체해 포렌식 탐지를 회피

공격 타임라인

  • 3월 30일 05:57 UTC: plain-crypto-js@4.2.0(정상 버전) 게시
  • 3월 30일 23:59 UTC: plain-crypto-js@4.2.1(악성 버전) 게시, postinstall 훅 추가
  • 3월 31일 00:21 UTC: axios@1.14.1 게시, 악성 의존성 삽입
  • 3월 31일 01:00 UTC: axios@0.30.4 게시, 동일한 악성 의존성 삽입
  • 3월 31일 03:15 UTC: npm이 두 악성 버전 삭제
  • 3월 31일 04:26 UTC: npm이 plain-crypto-js를 보안 홀더 스텁(0.0.1-security.0)으로 대체

axios 개요

  • axios는 JavaScript 생태계에서 가장 널리 사용되는 HTTP 클라이언트로, Node.js와 브라우저 모두에서 사용됨
  • 주간 다운로드 3억 회 이상으로, 단 한 번의 악성 릴리스도 광범위한 피해 가능성을 가짐
  • 일반 개발자는 npm install 시 악성 코드 설치를 인지하기 어려움

공격 단계

  • 1단계 — 유지관리자 계정 탈취

    • 공격자는 jasonsaayman npm 계정을 탈취하고 이메일을 ifstap@proton.me로 변경
    • 이후 1.x와 0.x 릴리스 브랜치 모두에 악성 빌드를 게시
    • 정상 릴리스는 GitHub Actions의 OIDC Trusted Publisher를 통해 게시되지만, axios@1.14.1은 수동 게시로 gitHead와 OIDC 서명이 없음
    • 공격자는 장기 유효한 npm 액세스 토큰을 이용한 것으로 추정됨
  • 2단계 — 악성 의존성 사전 배포

    • plain-crypto-js@4.2.1nrwise@proton.me 계정에서 게시됨
    • crypto-js위장하며 동일한 설명과 저장소 URL 사용
    • "postinstall": "node setup.js" 훅을 포함해 설치 시 자동 실행
    • 공격 후 package.mdpackage.json으로 교체해 증거 삭제 준비
  • 3단계 — axios에 의존성 삽입

    • plain-crypto-js@^4.2.1런타임 의존성으로 추가
    • 코드 내에서는 한 번도 import되지 않음 → 유령 의존성(phantom dependency)
    • npm install 시 자동 설치되어 postinstall 스크립트 실행

RAT 드로퍼(setup.js) 분석

  • 난독화 기법

    • 문자열을 배열 stq[]에 암호화 저장하고 _trans_1(XOR)과 _trans_2(Base64+역순)로 복호화
    • C2 URL은 http://sfrclak.com:8000/6202033
    • 복호화된 문자열에는 OS 식별자(win32, darwin), 파일 경로, 쉘 명령 등이 포함
  • 플랫폼별 페이로드

    • macOS

      • AppleScript를 /tmp에 작성 후 osascript로 실행
      • C2에서 RAT 바이너리를 받아 /Library/Caches/com.apple.act.mond에 저장 및 실행
      • 파일명은 Apple 시스템 데몬처럼 위장
    • Windows

      • PowerShell 경로 탐색 후 %PROGRAMDATA%\wt.exe로 복사
      • VBScript를 통해 C2에서 PowerShell RAT 다운로드 및 실행
      • 임시 파일(.vbs, .ps1)은 실행 후 삭제
    • Linux

      • curl/tmp/ld.py 다운로드 후 nohup python3으로 실행
      • /tmp/ld.py 파일이 남음
      • 세 플랫폼 모두 packages.npm.org/product0~2 POST 바디를 사용해 정상 npm 트래픽처럼 위장
  • 자기 삭제 및 은폐

    • setup.jspackage.json 삭제
    • package.mdpackage.json으로 교체해 정상 패키지로 위장
    • 이후 npm audit이나 수동 검토로는 탐지 불가
    • 단, node_modules/plain-crypto-js/ 존재 자체가 감염 증거

StepSecurity Harden-Runner를 통한 실행 검증

  • Harden-Runner는 GitHub Actions에서 네트워크·프로세스·파일 이벤트를 실시간 기록
  • axios@1.14.1 설치 시 두 번의 C2 연결(curl, nohup)이 감지됨
    • 첫 번째 연결은 npm install 시작 2초 후 발생
    • 두 번째 연결은 36초 후, 백그라운드 프로세스로 지속 실행
  • 프로세스 트리 분석 결과, nohup 프로세스가 PID 1(init) 에 고아 프로세스로 남아 지속 실행 확인
  • 파일 이벤트 로그에서 package.json이 두 번 덮어써짐
    • 첫 번째: 설치 중 악성 버전 작성
    • 두 번째: 36초 후 깨끗한 스텁으로 교체

침해 지표(IOC)

  • 악성 npm 패키지

    • axios@1.14.1 · shasum: 2553649f232204966871cea80a5d0d6adc700ca
    • axios@0.30.4 · shasum: d6f3f62fd3b9f5432f5782b62d8cfd5247d5ee71
    • plain-crypto-js@4.2.1 · shasum: 07d889e2dadce6f3910dcbc253317d28ca61c766
  • 네트워크

  • 파일 경로

    • macOS: /Library/Caches/com.apple.act.mond
    • Windows: %PROGRAMDATA%\wt.exe
    • Linux: /tmp/ld.py
  • 공격자 계정

    • jasonsaayman (탈취된 유지관리자)
    • nrwise (공격자 생성 계정)
  • 안전 버전

    • axios@1.14.0 (정상)

영향 확인 및 대응 절차

  • npm list axios 또는 package-lock.json에서 1.14.1 / 0.30.4 확인
  • node_modules/plain-crypto-js 존재 여부 확인
  • OS별 RAT 파일 존재 시 시스템 완전 감염으로 간주
  • CI/CD 로그에서 해당 버전 설치 이력 확인 후 모든 비밀키·토큰 교체 필요

복구 단계

  1. axios를 안전 버전(1.14.0 또는 0.30.3) 으로 고정
  2. plain-crypto-js 폴더 삭제 후 npm install --ignore-scripts 재설치
  3. RAT 흔적 발견 시 시스템 재구축
  4. 모든 자격 증명(AWS, SSH, CI/CD 등) 회전
  5. CI/CD 파이프라인 점검 및 비밀키 교체
  6. 자동 빌드 시 --ignore-scripts 옵션 사용
  7. C2 도메인/IP를 방화벽 또는 /etc/hosts로 차단

StepSecurity Enterprise 기능

  • Harden-Runner

    • GitHub Actions에서 네트워크 송신 화이트리스트 적용
    • 비정상 트래픽 차단 및 로그 기록
    • sfrclak.com:8000 연결을 사전 차단 가능
  • Dev Machine Guard

    • 개발자 PC에서 설치된 npm 패키지 실시간 모니터링
    • axios@1.14.1, 0.30.4 설치된 장비 즉시 탐지
  • npm Package Cooldown Check

    • 새로 게시된 패키지에 일시적 설치 차단 기간 적용
    • plain-crypto-js@4.2.1과 같은 신속한 악성 게시 탐지 가능
  • Compromised Updates Check

    • 실시간 악성 패키지 DB 기반으로 PR 병합 차단
    • axios@1.14.1, plain-crypto-js@4.2.1 즉시 등록
  • Package Search

    • 조직 전체 PR 및 저장소에서 특정 패키지 도입 위치 검색
    • 영향 범위(레포지토리, 팀, PR) 즉시 파악
  • AI Package Analyst

    • npm 레지스트리 실시간 모니터링 및 행동 기반 악성 탐지
    • 두 악성 버전 모두 게시 후 수분 내 탐지
  • Threat Center Alert

    • 공격 요약, IOC, 대응 절차 포함한 위협 인텔 알림 제공
    • SIEM 연동을 통한 실시간 가시성 확보

감사

  • axios 유지관리자 및 커뮤니티가 GitHub issue #10604를 통해 신속히 대응
  • GitHub은 탈취된 계정을 정지하고 npm은 악성 버전 삭제 및 보안 홀더 적용
  • 유지관리자·GitHub·npm 간 협력 대응으로 전 세계 개발자 피해 최소화

이정도 규모 패키지가 털릴거라고 생각해본적은 없는데, axios는 상상 이상이네요.

Hacker News 의견들
  • npm, bun, pnpm, uv 모두 패키지 최소 릴리스 기간 설정을 지원하게 되었음
    나는 ~/.npmrcignore-scripts=true를 추가해 두었는데, 이 설정만으로도 취약점을 완화할 수 있었음
    bun과 pnpm은 기본적으로 lifecycle 스크립트를 실행하지 않음
    각 패키지 매니저별 설정 예시는 다음과 같음:

    • uv: exclude-newer = "7 days"
    • npm: min-release-age=7
    • pnpm: minimum-release-age=10080
    • bun: minimumReleaseAge = 604800
      흥미롭게도 각자 시간 단위가 다름
      LLM 에이전트를 사용하는 경우, 이 설정으로 인해 실패가 발생할 수 있으므로 AGENTS.mdCLAUDE.md에 관련 지침을 추가해야 함
    • “시간 단위가 다 다르다니”라는 말에 “첫날 자바스크립트 써보는 사람인가?”라는 농담이 달림
    • pnpm이 이 기능을 가장 먼저 도입했음. npm은 11.10.0 이상에서만 지원하며, 2026년 2월 11일 릴리스부터 가능함
    • 설정 파일에서 단위를 명시하는 게 좋다는 의견도 있었음. 예를 들어 timeout 대신 timeoutMinutes처럼
    • npm은 주석을 지원하지 않을 수도 있음. min-release-age=7 # days가 실제로 적용되지 않을 가능성이 있음
    • yarn berry의 경우 ~/.yarnrc.ymlnpmMinimalAgeGate: "3d"로 설정 가능함
  • Axios가 공급망 공격에 노출되었다는 소식에 충격을 받았음
    Axios 내부에는 악성 코드가 없었지만, plain-crypto-js@4.2.1이라는 가짜 의존성을 주입해 RAT(원격 접근 트로이목마) 를 설치하는 postinstall 스크립트를 실행했음
    pnpm이나 bun처럼 postinstall 스크립트를 수동 승인해야 하는 사용자에게는 다행인 소식임

    • Node.js에 fetch가 기본 포함된 것은 v18 이후이며, 안정화는 v21부터였음. Axios는 훨씬 오래전부터 존재했고, 많은 프레임워크와 튜토리얼에 포함되어 있어 여전히 널리 사용됨
    • “pnpm/bun 사용자는 안전하다”는 말에, “그럼 예전 버전에서는 승인했을 가능성이 높지 않나?”라는 의문이 제기됨
    • pnpm이 하위 의존성의 postinstall도 차단하는지 궁금하다는 질문이 나옴
  • 패키지 매니저는 실패한 실험이라는 주장
    SQLite처럼 단일 .c 파일로 구성된 고품질 C 라이브러리들이 있는데, 이런 방식이면 transitive dependency 문제를 피할 수 있음
    대부분의 공격 표면은 이런 간접 의존성에서 발생함

    • 패키지 매니저는 이제 언어 채택의 필수 요소가 되었음. 문제는 품질 관리 부재와 보상 구조
      OpenSSL조차 코드 품질 문제로 재작성 중이며, JS는 표준 라이브러리 확장이 어려워 폴리필 난립이 심함
    • “조금 더 노력해야 하는 해결책은 커뮤니티가 받아들이지 않을 것”이라는 의견도 있었음
      대신 npm 같은 저장소에서 품질 기준을 강화하고 책임 있는 유지보수자만 등록하도록 해야 한다는 제안이 나옴
    • 웹 개발에서는 공격 표면이 훨씬 넓음. 수동 복사 방식은 업데이트 추적이 어렵기 때문에 패키지 매니저의 알림 기능이 여전히 유용함
    • NPM만 유독 공급망 공격이 심각한 생태계라는 지적도 있었음
    • Rust는 C보다 안전하고, 대부분의 crate는 고품질이라며 C 라이브러리 중심 주장은 과장이라는 반박도 있었음
  • 오늘은 어떤 npm 패키지가 털렸을까?”라는 자조 섞인 인사로 하루를 시작한다는 농담이 나옴

  • Linux 사용자라면 bwrap을 써서 npm, pip, cargo, gradle 등 모든 빌드 로직을 샌드박스화하길 권장함
    bwrap은 Docker처럼 격리 환경을 제공하지만 이미지가 필요 없음. Flatpak도 같은 기술 기반임
    서버 배포 시에는 컨테이너 하드닝이 중요하며, CI/CD 환경을 불신 영역으로 다루는 것이 핵심임
    AI 실행 시에도 같은 샌드박스를 쓰면 좋음

    • 하지만 이 방식은 postinstall 공격에만 유효하다는 지적이 있었음. 코드 내부에서 require만 해도 실행될 수 있음
    • Docker 기반의 개인용 샌드박스 amazing-sandbox를 만든 사람도 있었음
    • drop이라는 bwrap보다 상위 수준의 도구도 추천됨
    • firejail이 더 유연한 보안 샌드박스라는 의견도 있었음
    • SSH 소켓 포워딩은 개인 키 접근을 허용하므로 보안 이점이 없고, 비밀번호 보호 키를 쓰는 게 낫다는 지적도 있었음
  • 의존성 문제를 반복해서 보며 Rust 생태계도 언젠가 비슷한 일을 겪을까 걱정
    표준 라이브러리를 비대하게 만드는 건 어렵지만, 신뢰할 수 있는 패키지 품질 보장 체계가 필요함

    • 대형 패키지(Axios 등)는 MFA 승인자 여러 명이 출판을 승인해야 한다는 제안이 있었음
    • 검증된 의존성을 상업적으로 제공하는 서비스가 생길 것이라는 예측도 있었음
    • 또 다른 제안으로는, 의존성의 테스트를 직접 복사해 코드베이스에 포함하고, 자체 코드 리뷰 절차를 거치는 방식이 있었음
      AI 덕분에 이런 추가 작업이 가능해졌고, 사실 예전부터 이렇게 했어야 했다는 반성도 있었음
  • NPM 공급망 공격 노출을 최소화하기 위한 핵심 수칙

    • Yarn의 zero-installs 모드 사용
    • postinstall 스크립트 비활성화 또는 실행 전 확인
    • 개발 중 제3자 코드가 실행될 경우 VM/컨테이너 내에서만 실행
    • 패키지 추가 시 인기도는 플러스, 최근 커밋은 마이너스 요인으로 보고 코드와 변경 이력을 직접 검토
    • 의존성 트리 전체를 검증하고, 새로운 개발자가 추가될 때마다 신뢰 여부를 재평가해야 함
    • lockfile과 --frozen-lockfile 옵션만으로도 충분히 보호된다는 의견도 있었음
    • “나는 그냥 문제 많은 스택을 피한다”는 단순하지만 강력한 철학을 가진 사람도 있었음
  • “이런 공격을 완전히 피하려면 어떻게 해야 하나?”라는 질문에,
    Qubes OS로 전환해 비밀번호 관리자와 빌드 환경을 완전히 분리하고 싶다는 의견이 나옴

    • 어떤 팀은 NPM 관련 작업을 Apple 컨테이너 안에서만 수행하고, Python과 Rust도 같은 방식으로 옮길 계획임
      이는 마치 화학 실험실의 보호장비(PPE) 처럼, 개발 환경에서도 격리와 보호가 필요하다는 비유를 들었음
      pip도 이미 virtualenv 외부 설치를 막기 시작했으며, 앞으로는 패키지 매니저가 샌드박스 외부 실행을 거부하는 옵션을 제공하길 기대함
    • 어떤 사람은 아예 Node.js/npm을 시스템에 설치하지 않는다고 함. 지금까지 꼭 필요한 경우를 본 적이 없다고 함
  • pnpm과 bun은 이제 기본적으로 postinstall 스크립트를 무시하지만, npm은 여전히 실행함
    ~/.npmrcignore-scripts=true를 설정하는 것이 좋음
    npm은 여전히 주간 8천만 다운로드를 기록함

  • 이번 사건의 자격 증명 유출은 LiteLLM 이전 사건에서 비롯된 것일 가능성이 높다고 추측함
    Python이나 Node.js 사용이 불안하지만, 사실 보편적인 문제라고 느낌

    • 최소 릴리스 기간 설정이 도움이 되긴 하지만 여전히 의존성 업데이트가 두렵다는 의견이 많음
    • LiteLLM보다 Trivy 사건이 근본 원인이라는 주장도 있었음
    • 루트리스 임시 컨테이너에서 실행해 피해 범위를 제한하는 방법도 제안됨