3월 31일, 새벽 0시 21분(UTC).
npm 레지스트리에 axios@1.14.1이 올라옴. 주간 다운로드 1억 회. 자바스크립트 생태계에서 가장 많이 쓰이는 HTTP 클라이언트. 그게 감염된 채로 전 세계에 배포되기 시작함.
39분 뒤, axios@0.30.4도 올라옴. 구버전 브랜치까지 독을 넣은 것. 두 버전을 합치면 사실상 Axios를 쓰는 모든 프로젝트가 사정권에 들어감.
첫 감염은 게시 89초 만에 발생. CI/CD 파이프라인이 자동으로 최신 버전을 당겨온 거.
범인은 북한.
가짜 슬랙, 가짜 회의, 진짜 배신
시작은 한 통의 메시지였음.
Axios의 핵심 메인테이너 jasonsaayman에게 유명 기업 창업자를 사칭한 누군가가 접근함. 실제 창업자의 사진과 브랜딩을 도용했고, 가짜 Slack 워크스페이스까지 만들었음. 채널에는 실제 LinkedIn 게시물이 공유되고, 다른 오픈소스 메인테이너들의 프로필도 섞여 있었음.
정교했음.
Microsoft Teams 미팅이 잡혔고, 여러 명이 참석한 것처럼 보였음. 미팅 중 “시스템 컴포넌트를 업데이트해야 한다”며 소프트웨어 설치를 유도함.
그게 원격 접속 트로이목마(RAT)였음.
jasonsaayman의 컴퓨터에 침투한 공격자는 npm 인증 토큰을 탈취함. 2단계 인증이 걸려 있었지만 소용없었음. 이미 발급된 장기 토큰은 MFA를 우회하니까.
계정 이메일이 jasonsaayman@gmail.com에서 ifstap@proton.me로 바뀜. 그리고 그 계정으로 감염된 패키지가 올라감.
jasonsaayman은 나중에 이렇게 말함.
“어떻게 이런 일이 가능한지 이해하려고 노력 중이다. 거의 모든 곳에 2FA/MFA를 걸어놨는데.”
보이지 않는 독
감염된 Axios 버전에서 바뀐 건 딱 한 줄. package.json에 plain-crypto-js라는 의존성이 추가된 것뿐. Axios의 소스코드 85개 파일은 전부 정상 버전과 비트 단위로 동일했음.
plain-crypto-js는 정상적인 암호화 라이브러리 crypto-js의 56개 파일을 그대로 복사한 패키지. 다른 건 세 개뿐. package.json에 postinstall 스크립트가 추가됐고, setup.js라는 4.2KB짜리 드로퍼가 들어 있었음.
이 드로퍼가 핵심.
문자열 역순 + Base64 디코딩, 그 위에 XOR 암호화. 키는 OrDeR_7077. 위치 종속 인덱싱 공식 7 * i^2 % 10. 보안 스캐너가 정적으로 잡기 어려운 구조.
실행되면 운영체제를 판별하고, 맞춤형 RAT를 다운로드함. macOS에서는 /Library/Caches/com.apple.act.mond라는 이름으로 위장. 마치 Apple 시스템 데몬처럼. Windows에서는 PowerShell을 wt.exe로 복사해서 Windows Terminal인 척함.
그리고 약 15초 뒤, 드로퍼는 스스로를 삭제함. setup.js를 지우고, 악성 package.json도 지우고, 미리 준비해둔 깨끗한 파일로 교체함. npm list를 돌려봐도 아무 이상 없음. 버전도 4.2.0으로 표시됨.
사고 조사관이 4.2.1을 찾아봐도 아무것도 나오지 않는 구조.
18시간의 준비, 39분의 실행
이건 즉흥이 아니었음.
공격 18시간 전에 plain-crypto-js 정상 버전(4.2.0)이 먼저 올라감. 이력을 만들기 위한 미끼. 8시간 전에 C2 서버 도메인 sfrclak.com이 등록됨. macOS, Windows, Linux 세 가지 플랫폼용 페이로드가 미리 빌드되어 대기.
그리고 자정이 넘자마자, 39분 안에 양쪽 브랜치를 동시에 감염시킴.
StepSecurity의 분석.
“이건 기회주의적 공격이 아니었다. 정밀 타격이었다. 악성 의존성은 18시간 전에 준비됐고, 세 가지 플랫폼용 페이로드가 사전 빌드됐으며, 양쪽 릴리스 브랜치가 39분 안에 동시에 감염됐다.”
약 3시간 뒤인 03:15경, npm이 악성 버전을 내렸음. 토큰 폐기는 03:40.
그 사이 약 60만 건의 설치가 발생한 것으로 추정됨. 1만 2천 개 이상의 프로젝트에서 C2 서버와의 비정상적 통신이 감지됨.
UNC1069
4월 1일, Google 위협 인텔리전스 그룹(GTIG)이 공격자를 특정함. UNC1069. 2018년부터 활동해온 북한 연계 해킹 그룹. Microsoft는 같은 그룹을 Sapphire Sleet이라 부름.
목적은 돈. 북한 정권을 위한 암호화폐 탈취가 주 임무인 조직.
근거는 세 가지. RAT의 코드가 이전에 UNC1069이 사용한 WAVESHAPER 백도어의 직접적 진화형이었고, 인프라가 이 그룹이 과거에 사용한 특정 AstrillVPN 노드와 겹쳤으며, 같은 ASN에서 이전 작전 인프라가 확인됨.
Google의 John Hultquist.
“북한 해커들은 공급망 공격에 깊은 경험이 있다. 역사적으로 암호화폐 탈취에 이 방법을 사용해왔다.”
흥미로운 건 빌드 아티팩트에서 발견된 개발자 경로. /Users/mac/Desktop/Jain_DEV/client_mac/macWebT/. 누군가의 맥북 데스크톱에 있던 폴더.
npm은 원래 trust-by-default 시스템. 패키지를 올리면, 전 세계가 받음. 그 신뢰 위에 자바스크립트 생태계가 서 있음.
2018년 event-stream. 2021년 ua-parser-js. 2024년 Solana web3.js. 그리고 2026년 Axios. 공급망 공격은 계속 규모가 커지고 있음. event-stream이 주간 200만 다운로드였다면, Axios는 1억.
이번 사건 이후 npm, pnpm, Yarn, Bun 모두 “최소 릴리스 경과 시간” 정책을 도입함. 새 버전이 올라와도 일정 시간이 지나야 설치할 수 있게. npm v11.10.0부터 min-release-age=7d 설정이 가능해짐.
39분이면 충분했음. 하나의 토큰, 하나의 의존성, 한 줄의 코드. 그걸로 1억 개발자의 신뢰 기반에 금이 갔음.
그리고 그 39분을 만든 건, 가짜 Slack에서 나눈 친절한 대화 한 번이었음.