해킹을 방지하기 위한 7가지 팁 (npm, pnpm 및 bun)

BBetter Stack
컴퓨터/소프트웨어AI/미래기술

Transcript

00:00:00NPM 패키지를 설치한다면, 당신은 타깃입니다. 당장은 아니더라도,
00:00:05조만간 반드시 위험이 닥칠 겁니다. 지난 몇 달 동안에만 수백 개의 패키지가 해킹되었고,
00:00:09그 속도는 줄어들지 않고 있습니다. 그래서 이런 일이 터질 때마다 걱정하기보다는,
00:00:14직접 NPM, PNPM, BUN 환경 전반을 잠그기로 했습니다. 그런데 알고 보니
00:00:18당신을 구할 수 있는 대부분의 조치는 설정에 30초밖에 걸리지 않더군요. 그래서 이번 영상에서는
00:00:23가장 흔한 공격을 단 한 줄의 설정 변경으로 차단하는 방법부터,
00:00:27공격자들조차 인정하는, 패키지가 내 기기에 도달하기 전에 차단해 주는
00:00:30무료 도구까지, 7가지 변경 사항을 알려드리겠습니다. 시작해 보죠.
00:00:39첫 번째이자 가장 간단한 방법은 무조건적인 최신 패키지 다운로드를 멈추는 것입니다. 대부분의 공급망
00:00:44공격은 몇 시간 안에 드러납니다. 따라서 새로운 버전이 나오자마자 설치하지 않는 것만으로도,
00:00:48대부분의 공급망 공격을 완전히 피할 수 있습니다. 주요 Node.js
00:00:52패키지 매니저들은 이제 모두 릴리스 기간 제한(release age gating) 기능을 제공합니다. NPM의 경우 .npmrc
00:00:57파일에서 일(day) 단위로 설정합니다. PNPM은 전역 pnpm-config.yaml 파일에서 설정하거나,
00:01:03PNPM 워크스페이스 파일을 통해 프로젝트별로 설정할 수 있으며, 이 값은
00:01:08분(minute) 단위로 지정됩니다. PNPM 11부터는 기본값이 하루로 설정되어 있다는 점도 참고하세요.
00:01:14별도로 설정하지 않아도 어느 정도 보호가 됩니다. BUN은
00:01:17bunfig.toml 파일에서 전역 또는 프로젝트별로 설정하며, 여기는 초(second) 단위입니다.
00:01:23같은 설정인데 세 패키지 매니저의 단위가 제각각이라는 게 정말 놀랍습니다. 이것이 현재 생태계를
00:01:27아주 잘 보여주는 것 같네요. 설정을 마친 후,
00:01:32예를 들어 tanstack의 react-start를 설치하면, 최신 버전 대신
00:01:36제 설정(일주일 전)에 부합하는 최신 버전을 설치하게 됩니다.
00:01:41혹시라도 보안 이슈로 인해 즉시 최신 버전을 설치해야 한다면,
00:01:45명령줄에서 우회할 수 있습니다. 하지만 LLM 사용 시 주의하세요.
00:01:49LLM이 임의로 우회 설정을 적용해 최신 버전을 설치해 버리는 경우를 봤기 때문입니다. 또 기억해야 할 점은
00:01:54npx와 bunx는 이 설정을 무시하고 항상 최신 버전을 설치한다는 것입니다.
00:01:59이와 관련해 BUN에 수정 PR이 열려 있습니다. 이제 다른 설정으로 넘어가서,
00:02:03NPM의 설치 스크립트도 꺼버립시다. PNPM과 BUN은 기본적으로 이 기능을 차단하므로
00:02:07별도 설정이 필요 없습니다. 모르셨다면 알려드리죠. 패키지를 설치할 때,
00:02:12그 패키지는 설치 직후 자체 코드를 실행할 권한을 갖게 됩니다.
00:02:16이 기능은 네이티브 코드 컴파일이나 바이너리 다운로드 같은 정당한 용도로 만들어졌지만,
00:02:20문제는 거의 모든 공급망 공격이 이 메서드를 통해 설치 직후 악성 코드를 실행한다는 것입니다.
00:02:26기기에 악성 코드를 실행하는 것이죠.
00:02:30스크립트가 반드시 필요한 패키지가 있다면 개별적으로 허용할 수 있습니다. PNPM의 경우,
00:02:34설치 시 스크립트 실행이 필요한 패키지를 알려주며,
00:02:39esbuild처럼 말이죠. 이때 `pnpm approved-builds`를 실행해 허용 목록을 만들거나,
00:02:44설치 명령어에 `--allow-build` 플래그를 추가하면 됩니다.
00:02:48BUN 역시 기본적으로 설치 스크립트를 차단하지만, 자체적으로 관리하는
00:02:52허용 패키지 목록이 있고, 제가 설치한 esbuild도 포함되어 있습니다.
00:02:56이것은 package.json에 trustedDependencies를 추가하여 수정할 수 있으며,
00:03:00이렇게 하면 명시적으로 허용한 패키지만 post-install 스크립트를 실행할 수 있습니다.
00:03:04배열을 비우면 기본 목록이 무시된다고 되어 있지만,
00:03:09제 환경에서는 작동하지 않더군요. 깃허브에서 찾은 버그 같습니다.
00:03:12당장의 해결책은 목록에 아무 값이나 하나 넣는 것입니다. 그러면 기본 목록이 무시됩니다.
00:03:17BUN에서는 `bun pm untrusted` 명령으로
00:03:21스크립트를 실행하려는 비허용 패키지를 확인하고,
00:03:26`bun pm trust`를 통해 하나씩 허용하거나 아까 말한 목록에 추가할 수 있습니다.
00:03:30전역 BUN 설정에서 install-scripts를 false로 설정해 완전히 비활성화할 수도 있습니다.
00:03:35NPM은 조금 더 어렵습니다. 솔직히 NPM 대신 PNPM을 쓰시는 걸 권장하지만,
00:03:40어쩔 수 없이 NPM을 써야 한다면 Lavamote의 allow-scripts 패키지를 사용해
00:03:45package.json에서 허용 목록 기반의 스크립트 제어가 가능합니다.
00:03:50세 번째 팁은 git 기반 의존성을 차단하는 것입니다. NPM에서 의존성은
00:03:55레지스트리를 우회하는 git URL로 선언될 수 있고, 자체 .npmrc를 포함해
00:04:01라이프사이클 스크립트를 재활성화할 수도 있습니다. 이것이 최근 tanstack을 공격한 수법 중 하나입니다.
00:04:05NPM 설정에서 `allow-git`을 none으로 설정해 완전히 차단하거나,
00:04:10루트(root)로 설정하면, 루트 package.json에 명시된
00:04:15의존성만 git 기반으로 설치할 수 있게 제한할 수 있습니다.
00:04:19PNPM에는 `block-exotic-sub-dependencies` 옵션이 있습니다. 이를 true로 설정하면,
00:04:25루트 package.json에 직접 선언된 의존성만이
00:04:29git 저장소나 직접 URL 같은 외부 소스를 사용할 수 있습니다. 이 옵션은 아직 Bun에 없지만 PR이 열려 있습니다.
00:04:35설정 관련 마지막 팁으로, PNPM에는 trust-policy가 있는데,
00:04:40이를 `no-downgrade`로 설정하면 신뢰 수준이 이전 버전보다 낮아진
00:04:45패키지가 설치되려 할 때 PNPM이 오류를 냅니다. 만약
00:04:50신뢰받던 출처가 갑자기 증명이 불분명해지면 설치가 차단됩니다.
00:04:55출판 프로세스를 우회하려는 공격을 막는 데 도움이 됩니다.
00:05:00팁 4번은 아마 가장 강력할 겁니다. 바로 패키지를
00:05:04내 기기에 닿기 전에 미리 검사하는 도구를 사용하는 것이죠.
00:05:08완전 무료인 두 가지 강력한 도구를 소개합니다. 첫 번째는
00:05:14MPQ입니다. npm, pnpm, bun 명령에 별칭(alias)으로 지정해두면,
00:05:18설치 시마다 몇 가지를 검사합니다. Snyk 데이터베이스와 대조해
00:05:23취약점을 탐지하고, 출시 22일 미만인 패키지는 플래그를 표시합니다.
00:05:28타이포 스쿼팅도 잡아냅니다. 예를 들어 express를 expreess처럼
00:05:32오타를 노려 유도하는 경우죠. 레지스트리 서명과
00:05:37빌드 증명을 검증하고, 설치 스크립트 존재 여부도 경고합니다.
00:05:41추가로 다운로드 횟수, 리드미, 라이선스, 레포 URL,
00:05:46메인테이너의 이메일 도메인이 살아있는지도 확인합니다. 이 모든 과정을 거쳐
00:05:51설치할지 말지 결정할 수 있는 보고서를 제공합니다.
00:05:55두 번째는 Socket Firewall입니다. 제가 직접 쓰는 도구인데,
00:05:59마찬가지로 패키지 매니저에 별칭을 걸어 사용하며,
00:06:03파이썬이나 러스트 같은 JS 외 언어도 지원합니다.
00:06:08설치 시 확인된 악성 패키지는 차단하고, 사람이 아직 검토하지 않은
00:06:12AI 탐지 위협에 대해서는 경고를 줍니다. 솔직히
00:06:16가장 먼저 들어서 선택했지만, 실제 악성 패키지를 잘 잡아내고
00:06:21공격자들도 Socket이 패키지가 기기에 닿기 전에
00:06:25탐지한다고 언급할 만큼 성능이 증명되어 신뢰합니다.
00:06:30UV, pip, cargo 등 JS 이외 지원이 훌륭한 것도 장점입니다.
00:06:35설치 후에는 패키지 매니저의 캐시를 비워야
00:06:38모든 설치 패키지가 방화벽 검사를 통과하게 됩니다. 팁 5번은 락 파일입니다.
00:06:42락 파일에는 각 패키지의 실제 다운로드 URL이 들어있지만,
00:06:47아무도 PR에서 락 파일을 제대로 검토하지 않습니다. 그래서 공격자가 PR을 통해
00:06:51다운로드 URL을 자신이 조종하는 패키지로 바꾸고
00:06:56무결성 해시까지 맞춰두면 눈치채기 어렵습니다. 다음 설치 시
00:07:01공격자의 서버에서 코드를 받아오는 거죠. 다행히 PNPM은
00:07:05이런 취약점에서 자유롭습니다. 교체 가능한 소스를 유지하지 않으며,
00:07:09package.json에 없는 내용은 락 파일에 있어도 설치하지 않으니까요. Bun은
00:07:14확실치 않네요. PNPM을 안 쓴다면 `lockfile-lint`를 사용하세요.
00:07:18개발 의존성으로 설치해 락 파일을 검증하고, 모든 패키지가
00:07:23신뢰할 수 있는 레지스트리에서 왔는지 확인합니다.
00:07:28URL이 패키지 이름과 맞는지, 무결성 해시가 정상인지도 확인합니다.
00:07:32의심되면 설치를 실패시킵니다. 팁 6번은 CI/CD에서
00:07:37`npm install` 대신 clean install을 사용하는 것입니다. NPM은
00:07:42`npm ci`를 쓰면 되고, Bun과 PNPM은 `--frozen-lockfile` 플래그를 쓰거나
00:07:47CI 명령어를 사용하면 됩니다. PNPM은 CI 환경을 감지하면 기본적으로 작동합니다.
00:07:52Clean install은 락 파일에 명시된 그대로만 설치합니다.
00:07:57락 파일과 package.json이 맞지 않으면 즉시 중단하고 오류를 냅니다.
00:08:01멋대로 예측해서 설치하지 않죠. 따라서 공격자가
00:08:05바뀐 버전을 몰래 끼워 넣을 수 없습니다. 물론 락 파일을
00:08:09버전 관리 시스템에 커밋해야 효과가 있습니다. .gitignore에 넣지 마세요.
00:08:13어릴 때 코딩 처음 배울 땐 락 파일을 무시하는 건 줄 알았는데, 커밋하길 잘했죠.
00:08:17이제 마지막 팁, 습관에 대한 이야기입니다.
00:08:21무조건적인 업데이트를 멈추세요. `npm update`를 실행해서
00:08:25모든 걸 한 번에 최신으로 바꾸는 건 공격자들이 아주 좋아하는 행동입니다.
00:08:30하나씩 업그레이드하며 왜 필요한지 스스로 물어봐야 합니다.
00:08:35두 번째 습관은 점점 중요해지고 있습니다. 의존성을 줄이세요.
00:08:39의존성을 추가할 때마다 공격 표면이 늘어납니다.
00:08:43공격은 의존성의 의존성을 통해 퍼지니까요. Lodash나 Axios 대신
00:08:48작은 코드 조각이나 내장 fetch로 충분한지 고민하세요.
00:08:53요즘 같은 AI 코딩 시대엔 AI에게 간단한 기능을 짜달라고 하는 게 훨씬 쉽죠.
00:08:58세 번째는 의존성 버전을 고정하는 것입니다. 업그레이드가
00:09:03의도적인 선택이 되도록 말이죠. 하지만 이건 직접 선언한 패키지에만 적용됩니다.
00:09:08하위 의존성은 여전히 범위를 가질 수 있으므로
00:09:12아까 말한 릴리스 기간 제한이 중요합니다.
00:09:17이 모든 것을 조합하면 실수로 악성 패키지를 설치하지 않을 거라는 안심을 얻을 수 있습니다.
00:09:21절대 무적은 아니지만, 아무것도 안 하는 것보다는 훨씬 낫습니다.
00:09:25궁극적인 방법은 강화된 개발 컨테이너를 쓰는 거지만,
00:09:29좀 번거로워서 결국 로컬 개발로 돌아오게 되더군요.
00:09:34다른 좋은 방어법이 있다면 댓글로 알려주세요.
00:09:38구독해주시고, 늘 그렇듯 다음 영상에서 뵙겠습니다.
00:09:42시청해 주셔서 감사합니다.

Key Takeaway

릴리스 기간 제한 설정, 설치 스크립트 차단, 그리고 전문 보안 도구 활용을 통해 NPM 패키지 생태계의 공급망 공격을 사전에 효과적으로 방어할 수 있다.

Highlights

  • 릴리스 기간 제한 설정을 통해 새로운 패키지 설치 시 발생할 수 있는 공급망 공격을 차단할 수 있다.

  • NPM, PNPM, BUN 등 각 패키지 매니저는 각각 다른 시간 단위(일, 분, 초)로 릴리스 기간 제한 설정을 지원한다.

  • 패키지 설치 직후 자동으로 코드를 실행하는 설치 스크립트 기능을 차단하면 악성 코드 실행을 막을 수 있다.

  • MPQ와 Socket Firewall 같은 무료 도구를 사용하면 패키지가 기기에 도달하기 전에 악성 여부를 검사할 수 있다.

  • CI/CD 환경에서는 'npm ci'나 '--frozen-lockfile'을 사용하여 락 파일에 명시된 버전만 설치하도록 강제해야 한다.

  • 의존성 최소화와 함께 패키지 업데이트 시 일괄 업데이트 대신 하나씩 검토하며 진행하는 습관이 필요하다.

Timeline

공급망 공격 방어를 위한 설정

  • 패키지 설치 시 최신 버전 즉시 다운로드를 멈추는 릴리스 기간 제한 기능을 활용한다.
  • NPM은 .npmrc 파일에서 일 단위, PNPM은 pnpm-config.yaml에서 분 단위, BUN은 bunfig.toml에서 초 단위로 제한을 설정한다.
  • npx와 bunx는 이 설정을 무시하고 항상 최신 버전을 설치하므로 주의가 필요하다.

대부분의 공급망 공격은 패키지 배포 후 몇 시간 이내에 드러나므로, 새로운 패키지를 바로 설치하지 않는 것만으로도 대부분의 위험을 피할 수 있다. 이 설정은 프로젝트별 또는 전역으로 구성 가능하며, 필요시 명령줄을 통해 임시로 우회할 수 있다.

설치 스크립트 제어 및 git 의존성 차단

  • 패키지 설치 직후 코드를 실행하는 스크립트 기능을 차단하여 악성 코드 실행 권한을 원천 봉쇄한다.
  • PNPM과 BUN은 설치 스크립트 실행 제어를 기본적으로 지원하며, NPM은 Lavamote의 allow-scripts 패키지가 대안이 된다.
  • git URL을 통한 의존성 선언은 레지스트리를 우회할 수 있어 공격 수단으로 악용되므로 차단하는 것이 좋다.

설치 스크립트는 정당한 용도로 만들어졌으나 대부분의 공급망 공격이 이를 통해 악성 코드를 실행한다. 명시적으로 허용된 패키지에만 스크립트 실행을 허용하는 허용 목록 기반 방식을 사용하여 안전성을 높인다.

외부 도구 활용 및 락 파일 검증

  • MPQ와 Socket Firewall을 사용하여 설치 전 패키지의 악성 여부와 취약점을 검사한다.
  • 락 파일을 버전 관리 시스템에 반드시 커밋하여 공격자가 의도적으로 패키지를 교체하는 것을 방지한다.
  • 락 파일을 직접 검토하거나 lockfile-lint와 같은 도구로 무결성 해시와 소스를 확인한다.

패키지가 기기에 도달하기 전 미리 검사하는 도구는 데이터베이스 기반 탐지와 AI 위협 분석을 수행한다. 또한 락 파일은 공격자가 다운로드 URL을 조종하는 것을 막는 핵심 방어선이므로, CI/CD 환경에서 이를 철저히 검증하는 것이 필수적이다.

보안을 위한 개발 습관

  • 일괄 업데이트를 지양하고 의존성 개수를 최소화하여 공격 표면을 줄인다.
  • 의존성 버전을 고정하여 의도적인 업그레이드 선택이 이루어지도록 한다.
  • 안전을 위해 로컬 개발 환경에서의 주기적인 보안 설정 점검을 습관화한다.

의존성이 늘어날수록 공격 통로도 확장되므로 내장 기능을 최대한 활용하는 것이 바람직하다. 버전 고정과 검토 과정을 통해 보안 사고를 최소화하며, 무결점은 아니더라도 보안 태세를 갖추는 것이 중요하다.

Community Posts

No posts yet. Be the first to write about this video!

Write about this video