공지사항

Security Insights & Trends

[Npm 공급망 공격] 탈취된 패키지 12개 릴리스, PolinRider 로더 재등장

Raoul16 2026. 11. 6. 12:30

 

[Npm 공급망 공격]탈취된 패키지 12개 릴리스, PolinRider 로더 재등장

 

안녕하세요,

그로메트릭 입니다. 🐸

 

탈취된 npm 패키지에서 PolinRider 로더가 다시 발견되었습니다.

소나타입 리서치 랩스는 2026년 11월 4일, 탈취된 npm 패키지 10개의 릴리스 12건에서 숨겨진 런타임 로더를 확인했습니다.

 

영향받은 패키지는 Redux 연동, health API, 브라우저 활동 추적, 유틸리티 등 서로 무관한 기능을 제공합니다. 이 로더는 기존 PolinRider 활동에서 보고된 것과 동일한 하드코딩 이더리움 지갑이 보낸 트랜잭션을 조회해 페이로드 서버의 IP 주소를 알아낸 뒤, XOR로 인코딩된 JavaScript를 내려받아 실행하도록 설계되어 있습니다. 패키지 매니페스트에는 install·postinstall 훅이 없어, install 스크립트를 비활성화하는 것만으로는 이 코드를 막을 수 없습니다.

 

영향받은 코드는 패키지가 로드될 때, 또는 server-hemera-mongo처럼 번들된 애플리케이션이 시작될 때 실행될 수 있습니다. 또한 이더리움 지갑이 공격 서버 주소를 알려 주는 데드드롭 리졸버 역할을 하므로, 운영자는 악성 패키지를 새로 배포하지 않고도 새 서버 주소를 알릴 수 있고, 서버 하나를 차단해도 로더가 대체 서버를 찾는 능력은 여전히 남습니다.

 

개발 환경, CI 워커, 빌드 호스트, 배포된 서비스에서 해당 코드가 실행되었는지가 핵심 쟁점이며, 실행되었다면 해당 호스트는 침해된 것으로 간주해야 합니다. 아래에서 영향받는 패키지와 탐지 지표, 대응 절차를 소나타입 블로그 원문 순서대로 정리해보았습니다.

 

*아래 글은 소나타입(Sonatype)의 공식 블로그 글을 번역하여 작성되었습니다.


탈취된 NPM 패키지 12개 릴리스, PolinRider 로더 재등장

요약 

  • 소나타입 리서치 랩스는 탈취된 npm 패키지 10개의 릴리스 12건에서 숨겨진 런타임 로더를 확인했습니다.
  • 12개 릴리스 모두 이전에 보고된 PolinRider 활동에서 확인된 것과 동일한 하드코딩 지갑이 보낸 이더리움 트랜잭션에서 페이로드 서버 IP 주소를 디코딩한 뒤, JavaScript를 가져와 XOR 디코딩하고 실행하도록 설계된 로더를 포함하고 있습니다. 6개는 로더를 eval(atob(...)) 안에 숨기고, 나머지 6개는 문자 순열(character-permutation) 부트스트랩과 JavaScript 함수 생성자를 사용합니다.
  • 매니페스트에는 consumer 측 install 또는 postinstall 훅이 없습니다. 이 코드는 영향받은 엔트리포인트가 로드될 때, 또는 server-hemera-mongo의 경우 번들된 애플리케이션이 시작될 때 실행될 수 있습니다.
  • 직접 의존성과 전이 의존성, 락파일, 사내 npm 미러, 빌드 기록을 확인하십시오. 영향받은 코드가 실행되었다면 해당 호스트를 침해된 것으로 간주하고, 프로세스, 네트워크 활동, 접근 가능했던 자격 증명을 조사해야 합니다.

npm 릴리스 12건에서 발견된 숨겨진 로더 🔎

2026년 11월 4일, 소나타입 리서치 랩스는 npm 패키지의 악성 런타임 로더를 조사해 10개 탈취 패키지에 걸친 12개 영향 릴리스를 확인했습니다. 분석한 릴리스에는 동일한 이더리움 발신 지갑과 페이로드 수신 인프라를 사용하는 두 가지 로더 변형이 들어 있습니다. 소나타입은 이 건을 sonatype-2026-010515로 추적하고 있습니다.

 

영향받은 패키지는 Redux 연동과 헬스 API부터 브라우저 활동 추적, 유틸리티, 서버 예제에 이르기까지 서로 무관한 개발자 기능을 지원합니다. 이들의 공통점은 본래 정상적인 목적을 수행하는 코드 안에 은닉된 동작이 심겨 있다는 것입니다.

 

이 릴리스들은 해당 동작을 활성화하기 위해 consumer 측 설치 스크립트에 의존하지 않습니다. 애플리케이션이 영향받은 패키지를 로드할 때 해당 코드에 도달할 수 있으며, server-hemera-mongo 릴리스는 서비스가 시작될 때 실행됩니다.

패키지 영향받은 릴리즈 관찰된 동작
@adminide-stack/client-redux 0.0.16-18 Base64로 은닉된 이더리움 기반 로더
@adminide-stack/health-api-server 0.0.18-alpha.465 Base64로 은닉된 이더리움 기반 로더
@adminide-stack/user-activity-browser 1.1.3-alpha.114 Base64로 은닉된 이더리움 기반 로더
@adminide-stack/utils 0.0.16-129, 0.0.16-130 0.0.16-129는 Base64로 은닉된 이더리움 기반 로더, 0.0.16-130은 문자 순열 부트스트랩
server-hemera-mongo 0.0.15, 0.0.16 Base64로 은닉된 이더리움 기반 로더
cdeops 13.5.31 문자 순열 부트스트랩으로 은닉된 이더리움 기반 로더
@adminide-stack/forms-core 5.1.9-alpha.3 문자 순열 부트스트랩으로 은닉된 이더리움 기반 로더
@adminide-stack/yantra-help-browser 12.0.194 문자 순열 부트스트랩으로 은닉된 이더리움 기반 로더
@cdmbase/wiki-browser 12.0.18-alpha.53 문자 순열 부트스트랩으로 은닉된 이더리움 기반 로더
@pubngo-stack/help-browser 12.0.23-alpha.3 문자 순열 부트스트랩으로 은닉된 이더리움 기반 로더

** 관련 영상은 sonatype 공식 블로그 본문을 참고하세요

공통 로더는 이더리움 트랜잭션으로 원격 코드를 찾는다 🪪

12개 릴리스 모두 공개 이더리움 RPC 엔드포인트(Blockscout API를 대체 경로로 사용)를 조회해, 하드코딩된 지갑 주소( 0x1eef0849eda15d87f64e51c73c8442cf3c8bcef3)이 보낸 트랜잭션을 찾도록 설계된 로더를 포함하고 있습니다. 로더는 트랜잭션 수신 주소의 처음 4바이트를 기본 IPv4 주소로, 이어지는 4바이트를 보조 주소로 해석합니다. 그리고 기본 주소에서 TCP 포트 443을 통해 평문 HTTP로, /0x/cls와 /0x/ls 경로를 사용해 XOR로 인코딩된 JavaScript를 가져옵니다.

 

응답을 XOR 디코딩한 뒤, 로더는 /0x/cls에서 받은 JavaScript를 애플리케이션 프로세스의 eval 함수에 전달하는 동시에, 표준 입력 파이프를 통해 분리(detached)된 node -i 프로세스에도 전달합니다. /0x/ls에서 받은 JavaScript는 분리된 node -i 프로세스에만 전달됩니다. 이 자식 프로세스들은 참조가 해제(unreferenced)되어 있어 애플리케이션 프로세스와 별개로 계속 실행될 수 있습니다.

 

이더리움 지갑은 데드드롭 리졸버(dead-drop resolver) 역할을 합니다. 운영자는 악성 패키지를 갱신하지 않고도 다른 트랜잭션을 보내 새 페이로드 서버 IP 주소를 알릴 수 있습니다. 따라서 개별 서버를 차단하더라도 로더가 대체 서버를 찾는 능력은 무력화되지 않습니다.

6개 릴리스는 다른 부트스트랩으로 이더리움 로더를 은닉한다 😶‍🌫️

6개 릴리스는 이더리움 기반 로더를 복원해 실행하도록 설계된 문자 순열 부트스트랩을 사용합니다. 해당 릴리스는 @adminide-stack/utils@0.0.16-130, cdeops@13.5.31, @adminide-stack/forms-core@5.1.9-alpha.3, @adminide-stack/yantra-help-browser@12.0.194, @cdmbase/wiki-browser@12.0.18-alpha.53, @pubngo-stack/help-browser@12.0.23-alpha.3입니다. 이 6개 릴리스에서 복원한 로더 본문은 바이트 단위로 동일합니다. Base64로 감싼 6개 릴리스의 로더와는 다르지만, 동일한 이더리움 지갑, 수신 경로, XOR 키를 사용합니다.

 

  • utils@0.0.16-130에서는 패키지의 로거 코드 뒤에 난독화된 루틴이 이어집니다. 이 루틴은 내장된 blob을 순열·디코딩하고, JavaScript Function 생성자를 간접적으로 획득한 뒤 디코딩된 내용을 실행합니다.
  • cdeops@13.5.31에서는 client.js에서 모듈이 global.require(‘cdeops/client’)로 정상 위임되기 전에 난독화된 부트스트랩이 나타납니다. 이 부트스트랩은 내장된 내용을 디코딩해 동적으로 실행한 다음, 예상되는 client 위임을 이어서 수행합니다. 이 은닉 실행은 해당 shim의 문서화된 목적과는 별개입니다.

공유 지갑과 C2 인프라가 해당 릴리스를 PolinRider와 연결한다 🔗

소나타입 리서치 랩스는 12개 릴리스의 로더를 독립적으로 디코딩해, 모두에서 동일한 하드코딩 이더리움 발신 지갑 0x1eef0849eda15d87f64e51c73c8442cf3c8bcef3을 확인했습니다. 이 지갑은 GitGuardian의 2026년 10월 27일 보고서 “GhostAction Crosses Paths With PolinRider: Inside the October Escalation”에 등장하며, 침해된 GitHub 저장소에서 발견된 PolinRider 로더가 사용한 이더리움 리졸버로 식별되었습니다. 해당 이전 로더들 역시 포트 443의 /0x/cls와 /0x/ls에서 단계별 페이로드를 가져왔으며, 이는 이번 npm 샘플의 수신 경로 및 포트와 일치합니다.

 

이러한 연결 고리는 페이로드 서버 인프라로도 이어집니다. Greenflagged는 이번 npm 릴리스를 분석하면서 해당 지갑의 트랜잭션을 디코딩했고, 11월 3일 관찰 시점에 공지된 C2 주소로 95.135.181.151을 보고했습니다. GitGuardian은 이미 PolinRider 지속성(persistence) 스크립트의 대체 C2로 포트 443의 동일한 IP를 기재한 바 있습니다. 지갑의 정확한 일치, 서버 인프라의 중첩, 일치하는 단계별 수신 경로는 이번 악성 npm 릴리스를 기존에 문서화된 PolinRider 활동과 연결하는 근거가 됩니다. 이 연결은 난독화 방식의 유사성에만 의존하지 않으며, PolinRider와 GhostAction이 같은 운영자를 공유한다는 점을 확정하지는 않습니다.

악성 코드는 설치 훅을 기다리지 않는다 😈

패키지 매니페스트에는 consumer 측 install 또는 postinstall 훅이 없습니다. 실행은 런타임이 주입된 JavaScript 파일에 도달할 때 일어납니다. 예를 들어 require(‘cdeops/client’)는 영향받은 client.js를 선택하는 반면, 패키지의 ES 모듈 import는 별도의 client.mjs 파일로 매핑됩니다. server-hemera-mongo에서는 start 스크립트가 참조하는 애플리케이션 번들인 dist/main.js에 주입이 있습니다.

 

노출 여부는 의존성 기록과 실행 증거를 상호 대조해 평가하십시오. 락파일은 해석된 의존성 버전을 알려 주고, 캐시된 아티팩트와 다운로드 로그는 패키지 획득 경로를 추적하는 데 도움이 됩니다. 빌드 명령, 애플리케이션 설정, 프로세스 텔레메트리, 런타임 로그는 주입된 JavaScript 파일이 실행되었는지 판단하는 데 도움이 됩니다.

 

npm install 스크립트를 비활성화하는 것으로는 모듈 로딩이나 애플리케이션 시작 중에 실행되는 코드를 막을 수 없습니다. 핵심 질문은 영향받은 패키지 코드가 개발자 환경, CI 워커, 빌드 호스트, 배포된 서비스에서 실행되었는지 여부입니다.

지금 팀이 해야 할 일 ✅

조직은 영향받은 시스템을 식별하고 추가 노출을 제한하기 위해 다음 단계를 수행해야 합니다.

  1. 영향받은 버전 찾기: 직접·전이 의존성, package-lock.json, yarn.lock, pnpm-lock.yaml, 사내 npm 미러, 캐시, 빌드 기록, 아티팩트 메타데이터를 확인하십시오.
  2. 저장소 보호 수준 평가: 저장소 관리자나 의존성 방화벽이 이 릴리스들을 개발자 또는 빌드 시스템에 도달하기 전에 차단했는지 판단하십시오. 다운로드 및 정책 로그를 검토해 전달된 사례가 있는지 확인하고, 해당 8개 버전이 향후 사용되지 않도록 차단되어 있는지 확인하십시오. (원문 표기: 본문에서는 영향 릴리스를 12건으로 설명하나 이 항목에서는 “all eight versions”로 기재되어 있어, 원문 자체의 수치 불일치가 있습니다. 원문 표기를 그대로 옮겼습니다.)
  3. 영향받은 의존성 제거: 프로젝트의 영향받은 버전을 교체하고, 사내 미러와 캐시가 해당 버전을 제공하지 않도록 하십시오. 조사에 필요한 사본은 보존하고, 독립적으로 안전함이 검증된 대체 버전을 사용하십시오.
  4. 코드 실행 여부 판단: 애플리케이션이 어느 엔트리포인트를 로드했는지, 런타임이 주입된 코드를 실행할 수 있었는지 확인하십시오. 이를 프로세스 및 네트워크 활동과 대조하십시오. 락파일에 항목이 있다고 해서 악성 코드가 실행되었다는 증거는 아니며, import 오류가 났다고 해서 실행되지 않았다는 증거도 아닙니다. 주입된 코드가 실행된 뒤에 import가 실패했을 수 있습니다.
  5. 영향받은 호스트 조사: 코드가 실행되었다면 해당 호스트를 침해된 것으로 간주하십시오. 다른 Node 프로세스가 표준 입력 파이프와 함께 실행한 분리된 node -i 프로세스, 그리고 /0x/cls 또는 /0x/ls 경로를 사용해 TCP 포트 443으로 가는 평문 HTTP 요청을 찾으십시오. 페이로드 요청에는 Sec-V 헤더가 포함되며, Base64로 감싼 6개 릴리스에서는 A10-npm_inject, utils@0.0.16-130에서는 A10-II, cdeops@13.5.31에서는 A10-E 값이 들어 있습니다. 이 지표를 프로세스 계보, 타임스탬프, 의존성 기록과 함께 사용해 영향받은 시스템을 식별하십시오.
  6. 접근 권한 평가 및 자격 증명 교체: 영향받은 프로세스가 사용할 수 있었던 자격 증명과 시스템을 파악하십시오. 노출 가능성이 있는 자격 증명(배포 자격 증명, 소스 저장소 접근 권한, 클라우드 키, CI 시크릿 등)은 교체하십시오.

운영자는 npm 릴리스를 추가로 배포하지 않고도 원격에서 제공하는 JavaScript를 바꿀 수 있습니다. 사용 가능한 프로세스, 네트워크, 빌드, 런타임 기록으로 영향받은 시스템이 무엇을 실행했는지, 해당 프로세스가 어떤 자격 증명과 리소스에 접근할 수 있었는지 판단하십시오.

 

자세한 내용과 대응 가이드는 Sonatype Security Event 및 Sonatype Guide 리서치를 참고하십시오.


출처

소나타입 블로그 : PolinRider Loader Reappears in Hijacked npm Packages

 

PolinRider Loader Reappears in Hijacked npm Packages

Hijacked npm packages contain PolinRider loaders that use Ethereum transactions to locate and execute remote payloads. Learn how to identify exposure.

www.sonatype.com