Shai-Hulud 벌레가 AI 에이전트 자격 증명을 훔치는 방법: 20분 npm 감사
요약
Tensorlake npm SDK에 악성 코드가 포함된 버전이 배포되어, GitHub/npm 토큰, 클라우드 키 등 민감한 자격 증명을 수집했습니다. 이 공격은 사용자가 토큰을 취소하는 정상적인 보안 조치(토큰 로테이션)를 함정으로 삼아 시스템 파괴 명령(`rm -rf ~/`)을 실행하도록 설계되었습니다.
핵심 포인트
- 악성 패키지는 `preinstall` 훅을 이용해 개발자 머신에서 자격 증명을 수집합니다.
- 사용자의 토큰 취소 행위가 오히려 파괴적인 페이로드를 작동시키는 '인질 토큰' 방식입니다.
- 공격은 관리자 계정의 직접 푸시와 합법적 자동화 시스템을 결합하여 출처 증명만으로는 안전성을 보장할 수 없음을 보여줍니다.
이번 공격에서 가장 위험한 부분은 도난 그 자체가 아닙니다. 바로 올바른 행동을 했을 때 발동하는 함정입니다.
지난주, 악성 버전의 Tensorlake npm SDK가 벌레를 품고 배포되었습니다. 이 코드는 GitHub 및 npm 토큰, 클라우드 키, SSH 키, 그리고 AI 코딩 도구들의 설정 파일을 수집했습니다. 그런 다음 60초마다 저장된 GitHub 토큰이 여전히 작동하는지 확인하는 작은 백그라운드 서비스를 설치했습니다. 만약 사용자가 토큰을 취소하면, 이 서비스는 사용자 컴퓨터에 rm -rf ~/ 명령을 실행했습니다. 보안 연구원들은 이것을 '인질 토큰(hostage token)'이라고 부르기 시작했고, 그 이름은 과장이 아닙니다. 악성코드 소스에는 문자 그대로의 IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner라는 구문이 포함되어 있습니다.
만약 특정 24시간 동안 특정 패키지를 설치했다면, 사용자의 첫 번째 본능인 토큰 취소 행위가 바로 파괴적인 페이로드를 작동시키는 원인이 됩니다. 여기서 작업 순서가 중요하며, 이 글은 그에 대한 내용입니다: 어떻게 자신이 노출되었는지 확인하고, 무언가를 로테이션(rotate)하기 전에 무엇을 제거해야 하는지, 그리고 다음 웨이브의 벌레가 훔칠 것이 없도록 무엇을 변경해야 하는지를 다룹니다.
전체 공개 고지: 저는 매일 AI 코딩 에이전트를 사용하며, 이 사건을 직접 조사하지는 않았습니다. 아래 내용은 StepSecurity가 페이로드를 분해한 내용, Socket의 분석, Tensorlake 자체 보안 권고문, 그리고 The Register에 보도된 내용을 모두 출처로 삼았으며, 인라인으로 링크되어 있습니다. 제가 근거 기반 사실 대신 의견을 제시하는 부분은 그렇게 명시했습니다.
순서대로 발생한 일들
Tensorlake는 AI가 생성한 코드를 실행하기 위한 격리된(sandboxed) 인프라를 구축하며, 그 npm SDK는 주간 약 12,000회 다운로드와 1,000개 이상의 GitHub 스타를 보유하고 있었습니다. StepSecurity의 보고서에 따른 타임라인은 다음과 같습니다:
• 10월 7일, 01:20 UTC. 최초의 악성 커밋이 관리자 이름으로 Tensorlake 레포지토리의 메인 브랜치에 올라왔습니다. 이후 몇 시간 동안 7개의 커밋이 더 이어졌습니다. 이 중 어느 것도 풀 리퀘스트(pull request)를 거치지는 않았습니다.
• 10월 8일, 01:12 UTC. 해당 레포지토리의 자체 릴리스 워크플로우가 npm에 버전 0.5.144를 게시했습니다. 패키지가 프로젝트의 실제 레포지토리를 통해 프로젝트의 실제 워크플로우로 빌드되었기 때문에, npm은 이를 위한 유효한 출처 증명(provenance attestation)을 보여주었습니다.
• 10월 8일, 01:23 UTC. Socket이 게시된 지 약 11분 만에 이 릴리스를 플래그했습니다. 탐지 속도는 빨랐습니다. 하지만 그게 충분히 빨랐는지는 전적으로 사용자가 언제 npm install을 실행했는지에 달려 있습니다.
공격 경로는 주목할 가치가 있습니다. 공격자는 임의의 관리자 노트북을 탈취하거나 패키지를 위조하지 않았습니다. Snyk의 분석에 따르면, 누군가가 레포지토리 관리자 계정을 사용하여 GitHub 웹 인터페이스를 통해 페이로드(payload)를 메인 브랜치에 직접 푸시한 다음, 프로젝트의 합법적인 릴리스 자동화 시스템이 이를 게시하도록 했습니다. 출처 증명은 패키지가 어디서 빌드되었는지를 말해줍니다. 하지만 코드가 안전한지에 대해서는 아무것도 말해주지 않으며, 이번 사건은 그 차이를 보여주는 가장 명확한 예시가 되었습니다.
패키지를 설치할 때 preinstall 훅(hook)이 실행됩니다. 이는 CI 환경을 건너뛰고 개발자 머신을 대상으로 하여 Bun 런타임을 다운로드하고 이를 사용해 난독화된 856 KB의 JavaScript 파일을 실행합니다. 거기서부터 다음 정보를 수집합니다:
- 패키지 레지스트리 토큰:
.npmrc파일과 npm 토큰 API에서 가져온 npm 토큰, 그리고 OIDC 토큰 교환 자료. - 소스 제어 토큰: 저장소 및 워크플로우 권한을 확인하는 GitHub 토큰.
- 클라우드 자격 증명: Instance Metadata Service, ECS, Secrets Manager, Systems Manager Parameter Store에서 가져온 AWS 자격 증명.
- 인프라 비밀 정보: 로컬 인스턴스의 HashiCorp Vault 데이터, Kubernetes 서비스 계정 토큰, 그리고 kubeconfig 파일.
- 디스크상의 모든 가치 있어 보이는 것: SSH 키,
.env파일, 암호화폐 지갑, 저장된 브라우저 로그인 정보, 메시징 앱 데이터. - 당신의 AI 에이전트의 두뇌:
.claude,.cursor,.kiro, Windsurf, Zed에서 가져온 설정 및 MCP 파일.
마지막 항목은 AI 코딩 에이전트를 운영하는 누구에게나 신경 쓰이는 부분입니다. 당신의 .claude 폴더와 MCP 설정 파일은 단순한 환경설정이 아닙니다. 여기에는 API 키, 에이전트가 호출할 수 있는 서버 목록, 그리고 많은 설정에서 해당 서버들의 자격 증명이 포함되어 있습니다. 이것을 훔치는 벌레는 단순히 당신의 계정에 접근하는 것 이상을 얻습니다. 그것은 당신의 장치에 있는 모든 자동화된 자격 증명 경로를 한 곳에 정리한 지도를 얻게 됩니다.
유출된 데이터는 암호화되어 다음 두 곳 중 한 곳으로 전송됩니다: 피해자 자신의 계정으로 생성된 공개 GitHub 저장소로,
npm 토큰이 도난당하면, 이 웜은 피해자 자신의 패키지를 다운로드하고 자신을 추가하며 버전을 올린 후 재게시하여, 과정 전반에 걸쳐 유효해 보이는 Sigstore 출처(provenance)를 생성합니다. GitHub 토큰이 도난당하면, 피해자의 저장소에 .claude/settings.json 및 .vscode/tasks.json 파일을 커밋합니다. 이 파일들은 누군가 Claude Code나 VS Code에서 프로젝트를 열 때 페이로드(payload)를 재실행하도록 만듭니다. 이러한 커밋은 "chore: update dependencies"라는 메시지와 함께 [email protected]에 의해 작성됩니다.
다시 한번 읽어보세요. 훑어보고 지나갔다면 더욱 그렇습니다. 이 웜은 팀이 매일 생성하는 AI 지원 커밋과 정확히 똑같이 보이는 커밋 안에 숨어 있습니다. Claude noreply 주소에서 의존성을 업데이트하는 생소한 커밋은 2026년의 대부분 프로젝트에서는 눈에 띄지 않을 것입니다. 이는 자격 증명 탈취기(credential stealer)를 기반으로 구축된 진정으로 악랄한 사회 공학적 기법이며, 저는 이 특정 캠페인이 사라진 후에도 모방범들이 이를 재사용할 것이라고 예상합니다.
이것은 또한 올해 두 번째 Shai-Hulud 캠페인입니다. Socket은 페이로드의 구조(난독화된 Math_Symbol.js 파일을 실행하는 setup.mjs 로더)를, 지난 8월에 발생했던 keyv 및 cacheable 패키지의 침해 사례와 일치시킵니다. 이 기술은 이제 레시피가 되었고, 레시피는 그 주에 취약점을 가진 어떤 의존성에도 재현됩니다.
적절한 순서로 감사하기
Tensorlake의 권고문은 영향을 받은 모든 사람에게 버전 0.5.143으로 고정하고 자격 증명을 로테이션(rotate)하라고 알려줍니다. StepSecurity의 분석 보고서는 대부분의 사람들이 놓칠 단계, 즉 무언가를 취소(revoke)하기 전에 토큰 모니터링을 무력화하는 단계를 추가합니다. 이 순서대로 단계를 따르세요. 토큰 취소로 건너뛰지 마십시오.
1단계: 가지고 있는지 확인하기. 프로젝트에서 npm ls tensorlake를 실행하고, 잠금 파일(lockfiles)에서 0.5.144를 검색하세요:
grep -r "tensorlake" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
grep -rn "[email protected]" . --include="*lock*"
또한 10월 7일 이후 Tensorlake 리포지토리의 main 브랜치를 클론하거나 풀(pull)했고 그 내부의 typescript/ 디렉터리에서 npm install을 실행했는지 확인하세요. 악성 코드는 npm tarball에만 있던 것이 아니라 main 브랜치 자체에 존재했습니다.
2단계: 잃을 수 없는 모든 것을 백업하세요. 영속성 메커니즘(persistence mechanism)을 건드리기 전에, 작업 내용을 기기 외부의 장소로 복사해 두세요. 곧 연결 해제할 페이로드 중 하나는 홈 디렉터리를 삭제한 이력이 있으므로, 정리 과정에서 실수하는 것이 절대 파일 손실을 초래해서는 안 됩니다.
3단계: 토큰 모니터를 제거하세요 (존재한다면). ~/.config/gh-token-monitor/를 찾아보세요. Linux에서는 StepSecurity가 다음 정리를 제공합니다:
systemctl --user disable --now gh-token-monitor.service
rm -rf ~/.config/gh-token-monitor ~/.local/bin/gh-token-monitor.sh ~/.config/systemd/user/gh-token-monitor.service
macOS에서는:
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
rm -rf ~/.config/gh-token-monitor ~/.local/bin/gh-token-monitor.sh ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
Windows에서는 작업 스케줄러(Task Scheduler)를 확인하여 monitor.ps1 스크립트를 실행하는 로그온(logon) 작업을 찾아 삭제하세요. Linux 버전은 최대 24시간 동안 60초마다 폴링(polls)합니다. Windows 버전은 ONLOGON 예약 작업과 PowerShell 모니터를 사용합니다.
4단계: 이제 모든 것을 순환시키세요 (rotate). 모니터가 사라진 후에만 GitHub 토큰을 폐기하는 것이 안전합니다. 그런 다음 나머지 항목들을 처리하고, 이 목록의 맨 끝에 있는 AI 도구 항목들은 제가 읽었던 이전 npm 사고 사례들과 비교했을 때 새로운 것임을 주목하세요:
- npm 토큰 및 GitHub 개인 접근 토큰
- AWS 자격 증명(credentials), 그리고 해당 장치가 접근할 수 있는 Secrets Manager 또는 Parameter Store의 모든 내용물
- Kubernetes 서비스 계정 토큰 및 kubeconfig 내용물
- Vault 자격 증명
- SSH 키
- .env 파일에 저장된 비밀 정보(secrets)
- .claude, .cursor, Windsurf, Zed, 및 .kiro 설정 파일에 저장된 API 키와 해당 설정 파일이 참조하는 모든 MCP 서버의 자격 증명
- 브라우저에 저장된 비밀번호
단계 5: 이미 확산되었는지 확인합니다. GitHub 계정에서 "Shai-Hulud" 설명을 가진 새로운 공개 리포지토리, [email protected]이 작성한 커밋, 그리고 사용자가 추가하지 않은 .claude 또는 .vscode 파일을 찾아보세요. npm 계정에서는 자신이 게시하지 않은 패키지 버전을 확인하세요. Tensorlake의 권고 사항은 기억할 만한 세부 사항을 추가합니다: 10월 8일 UTC 기준 01:12부터 02:54 사이에 0.5.144로 해결된 설치가 있는지 CI 로그를 확인하십시오.
단계 6: 장치가 깨끗한지 확신할 수 없다면, 초기화(wipe)합니다. Socket과 StepSecurity 모두 같은 말을 합니다: npm 종속성을 제거한다고 해서 영구적인 감염물(persistent implant)이 제거되는 것은 아닙니다. 신뢰할 수 있는 이미지로부터 다시 구축해야 합니다.
하나의 범위 참고 사항을 알려드립니다. 이것은 중요합니다: preinstall 훅은 CI 환경을 건너뜁니다. 만약 해당 패키지가 오직 CI에서만 실행되었다면, 노출 정도가 다르지만, 그 작업이 볼 수 있었던 비밀 정보는 여전히 순환(rotate)되어야 합니다.
다음 공격을 지루하게 만드는 방법
감사 보고서는 이 파도가 자신에게 도달했는지 알려줍니다. 이러한 변경 사항들은 다음 공격의 피해 범위를 줄여주며, 새로운 도구가 필요하지 않습니다.
기본적으로 설치 스크립트를 비활성화합니다. 이것을 .npmrc에 추가하세요:
ignore-scripts=true
대부분의 패키지는 작동하는 데 라이프사이클 훅(lifecycle hooks)이 필요하지 않으며, 필요한 경우 명확하게 실패할 것이고, 이때는 기본값 대신 의식적인 결정을 내릴 수 있습니다. 이 한 줄만으로도 Tensorlake 페이로드와 지난 2년간의 대부분의 npm 악성 캠페지를 설치 단계에서 막을 수 있었을 것입니다.
버전을 고정하고 차이점을 확인하세요. 버전 범위에 떠다니는 lockfile은 출판 창의 첫 분 안에 오염된 릴리스를 기꺼이 가져올 것입니다. 배포하는 것을 고정하고, 업데이트가 도착했을 때 lockfile의 차이점(diff)은 눈에 보이지 않는 아티팩트가 아니라 검토 과정의 일부여야 합니다.
AI 에이전트 설정을 자격 증명 저장소로 취급하세요. 실제로 그렇습니다. .claude 및 .cursor 디렉터리와 MCP 구성을 실제로 감사하십시오. 원격 서버만 필요한 키는 로컬 설치 스크립트가 읽을 수 있는 파일에 속해서는 안 됩니다. 만약 MCP 서버가 자체 범위 지정 토큰(scoped token)을 가지고 있다면, 그 파일을 훔치는 것의 피해 반경은 전체 장치가 아닌 단일 취소로 제한됩니다.
토큰 범위를 좁게 지정하고 만료일을 설정하세요. 이 벌레는 자신이 훔친 GitHub 토큰들을 리포지토리 및 워크플로우 권한별로 등급을 매긴 후에 무엇을 할지 결정했습니다. 필요한 리포만 읽을 수 있는 토큰은 이러한 등급 매기기 단계를 훨씬 덜 보람 있게 만듭니다. 광범위한 범위(broad scopes)를 가지고 있고 만료일이 없는 클래식 토큰들이 최악의 경우이며, 우리 중 상당수가 여전히 그런 토큰을 사용하고 있습니다.
새로운 릴리스 채택 전 기다리세요. Socket은 출판 후 11분 만에 이 패키지를 플래그 지정했는데, 이는 정말 빠른 것이지만, 동시에 수백 번의 npm install 실행이 가능한 시간입니다. 중요 종속성(critical dependencies)의 새 버전에 하루나 이틀 정도의 냉각 기간을 두는 것은 거의 비용이 들지 않으며, 정확히 이런 종류의 신속 대응 탈취를 대부분 걸러냅니다. 이것은 출처가 명시된 모범 사례라기보다는 제 의견이지만, 이제 여러 사건에 걸쳐 수학적으로 입증되었습니다.
일반화되는 부분
자격 증명 도난 자체는 새로운 것이 아닙니다. 새로운 것은 목표 목록이며, 이는 공격자들이 가치가 어디로 이동했다고 생각하는지에 대해 무언가를 말해줍니다. 이 벌레가 AI 도구 구성을 읽는 이유는 .npmrc를 읽는 이유와 같습니다. 즉, 작동하는 자격 증명이 거기에 존재하기 때문입니다. 에이전트들이 더 많은 자율성(autonomy), 더 많은 통합(integrations), 그리고 더 오래 지속되는 토큰을 갖게 되면서, 설정 폴더는 코드보다 가치가 높은 목표가 됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기