
패키지 본체는 무사, Mastra의 npm 140여 개를 덮친 의존성 경유 공격
요약
AI 앱 구축용 프레임워크 Mastra의 npm 패키지들이 의존성 경유 공격을 받아 암호화폐 지갑 및 브라우저 기록을 탈취하는 사고가 발생했습니다. 공격자는 패키지 본체 대신 타이포스쿼팅된 악성 의존성을 추가하여 탐지를 회피했습니다.
핵심 포인트
- Mastra 패키지 140여 개가 의존성 주입 공격에 노출됨
- easy-day-js라는 타이포스쿼팅 패키지를 통해 악성 코드 실행
- postinstall 훅을 사용하여 백그라운드에서 데이터 탈취 및 영속화 수행
- 패키지 자체 코드는 변조하지 않아 코드 리뷰를 통한 탐지가 매우 어려움
@mastra/core를 npm install 해도, 내려받는 패키지의 코드는 이전 버전과 한 글자도 다르지 않다. 차이점은 단 한 줄, package.json의 의존성(dependency) 리스트에 생소한 easy-day-js가 추가되었을 뿐이다. 그럼에도 설치하는 순간, 터미널에서는 암호화폐 지갑 확장 프로그램의 목록을 조사하고 브라우저 기록을 탈취하는 동작이 시작된다. 2026년 6월 17일, AI 앱 구축용 TypeScript 프레임워크 Mastra의 npm 스코프(scope)에서 발생한 것은 바로 이러한 종류의 공격이었다.
흔히 발생하는 공급망 공격(Supply Chain Attack)은 인기 패키지 자체에 백도어(Backdoor)를 심는다. 이번 사례는 그 점이 다르다. 공격자는 npm 계정 ehindero(과거 컨트리뷰터였으며, 퇴임 후에도 공개 권한이 남아 있었음)를 사용하여, 17일 새벽(약 01:12~02:39 UTC)에 @mastra/core, mastra, create-mastra를 포함한 약 140개의 @mastra 패키지를 일제히 재배포했다. 내용은 정상 그대로였다. 유일한 변조는 의존성에 easy-day-js를 한 줄 추가한 것이었다.
easy-day-js는 유명한 날짜 라이브러리인 dayjs를 겨냥한 타이포스쿼팅(Typosquatting, 이름을 비슷하게 만든 가짜)이다. 별도의 계정 sergey2016이 6월 16일에 무해한 1.11.21 버전을 배포하여 신뢰를 쌓은 뒤, 다음 날인 17일에 독이 든 1.11.22 버전을 내놓았다. 오염된 @mastra 측은 ^1.11.21로 참조하고 있었기 때문에, 설치 시 자동으로 최신 버전인 1.11.22를 가져오게 된다.
Compromised package versions themselves contain unmodified code; the attack is delivered through an injected dependency.
의존성 트리(Dependency Tree)를 한 단계 파고들지 않으면 이상 징후를 볼 수 없다. @mastra/core 하나만으로도 주간 다운로드 수가 918K에 달하고, 오염된 스코프 전체로는 주간 110만 회를 넘는다는 점을 고려하면, 리뷰어의 눈을 피하도록 설계된 매우 정교한 공격이다.
트리거는 npm의 postinstall 훅(hook)이었다. easy-day-js의 package.json에는 다음과 같이 심어져 있었다.
"scripts": {
"postinstall": "node setup.cjs --no-warnings"
}
1단계인 setup.cjs(약 4.5KB)는 로더(Loader) 역할을 하며, 대략 다음 순서로 동작한다. TLS 인증서 검증을 해제하고(NODE_TLS_REJECT_UNAUTHORIZED=0), 공격자 서버 23.254.164.92:8000에서 2단계 코드를 가져온 뒤, 이를 분리된 백그라운드 프로세스로 실행하고, 마지막으로 자기 자신을 삭제하여 흔적을 지운다. 탐지를 어렵게 만드는 핵심은 바로 이 자기 삭제 기능에 있다.
2단계인 protocal.cjs(약 41KB)는 크로스 플랫폼 스틸러(Stealer)로, Windows는 레지스트리의 Run, macOS는 LaunchAgent, Linux는 systemd의 사용자 유닛(User Unit)을 통해 영속화(Persistence)된다. 그 후 166종의 암호화폐 지갑 확장 프로그램(MetaMask, Phantom 등)을 조사하고, Chrome, Edge, Brave의 기록을 탈취하며, C2 23.254.164.123:443와 통신한다. 이 동작은 Socket 분석과 StepSecurity의 분석 결과와 거의 일치한다.
주요 IOC(침해 지표)를 정리하면 다음과 같다.
| 구분 | 값 |
|---|---|
| 악성 패키지 | easy-day-js@1.11.22 |
| 관련 계정 | ehindero / sergey2016 |
| C2 | 23.254.164.92:8000 / 23.254.164.123:443 |
| 비콘 파일 | 임시 디렉토리 내의 .pkg_history, .pkg_logs |
이 지점이 이번 사건에서 가장 시사하는 바가 크다. Mastra는 LLM 프로바이더나 클라우드와 밀접하게 연결하기 위한 프레임워크로, 해당 의존성을 설치하는 개발 환경이나 CI(지속적 통합) 환경에는 LLM API 키, 클라우드 인증 정보, DB 연결 문자열 등이 환경 변수로 남아 있는 경우가 많다. 게다가 이번 페이로드(Payload)는 암호화폐 지갑까지 노린다. 공격자 입장에서 AI 개발자의 단말기는 각종 열쇠가 가득한 보물창고인 셈이다.
SafeDep은 이번 수법이 올해 Microsoft가 Sapphire Sleet(BlueNoroff, 북한계로 추정되는 APT)의 소행으로 지목한 Axios npm 오염과 유사하다고 지적한다. 다만, 이번 귀속(Attribution)은 확정되지 않았다. 이 부분은 추측으로 다루어야 한다. 확실한 것은 근본 원인이 고도의 제로데이(Zero-day)가 아니라는 점이다. 퇴임한 기여자(Contributor)의 공개 권한이 방치되어 있었다는, 액세스 권한의 위생(Hygiene) 문제다. 활발히 운영되는 프로젝트일수록 "누가 publish할 수 있는가"에 대한 전수 조사는 뒤로 밀리기 마련이며, 이는 뼈아픈 지적이다.
즉각적인 효과가 있는 방법은 라이프사이클 스크립트(Lifecycle script)를 기본적으로 중단하는 것이다.
# postinstall 등을 실행하지 않고 설치
npm install --ignore-scripts
# provenance(서명·attestation)가 없는 패키지를 차단
...
SafeDep은 "서명을 검증하는 설치 방식이었다면, 이번 파동의 모든 패키지를 거부할 수 있었을 것"이라고 밝혔다. CI에서 공개된 정식 버전이 가진 attestation을 오염된 버전은 가지고 있지 않았기 때문이다. 이미 @mastra를 사용 중인 환경에서는 해당 패키지를 제거하고, API 키·클라우드 인증 정보·토큰을 모두 교체하며, 임시 디렉토리의 흔적을 확인해야 한다.
# 비콘(Beacon) 파일 존재 여부 확인
ls -la "${TMPDIR:-/tmp}"/.pkg_history "${TMPDIR:-/tmp}"/.pkg_logs 2>/dev/null
--ignore-scripts를 상시 사용하면 일부 패키지의 빌드가 실패하는 부작용이 있다. 그럼에도 postinstall이 임의 코드 실행(Arbitrary Code Execution)의 입구로 계속 남아있는 현실을 고려한다면, CI에서는 기본적으로 꺼두고 필요한 것만 명시적으로 허용하는 운영 방식이 현실적인 해답이라고 생각한다. 이번 사건을 통해 기억해야 할 점은, package.json의 의존성에 한 줄이 추가되는 것은 새로운 코드 실행 경로가 하나 더 늘어나는 것과 같다는 사실이다.
1차 정보의 요약은 Socket의 분석글이 읽기 편하다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기