GitLab, 광범위한 NPM 공급망 공격 발견
(about.gitlab.com)- npm 생태계 전반에서 파괴적 악성코드 변종이 확산 중이며, GitLab 보안팀이 이를 탐지
- 악성코드는 Shai-Hulud의 진화된 형태로, 감염된 개발자의 다른 패키지까지 자동 감염시키는 웜형 전파 구조
- GitHub·AWS·GCP·Azure 등에서 자격 증명 탈취 후, 공격자 제어 GitHub 저장소로 데이터 유출
- GitHub 및 npm 접근이 동시에 차단되면 사용자 데이터를 즉시 삭제하는 ‘데드맨 스위치’ 내장
- GitLab은 자사 시스템이 감염되지 않았음을 확인했으며, Dependency Scanning과 GitLab Duo Chat을 통한 탐지·대응 방안 제공
공격 개요
- GitLab Vulnerability Research 팀이 npm 생태계에서 대규모 공급망 공격을 탐지
- 내부 모니터링 시스템이 여러 감염된 패키지를 발견
- 악성코드는 Shai-Hulud의 변종으로 확인
- 악성코드는 웜형 전파를 통해 감염된 개발자의 다른 패키지까지 자동 감염
- GitLab은 자사에서 악성 패키지를 사용하지 않았음을 확인하고, 보안 커뮤니티와 정보를 공유
공격 내부 구조
- 내부 모니터링 시스템이 탐지한 악성 npm 패키지는 다음 기능을 수행
- GitHub, npm, AWS, GCP, Azure의 자격 증명 수집
- 탈취한 데이터를 공격자 제어 GitHub 저장소로 전송
- 피해자의 다른 패키지를 자동 감염
- 인프라 접근이 차단되면 파괴적 페이로드 실행
- 여러 감염 패키지가 확인되었으며, 조사가 계속 진행 중
기술 분석: 공격 진행 방식
초기 감염 벡터
- 악성코드는 다단계 로딩 과정을 통해 시스템에 침투
- 감염된 패키지의
package.json에setup_bun.js스크립트가 추가 - 겉보기에는 Bun JavaScript 런타임 설치용으로 위장
- 실제로는
bun_environment.js(10MB, 난독화된 페이로드)를 실행
- 감염된 패키지의
- 작은 로더 파일과 대형 난독화 페이로드로 구성되어 탐지 회피
자격 증명 수집
- 실행 즉시 다양한 소스에서 토큰 및 비밀정보를 수집
-
GitHub 토큰(
ghp_,gho_) - AWS, GCP, Azure 자격 증명
-
npm 토큰(
.npmrc및 환경 변수) - Trufflehog 도구를 이용해 홈 디렉터리 전체를 스캔, API 키·비밀번호·Git 기록 등 탐색
-
GitHub 토큰(
데이터 유출 네트워크
- 탈취한 GitHub 토큰으로 “Sha1-Hulud: The Second Coming.” 설명이 포함된 공개 저장소 생성
- 해당 저장소가 데이터 드롭박스 역할 수행
- GitHub Actions 러너를 설치해 지속성 확보
- 권한이 부족한 경우, 동일 마커를 가진 다른 저장소를 검색해 다른 시스템의 토큰 재활용
- 이를 통해 분산형 토큰 공유 네트워크 형성
공급망 전파
- 탈취한 npm 토큰을 이용해 피해자의 모든 패키지를 감염
- 원본 패키지 다운로드
-
setup_bun.js를 preinstall 스크립트로 삽입 -
bun_environment.js페이로드 추가 - 버전 번호 증가
- 감염된 패키지를 npm에 재배포
데드맨 스위치
- 악성코드는 GitHub(데이터 유출)과 npm(전파) 접근을 지속적으로 모니터링
- 두 채널 모두 차단되면 즉시 데이터 파괴 실행
- Windows: 사용자 파일 삭제 및 디스크 섹터 덮어쓰기
-
Unix 계열:
shred명령으로 파일 덮어쓰기 후 삭제 - GitHub 저장소 일괄 삭제나 npm 토큰 대량 폐기 시, 감염된 시스템이 동시에 데이터 삭제를 수행할 위험 존재
침해 지표 (IoC)
- 주요 탐지 지표
- 파일:
bun_environment.js(악성 post-install 스크립트) - 디렉터리:
.truffler-cache/,.truffler-cache/extract/ - 프로세스:
del /F /Q /S "%USERPROFILE%*",shred -uvz -n 1,cipher /W:%USERPROFILE% - 명령어:
curl -fsSL https://bun.sh/install | bash,powershell -c "irm bun.sh/install.ps1|iex"
- 파일:
GitLab의 탐지 및 대응 지원
-
GitLab Ultimate 사용자는 내장 보안 기능으로 즉시 노출 여부 확인 가능
-
Dependency Scanning 활성화 시,
package-lock.json또는yarn.lock내 감염 패키지를 자동 탐지 - 감염 패키지가 포함된 머지 요청 시 경고 표시
-
Dependency Scanning 활성화 시,
-
GitLab Duo Chat과 연동해 빠른 질의 기반 탐지 가능
- 예: “Shai-Hulud v2 캠페인에 영향받은 종속성이 있는가?”
- Security Analyst Agent가 프로젝트 취약점 데이터를 조회해 즉답 제공
- 여러 저장소를 관리하는 팀은 CI/CD 기반 자동 탐지와 에이전트 기반 신속 대응 병행 권장
향후 전망
- 이번 공격은 공격 인프라 보호를 위해 피해자 데이터 파괴를 이용하는 새로운 형태의 공급망 공격으로 평가
- GitLab은 커뮤니티와 협력해 안전한 복구 전략을 개발 중이며, 자동 탐지 시스템으로 변종을 지속 모니터링
- 조기 정보 공유를 통해 데드맨 스위치로 인한 2차 피해 방지를 목표로 함
Hacker News 의견
-
한 달 전쯤 귀찮은 작업을 해야 해서 NPM 패키지를 찾았음
brew install npm을 실행하자 의존성 폭포가 쏟아졌고, 잠시 멈춰 리스크와 이득을 생각하다가 결국brew uninstall npm으로 되돌림
대신 오래된 Unix 유틸리티 파이프라인과 awk로 해결했는데, 지금 생각해보면 최고의 결정이었음- 교훈은 명확함 — 웹 기술을 로컬 스크립팅에 쓰지 말아야 함
NPM은 브라우저 호환성 문제를 해결하려 만든 도구라, 브라우저가 없는 환경에서 불필요한 복잡성을 초래함 - 이런 이유로 컨테이너화나 가상화가 필요함
Node나 Python처럼 의존성이 많은 생태계의 코드를 메인 시스템에서 직접 실행하면 공급망 공격에 노출될 위험이 큼
그래서 나는 아예 Python이나 JS 인터프리터를 기본 시스템에 설치하지 않음 - 나도 예전엔 npm을 Docker 컨테이너 안에서만 돌렸는데, 포럼에서 종종 비웃음을 샀음
결국 포기했지만 지금은 그게 맞았던 것 같음 - 나도 비슷한 경험이 있음. artillery가 끌어오려는 의존성 수를 보고 바로 포기했음
- 아이러니하게도, 개발자들은 늘 “상식이 최고의 보안”이라 말하면서도 검증되지 않은 수많은 패키지를 한 줄 명령으로 설치함
- 교훈은 명확함 — 웹 기술을 로컬 스크립팅에 쓰지 말아야 함
-
“GitHub이 악성 저장소를 대량 삭제하면 수천 개 시스템이 동시에 사용자 데이터를 파괴할 수 있다”는 말이 있었는데,
마치 인질극 같음 — 그래서 “인질을 쏴라” 는 농담이 나옴- 하지만 인질(사용자)이 스스로 위험에 들어간 셈이라, 결국 자기 선택의 결과임
-
나도 이번 npm 공격의 피해자임
GitHub CLI가 HOME 디렉토리에 평문 OAuth 토큰을 저장한다는 걸 알고 충격받았음
공격자가 접근하면 내 계정으로 거의 모든 행동이 가능했음- 사실 GitHub CLI는 keyring이 없을 때만 토큰을 평문으로 저장함
macOS에서는 OS 키체인에 안전하게 저장됨 — 관련 토론 - 맞음, 하지만 브라우저 쿠키 파일도 비슷한 문제를 가짐
Chrome은 OS 보호 기능을 쓰지만 Firefox는 아직 아님
결국 sandbox 기반 접근 제어가 근본적인 해결책임 - 모든 토큰은 보호된 키체인에 있어야 함
하지만 플랫폼 간 일관된 솔루션이 없고, MacOS에서도 완벽한 방법이 없음 - 나도 피해자임. Backstage 설치 후 감염됨
~/.dev-env디렉토리가 생기며 내 노트북이 GitHub runner로 변했음
Bluefin Linux의 읽기 전용 파일 시스템 덕분에 피해가 줄었을지도 모름
- 사실 GitHub CLI는 keyring이 없을 때만 토큰을 평문으로 저장함
-
모두 npm만 탓하지만, GitHub의 책임도 큼
악성 저장소를 빠르게 탐지하지 못했고, 이미 악성코드 저장소 문제가 심각함- 그래도 GitHub에 업로드된 덕분에 피해를 빨리 알아챈 사람도 있었음
만약 몰래 외부 서버로 전송됐다면 더 끔찍했을 것임 - Microsoft가 모든 걸 필터링해줄 순 없음
커뮤니티 차원의 도구와 관행이 필요함 - GitHub이 Microsoft 소유라 GoLang 패키지 저장소와도 얽혀 있음
상업적 규제나 빌드 워크플로 제한이 생기면 큰 문제로 이어질 수 있음 - 두 회사 모두 보안 수준이 평범하다는 점은 부정할 수 없음
- 이 악성코드는 동일한 패턴으로 저장소를 생성하므로, 간단한 규칙으로도 탐지 가능했을 것임
- 그래도 GitHub에 업로드된 덕분에 피해를 빨리 알아챈 사람도 있었음
-
왜 NPM만 공격 대상이 되는지 궁금했음
Python이나 Java도 인기 많은데, 최근엔 조용함- NPM은 post-install hook으로 임의 명령 실행이 가능하고,
버전 범위 의존성 문화 덕분에 감염이 빠르게 확산됨
Java는 특정 버전 고정이 일반적이라 이런 일이 드묾 - Node는 표준 라이브러리가 작고 커뮤니티 의존도가 높음
그래서 하나의 패키지가 수십 개의 하위 의존성을 끌어옴 - JS는 GitHub에서 가장 인기 있는 언어라 공격 표면이 넓고
경험이 적은 개발자들이 많아 보안이 약함 - JS 커뮤니티는 “최신 버전 강박증”이 심함
다른 언어 생태계는 검증 후 업데이트하지만 JS는 즉시 업그레이드함 - NPM은 보안 경계가 약함
설치 시 스크립트를 자유롭게 실행할 수 있었고, JVM이나 Python은 그렇지 않음
- NPM은 post-install hook으로 임의 명령 실행이 가능하고,
-
.npmrc에ignore-scripts=true를 추가하면 공격 벡터를 줄일 수 있음
관련 글- 설치 전에 preinstall/postinstall hook이 있는 패키지를 미리 확인할 방법이 있는지 궁금함
- “ignore scripts”가 안전하다면 왜 존재하는 옵션인지,
비활성화 시 의존성 깨짐 위험은 없는지도 의문임 - 하지만 결국 Node 환경에서 JS를 실행하면 환경 변수나 파일 접근은 막을 수 없음
- 혹은 그냥 pnpm을 쓰는 것도 방법임
-
이번 공격에서 가장 걱정되는 건 자격 증명 탈취임
감염된 패키지를 설치했다면 환경 변수나.npmrc토큰이 유출됐을 수 있음
즉시 토큰과 API 키를 회전해야 함
주기적 회전은 침해 후 대응이 아니라 사전 예방책임- 개발자가 비밀번호 재사용을 한다면 정말 심각한 문제임
자격 증명은 환경 변수나 파일에 저장하지 말고 보안 키나 암호화 파일을 써야 함 - 감염 여부를 모른다면 먼저 감염 탐지 → 정리 → 토큰 회전 순으로 해야 함
- 이번 공격은 단순한 감염이 아니라 생태계 전체를 인질로 잡는 형태라 더 위험함
마치 분산 원장에 악성 트랜잭션을 심는 것 같음 - 비밀은 반드시 보안 저장소(secret locker) 에 보관해야 함
여전히 많은 서비스가 평문 저장을 기본값으로 두는 게 문제임 - 그리고 이 악성코드는 전파가 멈추면 데이터 파괴까지 시도함
- 개발자가 비밀번호 재사용을 한다면 정말 심각한 문제임
-
이런 공격이 반복되는데, 왜 AI 기반 탐지 시스템이 작동하지 않는지 의문임
Microsoft가 AI를 그렇게 강조하는데, 정작 GitHub 보안엔 안 쓰이는 듯함- 하지만 “AI가 공격을 탐지했다”는 말은 이미 보안 마케팅의 상투어가 됨
방화벽이 막은 모든 시도를 공격으로 분류하던 시절처럼 의미가 퇴색됨 - 우리 회사는 SonaType Lifecycle을 쓰는데, AI 기반으로 이런 공격을 막는다고 함
내부적으로 SonaType IQ Server가 이를 지원함 - 현재의 “AI”는 생성형 모델이라 평가보다는 생성에 초점이 맞춰져 있음
실제로 curl 프로젝트가 AI 생성 보안 리포트 스팸에 시달린 사례도 있음
- 하지만 “AI가 공격을 탐지했다”는 말은 이미 보안 마케팅의 상투어가 됨
-
postinstall 스크립트를 계속 허용할 이유가 있는지 궁금함
사용자에게 실행 여부를 묻는 게 낫지 않을까 생각함- 하지만 대부분의 사용자는 “예”를 누를 것이고,
CI 서버에서는 비대화식 설치가 필요하므로 현실적으로 어렵다고 봄
- 하지만 대부분의 사용자는 “예”를 누를 것이고,
-
글이 매우 통찰력 있었는데, 마지막에 GitLab 보안 기능 홍보로 끝나서 아쉬웠음
- GitLab 사용자라면 유용했을 수도 있음
- 그래도 그게 글의 통찰력 자체를 깎지는 않음