TanStack 및 수많은 패키지가 받은 영향 - 심층 분석 및 조사

MMaximilian Schwarzmüller
Computing/SoftwareBusiness NewsInternet Technology

Transcript

00:00:00지금 또 하나의 거대하고, 정말 엄청난 공급망 공격이 발생했으며 여전히 진행 중입니다
00:00:06이 공격은 NPM에서 파이썬 생태계로까지 확산되었으니 지금 당장은 그 어떤
00:00:12NPM이나 파이썬 패키지도 설치하지 마세요. 그리고 전반적으로 시스템을 안전하게 설정해야 합니다. 관련해서
00:00:19다른 영상이 있으니 아래에 링크를 공유하겠고, 이번 영상에서도 다시 다루겠지만 먼저
00:00:23무엇이 영향을 받았는지, 본인이 대상인지 확인하는 방법에 대한 세부 정보를 알려드리고 싶습니다. 시작은
00:00:30TanStack 패키지들인 TanStack query, TanStack router, TanStack start 등이었습니다. 어제인 5월 11일,
00:00:36꽤 짧은 시간 동안 몇몇 악성 패키지들, 정확히는 모든 TanStack 패키지들이
00:00:43악성 버전으로 배포되었고 20분 만에 신속하게 격리되었습니다. 결국
00:00:50빠르게 감지되고 격리되었지만, 이 모든 악성 패키지들은 해당 기간,
00:00:57즉 이 짧은 시간 동안 배포되었습니다. 그리고 나서 계속 확산되었고 현재도
00:01:03확산 중입니다. 사용자가 4명뿐인 Mistral 패키지까지 퍼졌지만, 여전히
00:01:09영향을 받았는데, 이 맬웨어가 웜처럼 작동하여 데이터와 자격 증명을 훔치기 때문입니다. 여러분의 것도
00:01:16시스템에 설치되었다면 잠재적으로 위험합니다. 본인이 영향을 받았는지 확인하는 방법은 잠시 후에
00:01:20다시 설명하겠지만, 이것이 설계된 의도대로 더 많은 NPM 패키지로 계속 확산되었고
00:01:26심지어 파이썬 생태계까지 침투했으며 지금도 진행 중입니다. 이건 겨우 몇 시간 전의 일이고,
00:01:32제가 녹화하는 시점 기준으로는 2시간 전입니다. 자, 어떻게 영향을 받았는지 확인할까요?
00:01:39제 경우인 독일 시간 기준으로 어제 저녁에 TanStack 패키지를 하나라도 설치했다면,
00:01:45피해를 입었다고 간주해야 합니다. 그 시간대에 설치했다면, 해당 시간은
00:01:54UTC 기준이므로 본인의 시간대로 변환해서 생각해야 하며, 그 시간대라면 영향을 받은 것으로
00:02:00보아야 합니다. 하지만 이것이 Mistral 패키지와 제가 나열할 수 있는 것보다 훨씬 더 많은 자바스크립트 패키지로
00:02:06확산되고 있기 때문에, 여러분 자신이나 여러분의 기기가 영향을 받고 침해당했다고 생각해야 합니다.
00:02:13관련 게시물 링크를 아래에 공유해 드릴 테니 더 깊이 파고들어 배포된 모든
00:02:18영향받은 패키지의 전체 목록을 확인해 보세요. 하지만 언급했듯이 여전히 진행 중이므로,
00:02:22지금은 아무것도 설치하지 않는 게 좋을 수도 있습니다. 또한 침해 지표들도 존재합니다.
00:02:31특정 파일 해시들, 즉 라우터의 JS 파일에 대한 SHA 해시들을 찾아야 합니다. 이 게시물도 아래에 링크하겠습니다.
00:02:38그리고 본인의 기기에서 어떤 네트워크 요청이 발생했는지 모니터링할 방법이 있다면,
00:02:42이 URL로 향하는 아웃고잉 트래픽을 확인해 보세요. 이는 시스템에서 데이터가
00:02:48유출되었다는 또 다른 명확한 지표가 될 것입니다. 침해란 구체적으로 무엇을 의미할까요? 이는
00:02:55이 맬웨어가 크게 두 가지 일을 한다는 뜻입니다. 첫 번째 중요한 일은 데이터를 수집하는 것입니다.
00:03:03NPM 토큰, GitHub 토큰, AWS 자격 증명 및 기타 비밀 정보를 찾습니다. 즉, 자격 증명과 비밀 정보가
00:03:12저장되는 전형적인 위치를 시스템에서 스캔하여 수집한 다음, 제가 보여드린
00:03:18이 URL로 전송합니다. 그렇게 비밀 정보를 훔치는 것이죠. 하지만 그게 전부가 아닙니다. 언급했듯이,
00:03:26이것은 웜으로 작동하므로, 훔친 GitHub 토큰들도 활용합니다. 예를 들어,
00:03:33이 토큰들과 NPM 토큰을 사용하여 더 많은 오염된 패키지를 배포합니다. 여러분이 다른 패키지의
00:03:40관리자이거나, 해당 시간대에 실행된 CI/CD 워크플로가 있고 그것이
00:03:46어떤 TanStack 패키지에 의존하고 있었다면, 그 CI/CD 워크플로에서 악성 코드가 포함된,
00:03:53오염된 TanStack 패키지가 다운로드되었을 것입니다. 악성 코드가 거기서 실행되었을 수도 있고요. 그러면
00:04:00사용자의 기기가 아닌 해당 워크플로 내에서 특정 자격 증명을 훔쳐
00:04:06해당 CI/CD 워크플로가 빌드하려던 패키지의 악성 버전, 즉 오염된 버전을
00:04:14배포할 수도 있게 됩니다. 이것이 확산 방식입니다. 말씀드린 대로 웜처럼 행동하는 것이죠.
00:04:20훔친 자격 증명과 토큰을 사용하여 더 많은 오염된 패키지를 배포하는 것입니다. 그렇게
00:04:26Mistral로, 그리고 다른 자바스크립트 패키지로, 심지어 파이썬 생태계까지 퍼지게 됩니다.
00:04:32이것이 현재 상황입니다. 제가 아는 바로는 여전히 확산 중입니다. 그렇다면 어떻게
00:04:39방어할 수 있을까요? 제 다른 채널인 AkataMind에 관련 영상을 만들었습니다. 아래에 링크해 두겠습니다.
00:04:44요약하자면, 가능하다면 코드를 실행하거나 개발 작업을 수행할 때
00:04:51로컬 기기에서 직접 하지 말고, 가상 머신이나 데브 컨테이너(dev container) 같은 곳에서 하세요.
00:04:57가공되지 않은 비밀 정보를 기기에 저장하지 마세요. 예를 들어 AWS의 경우,
00:05:03기기에 IAM 자격 증명을 저장하는 대신 싱글 사인온(SSO) 방식을 사용하고,
00:05:10사용 중인 다른 서비스들에 대해서도 유사한 기술을 사용하세요. 추가로,
00:05:16비밀 정보를 로컬 하드 드라이브나 .env 파일에 저장하지 않고 클라우드에 저장하기 위해
00:05:25InPhysical이나 Doppler 같은 서비스를 사용하는 것도 고려해 볼 만합니다. 그렇게 하시는 게 좋습니다.
00:05:30다시 말씀드리지만, 그런 내용은 해당 영상에서 다룹니다. 또한 Bun이 지원하는 것처럼
00:05:38최소 릴리스 연령(minimum release age)을 설정할 수 있는 패키지 매니저와 구성을 사용하는 것이 좋습니다.
00:05:44bunfig.toml 파일에서 최소 릴리스 연령을 설정하면, bun install을 실행하더라도
00:05:49이 예시의 경우 최소 X초 또는 X일이 지난 패키지만 설치하도록 보장할 수 있습니다.
00:05:56pnpm에도 유사한 기능이 있고, npm의 최신 버전들에도 비슷한 기능이 있습니다.
00:06:02역시 다른 영상에서 다뤘습니다. Bun 같은 도구를 사용하거나 npm 설정을 제대로 해두면,
00:06:09예를 들어 Bun은 기본적으로 이를 수행하는데, 설치하려는 패키지의 수명 주기 스크립트인
00:06:15post install 스크립트 같은 것들의 실행을 차단합니다. 이는 또 다른 보안 메커니즘을 제공하는데,
00:06:21이러한 맬웨어들이 보통 시스템에서 그런 스크립트가 실행되는 것에 의존하기 때문입니다.
00:06:28따라서 안전한 패키지 매니저 혹은 패키지 매니저의 안전한 설정을 사용하고, 코드를 가상 머신이나
00:06:36데브 컨테이너에서 실행하며 시스템에 평문 비밀 정보를 저장하지 않는 것. 이것이 일반적으로
00:06:41권장되는 방법이지만, 이러한 공격들이 점점 더 심각해지고 있기 때문에 지금은 더욱 중요합니다.
00:06:46이 공격이 어떻게 작동했는지 살펴볼 텐데 정말 흥미롭습니다. 물론 우리는 앞으로 이런 일을
00:06:52더 많이 겪게 될 것입니다. 저는 거의 매달 또는 그보다 더 자주 이런 영상을 만들고 있는데,
00:06:58그 이유 중 하나는 이러한 공격이 실행하기가 더 쉬워졌다고 믿기 때문입니다. 이제 AI 시대에는
00:07:04영향을 주고자 하는 패키지나 종속성을 분석하고, 잠재적인 공격 벡터를 찾기 위해 소스 코드나
00:07:12CI/CD 설정을 분석하는 것이 더 쉬워졌습니다. 이번 TanStack의 경우도 바로 그런 일이 일어났습니다.
00:07:22유지 관리자의 기기가 감염된 것이 아니라, TanStack의 CI/CD 워크플로가
00:07:28공격을 받은 것입니다. 이 부분으로 다시 돌아오겠습니다. AI로 취약점을 찾는 것이 더 쉬워졌고,
00:07:34악성 코드를 포함한 코드를 작성하는 것도 물론 더 쉬워졌습니다. 동시에 소프트웨어가 폭발적으로 증가했습니다.
00:07:40그 어느 때보다 많은 소프트웨어가 작성되고 있습니다. 따라서 보안에 크게 신경 쓰지 않는
00:07:45많은 타겟을 포함하여, 공격할 대상이 더 많아졌습니다. 그래서 이러한 공격들이
00:07:51공격자들에게 더 매력적으로 다가오는 것입니다. 자, 이 모든 일이 어떻게 시작되었을까요?
00:07:57정말 흥미롭습니다. 말씀드린 것처럼 완전히 새로운 방식은 아니며 전에 본 적이 없는 것도
00:08:03아니지만, 여전히 꽤 정교합니다. TanStack 팀은 공격이 어떻게 일어났는지 설명하는
00:08:09사후 분석 기사를 게시했습니다. 그 링크도 아래에 걸어두겠습니다.
00:08:15하지만 여기서 요약을 해드리자면, 결과적으로 이번 공격은 제가 자세히 설명할
00:08:22주요 세 단계에 의존했습니다. 'Pull request target pwn request' 패턴. 이게 무엇인지
00:08:30설명하겠습니다. 그다음 '포크 기반 신뢰 경계를 넘나드는 GitHub Actions 캐시 오염'과
00:08:38'OIDC 토큰의 런타임 메모리 추출'입니다. 자, 이게 다 무슨 뜻일까요? 다시 말씀드리지만,
00:08:45모든 세부 사항은 기사를 읽어보시면 되지만, 제가 요약을 해드리겠습니다. 먼저
00:08:50'Pull request pwn request' 패턴부터 시작하죠. 그게 뭘까요? 이를 이해하려면
00:08:58GitHub Actions가 GitHub에서 제공하는 CI/CD 솔루션이자 제품이라는 점을 이해해야 합니다.
00:09:05참고로 GitHub Actions 설정 방법, CI/CD 작업에 제품을 사용하는 방법,
00:09:10패키지나 웹사이트 배포 방법 등을 배우고 싶으시다면 관련 강의도 준비되어 있습니다.
00:09:16모든 CI/CD 워크플로 도구와 마찬가지로 GitHub Actions는 워크플로를 트리거하는 이벤트에 의존합니다.
00:09:24CI/CD의 핵심은 무언가를 자동화된 방식으로 처리하는 것이니까요. 예를 들어 메인 브랜치에 푸시할 때
00:09:29웹사이트를 자동화된 방식으로 릴리스하거나 배포하는 것 등이 있습니다.
00:09:34워크플로를 트리거할 수 있는 다양한 이벤트가 있으며, 푸시(push)가 그중 하나입니다.
00:09:40메인 브랜치에 푸시하면 특정 작업을 수행하라고 명령할 수 있으며, 여러 브랜치에 대해
00:09:44필터링할 수도 있습니다. 그러면 종속성을 설치하고, 프로젝트를 빌드하고,
00:09:49서버에 업로드하는 등의 작업을 수행할 수 있죠. 자, 또 다른 트리거는
00:09:56'pull_request_target'입니다. 이 트리거는 본인의 저장소에
00:10:05풀 리퀘스트(PR)가 생성되었을 때 활성화됩니다. 즉, 누구나 본인의 저장소를 포크하고,
00:10:14거기서 무언가를 한 뒤 자신의 포크에 푸시하고, 본인의 저장소에 PR을 보낼 수 있다는 뜻입니다.
00:10:19그러면 이 워크플로가 트리거됩니다. 위험하게 들리나요? 실제로 좀 그렇습니다. 그리고 이게 공격의 시작이었습니다.
00:10:25일반적인 'pull_request' 트리거도 있습니다. 방금 'pull_request_target'을 말씀드렸지만,
00:10:31'pull_request' 역시 동일한 방식으로 작동하지만, 이는 CI/CD 워크플로를
00:10:38포크된 저장소의 컨텍스트에서 실행합니다. 따라서 거기서 어떤 악성 행위가 일어나더라도,
00:10:45기본 저장소가 아닌 포크된 저장소에서 발생하므로 문제가 되지 않습니다.
00:10:52반면에 'pull_request_target'은 기본 저장소의 컨텍스트에서 실행됩니다. 그리고 그것은
00:10:58잠재적으로 위험합니다. 누구나 PR을 열 수 있기 때문입니다. 이번
00:11:04TanStack 공격의 경우, 공격자는 해당 PR, 즉 해당 포크에
00:11:10악성 코드인 웜 코드와 맬웨어를 TanStack 저장소의 포크 안에 포함시켰습니다.
00:11:20그런 다음 공격자가 PR을 열었고, 이것이 'pull_request_target'의 실행으로 이어졌습니다.
00:11:26말씀드린 대로 그렇게 되면 GitHub Actions 러너가 가동되고, 기본 저장소의 컨텍스트에서
00:11:33실행되게 됩니다. 이것이 무엇을 의미할까요?
00:11:40공격자가 기본 코드에 접근하거나 악성 코드를 저장소에 병합할 수 있다는 뜻은 아니지만,
00:11:46예를 들어 거기서 사용되는 캐시가 기본 저장소에서 비롯된
00:11:53후속 GitHub Actions 실행들과 공유된다는 것을 의미합니다. 푸시 트리거와 같이 완전히
00:12:00다른 훅이나 이벤트 트리거로부터 발생한 실행들과도 말이죠.
00:12:05다음에 일어난 일은 캐시 오염이었습니다. 그런데 이게 무슨 뜻일까요?
00:12:11공격자는 자신의 포크에 코드를 추가하여, 'pull_request_target' 트리거로
00:12:17GitHub Actions가 실행될 때, 'hash files' 명령을 실행하도록 했습니다.
00:12:23이는 GitHub Actions가 지원하는 명령으로, 캐시에 무언가를 저장하는 데 쓰입니다.
00:12:28자, 그 캐시의 목적은 무엇일까요? GitHub Actions 캐시의 아이디어는 단순히
00:12:33워크플로의 속도를 높이는 것입니다. 예를 들어 종속성들을 해싱할 수 있습니다. 만약
00:12:39패키지가 의존하는 종속성이 바뀌지 않았다면, 굳이 전체 설치 과정을 다시
00:12:46거칠 필요가 있을까요? 시간은 곧 돈입니다. GitHub Actions 워크플로의 실행 시간에 따라 요금이
00:12:52청구되기 때문이죠. 그리고 당연히 워크플로가 영원히 걸리는 것을 원치 않으실 겁니다.
00:12:56그래서 대개의 워크플로에서, 예를 들어 TanStack 패키지를 빌드할 때,
00:13:00TanStack 패키지의 종속성을 설치한 다음 빌드 단계를 진행하여 패키지를 빌드합니다.
00:13:06다시 말하지만, TanStack의 종속성들이 변경되지 않았다면 왜 다시 설치하겠습니까?
00:13:12이것이 캐싱의 배경이고 일리가 있는 방식입니다. 하지만 문제는,
00:13:18문제는 pull request target GitHub actions 실행과 다른 GitHub action 실행들이,
00:13:24동일한 컨텍스트를 공유하기 때문에 캐시도 공유한다는 점입니다. 여기서 바로
00:13:31캐시 오염이 발생합니다. 공격자가 악성 버전을 캐싱하거나 악성 코드를
00:13:39TanStack의 종속성에 밀어 넣어 캐싱할 수 있었기 때문입니다. 그 후 공격자는
00:13:46TanStack 패키지에 대한 정상적인 GitHub Actions 워크플로가 실행되기를 기다리기만 하면 됐습니다.
00:13:53즉, 관리자가 코드를 푸시하면, 그 GitHub Actions 실행은 이전에 악성 실행에 의해
00:14:01설정된 동일한 캐시를 재사용하게 되고, 이제 악성 코드가 포함되어 준비된
00:14:08오염된 캐시를 끌어오게 됩니다. 이것이 악성 코드가 포크에서 시작해
00:14:13악성 코드의 영향을 받지 않은 일반 관리자의 정상적인 푸시에 의한 워크플로로 침투한 방법입니다.
00:14:21캐시가 두 GitHub Actions 실행 사이에서 전송 수단으로 사용된 셈입니다.
00:14:28그리고 세 번째 단계로, 악성 코드가 푸시 이벤트로 인해 정상적인
00:14:35TanStack CI/CD 워크플로 실행에 침투하자, 수명이 짧은 NPM 토큰, 즉 OIDC 토큰을
00:14:44훔쳐서 TanStack 패키지의 악성 버전을 배포하는 데 사용했습니다.
00:14:54제가 무엇을 말하는 걸까요? NPM에는 '신뢰할 수 있는 배포(trusted publishing)'라는 기능이 있는데,
00:15:00이론적으로는 NPM 패키지 배포를 더 안전하게 만들어줍니다. 왜냐하면
00:15:04NPM 패키지를 배포하는 방법은 크게 두 가지가 있다고 볼 수 있기 때문입니다. 하나는
00:15:11NPM 계정으로 토큰을 생성하고 이를 사용하여 새 버전의 패키지를 배포하는 것입니다. 문제는
00:15:19그 토큰을 도난당하면 누구든 해당 패키지의 새 버전을 배포할 수 있다는 점입니다. 보안을
00:15:26강화하기 위해 NPM은 '신뢰할 수 있는 배포' 프로세스를 도입하여, 본인의 기기에서 직접 패키지를
00:15:33배포할 수 없고 신뢰할 수 있는 공급자 중 하나를 거쳐야 한다고 규정했습니다. GitHub Actions가 그중
00:15:37하나이며, 이를 위해 설정할 수 있는 통합 기능이 있습니다. 그러면
00:15:44그 프로세스의 일부로 수명이 짧은 배포용 토큰이 검색되거나
00:15:50요청됩니다. 그리고 그 짧은 수명의 토큰이 배포되는 새 패키지 버전에
00:15:57서명하는 데 사용됩니다. 이론적으로는 어떤 관리자의 기기에도 토큰이 없기 때문에 훔치기 어렵고,
00:16:03게다가 수명도 짧다는 것이 장점입니다. 도난당하더라도 오래 활성화되지 않으니까요.
00:16:08문제는 해당 신뢰할 수 있는 토큰을 요청하는 CI/CD 워크플로에서 실행되는
00:16:15코드 자체가 감염되었다면, 그 악성 코드가 이 갓 생성된 따끈따끈하고 수명이 짧은
00:16:21신뢰할 수 있는 배포 토큰에 접근할 수 있게 된다는 점입니다. 바로 그 일이 여기서
00:16:27발생했습니다. 악성 코드는 이 토큰을 남용하거나 사용하여 NPM API에 접속했고,
00:16:36TanStack 패키지의 새 버전을 배포했습니다. 흥미롭게도 이 공격은 사실 약간 실패했는데,
00:16:44신뢰할 수 있는 토큰을 얻어 NPM API를 통해 웜과 악성 코드가 포함된
00:16:52TanStack 패키지의 새 버전을 배포하긴 했지만, 실제로는 CI/CD에 푸시된
00:16:58코드에 문제가 있어서 완료되지 못한 GitHub Actions 워크플로로 끝났기 때문입니다. 공격자들이
00:17:06유효한 코드가 푸시되는 시점에 공격을 수행하도록 주의를 기울였다면,
00:17:12해당 워크플로는 성공적으로 완료되었을 것이고, 그들은 NPM API에 개별적으로 접속해
00:17:19악성 패키지를 수동으로 배포할 필요 없이, 실제 워크플로에 악성 코드를 주입하여
00:17:26워크플로가 성공적으로 끝나게 뒀을 것입니다. 그러면 오염된 버전의 TanStack은 관리자의
00:17:32정상적인 푸시였고 워크플로도 성공했기 때문에 겉보기에는 매우 정당해 보였을 것입니다.
00:17:38공격이 진행된 방식이 워크플로 실패로 이어졌기 때문에, 외부 기여자가 결과적으로
00:17:45이 공격이 가능했던 이유는 해당 워크플로가 성공적으로 끝나지 않았기 때문이며,
00:17:51결과적으로 외부 기여자가 무슨 일이 일어나고 있는지 파악하기가 조금 더 수월했습니다.
00:18:00GitHub Actions 워크플로가 실패했음에도 불구하고 TanStack 패키지의
00:18:05새 버전이 게시된 것을 볼 수 있었기 때문이죠. 원래는 게시되지 않았어야 합니다.
00:18:12이러한 불일치 덕분에 공격을 감지하기가 좀 더 쉬웠고, 이 부분은
00:18:19TanStack 유지관리자들과 우리 모두에게 운이 좋았던 면이 있습니다. 하지만 상당히 정교한 공격이었죠.
00:18:26누구의 컴퓨터도 해킹하지 않고 이루어졌으며, 비록 빠르게 발견되었음에도
00:18:32심각한 피해를 입혔습니다. 말씀드렸듯이 여전히 확산 중이기 때문입니다.
00:18:41이 모든 내용에 대해 긴 에피소드였다는 것을 알지만, 여러분의 시스템을
00:18:49안전하게 만드는 데 힘써야 한다는 점을 강조하고 싶습니다. 이 영상에서 공유했듯이
00:18:56피해를 입을 위험을 줄여야 합니다. 이번 공격은 금방 발견되었지만 여전히 퍼지고 있습니다.
00:19:05그래서 아직 끝난 것이 아니며, 향후의 모든 공격이
00:19:11이렇게 빨리 발견되지 않을 수도 있습니다. 이번에는 운이 좀 좋았던 편이라
00:19:18감지가 더 어려웠다면 피해는 훨씬 컸을 것입니다. 지금도 이미 피해가 상당하고
00:19:24아직 끝나지 않았습니다. 앞으로도 이런 공격은 더 많아질 것이라 확신합니다.
00:19:31공격 표면이 점점 더 넓어지고 흥미로워지고 있기 때문입니다. 코드를 쓰는 사람은 많아졌지만
00:19:36자신이 무엇을 하는지 모르는 이들도 많고, AI는 이런 공격을 수행하는 데 도움을 줍니다.
00:19:42이것이 현재 상황입니다. 꼭 필요한 게 아니라면 설치를 자제하고 설정을 다시 확인하세요.
00:19:48더 자세히 알고 싶거나 영향을 받은 패키지 목록을 보려면 아래 링크를 확인해 주세요.
00:19:51목록 등을 확인해 보세요.

Key Takeaway

GitHub Actions의 pull_request_target과 캐시 공유 취약점을 이용한 공급망 공격이 발생하여 TanStack 및 파이썬 패키지 전반으로 확산 중이므로, 로컬 환경 대신 가상 머신을 사용하고 패키지 설치 시 최소 릴리스 연령 설정을 적용해야 합니다.

Highlights

  • 2026년 5월 11일 TanStack Query, Router, Start를 포함한 모든 TanStack 패키지가 악성 코드에 감염된 채 배포되었습니다.

  • 이 공격은 NPM을 넘어 파이썬(Python) 생태계까지 확산 중이며, 사용자 수가 4명뿐인 Mistral 패키지도 감염되었습니다.

  • 악성 코드는 웜(Worm)처럼 작동하여 NPM, GitHub, AWS 자격 증명과 비밀 토큰을 탈취한 후 외부 URL로 전송합니다.

  • TanStack의 배포 시스템인 CI/CD 워크플로 자체가 공격을 받아 관리자의 기기가 감염되지 않았음에도 악성 패키지가 생성되었습니다.

  • Bun 패키지 매니저의 '최소 릴리스 연령(minimum release age)' 설정을 활용하면 최근 배포된 위험한 패키지 설치를 차단할 수 있습니다.

  • 공격자는 GitHub Actions의 pull_request_target 이벤트를 악용하여 캐시를 오염시키고 정상적인 빌드 과정에 악성 코드를 주입했습니다.

Timeline

TanStack 및 파이썬 생태계 공급망 공격 현황

  • NPM에서 시작된 공급망 공격이 파이썬 생태계로 확산되고 있습니다.
  • TanStack Query와 Router를 포함한 모든 TanStack 패키지가 20분 동안 악성 버전으로 노출되었습니다.
  • 감염된 패키지는 웜처럼 동작하며 시스템 내의 데이터와 자격 증명을 탈취합니다.

5월 11일 발생한 이 공격은 매우 짧은 시간 동안 진행되었으나 신속하게 격리되기 전 이미 수많은 패키지로 퍼졌습니다. 사용자 기반이 매우 작은 Mistral 패키지까지 영향을 받은 것은 이 악성 코드가 스스로를 복제하고 확산하는 능력을 갖췄음을 의미합니다. 현재 진행 중인 공격이므로 신뢰할 수 없는 패키지 설치를 즉시 중단해야 합니다.

감염 여부 확인 및 피해 지표

  • UTC 기준 5월 11일 저녁 시간대에 TanStack 패키지를 설치했다면 감염된 것으로 간주합니다.
  • 라우터의 JS 파일에 대한 특정 SHA 해시값 유무로 시스템 침해 여부를 판단합니다.
  • 시스템의 아웃고잉 네트워크 트래픽 중 특정 악성 URL로 향하는 요청이 있는지 모니터링해야 합니다.

독일 시간 기준 어제 저녁에 패키지를 설치한 사용자들은 즉각적인 점검이 필요합니다. 공격자는 데이터를 외부로 유출하기 위해 특정 URL을 사용하며, 네트워크 로그 분석을 통해 데이터 유출 여부를 명확히 확인할 수 있습니다. 공개된 침해 지표(IOC)와 파일 해시 목록을 대조하여 기기 오염 상태를 확인하는 과정이 필수적입니다.

악성 코드의 작동 방식과 데이터 탈취 경로

  • 악성 코드는 시스템을 스캔하여 NPM, GitHub, AWS 자격 증명이 저장된 위치를 찾아냅니다.
  • 탈취한 GitHub 토큰을 사용해 공격자가 관리 권한을 가진 다른 패키지에 악성 코드를 심어 배포합니다.
  • CI/CD 워크플로 내에서 실행되어 개발자의 로컬 기기가 아닌 빌드 서버의 비밀 정보를 훔칩니다.

이 맬웨어는 단순한 정보 탈취를 넘어 웜 기능을 수행합니다. 탈취한 토큰을 이용해 감염된 사용자가 관리하는 또 다른 오픈소스 프로젝트에 악성 버전을 배포함으로써 기하급수적으로 확산됩니다. 특히 CI/CD 파이프라인에서 실행될 경우 해당 워크플로가 생성하는 결과물 자체를 오염시켜 신뢰할 수 있는 배포 과정을 공격의 도구로 전락시킵니다.

시스템 보호를 위한 보안 권장 사항

  • 로컬 기기 대신 가상 머신이나 데브 컨테이너(dev container) 환경에서 코드를 실행합니다.
  • AWS IAM 자격 증명 대신 SSO 방식을 사용하고 .env 파일에 평문 비밀 정보를 저장하지 않습니다.
  • Bun이나 pnpm을 사용하여 패키지의 최소 릴리스 연령(minimum release age)을 설정합니다.

평문 자격 증명을 하드 드라이브에 저장하는 습관은 공격에 매우 취약합니다. InPhysical이나 Doppler 같은 비밀 정보 관리 서비스를 클라우드 기반으로 사용하여 로컬 노출을 최소화해야 합니다. 또한 최신 패키지 매니저 기능을 활용해 배포된 지 얼마 지나지 않은 패키지의 설치를 강제로 차단하고, post install 스크립트 실행을 방지하는 보안 설정을 적용하는 것이 효과적입니다.

공격의 핵심 기술: PR Target 및 캐시 오염

  • 공격자는 GitHub Actions의 pull_request_target 트리거를 공격의 시작점으로 이용했습니다.
  • 포크된 저장소의 악성 코드가 기본 저장소의 캐시 영역을 오염시키는 '캐시 포이즈닝' 기법이 사용되었습니다.
  • 오염된 캐시는 이후 관리자가 수행하는 정상적인 빌드 워크플로에 주입되었습니다.

이 공격은 유지 관리자의 개인 컴퓨터를 해킹한 것이 아니라 CI/CD 시스템의 논리적 허점을 찔렀습니다. pull_request_target은 기본 저장소의 컨텍스트에서 실행되므로 외부 PR이 캐시를 생성하거나 수정할 수 있게 합니다. 관리자가 새로운 코드를 푸시할 때 시스템은 효율성을 위해 오염된 캐시를 그대로 가져다 썼고, 결과적으로 정상적인 배포 과정에 악성 코드가 섞여 들어가게 되었습니다.

OIDC 토큰 추출과 배포 프로세스 악용

  • NPM의 '신뢰할 수 있는 배포(trusted publishing)' 과정에서 생성되는 단기 OIDC 토큰이 메모리에서 탈취되었습니다.
  • 악성 코드는 이 토큰을 사용하여 NPM API에 직접 접속하고 TanStack의 악성 버전을 게시했습니다.
  • 공격 과정에서 발생한 워크플로 실패와 배포 기록 간의 불일치 덕분에 공격이 감지되었습니다.

신뢰할 수 있는 배포 방식은 보안을 강화하기 위해 도입되었으나, 실행 환경 자체가 감염되면 무력화됩니다. 공격자는 수명이 짧은 토큰을 가로채 수동으로 악성 패키지를 배포했습니다. 다행히 공격자의 코드 실수로 GitHub Actions 워크플로가 실패했음에도 불구하고 패키지가 게시된 이상 징후가 포착되어 빠른 격리가 가능했습니다. AI 기술의 발달로 이러한 복잡한 취약점 분석과 공격 코드 작성이 더욱 쉬워지고 있어 주의가 필요합니다.

Community Posts

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

Write about this video