사건 · 자체 전파 npm 악성코드
Shai-Hulud npm 웜: 개발자 Mac에서 한 일과 Kilobat에 표시되었을 내용
2025년 9월 npm을 통해 웜이 확산되었습니다. 감염된 각 패키지는 개발자나 CI 시스템의 토큰을 훔치고 발견한 npm 토큰으로 해당 관리자의 다른 패키지에 감염된 버전을 게시했으며 GitHub를 통해 비밀값을 유출했습니다. 11월의 두 번째 대규모 공격은 설치가 완료되기도 전에 실행되었습니다.
요약
- 보호: 도움이 되지 않음
Mac에서 차단할 것은 없었습니다. 트래픽은 npm, GitHub 및 클라우드 API로 향했으며 webhook.site는 개발자 기기가 아닌 GitHub 러너에서 호출되었습니다.
- 에이전트 보호: 도움이 되지 않음
요청이 GitHub Actions 러너에서 왔으므로 webhook.site 규칙은 적용되지 않았습니다. 다른 목적지는 잘 알려진 호스트 또는 로컬 주소였습니다.
- 에이전트 네트워크 로그: 제한적
에이전트가 실행한 설치의 경우 하나의 설치 아래에 TruffleHog, GitHub API를 호출하는 curl,
npm publish및 처음 접속한 클라우드 호스트가 표시되었을 것입니다.
발생한 일
GitHub에 따르면 2025년 9월 14일 공격을 통보받았습니다. Socket과 StepSecurity는 처음에 @ctrl/tinycolor 4.1.1 및 4.1.2와 약 40개의 다른 패키지를 보고했으며, 일주일 내 CrowdStrike가 게시한 패키지를 포함해 500개를 넘었습니다. 감염된 각 버전은 macOS와 Linux에서만 postinstall 훅을 통해 3.6 MB의 bundle.js를 실행했습니다.
스크립트는 환경을 덤프하고 GitHub 릴리스에서 오픈 소스 비밀값 스캐너 TruffleHog를 다운로드해 전체 파일 시스템에서 실행했으며 클라우드 기기에 자격 증명을 제공하는 클라우드 메타데이터 엔드포인트에 접근을 시도했습니다. AWS 또는 Google Cloud 자격 증명이 있으면 해당 계정의 비밀 관리자에서 비밀값을 나열하고 읽었습니다.
~/.npmrc 또는 환경에서 npm 토큰을 찾으면 npm의 whoami 엔드포인트로 토큰을 확인하고 관리자의 다른 패키지를 검색한 다음 각각에 자신을 추가하고 패치 버전을 올려 게시했습니다. 이것이 웜인 이유입니다. 개발자 한 명의 토큰으로 그가 게시할 수 있는 모든 패키지를 감염시켰습니다.
GitHub 토큰이 있으면 수집한 데이터를 이중 base64 data.json로 담은 Shai-Hulud라는 공개 저장소를 만들고, 토큰이 접근할 수 있는 저장소에 워크플로 .github/workflows/shai-hulud-workflow.yml가 포함된 shai-hulud 브랜치를 푸시했으며, 경우에 따라 비공개 조직 저장소를 “Shai-Hulud Migration”이 표시된 공개 저장소로 복사했습니다. 해당 워크플로는 GitHub에서 실행되어 저장소 비밀값을 webhook.site 주소로 전송했습니다. Wiz에 따르면 웹훅은 과도한 사용으로 비활성화되었지만 워크플로는 비밀값을 로그에도 출력했습니다.
11월 21일부터 23일 사이 업로드된 두 번째 공격은 새 파일 setup_bun.js 및 bun_environment.js를 사용하고 preinstall 단계에서 실행되었으며 설치가 느려 보이지 않도록 개발자 기기에서 백그라운드로 계속 실행되었습니다. 설명에 Shai-Hulud가 들어간 저장소에 비밀값을 게시했는데 Wiz 집계로 25,000개가 넘었으며, 감염된 기기를 SHA1HULUD라는 GitHub Actions 자체 호스팅 러너로 등록해 재접근 경로를 만들었습니다.
타임라인
- 2025년 9월 14일
GitHub에 npm 패키지의 자기 복제 웜이 통보됩니다(GitHub).
- 2025년 9월 15일
Socket과 StepSecurity가
@ctrl/tinycolor및 40개 이상의 패키지를 보고합니다. - 2025년 9월 16일
Socket이 CrowdStrike 패키지를 포함해 영향을 받은 패키지 500개에 가까운 수를 집계합니다. Aikido 등은 확산을 추적합니다.
- 2025년 9월 22일
GitHub가 500개 이상의 패키지를 제거하고 npm에서 웜 지표가 포함된 업로드를 차단하도록 한 뒤 로컬 게시 시 2FA 필수, 7일 세분화 토큰 및 신뢰할 수 있는 게시를 발표합니다.
- 2025년 9월 23일
CISA 경고는 개발자 자격 증명을 교체하고 webhook.site로 나가는 연결을 차단하도록 권고합니다.
- 2025년 11월 21~23일
두 번째 공격의 패키지가 npm에 업로드됩니다(Wiz, Aikido).
- 2025년 11월 24~26일
유출된 비밀값을 담은 저장소 수천 개가 나타나고 GitHub는 11월 26일 이를 제거하고 유출 토큰을 취소하기 시작합니다.
네트워크에서 보인 모습
개발자의 Mac에서 거의 모든 연결은 개발자가 매일 사용하는 호스트로 향했습니다. registry.npmjs.org에서 감염된 패키지를 가져온 다음 /-/whoami 및 /v1/search를 호출하고 관리자의 다른 패키지를 다운로드해 새 버전을 게시했습니다. 설치 도중 npm에서 업로드가 발생한 것입니다. api.github.com에서는 계정을 읽고 Shai-Hulud 저장소를 생성하고 브랜치와 워크플로 파일을 푸시했습니다. StepSecurity가 난독화를 해제한 코드에는 백도어 모듈이 curl로 이를 수행하는 모습이 나옵니다. TruffleHog는 GitHub 릴리스 다운로드에서 가져왔습니다.
클라우드 자격 증명이 있으면 AWS Secrets Manager와 Google Secret Manager API에도 접속했습니다. 클라우드 기기에서만 응답하는 메타데이터 주소 169[.]254[.]169[.]254 및 metadata[.]google[.]internal에도 접근을 시도했습니다. 프로세스 트리는 npm에서 셸을 거쳐 node bundle.js로 이어졌고, 이 프로세스가 curl를 실행하는 bash 스크립트 trufflehog와 npm publish를 시작했습니다.
유일하게 특이한 호스트인 webhook[.]site에는 Mac에서 전혀 접속하지 않았습니다. 웜이 GitHub에 푸시한 워크플로에 포함되어 있었으므로 워크플로 실행 시 GitHub 호스팅 러너에서 요청이 발생했습니다.
침해 지표
출처에 기록된 그대로입니다. 도메인은 링크가 아닌 텍스트이며 출처가 무해화한 경우 그대로 무해화했습니다([.]). 이 인프라는 대부분 오래전에 사라졌습니다. 지표는 현재 캠페인을 차단하기보다 오래된 로그, 파일 및 계정을 확인하는 데 도움이 됩니다.
| 지표 | 유형 | 표시 위치 | 참고 |
|---|---|---|---|
webhook[.]site/bb8ca5f6-4175-45d2-b042-fc9ebb8170b7 | URL | GitHub Actions 러너 | 삽입된 워크플로가 각 저장소의 비밀값을 전송한 곳입니다. 플랫폼에서 비활성화했습니다. |
api.github.com | 호스트(정상) | 내 Mac | npm 설치 중 이루어진 /user 및 /user/repos 호출과 브랜치 및 워크플로 파일 푸시. |
registry.npmjs.org/-/whoami, /v1/search?text=maintainer:… | npm API 호출 | 내 Mac | 토큰 확인과 관리자의 패키지 검색 후 npm publish 실행. |
secretsmanager.*.amazonaws.com, secretmanager.googleapis.com | 클라우드 API | Mac, CI | 클라우드 자격 증명이 있는 경우에만 해당합니다. |
46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09 | bundle.js의 SHA-256 | node_modules | 9월 페이로드. |
shai-hulud branch, .github/workflows/shai-hulud-workflow.yml | 브랜치, 파일 | GitHub 저장소 | 비밀값을 웹훅과 로그로 보낸 워크플로. |
Shai-Hulud repository with data.json; “Shai-Hulud Migration” | GitHub 저장소 | GitHub 계정 | 수집된 데이터 및 -migration 접미사를 붙여 공개로 복사된 비공개 저장소. |
setup_bun.js, bun_environment.js, SHA1HULUD | 파일, 러너 이름 | node_modules, GitHub | 11월 공격의 페이로드와 등록한 자체 호스팅 러너. |
Kilobat이 표시하거나 차단했을 내용
- 에이전트 네트워크 로그. 에이전트가 실행한 설치의 경우 세션에서 호스트를 프로세스별로 묶어 표시합니다. 설치 아래에서 시작된
trufflehog, api.github.com을 호출하는curl, registry.npmjs.org로 전송하는npm및 Mac에 클라우드 자격 증명이 있을 경우 처음으로 표시된 AWS 또는 Google API 호스트가 포함됩니다. launchd에 넘겨진 프로세스도 에이전트에 계속 귀속되므로 설치 후 백그라운드에서 계속 실행된 프로세스도 추적합니다. - 앱 트래픽. 에이전트가 없어도 Kilobat의 앱 목록에는 macOS의 일부가 아닌 명령줄 도구가 실행 파일 경로와 함께 표시되므로 설치한 적 없는
trufflehog또는 GitHub에 업로드하는 node 프로세스를 실행 중에 볼 수 있습니다. - 보호. Mac에서는 감지할 것이 없습니다. npm, GitHub 및 클라우드 제공업체는 위협이 아니며 방어자에게 차단하라고 안내된 유일한 호스트에는 GitHub 러너에서 접속했습니다.
- 에이전트 가드. 요청 수집기 규칙에는 webhook.site가 포함되어 에이전트와 그 에이전트가 시작한 모든 프로세스의 접속을 거부하지만, 이 사례의 요청은 Mac이 아닌 GitHub 러너에서 왔습니다. 메타데이터 주소는 링크 로컬 주소이며 Kilobat은 이를 로컬 네트워크로 취급합니다.
Kilobat이 도움이 되지 않았을 경우
- npm과 GitHub에서 피해를 일으킨 트래픽은 개발자 자신의 유효한 토큰을 사용했으며 일반적인 게시 및 푸시처럼 보입니다.
- 웹훅 유출과 워크플로 로그에 비밀값을 출력한 작업은 GitHub 러너에서 발생했습니다. CISA의 권고대로 webhook.site를 차단하는 것은 CI가 실행되는 곳에서 가장 중요합니다.
- Kilobat은 호스트와 바이트 수만 기록하고 내용은 기록하지 않으므로 TruffleHog가 무엇을 찾았는지 또는 저장소가 무엇을 받았는지 알 수 없습니다.
- Docker 컨테이너 또는 VS Code의 편집기 확장 호스트에서 실행된 설치는 에이전트에 귀속되지 않습니다.
- 감염된 패키지는 이후 제거되었으며 30일 에이전트 로그로는 2025년을 확인할 수 없습니다. 이제 확인할 대상은 잠금 파일, 캐시 및 GitHub 계정입니다.
지금 Mac 확인하기
잠금 파일을 Socket, StepSecurity 및 Wiz가 공개한 패키지 목록과 대조하세요. 한 패키지의 경우:
npm ls @ctrl/tinycolor프로젝트 폴더에서 두 공격 중 하나의 페이로드 파일을 찾으세요.
find . -path "*/node_modules/*" \( -name bundle.js -o -name setup_bun.js -o -name bun_environment.js \) -printbundle.js가 있다면 9월 해시와 비교하세요. 일치하면 웜입니다. 다른 bundle.js 파일은 흔하며 대개 무해합니다.find . -path "*/node_modules/*" -name bundle.js -exec shasum -a 256 {} + | grep 46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09이름이나 설명에 Hulud가 포함된 GitHub 저장소를 나열하세요.
gh repo list --limit 1000 --json name,description --jq '.[] | select((.name + " " + (.description // "")) | test("hulud"; "i")) | .name'각 조직과 저장소의 Settings › Actions › Runners에서 직접 설정하지 않은 자체 호스팅 러너를 제거하세요. 특히
SHA1HULUD라는 이름을 확인하세요. Mac에서 러너를 직접 실행하지 않는다면 이 명령은 아무것도 출력하지 않아야 합니다.pgrep -fl "Runner.Listener|Runner.Worker"일치하는 항목이 있으면 npm 토큰, GitHub 토큰 및 클라우드 키를 교체한 다음 npm 토큰을 나열해 오래된 토큰을 삭제하세요.
npm token list요청하지 않은 설치 스크립트가 실행되지 않도록 npm에서 이를 끄고 필요할 때 프로젝트별로 실행하세요.
npm config set ignore-scripts true다음에 대비해 에이전트가 실행한 각 설치가 어디에 접속했는지 확인하세요. 설치 중 npm 레지스트리로의 업로드와 GitHub API 호출을 주의해서 보세요.
kilobat agent-log --all
출처
- GitHub 블로그: 더 안전한 npm 공급망을 위한 계획
- CISA: npm 생태계에 영향을 미치는 광범위한 공급망 침해
- StepSecurity: Shai-Hulud 자체 복제 웜이 500개 이상의 npm 패키지 감염
- Socket: 인기 tinycolor npm 패키지가 40개 이상의 패키지에 영향을 미친 공급망 공격으로 감염됨
- Wiz: Shai-Hulud npm 공급망 공격
- Aikido: S1ngularity/nx 공격자들의 재공격
- Wiz: Sha1-Hulud 2.0 공급망 공격
- Unit 42: “Shai-Hulud” 웜이 공급망 공격으로 npm 생태계 감염
2026년 11월 1일에 확인했습니다.
에이전트와 앱이 어디에 연결하는지 확인하세요
Kilobat은 모든 연결 뒤에 있는 앱, 프로세스 및 에이전트의 이름을 표시하고 알려진 악성 호스트를 차단하며 각 에이전트 세션이 연결한 대상을 기록하는 Mac 메뉴 막대 앱입니다.
brew install seifip/kilobat/kilobat