글쓰기

axios 메인테이너는 어떻게 해킹을 당했는가?

트레폴·
·조회 0123456789001234567890·8

3월 31일에 axios 메인테이너 계정이 해킹당해서 악성코드가 심어진 버전이 npm에 올라간 사건이 있었다. 약 3시간 동안 그대로 노출되어 있었는데, axios가 워낙 많이 쓰이는 라이브러리다 보니 파장이 꽤 컸다. 사건 경위를 알고 나면 좀 무서운 이야기라서 정리해봤다.

axios가 얼마나 큰 라이브러리인지

JavaScript에서 HTTP 요청 보낼 때 가장 많이 쓰는 라이브러리다. 주간 다운로드 수가 1억 회가 넘고, Wiz 보고서에 따르면 클라우드 및 코드 환경의 약 80%에 존재한다고 한다. 웹 프론트엔드, 백엔드, CI/CD, 서버리스.. Node.js 돌아가는 곳이면 사실상 거의 다 있다고 봐도 된다.

그런 axios가 이번에 공급망 공격의 타겟이 됐다.

무슨 일이 있었나

3월 31일 00:21(UTC)에 axios@1.14.1이 npm에 올라왔다. 39분 뒤에는 axios@0.30.4도 올라왔다. 둘 다 정상적인 릴리즈가 아니었다. 공격자가 axios 리드 메인테이너인 jasonsaayman의 npm 계정을 탈취해서 직접 올린 거였다.

악성 버전에는 plain-crypto-js@4.2.1이라는 의존성이 추가되어 있었는데, axios 코드 어디에서도 import하지 않는 패키지다. 이 패키지의 유일한 역할은 postinstall 스크립트로 RAT(원격 접근 트로이목마)를 설치하는 것이었다. macOS, Windows, Linux 전부 지원하는 크로스 플랫폼 악성코드다.

준비도 치밀했다. plain-crypto-js를 18시간 전에 미리 npm에 올려놨는데, 처음에는 깨끗한 버전(4.2.0)을 올려서 레지스트리에 이력을 만들어놓고, 실제 공격 시점에 악성 페이로드가 담긴 4.2.1을 올리는 방식이었다. 세 플랫폼용 페이로드도 사전에 다 빌드해둔 상태였다.

npm install을 실행하면 postinstall이 자동으로 돌면서 RAT가 깔린다. Huntress에 따르면 axios@1.14.1이 올라온 지 89초 만에 첫 번째 감염이 확인됐다고 한다. 89초.. 진짜 순식간이다.

3시간 동안 Huntress 모니터링 환경에서만 135개 이상의 엔드포인트가 공격자 C2 서버에 접속한 게 관측됐고, Wiz가 스캔한 환경의 약 3%에서 악성 버전이 발견됐다. axios를 직접 안 쓰더라도 다른 패키지가 axios에 의존하고 있으면 연쇄적으로 영향을 받을 수 있어서, 실제 피해 범위는 이보다 넓을 수 있다.

특히 위험했던 건 CI/CD다. npx axios@latest 같은 식으로 항상 최신 버전을 가져오는 파이프라인이면, 그 3시간 사이에 빌드가 돌았을 때 악성 버전을 받았을 가능성이 높다.

메인테이너는 어떻게 당한 건가

여기가 좀 소름끼치는 부분이다. 공격의 시작점이 코드가 아니라 사람이었다.

사건 이후 올라온 포스트모템에 따르면, 이번 공격의 배후는 북한 연계 해킹 그룹이다. Microsoft는 Sapphire Sleet이라 부르고, Google 위협 인텔리전스 그룹(GTIG)은 2018년부터 활동해온 UNC1069(BlueNoroff)로 분류하고 있다.

이들이 약 2주에 걸쳐서 벌인 일을 순서대로 따라가보면 이렇다.

먼저 실존하는 유명 기업의 창업자를 사칭해서 axios 리드 메인테이너 Jason Saayman에게 접근했다. 단순히 이메일 한 통 보낸 게 아니다. 해당 기업의 브랜딩을 그대로 베낀 Slack 워크스페이스를 만들어서 초대를 했는데.. 이게 놀라울 정도로 정교하다. LinkedIn 게시물을 공유하는 채널이 있고, 팀원 프로필이 여럿 있고, 오픈소스 메인테이너 프로필까지 갖춰져 있었다. 진짜 회사의 팀 공간에 들어간 것 같은 느낌을 주도록 환경 자체를 통째로 만들어놓은 거다. 솔직히 이 정도면 의심 안 하는 게 자연스럽지 않나 싶다.

그 다음 Microsoft Teams 미팅을 잡았다. 여러 사람이 참여하는 것처럼 보이는 미팅이었고, 미팅 도중에 시스템 업데이트가 필요하다는 메시지를 띄우면서 소프트웨어 설치를 유도했다. Jason 본인 말로는 "모든 게 굉장히 체계적이고, 실제처럼 보였으며, 전문적인 방식으로 이루어졌다"고 한다.

그렇게 설치한 게 RAT였다.

RAT가 머신에 들어간 순간 게임 오버다. 공격자는 Jason의 브라우저 세션과 쿠키를 탈취해서 npm과 GitHub 계정을 모두 장악했다. "2FA 걸어놨으면 괜찮지 않나?"라고 생각할 수 있는데, Jason도 2FA를 쓰고 있었다. 근데 소용없었다.

"RAT가 머신에 들어오면 그들은 내 머신 위에 있는 모든 것에 대해 완전한 통제권을 가지게 됩니다. 네, 저는 2FA를 활성화해두고 있었습니다."

이게 왜 안 통하냐면, TOTP 기반 2FA는 머신이 이미 장악된 상태에서는 의미가 없기 때문이다. 공격자가 그 머신에서 직접 세션을 가져가버리면 OTP를 입력하는 과정 자체가 필요 없다. Feross Aboukhadijeh도 "침해된 머신에서 TOTP는 진정한 2FA가 아니다"라고 지적하면서 하드웨어 보안 키 사용을 권고했다.

3시간의 타임라인

악성 버전이 올라간 뒤 약 01:00 UTC부터 이상을 감지한 사람들이 GitHub에 이슈를 올리기 시작했다. 근데 공격자가 계정을 장악한 상태니까 올라오는 이슈를 삭제해버리기도 했다.

그래도 axios 콜라보레이터 DigitalBrainJS가 상황을 파악했다. 이 사람한테는 악성 버전을 직접 내릴 권한이 없었다. 근데 할 수 있는 걸 했다. 01:38 UTC에 deprecation PR을 열고 npm 보안팀에 직접 연락했다. Socket.dev는 악성 버전이 올라온 지 수 분 내에 탐지했다고 한다.

03:15 UTC에 악성 버전이 npm에서 삭제됐고, 03:29 UTC에 plain-crypto-js도 삭제됐다. 퍼블리시부터 제거까지 약 3시간.

타임라인을 정리하면 이렇다.

시각 (UTC)

~2주 전

소셜 엔지니어링 캠페인 시작

3월 30일 05:57

plain-crypto-js@4.2.0 npm에 게시

3월 31일 00:21

axios@1.14.1 악성 버전 게시

3월 31일 ~01:00

axios@0.30.4 게시, 커뮤니티 감지 시작, 공격자가 이슈 삭제

3월 31일 01:38

DigitalBrainJS가 deprecation PR 오픈, npm 보안팀 연락

3월 31일 03:15

악성 axios 버전 npm에서 제거

3월 31일 03:29

plain-crypto-js npm에서 제거

결과만 보면 빠른 대응이긴 한데, 포스트모템에서 스스로 인정하는 부분이 있다. "비인가 퍼블리시를 자동으로 탐지할 방법이 없었다. 탐지는 전적으로 커뮤니티가 이상을 눈치채는 것에 의존했다." 자동화된 안전장치가 아니라 사람들의 눈썰미가 이걸 잡아낸 거다. 좀 씁쓸한 부분이다.

다른 피해자들

GitHub 이슈 댓글에서 Pelle Wessman이 비슷한 경험을 공유했다. 가짜 팟캐스트 인터뷰에 초대받았는데, 소셜 미디어 검증까지 다 거친 뒤에 "스트리밍 소프트웨어"를 설치하라고 했고, 그 소프트웨어에 인포스틸러가 내장돼 있었다고 한다.

Ahmad Nassri는 npx의 비결정적 의존성 해석이 추가적인 공격 벡터가 된다는 점도 지적했다. CI/CD 프로세스 하드닝이 필요하다는 이야기다.

이런 걸 보면.. 오픈소스 메인테이너를 노린 소셜 엔지니어링 공격이 한두 건이 아니라 조직적으로, 지속적으로 이루어지고 있다는 뜻이다.

영향을 받았는지 확인하는 법

lockfile에 해당 버전이 있는지 확인:

bash

grep -E "axios@(1\.14\.1|0\.30\.4)|plain-crypto-js" \
  package-lock.json yarn.lock pnpm-lock.yaml bun.lockb 2>/dev/null

만약 걸렸다면:

  1. axios@1.14.0 또는 0.30.3으로 다운그레이드

  2. node_modules/plain-crypto-js/ 삭제

  3. 해당 환경의 크리덴셜, 시크릿, 토큰 전부 로테이션

  4. 네트워크 로그에서 sfrclak[.]com, 142.11.206.73:8000 연결 확인

  5. CI 환경이면 빌드 중 주입된 시크릿도 교체

axios 팀의 후속 조치

포스트모템에서 밝힌 보안 강화 계획은 이렇다.

  • 메인테이너 전원 디바이스 완전 초기화 + 크리덴셜 리셋

  • 개인 머신에서 퍼블리시하는 거 폐지하고 CI에서만 릴리즈하는 구조로 전환

  • OIDC 기반 퍼블리싱 도입 — 장기 크리덴셜 자체를 없앤다

  • GitHub Actions 보안 모범 사례 적용

커뮤니티에서도 여러 의견이 나왔다. Feross Aboukhadijeh는 가장 임팩트 있는 세 가지로 OIDC 퍼블리싱, CI 전용 릴리즈, 하드웨어 보안 키를 꼽았고, Cornelius Roemer는 소셜 엔지니어링 수법에 대해 더 상세한 공유를 요청했다. lockfile 체크를 npm뿐 아니라 pnpm, yarn, bun까지 해야 한다는 의견도 있었고, npx에 오프라인 모드나 사전 검증을 도입해야 한다는 의견, OpenJS Security Working Group 같은 업계 차원의 협력이 필요하다는 이야기도 나왔다.

생각

SANS 연구소는 이 사건을 "교과서적인 공급망 리스크 사례"라고 평가했다. 메인테이너 계정 하나가 뚫리면서 npm에서 가장 많이 쓰이는 패키지 중 하나가 공격 도구로 변했다.

개인적으로 두 가지가 좀 무섭다고 느꼈다.

하나는 소셜 엔지니어링의 수준이다. Slack 워크스페이스를 통째로 만들고 가짜 팀원 프로필까지 세팅하고 Teams 미팅까지 하는 건 보통 노력이 아니다. 근데 이런 공격을 하는 주체가 개인이 아니라 국가 단위의 조직이라는 게.. 이건 진짜 개인이 아무리 조심해도 뚫릴 수밖에 없는 수준인 것 같다.

다른 하나는 npm 생태계의 구조적인 문제다. 주간 1억 다운로드짜리 패키지의 보안이 결국 메인테이너 한 명의 개인 머신 보안에 달려 있었다는 거다. axios 팀이 CI 전용 릴리즈랑 OIDC로 전환하겠다고 한 건 올바른 방향이지만, 이건 axios만의 문제가 아니다. 아직도 개인 머신에서 npm publish 치는 인기 패키지가 수두룩하다.

이번에는 3시간 만에 잡았다. 커뮤니티 반응이 빨랐고, DigitalBrainJS 같은 사람이 권한 없는 상황에서도 할 수 있는 걸 다 했다. 근데 다음번에도 이렇게 빠르게 잡을 수 있다는 보장은 없다.

Jason Saayman이 포스트모템에 남긴 말이 좀 씁쓸하다.

"사람을 전혀 신뢰할 수 없는 상황이 슬프다. 나의 유일한 목표는 코드로 유용한 것을 만드는 것인데, 세상은 매 순간 무언가를 훔치거나 악용하려는 사람들로 가득하다."

오픈소스로 좋은 걸 만들어서 공유하겠다는 사람이 국가 단위 해킹 조직의 타겟이 되는 세상이라니.. 참 아이러니하다.

댓글

아직 댓글이 없습니다.

로그인하고 의견 남기기

로그인하면 댓글·투표·북마크를 사용할 수 있어요.

제목작성자작성일추천조회수
068트레폴··추천 0·조회 68·11
axios 메인테이너는 어떻게 해킹을 당했는가?
093트레폴··추천 0·조회 93·8