Transcript
00:00:00샤이 훌루드가 네 번째 속편으로 돌아왔습니다.
00:00:02이번엔 TanStack 같은 패키지들을 노리고 있죠.
00:00:04제가 Next.js 영상을 올린 지 불과 몇 시간 만에 말이죠,
00:00:07정말 절묘한 타이밍이었습니다.
00:00:08이번 건은 단순한 TanStack을 넘어선
00:00:11대규모 NPM 공급망 공격입니다.
00:00:13UiPath, Mistral 같은 패키지들은 물론,
00:00:15그 외 160개가 넘는 패키지가 포함되었습니다.
00:00:17Guardrails.ai 같은 PyPy 패키지들까지 포함해서요.
00:00:20이번 공격이 더 흥미로운 점은
00:00:22데드맨 스위치가 포함됐다는 겁니다.
00:00:24탈취한 키를 변경했다는 게 감지되면,
00:00:26당신의 PC를 완전히 삭제해 버리죠.
00:00:28게다가 국제 정세 요소까지 반영되어 있습니다.
00:00:30자, 자세히 알아보죠.
00:00:36이번 속편에서 '벌레(The Worm)'의 목표는 동일합니다.
00:00:39개발자 머신과 CI/CD 러너에서 자격 증명을 훔치고,
00:00:42그 자격 증명으로 더 많은 패키지에 접근하는 것이죠.
00:00:44TanStack의 경우, 단 몇 분 만에 42개 패키지에 걸쳐
00:00:4784개의 악성 버전을 배포하는 결과를 낳았습니다.
00:00:51이제 어떻게 TanStack을
00:00:52감염시켰는지 살펴보겠지만,
00:00:54우선 이런 감염된 패키지를 설치하면
00:00:56악성 코드가 어떤 일을 하는지 먼저 보죠.
00:00:58악성 패키지 내부를 보면,
00:00:59routerinit.js라는 새 파일과 함께
00:01:02주입된 옵션 의존성(optional dependency)이 발견됩니다.
00:01:04이 의존성은 겉보기엔
00:01:05합법적인 TanStack 라우터 GitHub 링크처럼 보이지만,
00:01:08사실은 공격자의 포크(fork)에 있는 고립된 커밋입니다.
00:01:10이건 GitHub가 포크 링크를 처리하는 방식일 뿐이라,
00:01:13URL만 봐서는 마치
00:01:14원래 프로젝트의 것인 양 보일 수 있죠.
00:01:16하지만 커밋은 사실 포크에서 온 겁니다.
00:01:18그 포크 안에는 'prepare'라는
00:01:20수명 주기 스크립트가 있어 'bun run task runner JS'를 실행하고,
00:01:22마지막에 exit 1을 포함합니다.
00:01:24이는 페이로드가 실행된 후 옵션 의존성만
00:01:27실패하게 만드는 영리한 방법이죠.
00:01:28그래서 설치는 정상적으로 계속 진행되고,
00:01:30설치 로그에는 흔적을 덜 남기게 됩니다.
00:01:33그리고 제가 아까 언급했던
00:01:35routerinit.js 파일이 실행되지 않는다고 생각할 수도 있겠지만,
00:01:37일단 이 두 파일이
00:01:38이름만 다를 뿐 같은 역할을 한다고 보시면 됩니다.
00:01:40핵심은 설치 시 이 스크립트가 실행된다는 거죠.
00:01:42가장 먼저 하는 일은
00:01:44일반적인 설치 흐름에서 자신을 분리하는 것입니다.
00:01:46그래서 백그라운드에서
00:01:47이미 실행 중인지 확인하고, 아니라면
00:01:50스스로의 분리된 복사본을 생성(fork)한 뒤
00:01:51부모 스크립트를 깔끔하게 종료합니다.
00:01:53이렇게 하면 npm 설치 로그에
00:01:54스크립트의 출력이 전혀 남지 않는데,
00:01:57멀웨어가 이미 프로세스에서
00:01:58분리되어 백그라운드에서 돌고 있기 때문이죠.
00:02:00그 후 아주 영리한 짓을 합니다.
00:02:01자신의 복사본을 당신의 'Clawed Code' 후크 디렉토리에 쓰고,
00:02:04해당 프로젝트에서 Clawed Code를 사용할 때마다
00:02:06이 후크를 실행하도록 설정합니다.
00:02:07이렇게 하면 원래 설치 과정이 끝난 후에도 살아남아
00:02:08프로젝트에서 Clawed Code를
00:02:10열 때마다 계속 재실행됩니다.
00:02:12VS Code의 작업 실행기(task runner)에서도
00:02:13똑같은 짓을 하죠. 그 안에 자신을 복제해서,
00:02:16VS Code의 작업 공간 자동 실행 기능을 쓰면
00:02:17똑같은 문제에 직면하게 됩니다.
00:02:20심지어 'GitHub Token Monitor'라는
00:02:22OS 수준의 서비스까지 설정하는데,
00:02:23그건 나중에 다시 다루죠. 아주 악랄하니까요.
00:02:26아직 구독 안 하신 것도 꽤 악랄하네요.
00:02:28그다음 페이로드가 하는 일은
00:02:29'GitHub Token Monitor'라는 OS 수준의 서비스를 설정하는데,
00:02:31아주 샅샅이 뒤지죠.
00:02:32GitHub Actions에서는 러너 환경 내의
00:02:34자격 증명과 비밀 정보를 찾습니다.
00:02:35더 구체적으로는 마스킹된 비밀 정보를 포함하여
00:02:37GitHub Actions 러너 워커 프로세스 메모리를
00:02:38스크래핑하고,
00:02:40CodeQL처럼 위장한 GitHub 워크플로우를 삽입해
00:02:41저장소 비밀 정보를 직렬화한 뒤 나중에
00:02:43탈취해 갑니다.
00:02:45AWS 비밀 정보도 노리는데,
00:02:47처음에는 환경 변수와 로컬 설정 파일을 노리지만,
00:02:48IMDS v2나 ECS 작업 메타데이터 같은
00:02:50AWS 메타데이터 서비스도 공격합니다.
00:02:52심지어 코드QL처럼 보이는 가짜 GitHub 워크플로우를 삽입해서
00:02:55인증서를 훔쳐 클러스터 내 API 접근 권한을 얻는데,
00:02:57해당 포드의 서비스 계정이 가진 RBAC 권한에 따라,
00:02:58잘못 구성된 클러스터에서는
00:03:00관리자급으로까지 권한이 확장될 수 있습니다.
00:03:02더 나쁜 건 HashiCorp Vault도 노린다는 겁니다.
00:03:03Vault 관련 환경 변수와 토큰을 모조리 수집한 뒤,
00:03:06탈취한 Kubernetes 권한을 이용해
00:03:09Vault로 관리되는 비밀 정보를 모두 가져가죠.
00:03:11이건 단지 CI 배포 환경에 하는 짓일 뿐입니다.
00:03:14워크스테이션에 있다면 SSH 키,
00:03:17NPM 자격 증명, Git 자격 증명,
00:03:19쉘 기록, 클라우드 제공업체 자격 증명,
00:03:21암호화 키, 그리고 시그널, 슬랙,
00:03:22디스코드 파일까지 전부 노립니다.
00:03:24게다가 Clawed Code의
00:03:25HashiCorp Vault까지 노려서,
00:03:27만약 Clawed에 자격 증명을 줬거나,
00:03:29비밀 정보가 포함된 파일을 읽게 했다면,
00:03:30그 정보들도 모두 접근 가능한 겁니다.
00:03:32말씀드렸다시피, 손에 넣을 수 있는
00:03:34모든 것을 노리고 있습니다.
00:03:35그리고 이 데이터는 Session 메신저 네트워크를 통해
00:03:37외부로 유출하죠.
00:03:38백업으로 탈취한 데이터를
00:03:39GitHub 저장소에 올려두기도 합니다.
00:03:41공격 컨셉에 맞춰
00:03:42브랜치 이름도 듄(Dune) 관련 명칭을 쓰죠.
00:03:43자, 자격 증명까지 다 털렸습니다.
00:03:44더 나빠질 게 있을까요?
00:03:45네, 더 있습니다.
00:03:45있고요.
00:03:45아까 머신에 설정된
00:03:46서비스를 기억하시나요?
00:03:47그 서비스는 GitHub 토큰을 계속 모니터링하며
00:03:49재유출 시도를 합니다.
00:03:51매분마다 토큰이 여전히 유효한지 확인하고,
00:03:53만약 유효하지 않다면
00:03:55사용자 디렉토리에 'rm -rf'를 실행해
00:03:56모든 것을 날려버립니다.
00:03:57또한 당신의 자격 증명을 이용해
00:03:58NPM 토큰을 생성하면서,
00:04:00설명란에 이런 문구를 넣습니다.
00:04:02'이 토큰을 취소하면,
00:04:02소유자의 컴퓨터를 삭제하겠다.'
00:04:04GitHub 저장소에 버렸죠.
00:04:05모든 공격의 테마에 맞춰서
00:04:07이 브랜치들은 듄(Dune) 관련 이름으로 명명되었습니다.
00:04:09자, 이제 자격 증명까지 털렸네요.
00:04:11이보다 더 나쁠 순 없겠죠?
00:04:12글쎄요.
00:04:13아니요, 더 나빠질 수 있습니다.
00:04:14그뿐만이 아닙니다.
00:04:15아까 제가 말씀드린
00:04:16여러분의 컴퓨터에 설치된 그 서비스가
00:04:18여러분의 GitHub 토큰을 계속 감시합니다.
00:04:19검사 로직이 포함되어 있습니다.
00:04:21만약 그렇다면
00:04:22그냥 멈춰버리죠.
00:04:24그리고 이스라엘이나 이란의
00:04:25기기로 보이면 1에서 6 사이의
00:04:27무작위 숫자를 생성합니다.
00:04:28그 숫자가 2라면,
00:04:30파괴적인 삭제 명령을 실행하고
00:04:31MP3 파일을
00:04:32최대 볼륨으로 재생합니다.
00:04:33안타깝게도,
00:04:35그 MP3가 무엇인지
00:04:36찾아낼 수는 없었네요.
00:04:38어쨌든,
00:04:39이제 모든 일이 끝났지만,
00:04:40최악의 상황은 아직 오지 않았습니다.
00:04:42이제 겨우 1단계였으니까요.
00:04:442단계는 자기 전파인데,
00:04:46이번 공격에서 가장 위험한 부분입니다.
00:04:47우선,
00:04:48당신의 머신에서 이중 인증(2FA) 없이
00:04:49게시할 수 있는 유효한
00:04:51NPM 토큰을 찾습니다.
00:04:53하나라도 찾으면,
00:04:53해당 계정이 접근할 수 있는 모든 패키지를
00:04:54스캔한 뒤,
00:04:55그 자격 증명을 사용해
00:04:56스스로를 그 패키지들에 추가하고
00:04:58새로운 감염된 버전을 배포합니다.
00:04:59정말 끔찍한 일이지만,
00:05:00애초에 이중 인증을 우회할 수 있는
00:05:01토큰을 방치해 두는 건
00:05:03좋지 않은 일입니다.
00:05:04가장 무서운 부분은,
00:05:05이 공격이 CI/CD 내에서
00:05:05실행될 때 발생하는 일입니다.
00:05:07CI 환경에서는,
00:05:07공격자가 수명이 긴
00:05:08NPM 토큰이 필요하지 않은데,
00:05:09제대로 구축된 환경이라면
00:05:11OIDC를 사용하기 때문이죠.
00:05:13이 부분은 나중에 더 자세히 다루겠습니다.
00:05:15그럼 이제,
00:05:16무엇이 문제인지
00:05:16다시 한번 짚어보죠.
00:05:17사실 많은 개발자가
00:05:19자신의 자격 증명을
00:05:19너무 쉽게 다루고 있습니다.
00:05:21이제 어떻게 이런 공격을
00:05:22방지할 수 있을지
00:05:24살펴볼 시간입니다.
00:05:26가장 먼저 해야 할 일은
00:05:26의존성 검사를 강화하는 것인데,
00:05:28이는 매우 기초적인 단계입니다.
00:05:30하지만 그것만으로는 충분하지 않습니다.
00:05:32더 나아가, 개발 환경에서
00:05:33어떤 스크립트가
00:05:33실행되는지 모니터링해야 합니다.
00:05:34우회할 수 있는
00:05:352단계 인증을
00:05:36더 무서운 버전의 이 공격은
00:05:38어떤 일이 벌어질지
00:05:39CI/CD 내부에서 실행될 때입니다.
00:05:41왜냐하면 CI에서는
00:05:42공격자가 굳이
00:05:43장기 NPM 토큰이
00:05:44필요 없기 때문이죠.
00:05:45좋은 설정들은 종종 OIDC를 사용하는데,
00:05:47이게 더 안전하다고 여겨집니다.
00:05:48본질적으로,
00:05:49NPM 토큰을
00:05:50비밀값으로 저장하는 대신,
00:05:51GitHub Actions가 NPM에 증명합니다.
00:05:53“저기,
00:05:53내가 이 저장소이고
00:05:54이 워크플로우를 실행 중이며
00:05:55이 브랜치에서 실행 중이야.”
00:05:56그러면 NPM은
00:05:57단기 게시용 토큰을 줍니다.
00:05:59하지만 여기서 문제는
00:06:00스크립트가 권한을 얻으면
00:06:01신뢰받는 GitHub Actions 환경에 접근하여
00:06:03합법적인 게시자와 동일한 위치에
00:06:04설 수 있다는 점입니다.
00:06:06그래서 멀웨어는
00:06:07GitHub이 작업에 노출하는
00:06:08OIDC 관련 환경을 사용하여
00:06:10GitHub 토큰 엔드포인트에서
00:06:12OIDC JWT 토큰을 요청합니다.
00:06:14그 다음 그 JWT 토큰을
00:06:16NPM과 교환하여
00:06:17단기 게시용 토큰을 받습니다.
00:06:18NPM 신뢰 게시 시스템을
00:06:19통해서 말이죠.
00:06:20이제 공격자는
00:06:22영구적인 NPM 토큰을
00:06:22훔칠 필요 없이
00:06:24완전히 합법적으로 보이며 게시할 수 있습니다.
00:06:26이 경우,
00:06:26멀웨어는 그 router init.js 파일
00:06:27복사본을 패키지 테이블에
00:06:29함께 묶어두고,
00:06:30악성 선택적 의존성을
00:06:31추가한 뒤,
00:06:32전부 최신 태그로
00:06:33그 패키지를
00:06:34게시합니다.
00:06:35그래서 누군가
00:06:35또는 CI/CD 파이프라인이
00:06:37이 패키지들을 설치하면
00:06:38루프가 다시 시작되어
00:06:40가능한 한 멀리
00:06:40퍼져나가게 됩니다.
00:06:42정말
00:06:42미친 소리 같죠?
00:06:43하지만 이제
00:06:44환자 제로인,
00:06:46TanStack에 집중해 봅시다.
00:06:46어떻게 처음부터
00:06:47감염된 걸까요?
00:06:48글쎄요,
00:06:49그들의 사후 분석에 따르면
00:06:50공격자가 그
00:06:51GitHub Actions 파이프라인을 악용했습니다.
00:06:53그들은 악성 패키지가
00:06:53실제로 게시되기 전날부터
00:06:54움직였습니다.
00:06:56TanStack router의 포크를 만들었는데
00:06:57이름을
00:06:58configuration으로 바꿔서
00:06:59찾기 어렵게
00:06:59만들려고 했죠.
00:07:01뻔한 포크 이름들을
00:07:02검색해서 찾기 힘들게요.
00:07:04그러고 나서 그들은
00:07:04이 포크에 악성 커밋을
00:07:05추가했는데,
00:07:06Claude가 작성한 것처럼 조작했고
00:07:07커밋 메시지에는
00:07:07skip CI가
00:07:08접두어로 붙어서
00:07:09푸시 이벤트 시 바로
00:07:10CI가 실행되지 않게 했습니다.
00:07:11다음 날,
00:07:13그들은 TanStack router에
00:07:13PR을 열었는데
00:07:14이름은 '진행 중인 작업(WIP):
00:07:15기록 빌드 단순화'였습니다.
00:07:16여기서 실제 공격이 발생하죠.
00:07:18요약하자면
00:07:18TanStack은 bundle-sized GitHub Actions 워크플로우를
00:07:20사용했는데 그게
00:07:21pull request target을
00:07:22사용했다는 점이 주목할 만합니다.
00:07:23왜냐하면 pull request target은
00:07:25포크가 아니라
00:07:26베이스 저장소 보안 컨텍스트에서
00:07:27실행되기 때문이죠.
00:07:28즉, 베이스 저장소의
00:07:29캐시 범위와
00:07:30GitHub 토큰에
00:07:31접근할 수 있다는 뜻입니다.
00:07:32그래서 이 워크플로우는 PR을 체크아웃하고,
00:07:33의존성을 설치한 뒤,
00:07:35벤치마크 빌드를 실행했습니다.
00:07:35하지만 문제는 그 포크에
00:07:36악성 코드가 포함되어 있었다는 점입니다.
00:07:38이 경우,
00:07:39PNPM 패키지 저장소를
00:07:40오염시키는
00:07:40셋업 스크립트였는데,
00:07:41릴리스 액션이
00:07:42나중에 사용할
00:07:43바로 그 캐시 키 아래에서
00:07:44말이죠.
00:07:45그들은 워크플로우가 사용하는
00:07:47정확한 수식을 사용하여
00:07:48공용 PNPM 락 파일을 통해
00:07:49이를 미리 계산했습니다.
00:07:50오염된 캐시가 저장되자,
00:07:51그들은 그 브랜치를
00:07:52현재 메인 브랜치와 일치하도록
00:07:54재설정했습니다.
00:07:56그래서 보이는 PR은
00:07:57파일 변경이 없는
00:07:57노옵(no-op)처럼 보였고,
00:07:58그 뒤 그들은
00:07:59해당 PR을 닫고
00:07:59악성 브랜치를 삭제했습니다.
00:08:00외부에서 보면
00:08:01아무 일도 일어나지 않은 것처럼
00:08:02보이지만,
00:08:03그들은 GitHub Action 캐시를
00:08:04오염시킨 겁니다.
00:08:05즉, 8시간 후,
00:08:06평범한 관리자가
00:08:07관련 없는 PR을
00:08:07메인에 병합했을 때,
00:08:08TanStack의
00:08:09릴리스 워크플로우가 트리거되어
00:08:10그 GitHub 액션 캐시를 오염시킨 거죠.
00:08:11이제 공격자가 제어하는 코드가
00:08:12그 릴리스 액션 내부에서
00:08:13실행되게 된 겁니다.
00:08:14그 후 OIDC와 동일한 로직을 사용하여
00:08:15NPM 게시 토큰을 얻었고,
00:08:1642개의 TanStack 패키지에 걸쳐
00:08:17자기 자신의
00:08:1884개 버전을 게시했습니다.
00:08:19심지어 액션의
00:08:20패키지 게시 단계까지
00:08:22갈 필요조차 없었습니다.
00:08:23우스운 점은,
00:08:24일부 테스트가 실패해서
00:08:25액션이 실제로 실패했는데,
00:08:26그 단계까지 도달하지 못했음에도
00:08:28악성 코드가 실행되어
00:08:29그 모든 것을
00:08:30어쨌든 게시해 버렸다는 거죠.
00:08:32공격자는 이렇게
00:08:33세 개의 신뢰 경계를
00:08:34연쇄적으로 통과했습니다.
00:08:35첫째,
00:08:36포크 PR 코드가
00:08:36베이스 저장소의 캐시를 오염시켰고,
00:08:37둘째,
00:08:39베이스 저장소의 캐시가
00:08:40실제 릴리스 워크플로우 안에서 복원되었으며,
00:08:41셋째,
00:08:43실제 릴리스 워크플로우가
00:08:43OIDC 권한을 가지고 있어서
00:08:44NPM 게시 권한으로 변환되었기에
00:08:46완전히
00:08:47합법적으로 보이는 패키지를
00:08:47게시할 수 있었던 겁니다.
00:08:48이게 바로 제가 생각하는
00:08:49공급망 공격의
00:08:51정말 무서운 점입니다.
00:08:52그들은 이제 한 관리자의 토큰을
00:08:53훔치는 것에서 벗어나
00:08:54CI/CD 시스템 자체를
00:08:56악용하는 쪽으로 가고 있습니다.
00:08:57즉, 우리의 모든 신뢰 신호가
00:08:58오히려 공격자를 위해
00:08:59작동하기 시작했다는 겁니다.
00:08:59이건 실제 워크플로우에 의해 게시된
00:09:01유효한 증명을 가진
00:09:02서명된 패키지였습니다.
00:09:03자, 여기까지가 ShaiHalud4입니다.
00:09:05혹시라도 이 패키지 중 하나로
00:09:05공격받았는지
00:09:06확인하고 싶으시다면,
00:09:07제가 아래에
00:09:08블로그 포스트 링크를 남겨둘게요.
00:09:10어떻게 알 수 있는지,
00:09:11만약 설치했다면
00:09:12무엇을 해야 하는지가
00:09:13담겨 있습니다.
00:09:14이 모든 상황과
00:09:15NPM 생태계에 대해
00:09:16어떻게 생각하는지 댓글로 알려주세요.
00:09:18댓글 남기는 김에
00:09:19바로 ShaiHalud4입니다.
00:09:20그럼 늘 그렇듯,
00:09:21다음 영상에서 뵐게요.
00:09:21이런 패키지들에 의해
00:09:23제가 링크를
00:09:23아래 블로그 포스트에 남겨둘게요.
00:09:25그 포스트에서
00:09:25어떻게 확인할 수 있는지,
00:09:26그리고 어떻게 대처해야 하는지
00:09:27이 패키지들 중 하나라도
00:09:28설치했다면 알 수 있을 겁니다.
00:09:29이 모든 것에 대해
00:09:30여러분의 생각이 어떤지
00:09:30댓글로 알려주시고,
00:09:31NPM 생태계에 대해서도요.
00:09:33댓글 남기는 김에,
00:09:33구독도 부탁드려요.
00:09:34언제나 그렇듯,
00:09:34다음 영상에서 뵐게요.
Community Posts
No posts yet. Be the first to write about this video!
Write about this video