TeamPCP SANDCLOCK 침해가 드러낸 GitHub 공급망 보안 설계

TeamPCP의 SANDCLOCK 자격증명 탈취 사례를 바탕으로 GitHub Actions, OIDC, SBOM, 사고 대응을 설계하는 공급망 보안 가이드

2026-08-14 · 최초 발행 2026-05-14

2026년 초 TeamPCP(구글 GTIG 추적명 UNC6780)는 Trivy, Checkmarx KICS, LiteLLM처럼 개발자가 신뢰하는 도구의 GitHub 저장소를 노렸다. 이들은 SANDCLOCK 자격증명 탈취 악성코드를 심어 500만 개 이상의 머신에 영향을 끼쳤고, 탈취한 클라우드 시크릿은 랜섬웨어 그룹과의 협력을 통해 수익화됐다.

이 사건은 취약점 스캐너나 CI/CD 액션을 신뢰 목록에 올려두는 것만으로는 공급망을 방어할 수 없다는 점을 보여준다. 저장소, 워크플로우, 자격증명, 런타임 네트워크 행위를 하나의 방어 경계로 다뤄야 한다.

보안 도구를 경유한 TeamPCP의 침해 경로

TeamPCP는 2025년 9월부터 활동을 시작한 금전적 동기의 위협 행위자다. PCPcat, ShellForce, DeadCatx3 등의 이름으로도 추적되며, 2026년 2월 말부터 3월까지 개발자 생태계를 겨냥한 공급망 공격 시퀀스를 실행했다.

대상 도구 역할 침해 방식
Trivy 컨테이너 취약점 스캐너 GitHub Actions 워크플로우 악성 코드 삽입
Checkmarx KICS 인프라 코드 보안 스캐너 CI/CD 파이프라인 독성화
LiteLLM AI 게이트웨이 공식 컨테이너 엔트리포인트 대체
Telnyx 통신 플랫폼 의존성 체인 오염

공격자는 보안 검증 도구를 통해 자격증명을 수집했다. 도구가 겉으로 정상 동작하더라도, CI/CD 러너의 시크릿과 실행 환경은 이미 외부로 전달될 수 있는 구조였다.

SANDCLOCK이 수집하고 검증하는 정보

구글 GTIG가 SANDCLOCK으로 명명한 악성코드는 3단계 페이로드 구조로 자격증명을 수집한다. 수집 이후에는 유출한 키의 유효성을 확인하고 클라우드 환경을 탐색해 수익화 단계로 이어진다.

(1) 침투: 저장소 악성 코드삽입(2) 실행: CI/CD 러너에서 악성페이로드 작동(3) 수집: 환경 지문 수집시크릿 탈취환경 변수 탈취(.env 파일, AWS/Azure 설정)메모리 추출(GitHub Actions OIDC 토큰,SSH 키)파일시스템 스캔(Kubernetes 설정, 크립토지갑)(4) 암호화 외부 유출C2 서버 전송 (300GB 이상데이터)(5) 자격증명 검증(TruffleHog 활용 실시간 API호출)(6) 클라우드 환경 탐색(AWS IAM, S3, ECS 열거)(7) 수익화(랜섬웨어 그룹 협력 데이터판매)

탈취 대상은 특정 클라우드 계정에 한정되지 않는다.

  • 클라우드 자격증명: AWS 액세스 키/시크릿, Azure 서비스 주체, GCP 서비스 계정 키
  • CI/CD 시크릿: GitHub Actions 시크릿, OIDC 토큰, npm 배포 토큰
  • 인프라 키: SSH 개인 키, Kubernetes 설정 파일, kubeconfig 토큰
  • 애플리케이션 시크릿: .env 파일의 데이터베이스 패스워드, API 키, OAuth 토큰

Wiz CIRT에 따르면 초기 악성코드 배포 수 시간 후인 2026년 3월 19일에는 이미 탈취 시크릿을 사용한 활동이 탐지됐다. 공격자는 TruffleHog로 자격증명의 유효성을 자동 검증한 뒤 24시간 이내에 피해자의 AWS 환경 탐색을 시작했다.

저장소와 의존성에서 신뢰 경계를 검증하는 방법

공급망 공격 대응은 커밋의 출처와 변경 내용을 확인하는 단계에서 시작한다. 브랜치 보호 규칙으로 서명과 리뷰, 보안 검사를 강제하고, 배포 산출물의 구성 정보를 SBOM으로 남겨야 한다.

커밋 서명은 저장소 변경의 검증 기준을 제공한다.

# GitHub 저장소 브랜치 보호 설정 예시
# 모든 커밋에 GPG 서명 요구
git config --global commit.gpgsign true
git config --global user.signingkey <GPG_KEY_ID>

# GitHub CLI로 브랜치 보호 규칙 적용
gh api repos/{owner}/{repo}/branches/{branch}/protection \
  --method PUT \
  --field required_signatures=true \
  --field required_status_checks='{"strict":true,"contexts":["security-scan"]}' \
  --field required_pull_request_reviews='{"required_approving_review_count":2}'

SBOM 자동 생성은 의존성의 출처와 구성을 추적하는 기반이다. Syft, SPDX, CycloneDX 형식을 사용해 의존성 정보를 관리할 수 있다.

# .github/workflows/sbom-generation.yml
name: SBOM Generation & Attestation
on:
  push:
    branches: [main]
permissions:
  id-token: write
  contents: read
  attestations: write
jobs:
  sbom:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          format: spdx-json
          output-file: sbom.spdx.json
      - name: Attest SBOM
        uses: actions/attest-sbom@v1
        with:
          subject-path: ./dist
          sbom-path: sbom.spdx.json

새 버전의 오픈소스 의존성은 차이분석을 거쳐야 한다. TeamPCP가 Trivy 저장소에 삽입한 악성 커밋처럼, 워크플로우 의존성의 변경은 CI 실행 전에 검토 대상이 되어야 한다.

# GitHub Actions 워크플로우 의존성 잠금 (2026 신기능)
# workflow YAML의 dependencies 섹션으로 의존성 SHA 고정
dependencies:
  actions/checkout: "sha256:abc123..."
  aquasecurity/trivy-action: "sha256:def456..."

StepSecurity의 Harden-Runner나 Socket Security 같은 도구는 CI 실행 중 네트워크 이상 접근을 실시간으로 감지한다. 취약점 스캐너가 외부 C2 서버로 데이터를 전송하는 경우 즉시 경보가 발생하도록 구성할 필요가 있다.

장기 시크릿을 OIDC 단기 자격증명으로 바꾸기

OIDC(OpenID Connect)는 2026년 현재 CI/CD 파이프라인 보안의 표준 접근 방식이다. 워크플로우 실행마다 단기 토큰을 발급받으면 장기 유효 시크릿을 제거할 수 있다.

"AWS 서비스""AWS STS""GitHub OIDC 공급자""GitHub Actions 러너""AWS 서비스""AWS STS""GitHub OIDC 공급자""GitHub Actions 러너""장기 시크릿 없음 - 만료 시 자동 무효화""(1) OIDC 토큰 요청""(2) 단기 JWT 토큰 발급 (수분 유효)""(3) AssumeRoleWithWebIdentity 요청""(4) OIDC 토큰 검증 및 신뢰 정책 확인""(5) 임시 AWS 자격증명 (1시간 유효)""(6) AWS 작업 수행"
# OIDC를 활용한 AWS 인증 예시
name: Deploy with OIDC
on:
  push:
    branches: [main]
permissions:
  id-token: write  # OIDC 토큰 요청 권한
  contents: read
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
          role-session-name: GitHubActions-${{ github.sha }}
          aws-region: ap-northeast-2
          # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 시크릿 불필요!

조직은 OIDC 마이그레이션으로 정적 클라우드 자격증명의 95%를 제거할 수 있다. TeamPCP가 토큰을 탈취하더라도 수분 내 만료되는 토큰은 악용할 수 없다.

시크릿 노출을 줄이려면 GitHub의 보호 기능도 함께 설정해야 한다.

보안 통제 설정 방법 TeamPCP 대응 효과
Push Protection Settings > Code security > Secret scanning > Push protection 시크릿 커밋 사전 차단
Secret Scanning Alerts 모든 저장소 자동화 유출 시크릿 즉시 탐지
Dependabot Security Updates .github/dependabot.yml 취약 의존성 자동 업데이트
Required Reviewers for Actions 브랜치 보호 규칙 악성 워크플로우 수동 승인
Pinned Actions (SHA) uses: action@sha256:... 손상된 액션 태그 회피

AWS Secrets Manager의 로테이션 자동화는 유출 이후 자격증명 교체를 반복 가능한 절차로 만든다.

# Lambda를 활용한 데이터베이스 자격증명 자동 로테이션
import boto3

def rotate_secret(event, context):
    client = boto3.client('secretsmanager')
    secret_id = event['SecretId']
    token = event['ClientRequestToken']
    step = event['Step']
    
    if step == 'createSecret':
        # 새 자격증명 생성
        new_password = generate_secure_password()
        client.put_secret_value(
            SecretId=secret_id,
            ClientRequestToken=token,
            SecretString=json.dumps({'password': new_password}),
            VersionStages=['AWSPENDING']
        )
    elif step == 'finishSecret':
        # 새 버전을 AWSCURRENT로 이동
        client.update_secret_version_stage(
            SecretId=secret_id,
            VersionStage='AWSCURRENT',
            MoveToVersionId=token
        )

침해 직후 워크플로우와 자격증명을 끊는 절차

자격증명 탈취가 의심되면 대응 속도가 중요하다. Wiz CIRT 분석에서는 시크릿 탈취 후 24시간 이내에 AWS 환경 탐색이 시작됐다.

**T+0 (즉시)**에는 영향받은 GitHub Actions 워크플로우를 비활성화한다.

# GitHub CLI로 워크플로우 즉시 비활성화
gh workflow disable "compromised-workflow.yml" --repo org/repo

T+15분에는 클라우드 자격증명을 일괄 무효화한다.

# AWS: 모든 액세스 키 비활성화
aws iam list-access-keys --user-name ci-user \
  --query 'AccessKeyMetadata[].AccessKeyId' --output text | \
  xargs -I{} aws iam update-access-key --access-key-id {} --status Inactive --user-name ci-user

# GitHub: 토큰 전체 취소
gh api user/installations --paginate | jq '.installations[].id' | \
  xargs -I{} gh api -X DELETE /app/installations/{}

T+1시간에는 침해 범위를 산정한다.

# AWS CloudTrail로 침해 기간 동안의 의심 API 호출 조회
aws logs filter-log-events \
  --log-group-name CloudTrail/API-calls \
  --filter-pattern '{ $.userIdentity.accessKeyId = "AKIAIOSFODNN7EXAMPLE" }' \
  --start-time $(date -d '2026-03-19' +%s000) \
  --query 'events[].message'

조사 우선순위는 TeamPCP의 사후 침해 패턴을 기준으로 잡을 수 있다.

  1. IAM 열거 흔적: iam:ListRoles, iam:GetAccountAuthorizationDetails 호출 확인
  2. S3 데이터 유출: s3:GetObject, s3:ListBuckets 비정상 패턴 분석
  3. ECS Exec 남용: ecs:ExecuteCommand로 실행된 Bash/Python 명령 조사
  4. AWS Secrets Manager 접근: secretsmanager:GetSecretValue 호출 이력
  5. 신규 IAM 자격증명 생성: 공격자가 백도어용으로 생성한 사용자/역할 식별

Microsoft Security Blog가 제시한 Trivy 공급망 침해 탐지 시그니처를 기반으로 다음과 같은 SIEM 규칙을 적용할 수 있다.

// Kusto Query Language (Microsoft Sentinel)
// TeamPCP SANDCLOCK 탈취 행위 탐지
DeviceProcessEvents
| where ProcessCommandLine has_any (
    "curl -s http://", 
    "wget -q --post-data",
    "python3 -c 'import base64"
)
| where InitiatingProcessFileName in ("trivy", "kics", "litellm")
| project Timestamp, DeviceName, ProcessCommandLine, 
          InitiatingProcessFileName, AccountName
| order by Timestamp desc

공급망 보안 성숙도를 높이는 기준

레벨 1기초 위생레벨 2자동화 스캐닝레벨 3SBOM 검증레벨 4제로트러스트 CI/CD- 의존성 버전 고정- Dependabot 활성화- 시크릿 스캐닝- SAST/SCA 자동화- 컨테이너 스캔- 악성 패키지 탐지- SBOM 자동 생성- Sigstore 서명- SLSA 프레임워크- OIDC 단기 자격증명- 에이전트 스캐닝- 런타임 행위 감시

TeamPCP 수준의 공격을 방어하려면 최소 레벨 3 이상의 성숙도가 요구된다. 2026년 GitHub의 Actions 보안 로드맵은 워크플로우 의존성 잠금(go.mod와 유사한 SHA 기반 고정), 세분화된 정책 제어, CI/CD 관찰 가능성을 포함해 레벨 4 달성을 지원한다.

신뢰하는 도구가 침해될 수 있다는 전제에서, 공급망 보안은 OIDC 기반 단기 자격증명, SBOM 자동 생성, 런타임 행위 감시를 함께 갖춰야 한다. 사고 시에는 T+0의 워크플로우 중단과 자격증명 무효화, 이어지는 피해 범위 산정이 대응의 중심이 된다.

Sources

공급망 보안GitHub ActionsSANDCLOCKOIDCSBOM자격증명 탈취