‘막을 방법이 없다’, 이런 일이 정기적으로 일어나는 유일한 패키지 매니저가 말하다
(kevinpatel.xyz)- npm 레지스트리 공급망 공격으로 수백만 기업 앱과 수십억 사용자 기록이 노출됐지만, 생태계는 이를 피할 수 없는 일처럼 받아들임
- Senior Frontend Engineer Mark Vance는 문자열 대문자 변환에도 검증 안 된 패키지의 40단계 중첩 의존성에 기대는 현실을 꼬집음
- 오래 방치된 유틸리티 패키지 탈취로 전 세계 프로덕션 빌드에 crypto-miner가 주입되는 상황이 자연재해처럼 다뤄짐
- Node.js 생태계는 악성 원격 코드 실행을 예측 불가능한 비극처럼 받아들이고, DevOps 팀은 AWS 키 교체에 매달림
- Go, Rust, 네이티브 Web API 생태계는 강한 표준 라이브러리와 암호학적 검증으로 서드파티 의존을 줄이는 대비점이 됨
npm 공급망 공격 풍자
- npm 레지스트리의 공급망 공격으로 수백만 개 기업 애플리케이션이 침해되고 수십억 사용자 기록이 노출됐지만, JavaScript 생태계 개발자들은 이를 “완전히 피할 수 없었다”는 식으로 받아들임
- Senior Frontend Engineer Mark Vance는 단일 문자열을 대문자로 만들기 위해 검증되지 않은 패키지의 40단계 중첩 의존성 트리에 기대는 현실을 현대 웹 앱 개발의 대가로 봄
- 오래 방치된 유틸리티 패키지가 탈취돼 전 세계 프로덕션 빌드에 crypto-miner가 주입되는 상황은 자연재해처럼 취급됨
- Node.js 생태계는 악성 원격 코드 실행을 예측 불가능한 비극처럼 받아들이고, AWS 키를 교체하느라 바쁜 DevOps 팀에 “생각과 기도”를 보냄
다른 생태계와 npm의 대비
- Go, Rust, 네이티브 Web API 생태계는 강한 표준 라이브러리로 서드파티 코드 의존을 크게 줄이고, 핵심 도구 체인에 엄격한 암호학적 검증을 포함함
- 해당 생태계에서는 “대학 중퇴자의 주말 프로젝트”가 글로벌 물류 인프라를 망가뜨리는 일이 오늘 0건이었다는 식으로 대비됨
- npm 대변인은 악의적 행위자가 존재하는 세상에서는 이를 받아들여야 하며, 막을 수 있는 레지스트리 정책이나 빌드 샌드박스 가드레일은 없다고 못박음
- npm 레지스트리는 로컬 머신에서 임의의 설치 스크립트를 기본 실행하는 오픈소스 레지스트리로 그려져, 대변인의 말과 구조적 위험이 맞물림
- 마지막에는 피해자에게 위로를 전하면서도 “내일 아침 다음 필연적 침해” 전까지 회복력을 유지해야 한다는 식으로 마무리됨
댓글과 토론
Hacker News 의견들
-
쿨다운에 대해 각자 생각은 있겠지만, axios, tanstack을 비롯한 최근 npm 공급망 공격 상당수는 쿨다운으로 피할 수 있었을 것임
Artifactory / Nexus를 쓰면 이미 쿨다운이 있을 가능성이 높고, 없어도 설정은 쉬움
npm이나 PyPI 침해는 대부분 몇 시간 안에 내려갔으므로, 쿨다운은 “릴리스된 지 N일 안 된 패키지는 무시”하자는 뜻임. 1일도 효과가 있고, 3일은 괜찮으며, 7일은 약간 과하지만 동작함
설정 방법으로는 기본 1일 쿨다운을 넣은 최신 pnpm을 쓰거나 https://pnpm.io/supply-chain-security, 한 번에 고치고 싶으면 npm, pnpm, yarn, bun, uv, dependabot에 쿨다운과 권장 설정을 넣어주는 https://depsguard.com을 쓸 수 있음. 내가 유지관리자임
또는 쿨다운에 더 집중한 https://cooldowns.dev도 있고, 로컬 설정을 돕는 스크립트도 있음. 모두 오픈소스이거나 무료임
~/.npmrc 등을 직접 편집할 줄 알면 굳이 필요 없지만, 원클릭 해결이 필요한 주변 사람에게는 다음 공격을 피하게 해줄 가능성이 큼
단, 새 치명적 CVE를 패치해야 할 때는 쿨다운을 우회해야 하고, 각각 우회 방법이 있음. 정확한 수치는 없지만 지난 몇 주 동안은 새 제로데이 CVE보다 소프트웨어 공급망 공격에서 더 큰 위험이 온 것처럼 보임- 7일이 과하다는 생각이 오히려 이상함. 특정 새 기능이 꼭 필요한 게 아니라면, 새 프로젝트를 시작할 때도 보통 몇 달 전에 나온 의존성 버전으로 충분해야 함
정기적인 의존성 업그레이드도 마찬가지임. 다만 취약점 대응처럼 즉시 올려야 하는 경우가 있는데, 그때는 개발자가 원하는 새 버전을 명시적으로 지정하게 해도 괜찮다고 봄 - 그러면 문제가 그냥 7일 뒤로 밀리는 것 아닌가 싶음. 이런 사고는 누군가 감염되고 알아차리면서 끝나는 것이지, 변경 사항을 감사하는 군대가 있어서 잡히는 게 아니라고 생각했음
모두가 7일 쿨다운을 걸면 그냥 더 늦게 터지는 것 아닌가? - 빠뜨린 문구가 있어 보임:
Disclaimer: I maintain depsguard
- 쿨다운이 효과적일지 확신은 없음. 누군가는 쿨다운을 우회해서 문제 있을 수 있는 릴리스를 설치하고 문제를 발견해야 함. 아무도 그러지 않으면 문제를 3/7/10/14일 늦춘 것뿐임
쓰면서 더 생각해보니, 그래도 최근 10일 안에 나온 것은 설치하지 않는 10일 쿨다운에는 동의함. 다만 이것이 유일한 완화책이라고 기대해서는 안 된다고 봄 - Linux 배포판처럼 최신/안정/장기지원 식으로 별도 배포판이나 채널을 만들면 안 되나?
- 7일이 과하다는 생각이 오히려 이상함. 특정 새 기능이 꼭 필요한 게 아니라면, 새 프로젝트를 시작할 때도 보통 몇 달 전에 나온 의존성 버전으로 충분해야 함
-
Go나 Rust가 Python/npm에 비해 실제로 보장하는 것이 무엇인지 궁금함. 그냥 Python/npm이 더 맛있는 표적이라 그런 것 같기도 함
점점 모든 서드파티 패키지를 피하려고 하는 중임- 패키지와 이름공간의 소유권을 어떻게 부여할지는 100% 패키지 관리자 운영 주체에 달려 있음
Maven Central은 수십 년째 존재하지만 이름공간을 훔친 사고는 매우 적음
ycombinator.com 도메인을 소유한다는 검증 없이 groupId "com.ycombinator"로 패키지를 올릴 수 없음. 그리고 한 번 올라간 패키지는 악성 코드가 들어 있어도 100% 불변임. 물론 그런 라이브러리는 취약하다고 곳곳에서 표시됨
NPM이 그렇게 오래 Maven Central 같은 안전장치를 복제하지 못했다는 게 이해되지 않음 - 글의 핵심 중 하나는 다른 인기 언어 대부분에는 포괄적인 표준 라이브러리가 있다는 것임. JS의 표준 라이브러리는 놀라울 정도로 작음
언어와 함께 배포되는 검증된 라이브러리 묶음이 있는 대신, 애플리케이션은 직접 만들거나 서드파티 패키지 저장소에서 가져와야 함. NIH를 피하라고 계속 가르쳐왔기 때문에 사람들은 패키지를 집어 드는 편임
그 자체가 꼭 나쁜 건 아니지만, 필요한 것보다 더 많은 코드를 끌어오는 경우가 많음. JS 생태계는 작은 모듈도 선호해서 많은 모듈이 필요하고, 모두가 그 위에 다시 쌓으면서 의존성 그래프가 거대해짐. 의도적이든 아니든 문제가 생길 표면적이 너무 큼
다른 언어는 기본 제공 기능이 많음. 버그와 보안 이슈가 없었던 건 아니지만, JS 생태계에서 보는 것에 비하면 새발의 피임. 외부 의존성 그래프가 훨씬 작고 핵심 기능은 신뢰된 제3자에서 옴 - 공격자는 피해자가 있는 곳으로 감. 프런트엔드는 대다수가 NPM을 쓰는 단일 문화에 가깝고, 백엔드는 그 정도가 덜함
이게 NPM의 변명이 되지는 않고, 오히려 NPM에 불리한 요소가 하나 더 늘어난 셈임
이런 공격이 프런트엔드 개발자와 백엔드 개발자의 차이를 더 깊게 보여준다고도 말할 수 있겠지만, 거기까진 가지 않겠음 - 솔직히 Rust도 정확히 같은 공급망 공격 패턴을 갖고 있음. 다만 더 새롭고 지금은 더 잘 관리되고 있을 뿐임. 10년만 기다려보면 됨
- 마지막으로 확인했을 때 npm은 게시에 2단계 인증이 있었지만 cargo에는 없었음. cargo가 npm보다 딱히 낫다고 생각하지 않고, 그저 그만큼 매력적인 표적이 아닐 뿐임
- 패키지와 이름공간의 소유권을 어떻게 부여할지는 100% 패키지 관리자 운영 주체에 달려 있음
-
여러 직장에서 모든 개발자 머신에 안전한 전역 npm 설정을 깔고, 사람들이 끄지 말라고 요청하고, MDM 도구로 확인하느라 고생이 많았음
더 안전한 기본 설정은 진작 나왔어야 함- npm을 쓰지 않으면 됨. 기본으로 postinstall을 실행하지 않는 패키지 관리자를 쓰면 되고, 전환은 믿을 수 없을 만큼 간단함
- 안전한 설정이 무엇을 뜻하는지 궁금함. 쿨다운 기간이나 패키지 허용/차단 목록을 강제하려는 거라면, 올바른 접근은 회사가 통제하는 저장소를 설정해서 상류 npm 저장소에서 가져오되 원하는 정책을 강제하는 것임
-
postinstall 스크립트가 존재해야 할 정당한 이유는 없음. npm 팀은 이제 성숙해져서 “npm 버전 얼마부터는 ${today} 이전에 게시된 패키지 버전에 대해서만 postinstall 스크립트를 실행한다”고 선언해야 함
- 최근 인기 패키지의 postinstall 스크립트를 여럿 감사했음. 대부분은 네이티브 바이너리를 사용하거나 다운로드하고, 플랫폼 호환성을 감지하고, Node가 부트스트랩하게 두는 대신 직접 연결하고, 오래된 npm 버전 문제를 우회하는 내용이었음
esbuild 같은 개발 도구 체인이 컴파일 언어로 만들어지고 npm 저장소를 통해 바이너리로 배포되기 때문임. 최신 Node/npm과 흔한 최신 OS/플랫폼을 쓴다면, 정당한 문제 없이 모든 postinstall 스크립트를 비활성화할 수 있어야 함 - 설치 스크립트는 패키지 서명과 마찬가지로 주의를 흐리는 요소임. 어느 기능을 추가하거나 제거해도 이 패키지 생태계의 웜화 가능성에는 큰 영향이 없음
설치된 npm 코드는 거의 예외 없이 실행됨 - Rust 패키지가 빌드될 때 샌드박스 없이 실행될 수 있다는 사실도 그다지 정당하지 않음
- 이건 문제를 실제로 고치지는 못함. 패키지 코드도 빌드 시점과 테스트 중에 실행되기 때문임. 범위를 조금 줄일 수는 있겠지만 그 정도임
- 조심스럽게 말하자면, postinstall 스크립트는 완전히 허상에 가까운 쟁점임. 남이 제어하는 코드가 내 컴퓨터에서 실행되고 나쁜 일을 할 수 있기 때문에 놀라는 것인데, 맞음, 그럴 수 있음
하지만 그 패키지 안의 일반 코드도 마찬가지임. 설치 시점에는 실행되지 않더라도 결국 그 안의 무언가는 실행됨. 그렇지 않다면 애초에 의존성에 포함되지 않았을 것임
postinstall 스크립트를 없애는 것이 악용률에 순간 이상의 영향을 줄 거라고 생각하는 건 문제를 끝까지 생각하지 않은 신호임. 안타깝게도 이 문제는 원문이 암시하는 것보다 훨씬 미묘함
“날개가 떨어지는 버튼을 전등 스위치 옆에 두지 말자” 같은 문제가 아니라, 우리가 막고 싶은 것인 “남의 나쁜 코드가 내 컴퓨터에서 실행되는 것”과 우리가 원하는 것인 “남의 좋은 코드가 내 컴퓨터에서 실행되는 것”을 구분할 방법이 매우 고된 수작업 없이는 없다는 게 핵심임. 그리고 그 고된 수작업을 피하려고 남의 코드를 실행하는 것임
- 최근 인기 패키지의 postinstall 스크립트를 여럿 감사했음. 대부분은 네이티브 바이너리를 사용하거나 다운로드하고, 플랫폼 호환성을 감지하고, Node가 부트스트랩하게 두는 대신 직접 연결하고, 오래된 npm 버전 문제를 우회하는 내용이었음
-
모든 Node.js 프로젝트는 npm install로 시작하고, 갑자기 원하지도 않은 패키지 500개가 생김. 그중 절반은 몇 년째 손대지 않은 것들임
-
이미 잘 동작하는 것까지 가능한 한 최신 패키지로 올리고 싶어 하는 문화적 문제가 있음. 적용 가능한 변경인지 보려고 변경 기록조차 읽지 않는 경우도 많음
쿨다운은 유지관리자에게 약간의 인내심을 강제하는 방법일 뿐이고, 실제로 효과가 있음- 준수 요건이 있으면 오래된 버전에 쏟아지는 CVE 취약점 때문에 업데이트해야 함. 대부분은 “정규식 DoS” 같은 가짜에 가깝지만, 절차를 만족시켜야 하니 어쨌든 업데이트해야 함
- 그리고 패키지 소유자가 오래되고 방치된 것처럼 보이지 않으려고 업데이트할 필요 없는 것을 업데이트하는 것도 있음
Lisp 패키지는 15년 동안 변경 없이 잘 쓸 수 있는데, JS 패키지는 유지보수 안 됐다니 큰일 난 것처럼 취급됨. 15년 전에 이미 완성됐는데도 npm과 GitHub에서 관리되는 것처럼 보이려고 아무것도 추가하지 않거나, 때로는 깨지는 변경까지 넣고 버전을 올림. 그러면 모든 것이 업데이트됨
-
7일 쿨다운은 적은 노력으로 붙이는 임시방편처럼 느껴짐. 진짜 해결책은 아마 재현 가능한 빌드와 서명된 증명일 텐데, 대부분의 팀은 이미 당하기 전까지 그 비용을 내지 않을 것임
-
이건 Onion 기사처럼 읽힘
residents of the Node.js ecosystem stood unified in their belief that the malicious remote-code execution was a completely unpredictable tragedy
정말 저 주장을 믿는 사람이 있나? 반례가 너무 많았음
생태계의 실패를 잘 찌르는 풍자지만, 결국 오락물일 뿐임. 어쩌면 마케터들이 자기 물건을 내놓는 계기가 될 수도 있음. 이를테면 depsguard 유지관리자가 자기 글에서 그 사실을 지웠다가 다시 넣고, 다시 지운 것처럼. 이 글을 쓰는 시점에는 그 글이 최상단임- 맞음, 대량 총기 난사에 대한 The Onion 기사에서 파생된 형식임: https://en.wikipedia.org/wiki/%27No_Way_to_Prevent_This,%27_...
-
이 링크는 Xe Iaso가 오래 해온 농담을 명백히 AI로 세탁한 버전임. 아쉽다
https://xeiaso.net/shitposts/no-way-to-prevent-this/CVE-2024...
https://news.ycombinator.com/item?id=40438408- 둘 다 The Onion의 오래된 연재 기사에 대한 패러디임.[0]
[0]: https://en.wikipedia.org/wiki/%27No_Way_to_Prevent_This,%27_... - 이 문구 형식은 적어도 2014년 The Onion까지 거슬러 올라감
https://en.wikipedia.org/wiki/%27No_Way_to_Prevent_This,%27_... - 솔직히 한동안 같은 종류의 것을 만들 생각을 해왔지만, 현재처럼 생성기가 정확히 69줄 코드인지 확인해야 함: https://github.com/Xe/site/blob/main/cmd/no-way-to-prevent-t...
- 둘 다 The Onion의 오래된 연재 기사에 대한 패러디임.[0]