2026년에 실제로 발생한 CI/CD 파이프라인 공급망 공격 세 가지 사례를 분석하고, GitLab 환경에서의 구체적인 방어 전략을 정리한다. 소스 코드 무결성이 아니라 "파이프라인이 무엇을 실행할지 정하는 층"이 뚫렸다는 공통점에서 출발해, 각 공격 유형에 대응하는 GitLab 기능을 유형별로 매핑한다.
핵심 포인트- 유형 1(pwn request): `pull_request_target` + 외부 포크 head 체크아웃 조합 → 저장소의 GITHUB_TOKEN과 시크릿 탈취, Trivy·Microsoft·DataDog 저장소 표적
- 유형 2(태그 재지정): 탈취된 크리덴셜로 `trivy-action` 태그 75개를 악성 커밋으로 force-push → Runner 메모리 덤프 + 50개 이상 파일 시스템 경로 스캔으로 시크릿 수집 (약 12시간 지속)
- 유형 3(워크플로 직접 삽입): 탈취 토큰으로 5,561개 저장소에 정상 CI 유지보수로 위장한 5,718개 악성 커밋 push, `id-token: write`로 OIDC 클라우드 인증 획득
- GitLab 대응 1: 보호된 변수(포크 파이프라인의 부모 변수 접근 불가 기본값) + CODEOWNERS로 `.gitlab-ci.yml` 변경 시 리뷰어 승인 강제
- GitLab 대응 2: CI/CD Components를 태그 대신 커밋 SHA로 고정(`@<commit-sha>` 형식)으로 태그 재지정 공격 무력화
- GitLab 대응 3: Pipeline execution policies(Ultimate)로 보안 Job을 개발자 수정과 무관하게 강제 주입, Job 토큰 허용 목록 + ID 토큰 audience 분리로 침해 반경 축소
상세 정리- 세 유형 공통점: 애플리케이션 소스 코드는 무결했으나 파이프라인 실행 통제 층이 변경됨
- 파이프라인은 워크플로 파일에 적힌 대로 실행되며 저장소 권한이 그대로 따라붙음
- 눈에 보이는 이상 신호 없음 — 정상 동작, 정상 성공, 또는 실행 기록 자체 부재
- 유형 1 원인: `pull_request_target` 트리거 + 외부 포크 head 체크아웃 조합 (GitHub "pwn request" 패턴)
- 유형 2 원인: 태그는 특정 커밋의 레이블일 뿐, force-push로 다른 커밋을 가리키도록 변경 가능
- 유형 3 원인: 워크플로 파일 변경이 코드 변경과 동일한 리뷰 통제를 받지 않고 직접 push 허용
- GitLab 기본값: 포크 프로젝트 파이프라인은 부모 CI/CD 변수 접근 불가 (타 플랫폼 대비 안전한 기본값)
- SHA 고정: `component: .../scan@e3262fdd...` 형식으로 태그 재지정 공격 원천 차단
- Pipeline execution policies(Ultimate): 보안 정책 프로젝트에서 정의한 Job을 대상 프로젝트에 강제, `.gitlab-ci.yml` 수정으로도 제거 불가
- CI/CD Job 토큰 허용 목록: 각 Job 토큰이 접근 가능한 프로젝트를 명시적 화이트리스트로 제한
- ID 토큰 audience 분리: `aud` 클레임을 서비스별로 구분해 한 토큰의 인증 범위를 최소화
왜 읽나2026년 실제 발생한 CI/CD 공급망 공격 3종(pwn request·태그 재지정·워크플로 삽입)의 공격 원리와 GitLab 방어 기능을 유형별로 1:1로 매핑해 설명한다. 커밋 SHA 고정·파이프라인 실행 정책·Job 토큰 허용 목록을 실제 적용 코드와 함께 확인할 수 있다.