npm debug와 chalk 패키지 해킹 사고
(aikido.dev)- 9월 8일, 인기 npm 패키지들의 악성코드 삽입이 탐지됨
- 영향을 받은 패키지는 총 18개로, 전 세계적으로 주당 20억 건 이상의 다운로드를 기록함
- 공격자는 웹사이트 방문자의 브라우저에서 암호화폐 및 Web3 작업을 은밀히 가로채고, 지갑 내 승인 및 자금 흐름을 공격자 계정으로 전환하는 코드 포함
- 주요 패키지 파일(index.js)에 난독화된 자바스크립트 코드가 추가된 것으로 확인됨
- 얽힌 사건은 대상 패키지 업데이트와 동시에 시작, 현재 커뮤니티가 대응 중임
사건 개요
- 9월 8일 13:16 UTC 기준, Aikido의 보안 모니터링 피드에서 npm에 악성코드가 포함된 여러 패키지가 업로드되는 현상 포착
- 이 패키지들은 npm에서 매우 인기 많으며, 한 주에 20억 건 이상의 다운로드 수를 기록함
공격 방식 및 내용
- 악성 업데이트 이후, 해당 패키지를 사용하는 웹사이트의 방문자 브라우저에서 자바스크립트 악성코드가 비밀리에 실행되는 구조 확인
- 이 코드의 목적은 암호화폐 및 웹3 활동 감시, 지갑 상호작용 변조, 결제 대상 주소의 무단 변경임
- 사용자는 화면상 특별한 변화 없이 자금과 승인 권한이 공격자가 지정한 암호화폐 주소로 전송될 수 있음
악성 코드 상세 분석
- 대표적으로,
is-arrayish
등에서 index.js 파일이 변조되어 난독화된 복잡한 자바스크립트가 삽입됨 - 코드 내에서는
window.ethereum
인터페이스를 활용해 지갑 계정 정보를 확인하고, 공격 코드 발동 조건 확인 절차를 거침 - 내부적으로 다수의 암호화폐 주소(비트코인, 이더리움 등)와 함수 로직이 포함되어 있으며, 지갑 주소 및 트랜잭션 세부 정보를 공격자 주소로 치환하는 기능 구현
- 이를 통해 실 사용자의 암호화폐 자산이 모르는 사이 유출 및 무단 이전될 위험성 발생
현재 상황 및 커뮤니티 대응
- 문제의 악성 패키지 버전은 주요 npm 저장소에서 신속히 제거되고 있음
- IT/오픈소스 커뮤니티에서는 관련 패키지의 사용 중지 및 업그레이드 안내, 추가 감염 탐지 및 조치 활동을 활발하게 진행 중임
- 이번 해킹 사고로 인해, 패키지 공급망 보안, 코드 난독화 탐지, Web3 브라우저 확장 보호에 대한 경각심이 크게 증가하는 계기가 되고 있음
2023년 9월 8일,
환각으로 날짜가 잘못 나왔습니다. 2023년 얘기가 아니라 지금이네요
npm은 꽤 자주 해킹 소식이 들리는 거 같습니다. 문제가 있어보여요
Hacker News 의견
-
네, 저 해킹당했음. 정말 창피하고 모두에게 미안함. 자세한 내용은 여기와 여기를 참고해줬으면 좋겠음. 영향을 받은 패키지 목록을 올려둠. 이번 공격은 타겟형 공격 같음. 댓글 수정 가능 시간이 끝나기 전까지 지속적으로 업데이트할 예정임. Chalk 패키지는 복구했지만, 나머지는 여전히 탈취당한 상태임 (8일 17:50 CEST). NPM은 아직 아무 소식 없음. 내 NPM 계정은 접속 불가 상태이고, 패스워드 복구 기능도 작동하지 않음. 지금 내가 할 수 있는 건 기다리는 것 뿐임. 지원팀 이메일이 npmjs dot help에서 왔고, 아주 그럴듯하게 보였음. 변명은 아니지만, 피곤한 아침이었고 할 일 하나라도 처리하려다 무심코 평소처럼 공식 홈페이지 직접 접속하지 않고 링크를 눌러버린 게 실수였음(모바일이라 그랬던 듯함). 이번 공격은 NPM만 영향받았고,
/debug-js
링크에 계속 업데이트 올릴 예정임. 다시 한 번 정말 미안함-
이런 스트레스 많은 상황에서 빠르고 투명하게 대응하고 있어서 정말 본보기로 삼을 만함. 이제는 피싱에 속지 않을 것 같지만, 몇 가지 팁을 덧붙이면: 1) 이메일 링크로 절대로 로그인하지 말았으면 함. 피싱과 정상 이메일이 너무 구분 힘들어서 안 속으려면 아예 시도를 안 하는 게 유일한 방법임. 2) U2F/Webauthn 보안키를 2차 인증으로 쓰면 피싱 방지에 거의 완벽함. TOTP는 그렇지 못하니까 참고 바람. 결국 사람은 피곤하거나 바쁠 때 실수 가능성이 언제나 있음. 이번엔 하필 그 대상이었던 것임. 다시 한 번 잘 대응해서 멋지다는 말 하고 싶음
-
sindresorhus의 안내로, 내 의존성 트리에 악성코드가 있는지 아래 명령어로 확인할 수 있음:
rg -u --max-columns=80 _0x112fa8
(ripgrep 필요, 설치는brew install rg
). 원문 댓글도 참고 바람 -
예전에 대학 때 술에 취해 피싱당한 적 있었음(오래 전 일이긴 함). 누구라도 피해자가 될 수 있음. 그런데 NPM의 대응이 너무 느려서 의외임. 이런 게 공격자에게 더 유리하게 작용할 거란 생각이 듦
-
Socket에서 이번 사태를 곧바로 탐지했음. 관련 블로그에서도 다룸. 이번 일은 안타깝지만, 오픈소스 생태계가 정말 빠르게 대응했다는 점은 좋게 평가함. 이런 사건을 보면 패키지 스캔이 왜 중요한지 다시 한번 알게 됨
-
경고를 빨리 해줘서 고마움. porkbun에 도메인 차단 요청 메일을 보냈음
-
-
이 악성코드 페이로드에서 충분히 주목받지 못하는 교묘한 부분이 있음. 바로 지갑 주소를 무작위로 대체하는 게 아니라, 올바른 주소와 자신의 목록에 있는 주소 각각을 Levenshtein 거리로 계산해서 가장 비슷한 공격자 지갑을 고름. 그래서 일반적으로 주소의 앞뒤만 대충 확인하는 흔한 보안 습관을 뚫으려고 설계된 것임. 이 특이한 기능까지 페이로드 전체를 디오버스케이트해서 분석하고, 자세히 정리한 글이 있으니 참고 바람. 모두 안전 유의하길 바람
-
글에서 한 부분이 헷갈리는데, package-lock.json에 특정 버전을 "정확하게" 고정하는 걸로 알고 있었음. package.json은 "x 버전 이상" 정의가 가능하지만, lockfile엔 각 의존성의 지정 버전과 tarball URL이 직접 박혀 있음. lockfile이 있을 때 CI 환경에서 패키지가 자동으로 업데이트될 일이 없어야 정상인데, 내가 npm/yarn/pnpm lockfile 작동방식을 오해했는지 궁금함. npm 공식문서 인용도 참고해주었으면 함
-
해시값을 각 문자별 색상(전경/배경 컬러)을 그 해시와 인덱스로 지정한 컬러 스킴으로 보여주면, 시각적으로 비슷하게 따라 만든 해시 구분이 훨씬 쉬워질 거라고 생각함
-
혹시 이 기법이 특정 해킹 그룹과 연관이 있는지 알 수 있는 정보가 궁금함
-
이 공격 코드가 기발하긴 하지만, 사실 웹에서는 유사 주소 공격을 수십 년간 싸워왔고, 이건 그저 더 동적인 버전에 지나지 않는 것임. 그걸 뛰어나게 과대평가하는 것은 동의하지 않음. 솔직히 이 분석글 전체가 오히려 AI가 쓴 듯한 느낌이어서 신중한 분석으로는 안 보임
-
-
12일 전에 Nx 쪽도 똑같이 당했을 때 비슷한 코멘트를 남겼음. 이건 한 사람의 실패가 아니라 업계 전체의 실패임. 공급망 공격은 영향이 엄청난데다가, 사실 이미 다 해결된 문제들이라고 생각함. 우리가 소프트웨어 개발자니까, 표준 보안 조치(코드 서명, 아티팩트 서명, 계정 이상행동 탐지, 2FA 등)만 전면적으로 적용하면 막을 수 있음. 아직도 모든 패키징 플랫폼이 이걸 안 하는 건 기술적 한계 때문이 아니라, 누가 강제하지 않아서임. AI 발전과 실전 공격의 반복 성공으로 인해 앞으로 더 심각해질 것임. 이제라도 강력한 보안 기준을 '의무화'해야 한다고 생각함
-
이런 보안 조치들은 트레이드오프가 존재함. 예를 들어 휴리스틱이나 증명 기반 장치를 적용하면 꽤 많은 자동화 시스템이나 일반 사용자들이 배제될 수 있음. SMS 기반 2FA는 보안성이 약하고, 이메일도 피싱 위험이 높음. TOTP는 오픈 스탠다드로 쓸 때만 의미가 있는데, 그래도 피싱을 완전히 막진 못함. 하드웨어 기반 인증만이 유일하게 효과적이지만, 그건 대규모 플랫폼에서 현실적으로 적용하기 어렵다는 한계가 있음
-
"보안표준만 잘 지키면 모두 막을 수 있다"는 말만큼 단순하지 않음. 아무리 완벽한 보안 조치를 적용해도, 인간이 실수하면 시스템 전체가 취약해짐. 완전히 안전한 시스템은 존재하지 않음. AI 발전으로 피싱메일이 진짜와 구별 불가해지는 상황이지만, 반대로 AI를 이용해서 이런 공격 자체를 더 잘 탐지할 수도 있음. 결국 AI로 방어할 수밖에 없음
-
예전에는 Windows를 노린 해킹이 많았는데, 지금은 JavaScript와 Python 개발자 인구가 훨씬 더 많아짐. 앞으로 이런 공격은 점점 더 심해질 것임
-
-
NPM도 일정 부분 책임 있다고 생각함. 다양한 외부 보안 벤더나 스타트업들은 이런 악성 활동을 빨리 알아채는데, 모든 패키지와 시큐리티 이벤트를 실시간으로 볼 수 있는 NPM이 왜 이렇게 반복적으로 무력한지 이해하기 어려움. 거의 일부러 모른척하는 수준에 가까움
-
NPM은 이제 GitHub, 즉 Microsoft 소유임. 그들은 Copilot 같은 생성형 AI를 온갖 앱에 넣느라 바쁜 모양임
-
여러 명이 관리하는 패키지는 최소한 다른 관리자가 퍼블리시를 승인해야 배포 가능하도록 옵션을 제공해야 된다고 생각함
-
동일한 공격자가 오랜 시간 비활성화되어 있던 22개 이상의 패키지에 난독화된(게다가 매우 수상해 보이는) 페이로드를 한 번에 투입하고 동시에 배포함. NPM에서 그걸 잡아내라는 건 거의 불가능하다고 생각함. 자신은 다른 소프트웨어 플랫폼에 앱/익스텐션을 배포해온 입장에서 때로는 며칠, 몇 주씩 대기해야 하는 일이 비일비재함. 그런데 NPM은 MS와 GitHub가 온갖 보안 솔루션을 판매해도 서비스에 제대로 투자된 흔적이 없어서 놀랍기만 함
-
NPM이 굳이 뭘 바꿔야 할 이유도 없다고 생각함. 이미 10년 넘게 악성코드 배포의 근원이었지만, 아무도 사용을 멈추지 않으니까 비즈니스엔 타격도 없음
-
원인 중 하나가 패키지 매니저의 지나친 확산임. 원래부터 이렇게 사소한 패키지까지 의존하는 게 싫었음. 최신 버전을 무작위로 끌어오다가 환경이 깨지는 경험도 정말 싫음. npm만 싫은 게 아니라 패키지 매니저 일반이 다 똑같이 짜증남
-
-
이제 비밀번호 매니저 없이, 공식 도메인과 안 맞는 웹사이트에 직접 비밀번호를 입력하는 사람이라면 인터넷에서 중요한 걸 할 자격이 없다고 생각함
-
비밀번호 매니저/브라우저 자동완성이 이런 위조 도메인을 경고해줬을 테고, 이번 NPM 피싱처럼 npmjs.help와 공식 도메인 불일치도 막아줬을 것임
-
이는 사실이지만, 실제로 공식 앱과 웹사이트가 아예 완전히 다른 도메인을 쓸 때도 꽤 여러 번 겪었음. 모바일 앱과 웹이 아예 도메인이 다른 케이스가 최악임. 누가 그런 설계를 했는지 모르겠음
-
-
이런 사건이 반복될 때마다 왜 패키지 레지스트리가 모든 패키지를 암호학적으로 서명하도록 의무화하지 않는지 이해가 안 감. 물론 CI/CD에서 자동화로 서명했다가 거기까지 탈취당하면 뚫릴 수 있지만, 대다수 문제는 막을 수 있음. 개발자가 수동으로 아티팩트 다운로드, 서명, 업로드 등 추가 단계를 거치게 되기는 함
-
진짜 레지스트리라면 Debian처럼 이미 하고 있음. npm은 아마추어적이라서 대기업 환경에선 사용 금지되는 경우가 많음
-
내가 좋아하는 방식은 사후 확인임. CI/CD 자동 업로드 후, 웹에서 사람이 한 번 더 클릭해야 실제 배포가 이뤄지게 하면, 릴리즈 과정의 마찰은 줄이고 그래도 인간이 한 번 더 승인하는 절차를 거칠 수 있음
-
근데 '어떤 서명 키를 신뢰해야 하나'라는 어려운 문제가 남음. 2FA를 탈취당한 누구라도 새 키를 올려 서명할 수 있으니까, 뭔가 의심스러운 계정 활동이 감지됐을 때 신규 서명 키 등록을 지연시키는 방법 등이 필요해 보임
-
-
npm registry를 피하는 편이 훨씬 이득이라고 결론 내림. 대신 (git) 레포지토리에서 직접 패키지를 가져오는 게 더 낫다고 생각함. npm registry는 공급망 공격의 주된 통로기도 하고, 소스와 배포 코드가 완전히 분리되어 있는 문제도 있음. 'npm publish'는 로컬의 어떤 코드든 직접 올릴 수 있어서 악의적인 사용자가 쉽게 악성 코드를 삽입할 수 있음
-
GitHub builds에서 npm으로 배포할 때 진위 검증 기능이 있긴 한데, 그래도 다른 환경에서 퍼블리시가 가능한 것 같아서 명확히 이해가 안 감
-
C 개발자로서 한때 의존성 최소화, 단일 헤더 라이브러리 사용, 벤더링 등이 옛날 방식이라 비판받다가 요즘 들어서야 모두가 다시 필요한 방식이었음을 깨닫는 현실이 아주 이상한 느낌임
-
npm의 최근 provenance 기능이 이 문제를 해결해줌. 설정도 꽤 쉽고, 이런 공격을 예방하는 데 도움이 될 것임. 대형 패키지가 차례로 도입하는 걸 보니 반가움
-
CI 환경에서도 이 방식을 쓰는지 궁금함. 보통 서버에서 npm install로 가져오던 걸 git clone으로 대체하는 건지 알고 싶음
-
"git 저장소에서 직접 패키지 가져오기"는 이론상으론 좋아 보이지만, 실제로는 npm에 버그가 많고, git 디펜던시 설치에 영향 가는 문제도 있음. 이슈에도 남겼듯이, 빌드 스텝 문제로 2020년까지 제대로 동작 안 했고, 글로벌로 npm install할 때 여전히 문제가 있음. 심지어 prepack 스크립트가 npm 문서에 명시됐다 해도, 실제로는 git 기반 의존성에선 작동하지 않음. TypeScript 컴파일러 팀도 이 버그로 희한한 우회 방법을 쓸 수밖에 없었고, 해결용 코드와 버그도 남아 있음. prepack 실패해도 exit code를 전달하지 않아서 npm install이 그냥 깨진 상태로 마무리되는 것도 문제임. 이런 걸 보면 npm은 제대로 운영감독이 절실하거나 아예 새로운 패키지 매니저로 대체돼야 한다고 생각함
-
-
npm 생태계 외부인 입장에서 보면, 이 많은 패키지들을 도대체 왜 이렇게 사소한 일마다 엄청나게 가져다 쓰는지 놀랍다고 생각함
-
이유는 표준 라이브러리가 너무 부실하고, 기본적인 기능도 외부 패키지를 써야 하기 때문임. 직접 만들려면 사소한 구현도 엄청 많아짐
-
15년 개발 경험을 바탕으로 이야기해보면, 직장인 JavaScript 개발자 다수가 사실상 코딩을 거의 못하는 경우가 많음. 지적 능력이 아니라 교육과 문화적 요인임. 독자 코드를 짜는 데 극심한 두려움이 있고, 결국은 사소한 부분도 무조건 외부 의존성에 기대게 됨. 취미 프로젝트 개발자들은 오히려 그런 게 없고 코드도 더 견고한 경우가 많음. 관심 있다면 회사 팀원들에게 대형 프레임워크 없이 직접 빌드하도록 시키면 바로 알 수 있을 것임
-
작은 트리비얼 모듈 단위로 가져오는 것이 불필요한 코드까지 클라이언트에 포함시키지 않게끔 하려는 목적임. 종합 라이브러리는 더 깔끔한 DX를 주긴 하지만, 원하지 않는 코드까지 싣게 됨(tree-shaking이 있긴 해도 만능은 아님)
-
leftpad 사건 이후로 이런 논쟁은 계속됨. JS 표준 라이브러리가 너무 작아서일 수도 있음
-
코드 변경 PR로 "trusted"해 보이는 모듈을 한 줄 import 하는 게 대규모 코드 변경보다 검토자 입장에선 훨씬 수월해 보임. 실제로 더 안전한 건 아니지만, 피곤한 상태에선 더 그럴 듯하게 느껴짐
-
-
지난번 nx 사건 때도 같은 의견을 냈는데, 패키지 매니저가 새로운 패키지를 일정 시간(예: 24시간) 무조건 건너뛰는 '그레이스 피리어드'가 필요하다고 생각함. 대부분 이런 공격은 출시 직후 빠르게 탐지 및 차단이 되기 때문에, 해당 기간 동안 이용자들이 최신판을 자동 설치하지 않는다면 실질적 피해를 줄일 수 있을 것임
-
저자가 겪었을 고통과 스트레스가 상상됨. 단 한 번의 실수 때문에 계속 설명해야 하는 것이 너무 힘들 것 같음. 오픈소스 생태계가 사실상 한 사람의 소유 패키지에 크게 의존하고 있다는 것도 보여주는 사례임. 누구나 실수로 해킹당할 수 있음을 인정해야 한다고 생각함. 기술적으로 보자면 AI가 이렇게 많이 활용되는 상황인데, deno/node/bun 등에서 의심스러운 코드에 대해 경고를 띄우거나, debian 인증 기반의 @verified 형식의 배포로 신뢰성을 높이고, 최신값 대신 검증된 버전을 쓰는 식의 문화 전환이 필요해 보임. 저자도 사람이고 우리 모두가 친절하게 대해야 함. 사태가 수습되면 좀 더 기술적인 상세 분석이나 포스트모템도 보고 싶음