블로그로 돌아가기
보안 사고보안

보안 스캐너가 해킹 도구가 된 날 — Trivy 공급망 공격의 사이버 킬체인 분석

Aqua Security의 Trivy가 해킹당해 LiteLLM까지 연쇄 감염된 TeamPCP 공급망 공격. 사이버 킬체인 7단계와 MITRE ATT&CK 매핑으로 분석하고, NHI 거버넌스 실패가 만든 연쇄 재앙의 교훈을 정리합니다.

김동현
작성자
김동현
17
Share:
보안 스캐너가 해킹 도구가 된 날 — Trivy 공급망 공격의 사이버 킬체인 분석

Key Takeaways

  • Aqua Security의 Trivy(오픈소스 보안 스캐너)가 TeamPCP에 의해 해킹, GitHub Actions 태그 76/77개가 악성 커밋으로 force-push (2026.02~03)
  • 해킹된 Trivy가 CI/CD 파이프라인에서 실행되며 LiteLLM의 PyPI 배포 토큰을 탈취, 악성 버전(1.82.7, 1.82.8) 배포
  • .pth 파일 메커니즘으로 Python 인터프리터 시작 시마다 자동 실행 — import 없이도 감염
  • K8s 클러스터 내 lateral movement, 44개 Aqua 리포지토리 디페이스, 5개 생태계(GitHub Actions, Docker Hub, npm, Open VSX, PyPI) 침투
  • Aqua의 3/1 크리덴셜 로테이션이 불완전하고 비원자적이어서 공격자가 재침입에 성공
  • 근본 원인: long-lived PAT, 핀되지 않은 GitHub Actions, NHI 크리덴셜 거버넌스 부재

보안 도구를 믿을 수 없다면, 무엇을 믿을 수 있는가

2026년 3월 24일, 전 세계 개발자들의 CI/CD 파이프라인에서 평소와 다름없이 Trivy가 실행되고 있었습니다. 컨테이너 이미지의 취약점을 스캔하고, 보안 리포트를 생성하고, 빌드를 통과시키거나 실패시키는 일상적인 작업이었습니다.

하지만 이 "보안 스캐너"는 더 이상 보안 스캐너가 아니었습니다.

Trivy의 GitHub Actions 태그 76개가 이미 악성 커밋으로 교체된 상태였습니다. 스캐너를 실행할 때마다 정상적인 취약점 스캔이 완료된 후, 뒤에서는 GitHub Actions 러너의 프로세스 메모리를 읽어 시크릿을 탈취하고, 암호화해서 공격자의 서버로 전송하는 코드가 조용히 실행되고 있었습니다. 그리고 스캔 결과는 정상적으로 출력되었습니다. 아무도 눈치채지 못하도록.

이 공격의 가장 잔인한 점은 신뢰의 무기화입니다. Trivy는 Aqua Security가 만든 오픈소스 보안 스캐너로, GitHub에서 25,000개 이상의 스타를 가진 업계 표준 도구입니다. "보안을 위해 실행하는 도구"가 바로 공격의 진입점이 되었다는 사실은, 개발자와 보안 팀 모두의 위협 모델에 근본적인 질문을 던집니다.

공격자는 TeamPCP. 2025년 12월부터 활동을 시작해, 2026년 2월 말 Trivy에 최초 침입한 후 약 한 달간 GitHub Actions, Docker Hub, npm, Open VSX, PyPI라는 5개 생태계를 관통하는 캠페인을 벌였습니다. 이 과정에서 LiteLLM(일일 다운로드 340만 회의 AI 프록시 패키지)이 연쇄 감염되었고, Kubernetes 클러스터를 자동으로 감염시키는 웜까지 배포되었습니다.

라틴어에 이런 말이 있습니다. "Quis custodiet ipsos custodes?" — 감시자를 감시하는 자는 누구인가. TeamPCP는 이 질문에 대한 답을 보여주었습니다. 아무도 감시하지 않았다고.

이 글에서는 사이버 킬체인(Cyber Kill Chain) 7단계와 MITRE ATT&CK 프레임워크를 기준으로 이 한 달간의 캠페인을 해부합니다.

타임라인: 한 달간의 캠페인

2026년 2월 말부터 3월 25일까지 TeamPCP 캠페인 타임라인
2026년 2월 말부터 3월 25일까지 TeamPCP 캠페인 타임라인

날짜 | 이벤트 | 심각도

2025.12 | TeamPCP 활동 개시 (Docker API 익스플로잇 등) | 정찰

2026.02 말 | Trivy GitHub Actions 미스컨피그 악용, Argon-DevOps-Mgt PAT 탈취 | 초기 침입

2026.03.01 | Aqua Security 크리덴셜 로테이션 시도 — 불완전하고 비원자적 | 대응 실패

2026.03.19 17:43 UTC | trivy-action 76/77 태그 force-push, setup-trivy 7/7 태그 교체 | 공격 개시

2026.03.20 | CrowdStrike 리눅스 센서가 이상 스크립트 실행 탐지 | 탐지

2026.03.22 | 공격자 재침입. 악성 Docker 이미지(v0.69.5, v0.69.6) 푸시. 44개 Aqua 리포 디페이스 (2분 만에 완료) | 재침입

2026.03.23 | CVE-2026-33634 할당 (CVSS 9.4). Checkmarx KICS도 침해 확인 | 확산

2026.03.24 10:39 UTC | 악성 litellm 1.82.7 PyPI 배포 | 연쇄 감염

2026.03.24 10:52 UTC | 악성 litellm 1.82.8 PyPI 배포 (.pth 메커니즘 추가) | 에스컬레이션

2026.03.24 11:48 UTC | 보안 연구원 Callum McMahon이 LiteLLM 침해 공개 | 공개

2026.03.24 ~13:38 UTC | PyPI가 해당 버전 격리 (~3시간 노출) | 격리

2026.03.25 | Microsoft 탐지/방어 가이드 발행 | 후속 대응

주목할 점은 2월 말 최초 침입부터 3월 19일 본격 공격까지 약 3주의 잠복 기간이 있었다는 것입니다. 이 기간 동안 공격자는 탈취한 PAT로 인프라를 정찰하고, 도메인을 등록하고, 페이로드를 준비했습니다. 그리고 3월 1일 Aqua가 로테이션을 시도했지만 실패한 후에도, 공격자는 여전히 유효한 접근 경로를 보유하고 있었습니다.

킬체인 7단계 분석

사이버 킬체인 7단계 분석 및 MITRE ATT&CK TTP 오버레이
사이버 킬체인 7단계 분석 및 MITRE ATT&CK TTP 오버레이

Phase 1: 정찰 (Reconnaissance)

TeamPCP의 첫 번째 움직임은 Trivy의 GitHub Actions 워크플로우를 분석하는 것이었습니다.

Trivy의 GitHub Actions 설정에는 치명적인 미스컨피그가 있었습니다. 외부 풀 리퀘스트에 의해 트리거되는 워크플로우가 리포지토리 시크릿에 접근할 수 있도록 구성되어 있었던 것입니다. 이것은 GitHub Actions 보안에서 가장 잘 알려진 위험 패턴 중 하나입니다. pull_request_target 이벤트와 결합된 과도한 권한은, 사실상 외부 기여자에게 시크릿을 건네주는 것과 같습니다.

공격자가 발견한 핵심 자산은 Argon-DevOps-Mgt라는 서비스 계정의 Personal Access Token(PAT)이었습니다. 이 계정은 2023년 7월에 생성되었으며, GitHub ID 139343333을 가지고 있었습니다. 결정적으로, 이것은 long-lived PAT였습니다. 스코프가 제한된 GitHub App 토큰이 아니라, 만료 기한 없이 광범위한 권한을 가진 개인 액세스 토큰이었습니다. MFA도 요구하지 않았습니다.

MITRE ATT&CK Mapping

TacticTechniqueIDTeamPCP 적용
ReconnaissanceActive ScanningT1595GitHub Actions 워크플로우 설정 분석
ReconnaissanceGather Victim Host InformationT1592CI/CD 러너 환경 정보 수집
Resource DevelopmentObtain CapabilitiesT1588미스컨피그된 워크플로우를 통한 PAT 획득

이 단계에서 가장 주목할 점은, 공격자가 악용한 미스컨피그가 2025년 10월부터 존재했다는 것입니다. 약 5개월간 잠재적 공격 표면이 노출되어 있었지만, 아무도 이를 감지하거나 수정하지 않았습니다.

Phase 2: 무기화 (Weaponization)

PAT를 확보한 TeamPCP는 공격 인프라를 구축하기 시작했습니다.

첫 번째 작업은 타이포스쿼팅 도메인 등록이었습니다. scan.aquasecurtiy.org — "security"에서 'i'와 'y'의 위치가 바뀐 것을 눈치채셨나요? 이 미묘한 오타는 로그를 검토하는 보안 분석가의 눈도 속일 만큼 정교했습니다. 데이터가 정상적인 Aqua Security 인프라로 전송되는 것처럼 보이게 만드는 전략이었습니다.

두 번째는 악성 entrypoint.sh의 제작이었습니다. 정상적인 Trivy Action의 entrypoint.sh는 2,855바이트입니다. TeamPCP가 만든 버전은 17,592바이트. 약 6배 크기입니다. 추가된 코드는 정상적인 Trivy 스캔 로직 뒤에 숨겨져 있었고, 에러 핸들링이 의도적으로 무음 처리되어 실패해도 어떤 출력도 생성하지 않도록 설계되었습니다.

공격자는 또한 4096비트 RSA 공개 키를 하드코딩했습니다. 탈취한 모든 데이터는 이 키로 암호화된 후 전송되었기 때문에, 네트워크 트래픽을 가로채더라도 내용을 확인할 수 없었습니다. 같은 RSA 키가 이후 Trivy, KICS(Checkmarx), LiteLLM 공격에서 모두 사용되었는데, 이것이 단일 캠페인임을 확인하는 핵심 증거가 되었습니다.

MITRE ATT&CK Mapping

TacticTechniqueIDTeamPCP 적용
Resource DevelopmentDevelop Capabilities: MalwareT1587.001악성 entrypoint.sh (17,592 bytes) 제작
Resource DevelopmentAcquire Infrastructure: DomainsT1583.001aquasecurtiy.org 타이포스쿼팅 도메인 등록
Resource DevelopmentDevelop Capabilities: Digital CertificatesT1587.0034096-bit RSA 키 생성, 데이터 암호화

Phase 3: 전달 (Delivery)

git push --force로 76/77 태그를 악성 커밋으로 리다이렉트한 태그 포이즈닝 메커니즘
git push --force로 76/77 태그를 악성 커밋으로 리다이렉트한 태그 포이즈닝 메커니즘

2026년 3월 19일 17:43 UTC. TeamPCP는 탈취한 PAT를 사용해 git push --force를 실행했습니다.

이 단일 명령으로 aquasecurity/trivy-action 리포지토리의 76개 릴리스 태그(77개 중)가 악성 커밋을 가리키도록 변경되었습니다. aquasecurity/setup-trivy의 7개 태그도 전부 교체되었습니다. 유일하게 살아남은 것은 trivy-action의 v0.35.0뿐이었습니다.

이것이 태그 포이즈닝(Tag Poisoning)입니다. Git 태그는 본질적으로 특정 커밋을 가리키는 포인터에 불과합니다. git push --force로 이 포인터를 다른 커밋으로 바꾸면, 해당 태그를 참조하는 모든 워크플로우가 악성 코드를 실행하게 됩니다.

공격의 정교함은 여기서 드러납니다. TeamPCP는 악성 커밋의 Git 메타데이터(작성자, 커밋 메시지, 타임스탬프)를 위조했습니다. 커밋이 마치 정상적인 Aqua Security 개발자가 작성한 것처럼 보이게 만든 것입니다. 타임스탬프까지 2024년 7월로 백데이팅하여, 최근에 변경된 흔적을 감추려 했습니다.

이 공격이 특히 위험한 이유는 GitHub Actions에서 태그를 참조하는 방식 때문입니다. 많은 프로젝트가 워크플로우에서 이런 식으로 Action을 참조합니다:

# 위험: 태그 참조 (가변)
- uses: aquasecurity/trivy-action@v0.69.4

# 안전: 커밋 해시 참조 (불변)
- uses: aquasecurity/trivy-action@a1234567890abcdef...

태그는 가변(mutable)입니다. 같은 태그가 내일은 완전히 다른 코드를 가리킬 수 있습니다. 반면 커밋 해시는 불변(immutable)입니다. 해시 핀닝을 사용했다면, force-push가 일어나도 워크플로우는 원래의 안전한 커밋을 계속 참조했을 것입니다.

MITRE ATT&CK Mapping

TacticTechniqueIDTeamPCP 적용
Initial AccessSupply Chain Compromise: Software Supply ChainT1195.002trivy-action 태그 포이즈닝
Defense EvasionMasqueradingT1036Git 커밋 메타데이터 위조 (작성자, 타임스탬프)
PersistenceAccount ManipulationT1098탈취한 PAT로 리포지토리 태그 조작

Phase 4: 악용 (Exploitation)

태그 포이즈닝이 완료된 후, 전 세계의 CI/CD 파이프라인이 악성 Trivy를 실행하기 시작했습니다. 개발자가 직접 무언가를 설치하거나 클릭할 필요가 없었습니다. 이미 설정되어 있는 워크플로우가 자동으로 악성 코드를 가져와 실행했습니다.

악성 entrypoint.sh는 5단계로 작동했습니다.

1단계: 프로세스 열거. GitHub Actions 러너에서 실행 중인 프로세스를 식별합니다. 특히 Runner.Worker 프로세스를 타겟으로 삼았는데, 이 프로세스가 워크플로우의 환경 변수와 시크릿을 메모리에 보유하고 있기 때문입니다.

2단계: 메모리 스크래핑. GitHub 호스티드 러너에서는 /proc/{PID}/mem을 직접 읽어 프로세스 메모리를 덤프했습니다. 셀프 호스티드 러너에서는 파일시스템에서 직접 크리덴셜을 수집했습니다. 이 과정에서 SSH 키, 클라우드 크리덴셜(AWS/GCP/Azure IMDSv2 포함), Kubernetes 시크릿, Docker 설정, .env 파일, 셸 히스토리, 심지어 암호화폐 지갑 시드 구문까지 수집했습니다.

3단계: 암호화 및 전송. 수집된 데이터는 AES-256-CBC로 암호화되고, 세션 키는 하드코딩된 RSA 공개 키로 래핑된 후, tpcp.tar.gz로 번들링되어 scan.aquasecurtiy.org로 전송되었습니다.

4단계: 이중 유출 경로. HTTPS 전송이 실패할 경우를 대비해, GitHub 릴리스를 통한 대체 유출 경로도 구현되어 있었습니다.

5단계: 위장. 가장 교활한 부분입니다. 모든 악성 행위가 완료된 후, 정상적인 Trivy 스캔을 실행합니다. 워크플로우 로그에는 평소와 동일한 스캔 결과가 출력됩니다. 보안 팀이 로그를 검토해도 아무런 이상을 발견할 수 없습니다.

이 과정에서 탈취된 크리덴셜 중 하나가 LiteLLM의 PYPI_PUBLISH 토큰이었습니다. LiteLLM은 CI/CD 파이프라인에서 보안 스캐닝을 위해 Trivy를 사용하고 있었고, 핀되지 않은 버전을 참조하고 있었습니다.

MITRE ATT&CK Mapping

TacticTechniqueIDTeamPCP 적용
Credential AccessUnsecured Credentials: Credentials in FilesT1552.001.env, SSH 키, 클라우드 토큰 수확
Credential AccessOS Credential Dumping: /proc FilesystemT1003.007/proc/{PID}/mem 메모리 스크래핑
CollectionData from Information RepositoriesT1213K8s 시크릿, Docker 설정 수집
ExfiltrationExfiltration Over C2 ChannelT1041AES-256-CBC + RSA 래핑 후 HTTPS 전송
Defense EvasionIndicator RemovalT1070정상 Trivy 스캔 실행으로 위장

Phase 5: 설치 (Installation)

litellm 1.82.7 소스 인젝션 vs 1.82.8 .pth 파일 페이로드 비교
litellm 1.82.7 소스 인젝션 vs 1.82.8 .pth 파일 페이로드 비교

2026년 3월 24일 10:39 UTC. TeamPCP는 탈취한 PyPI 토큰을 사용해 litellm 1.82.7을 배포했습니다. 13분 후인 10:52 UTC에 1.82.8이 뒤따랐습니다. 두 버전은 서로 다른 감염 메커니즘을 사용했는데, 이것은 공격자가 얼마나 체계적으로 접근했는지를 보여줍니다.

litellm 1.82.7 — 소스 코드 인젝션

litellm/proxy/proxy_server.pydouble-base64 인코딩된 페이로드가 삽입되었습니다. 이 코드는 litellm --proxy 명령을 실행하거나 litellm.proxy.proxy_server를 임포트할 때 실행되었습니다. 즉, LiteLLM의 프록시 기능을 사용하는 개발자만 감염되는 조건부 트리거였습니다.

litellm 1.82.8 — .pth 파일 에스컬레이션

13분 만에 업그레이드된 두 번째 버전은 훨씬 더 공격적이었습니다. litellm_init.pth 파일이 추가되었는데, .pth 파일은 Python의 사이트 패키지 디렉토리에 위치하면 Python 인터프리터가 시작될 때마다 자동으로 실행됩니다. import가 필요 없습니다. 심지어 pip install 중에도 실행됩니다. 이것은 전통적인 import 기반 트리거보다 훨씬 효과적인 감염 메커니즘입니다.

3단계 멀웨어 페이로드:

Stage 1 — 크리덴셜 탈취: SSH 키, 클라우드 토큰(AWS/GCP/Azure), K8s 시크릿과 서비스 어카운트 토큰, Docker 설정, Git 크리덴셜, .env 파일, 셸 히스토리, 암호화폐 지갑 시드(Bitcoin, Ethereum, Solana, Monero)까지 수집.

Stage 2 — 암호화 및 유출: 32바이트 AES-256-CBC 세션 키 생성 → 데이터 암호화 → RSA-OAEP로 세션 키 래핑 → tpcp.tar.gz로 번들링 → models.litellm.cloud로 POST.

Stage 3 — 지속성 및 측면 이동: ~/.config/sysmon/sysmon.py에 백도어 설치. systemd 유저 서비스로 등록하여 5분마다 checkmarx.zone/raw를 폴링하며 추가 페이로드를 수신. 흥미로운 점은 URL에 youtube.com이 포함되면 실행을 중단하는 킬 스위치가 내장되어 있었다는 것입니다.

MITRE ATT&CK Mapping

TacticTechniqueIDTeamPCP 적용
ExecutionEvent Triggered ExecutionT1546.pth 파일로 Python 시작 시 자동 실행
PersistenceCreate or Modify System Process: Systemd ServiceT1543.002sysmon.service 등록 (5분 폴링)
Defense EvasionObfuscated Files or Information: Software PackingT1027.002Double-base64 인코딩 페이로드
Defense EvasionIndicator Removal on HostT1070에러 핸들링 무음 처리

Phase 6: 명령 & 제어 (Command and Control)

TeamPCP의 C2 인프라는 단순한 HTTP 콜백이 아니었습니다. Internet Computer Protocol(ICP) 블록체인을 C2 채널로 활용한 것입니다.

블록체인 기반 C2의 장점은 명확합니다. 전통적인 C2 서버는 도메인 차단이나 IP 블랙리스트로 무력화할 수 있지만, 블록체인에 호스팅된 C2 엔드포인트는 테이크다운이 거의 불가능합니다. 분산 네트워크의 합의를 깨야 하기 때문입니다.

CanisterWorm이라는 이름의 이 C2 메커니즘은 ICP 블록체인의 캐니스터(스마트 컨트랙트)에 명령을 저장하고, 감염된 머신의 sysmon.service가 주기적으로 이를 폴링하는 구조였습니다.

동시에 전통적인 C2 경로도 유지했습니다. checkmarx.zone/raw를 50분 간격으로 폴링하며 추가 페이로드를 다운로드했습니다. "checkmarx"라는 도메인명도 의도적인 선택입니다 — 실제로 Checkmarx의 KICS GitHub Action도 같은 캠페인에서 침해당했기 때문입니다.

MITRE ATT&CK Mapping

TacticTechniqueIDTeamPCP 적용
Command and ControlWeb Service: Dead Drop ResolverT1102.001ICP 블록체인 캐니스터를 C2 채널로 활용
Command and ControlEncrypted Channel: Asymmetric CryptographyT1573.002RSA-4096 + AES-256 암호화 통신
Command and ControlApplication Layer Protocol: Web ProtocolsT1071.001HTTPS 폴링 (checkmarx.zone)
Command and ControlMulti-Stage ChannelsT1104블록체인 C2 + HTTPS 폴링 이중 채널

Phase 7: 목표 달성 (Actions on Objectives)

Kubernetes 웜 측면 이동 - 클러스터 전체 privileged pod 배포
Kubernetes 웜 측면 이동 - 클러스터 전체 privileged pod 배포

TeamPCP의 최종 목표는 다층적이었습니다.

Kubernetes 웜: 탈취한 K8s 서비스 어카운트 토큰으로 클러스터의 모든 네임스페이스 시크릿을 열거하고, 각 노드에 node-setup-{node_name}이라는 이름의 privileged podkube-system 네임스페이스에 배포했습니다. 이 pod는 호스트 파일시스템을 마운트하고, chroot를 통해 노드 OS에 직접 접근하여 백도어를 설치했습니다. 흥미롭게도, 이란 클러스터에는 "kamikaze"라는 이름의 컨테이너를 가진 privileged DaemonSet을 배포하는 와이퍼(wiper) 컴포넌트가 적용되었고, 비이란 클러스터에는 CanisterWorm이 설치되었습니다.

44개 리포지토리 디페이스: 2026년 3월 22일 20:31:07~20:32:26 UTC. 정확히 1분 19초 만에 aquasec-com GitHub 조직의 44개 리포지토리가 스크립트에 의해 자동으로 디페이스되었습니다. 모든 리포지토리가 "tpcp-docs-" 접두사로 이름이 변경되었습니다. 이 과정에서 Tracee(런타임 보안 도구), 내부 Trivy 포크, CI/CD 파이프라인 설정, K8s 오퍼레이터 등의 소스 코드가 노출되었습니다.

5개 생태계 침투: 이 캠페인은 단일 진입점(Trivy)에서 시작해 5개 소프트웨어 생태계로 확산되었습니다:

  1. GitHub Actions — trivy-action, setup-trivy 태그 포이즈닝
  2. Docker Hub — 악성 Trivy 이미지 (v0.69.5, v0.69.6)
  3. npm — 44개 패키지 침해, CanisterWorm 배포
  4. Open VSX — VS Code 확장 프로그램
  5. PyPI — LiteLLM 백도어

LiteLLM의 경우, Wiz의 분석에 따르면 전체 클라우드 환경의 36%에 존재합니다. 약 3시간의 노출 창에서도 일일 340만 다운로드 규모를 고려하면, 잠재적 영향 범위는 막대했습니다.

MITRE ATT&CK Mapping

TacticTechniqueIDTeamPCP 적용
Lateral MovementDeploy ContainerT1610K8s privileged pod 배포 (node-setup-*)
ImpactData DestructionT1485이란 클러스터 대상 와이퍼 배포
ImpactDefacement: External DefacementT1491.00244개 Aqua 리포 디페이스 (1분 19초)
CollectionData from Cloud StorageT1530클라우드 크리덴셜로 스토리지 접근
Lateral MovementUse Alternate Authentication MaterialT1550탈취한 서비스 어카운트 토큰으로 K8s API 접근

불완전한 로테이션 — 한 번의 침해를 캠페인으로 만든 결정적 실수

이 사건에서 가장 뼈아픈 교훈은 기술적 취약점이 아닙니다. 대응의 실패입니다.

2026년 3월 1일, Aqua Security는 침해를 인지하고 크리덴셜 로테이션을 시도했습니다. 표면적으로는 올바른 대응이었습니다. 침해된 크리덴셜을 폐기하고 새로운 크리덴셜을 발급하는 것은 인시던트 대응의 기본입니다.

문제는 이 로테이션이 불완전하고 비원자적(non-atomic)이었다는 것입니다.

"비원자적"이란, 모든 크리덴셜이 동시에 교체되지 않았다는 의미입니다. 일부 크리덴셜은 폐기되었지만, 다른 접근 경로는 여전히 열려 있었습니다. 공격자는 아직 유효한 크리덴셜을 통해 재침입에 성공했고, 3월 22일에는 악성 Docker 이미지를 푸시하고 44개 리포지토리를 디페이스하는 2차 공격을 감행했습니다.

GitGuardian의 분석이 이를 정확히 짚었습니다: "한 번의 침해를 캠페인으로 만든 것은 불완전한 로테이션이었다."

이것이 NHI(Non-Human Identity) 거버넌스의 핵심 문제입니다. Argon-DevOps-Mgt 계정의 PAT는:

  • 2023년 7월에 생성되어 거의 3년간 로테이션 없이 사용
  • 스코프가 과도하게 넓어 리포지토리 태그 조작, Docker 이미지 푸시, 조직 설정 변경이 모두 가능
  • MFA가 요구되지 않아 토큰 하나만으로 전체 접근이 가능
  • 사용 모니터링이 없어 비정상적인 사용 패턴(예: 76개 태그 동시 force-push)을 탐지할 수 없었음

GitGuardian의 표현을 빌리자면: "진짜 어려운 문제는 시크릿이 유출된 후 찾는 것이 아닙니다. 진짜 어려운 문제는 그 시크릿이 공격자의 다음 거점이 되는 것을 막는 것입니다."

왜 기존 방어가 실패했는가

이 사건은 현재 소프트웨어 공급망 보안의 구조적 약점을 여러 층위에서 노출합니다.

핀되지 않은 GitHub Actions

대부분의 프로젝트가 GitHub Actions를 태그로 참조합니다 (@v1, @v0.69.4). 태그는 가변입니다. git push --force 한 번이면 같은 태그가 완전히 다른 코드를 가리키게 됩니다. 커밋 해시 핀닝(@a1b2c3d...)을 사용했다면 이 공격은 불가능했습니다. 하지만 편의성 때문에 대부분이 태그를 사용합니다. 이번 사건 이후 GitHub는 태그 핀닝을 더 강력하게 권장하기 시작했고, Dependabot도 해시 핀닝을 지원하도록 업데이트되었습니다.

Long-lived PAT의 위험

GitHub App 토큰은 스코프가 제한되고, 짧은 수명을 가지며, 설치 수준의 권한 관리가 가능합니다. 반면 PAT는 사용자 수준의 광범위한 권한을 가지며, 기본적으로 만료 기한이 없습니다. Argon-DevOps-Mgt의 PAT가 GitHub App 토큰이었다면:

  • 스코프가 특정 리포지토리와 권한으로 제한되어 태그 force-push가 불가능했을 가능성이 높고
  • 단기 수명으로 인해 2월 말 탈취되었더라도 3월 19일에는 이미 만료되었을 것이며
  • 사용 로그가 더 상세하게 기록되어 이상 탐지가 가능했을 것입니다

"보안 스캐너를 스캔하는 사람은 없다"

이것은 가장 근본적인 문제입니다. 조직은 소스 코드, 컨테이너 이미지, 의존성의 보안을 검증하기 위해 보안 도구를 사용합니다. 하지만 보안 도구 자체의 보안을 검증하는 프로세스는 거의 없습니다. Trivy를 CI/CD에 추가할 때, 그것의 GitHub Actions 워크플로우 보안 설정을 검토한 팀이 얼마나 될까요? 대부분은 "Aqua Security가 만들었으니까 안전하겠지"라는 암묵적 신뢰에 의존합니다.

이 암묵적 신뢰가 바로 공급망 공격의 핵심 공격 표면입니다.

방어선 재구축: Cremit 관점

킬체인 위의 Cremit 방어 포인트 - NHI 거버넌스 차단 지점
킬체인 위의 Cremit 방어 포인트 - NHI 거버넌스 차단 지점

이 사건의 근본 원인을 추적하면, 결국 NHI(Non-Human Identity) 거버넌스의 부재에 도달합니다.

1. NHI 인벤토리와 가시성

Argon-DevOps-Mgt PAT는 3년간 관리되지 않은 채 존재했습니다. 이것은 예외가 아니라 규범입니다. 대부분의 조직에서 서비스 어카운트, PAT, API 키, OAuth 토큰 같은 NHI 크리덴셜은 발급 후 추적되지 않습니다.

Cremit의 NHI 관리 기능은 조직 전체의 비인간 자격 증명에 대한 완전한 인벤토리를 제공합니다. 어떤 토큰이 어디서 사용되고, 언제 마지막으로 사용되었고, 어떤 권한을 가지고 있는지를 한눈에 파악할 수 있습니다. Aqua가 이런 가시성을 가지고 있었다면, Argon-DevOps-Mgt PAT의 과도한 권한과 장기 미사용을 사전에 식별하고 조치할 수 있었을 것입니다.

2. 크리덴셜 유출 실시간 탐지

TeamPCP가 GitHub Actions 메모리에서 크리덴셜을 스크래핑하는 순간, Cremit의 시크릿 탐지 엔진이 비정상적인 크리덴셜 접근 패턴을 포착할 수 있습니다. CI/CD 파이프라인 내에서 발생하는 크리덴셜 유출을 실시간으로 모니터링하고, 알려진 유출 패턴(메모리 덤프, 환경 변수 수집 등)과 매칭하여 즉각적인 경고를 제공합니다.

3. 로테이션 검증과 완전성 보장

Aqua의 가장 큰 실수는 불완전한 로테이션이었습니다. Cremit은 크리덴셜 로테이션 시 모든 연관된 접근 경로가 동시에 폐기되었는지를 검증합니다. 하나의 PAT가 여러 시스템에서 사용되고 있다면, 모든 사용처에서 교체가 완료될 때까지 로테이션을 "미완료" 상태로 유지하고 경고합니다.

4. CI/CD 파이프라인 크리덴셜 모니터링

GitHub Actions, Jenkins, GitLab CI 등 CI/CD 환경에서 사용되는 시크릿의 접근 패턴을 모니터링합니다. "평소에 주 1회 사용되던 PyPI 배포 토큰이 갑자기 외부 IP에서 접근되었다"와 같은 이상 패턴을 탐지하여, LiteLLM의 PYPI_PUBLISH 토큰이 탈취되어 악성 패키지 배포에 사용되기 전에 차단할 수 있습니다.

내일 당장 확인할 5가지

이 사건에서 배운 교훈을 즉시 적용할 수 있는 체크리스트입니다.

1. GitHub Actions 해시 핀닝

모든 서드파티 GitHub Action 참조를 태그에서 커밋 해시로 변경하세요. Dependabot이나 Renovate를 설정하면 해시 핀닝 상태에서도 자동 업데이트를 받을 수 있습니다.

# Before (취약)
- uses: aquasecurity/trivy-action@v0.69.4

# After (안전)
- uses: aquasecurity/trivy-action@a1234567890abcdef1234567890abcdef12345678

2. PAT → GitHub App 마이그레이션

long-lived PAT를 사용하는 모든 자동화를 GitHub App 기반 인증으로 마이그레이션하세요. GitHub App 토큰은 스코프가 제한되고, 단기 수명(1시간)이며, 설치 수준의 세밀한 권한 관리가 가능합니다.

3. 크리덴셜 인벤토리 수행

조직에서 사용 중인 모든 NHI 크리덴셜(PAT, 서비스 어카운트 키, API 토큰, OAuth 앱)의 인벤토리를 수행하세요. 각 크리덴셜의 생성일, 마지막 사용일, 권한 범위, 소유자를 파악하고, 과도한 권한이나 장기 미사용 크리덴셜을 식별하세요.

4. 로테이션 절차 검증

크리덴셜 침해 시 로테이션 절차가 원자적(atomic)인지 확인하세요. 하나의 크리덴셜이 여러 시스템에서 사용될 경우, 모든 사용처에서 동시에 교체되어야 합니다. Aqua의 실패처럼, 일부만 교체하고 나머지를 방치하면 공격자에게 재침입 경로를 제공합니다.

5. "보안 도구의 보안" 검토

CI/CD 파이프라인에서 사용하는 보안 도구(Trivy, Snyk, SonarQube 등) 자체의 보안 설정을 검토하세요. 이 도구들이 어떤 권한으로 실행되는지, 어떤 시크릿에 접근할 수 있는지, GitHub Actions 워크플로우 설정이 안전한지 확인하세요.

TeamPCP는 텔레그램에서 이렇게 선언했습니다: "여러분이 좋아하는 많은 보안 도구와 오픈소스 프로젝트들이 앞으로 몇 달 안에 타겟이 될 것입니다." 이 위협이 허세인지 현실인지는 아직 모릅니다. 하지만 확실한 것은, 보안 도구를 맹목적으로 신뢰하는 시대는 끝났다는 것입니다.

Quis custodiet ipsos custodes? — 이제 그 답은 당신의 NHI 거버넌스에 달려 있습니다.

관련 글:

참고 자료:

공급망 공격CI/CD 보안NHI 보안GitHubDevSecOps클라우드 보안

이 글이 유익하셨나요?

네트워크에 공유해보세요

Share: