
어제 오전에 일어나서 커피 한 잔 들고 슬랙 열었는데, 보안 채널이 불이 나 있었습니다.
"axios 감염됐대요. 지금 바로 확인해야 해요."
axios. 주간 다운로드 1억 회. 프론트엔드든 백엔드든 Node.js 쓰는 사람이라면 99%는 써봤을 그 라이브러리. 솔직히 처음엔 "설마 그게?" 싶었어요. 근데 아니었습니다.
무슨 일이 있었냐면
2026년 3월 31일 오전 0시 21분(UTC), axios의 핵심 메인테이너 중 한 명인 jasonsaayman의 npm 계정이 해킹당했습니다. 공격자는 이 계정으로 [email protected]과 [email protected] 두 개의 악성 버전을 배포했고, 약 3시간 동안 npm에 살아있었습니다.
한국 시간으로는 오전 9시~12시 사이에 npm install을 실행한 모든 환경이 잠재적 피해 대상입니다. CI/CD 파이프라인이 그 시간에 자동으로 돌았다면 더 위험하고요.
왜 이게 특히 무서운가
공격이 교묘했습니다. axios 코드 자체를 건드린 게 아니에요.
공격자는 18시간 전에 미리 [email protected]이라는 패키지를 등록해서 "히스토리 있는 멀쩡한 패키지" 행세를 하게 만들었습니다. 그리고 공격 당일 4.2.1에 악성 postinstall 훅을 심은 뒤, axios의 package.json에 이 패키지를 의존성으로 슬쩍 추가한 거예요.
npm install axios 실행 → [email protected] 함께 설치 → postinstall 자동 실행 → RAT 드로퍼 동작.
더 나쁜 건 이겁니다. 악성코드가 실행된 뒤 자기 흔적을 지웁니다. plain-crypto-js의 package.json을 클린 버전(4.2.0)으로 교체하고 악성 파일을 삭제하기 때문에, 나중에 npm list로 보면 아무것도 안 걸립니다. 그래서 npm audit은 이 공격에 완전히 무용지물입니다.
실제로 Elastic Security Labs에 따르면 감염된 시스템에서 npm install 직후 89초 만에 RAT이 C2 서버에 첫 신호를 보냈다고 합니다. 60초 주기 비컨 루프를 돌면서 임의 명령 실행, 파일 탈취, 바이너리 인젝션까지 가능한 수준이었고요. Google 위협 인텔리전스 팀은 이 공격의 배후로 북한 연계 위협 행위자를 지목했습니다.
그래서 어떻게 확인하냐고요
처음에는 저도 npm audit 돌리고 "이상없음" 나와서 안심했습니다. 근데 알고 보니 그게 함정이었어요.
제대로 된 확인 방법은 세 가지입니다:
악성 axios 버전이 설치됐는지 — package-lock.json 기준으로 봐야 함 (npm list 말고)
plain-crypto-js 디렉토리가 남아있는지 — 파일은 지워도 디렉토리는 남음
의심 네트워크 연결이 있는지 — RAT의 C2 통신 흔적
이 세 가지를 한 줄로 묶은 명령어가 바로 이겁니다:
npm list -g --depth=0 2>/dev/null | grep -E "axios@(1\.14\.1|0\.30\.4)" && echo "🚨 GLOBAL 감염" || echo "✅ global 이상없음" && \
find ~ -name "package-lock.json" -not -path "*/\.*" -not -path "*/node_modules/*" 2>/dev/null | xargs grep -l '"axios"' 2>/dev/null | while read f; do ver=$(grep -A2 '"node_modules/axios"' "$f" | grep '"version"' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1); echo "$ver $f"; done | grep -E "^(1\.14\.1|0\.30\.4)" && echo "🚨 로컬 프로젝트 감염" || echo "✅ 로컬 프로젝트 이상없음" && \
find ~ -type d -name "plain-crypto-js" -not -path "*/\.*" 2>/dev/null | grep . && echo "🚨 plain-crypto-js 발견" || echo "✅ plain-crypto-js 없음" && \
ss -tnp 2>/dev/null | grep -E ":4444|:5555|:8888|:9999|:1337" && echo "🚨 의심 네트워크 연결" || echo "✅ 네트워크 이상없음"
터미널에 복붙하면 4가지 체크를 순서대로 다 돌고, 각각 ✅ 또는 🚨으로 결과를 뱉어줍니다.
한 가지 주의: 마지막 네트워크 체크에서 이 특정 RAT만의 전용 포트가 공개된 건 아닙니다. 4444, 5555, 1337 등은 RAT이 일반적으로 많이 쓰는 포트들이라 참고 지표 정도로 보시면 됩니다. 결정적 증거는 2번(악성 버전)과 3번(plain-crypto-js 디렉토리)이에요.
감염됐다면
단순히 패키지 지우는 걸로 끝나는 문제가 아닙니다. RAT이 실행됐다는 건 그 시스템에 있던 모든 크리덴셜이 유출됐다고 봐야 합니다. npm 토큰, SSH 키, AWS/GCP 액세스 키, DB 비밀번호를 포함해서 전부 교체하고, CI/CD 환경은 깨끗하게 재구축하는 게 맞습니다.
앞으로를 위해
npm config set min-release-age 3
이 설정 하나면 배포된 지 3일 미만인 패키지 버전은 자동으로 당겨오지 않습니다. 이번 공격처럼 새벽에 몇 시간만 살아있던 악성 버전은 아예 설치가 안 되는 거죠.
저도 이번 사건 이후로 모든 프로젝트 package-lock.json에 버전 고정을 다시 점검했습니다. axios 같은 "당연히 안전한 줄 알았던" 패키지도 예외가 없다는 걸 다시 한번 느꼈어요.
혹시 다른 확인 방법이나 추가 IOC(침해지표) 정보 있으시면 댓글로 공유해 주세요. 정보가 추가되면 글도 바로 업데이트하겠습니다.