CI/CD 파이프라인의 가장 중요한 10가지 보안 위험과 이를 완화하는 방법.
OWASP 상위 10대 CI/CD 보안 위험은 지속적인 통합 및 지속적인 전달 파이프라인에서 가장 중요한 보안 위험을 식별합니다. CI/CD 파이프라인은 소스 코드, 비밀 및 프로덕션 환경에 직접 액세스할 수 있기 때문에 가치가 높은 대상입니다. 이 가이드에서는 흐름 제어, ID 관리, 종속성 공격, 파이프라인 중독 등을 다룹니다.
불충분한 흐름 제어 메커니즘으로 인해 공격자는 적절한 검토 또는 승인 게이트 없이 CI/CD 파이프라인을 통해 악성 코드를 푸시할 수 있습니다. 여기에는 분기 보호 규칙 우회, 필수 승인 누락, 프로덕션 배포를 트리거할 수 있는 사람에 대한 시행 부족 등이 포함됩니다.
적절한 흐름 제어가 없으면 손상된 단일 개발자 계정이나 악의적인 내부자가 코드 검토, 보안 검색 및 승인 프로세스를 우회하여 코드를 프로덕션에 직접 푸시할 수 있습니다. 이로 인해 백도어, 데이터 유출 코드 또는 파괴적인 페이로드가 배포될 수 있습니다.
# No branch protection — anyone can push directly to main name: Deploy on: push: branches: [main] # Triggers on any push to main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: | # Deploys immediately — no approval gate! kubectl apply -f deploy.yaml kubectl rollout status deployment/myapp
name: Deploy on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test deploy: needs: test runs-on: ubuntu-latest environment: production # Requires manual approval steps: - uses: actions/checkout@v4 - run: | kubectl apply -f deploy.yaml kubectl rollout status deployment/myapp
CI/CD 시스템에는 인간 사용자, 서비스 계정, 봇 토큰, 머신 ID 등 여러 ID가 포함됩니다. 부적절한 IAM은 리포지토리, 파이프라인 및 배포 대상에 대한 과도한 액세스를 허용합니다. 공유 자격 증명, 오래된 계정, MFA 부족으로 인해 위험이 가중됩니다.
과도한 권한이 있는 손상된 CI/CD ID는 파이프라인을 수정하고, 비밀에 액세스하고, 빌드 아티팩트를 변경하고, 프로덕션에 배포할 수 있습니다. 공유 서비스 계정을 사용하면 누가 작업을 수행했는지 감사할 수 없습니다.
# Over-permissive workflow with admin token name: CI on: push permissions: write-all # Full permissions to everything! jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: | # Using org-wide PAT with admin access curl -H "Authorization: token ${{ secrets.ADMIN_PAT }}" \ https://api.github.com/repos/org/other-repo/contents/
name: CI on: push permissions: contents: read # Minimum required permissions packages: write # Only what's needed jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/create-github-app-token@v1 # Scoped app token id: app-token with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} repositories: other-repo # Scoped to specific repo
CI/CD 파이프라인은 외부 패키지 레지스트리(npm, PyPI, Maven, Docker Hub)에서 종속성을 가져옵니다. 공격자는 종속성 혼란, 타이포스쿼팅, 손상된 관리자 계정 및 악성 패키지를 통해 이를 악용합니다. 단일 중독 종속성은 빌드 중에 임의의 코드를 실행할 수 있습니다.
악성 종속성은 설치(설치 후 스크립트, setup.py) 중에 코드를 실행하고, 비밀을 도용하고, 빌드 아티팩트에 백도어를 삽입하거나 지속성을 설정합니다. SolarWinds 및 Codecov와 같은 공급망 공격은 치명적인 영향을 보여줍니다.
pipeline { agent any stages { stage('Build') { steps { // No lockfile verification, no integrity checks sh 'npm install' // Fetches latest — could be compromised! sh 'pip install -r requirements.txt' // No hash verification } } } }
pipeline { agent any stages { stage('Build') { steps { // Use lockfile with integrity verification sh 'npm ci' // Uses package-lock.json sh 'npm audit --audit-level=high' // Python: verify hashes from lockfile sh 'pip install --require-hashes -r requirements.lock' } } stage('SCA Scan') { steps { // Software Composition Analysis sh 'trivy fs --scanners vuln,secret .' } } } }
PPE(Poisoned Pipeline Execution)는 공격자가 CI/CD 파이프라인 정의를 수정하거나 파이프라인 컨텍스트 내에서 실행되는 악성 코드를 삽입할 수 있을 때 발생합니다. 이는 브랜치의 파이프라인 구성 파일 조작, 포크의 풀 요청 또는 공유 파이프라인 템플릿 수정을 통해 발생할 수 있습니다.
파이프라인 구성을 수정할 수 있는 공격자는 파이프라인에 사용 가능한 모든 비밀, 자격 증명 및 권한에 대한 액세스 권한을 얻습니다. 신뢰할 수 있는 실행 컨텍스트 내에서 비밀을 유출하거나, 빌드 출력을 조작하거나, 악성 코드를 배포할 수 있습니다.
# Runs pipeline from fork PRs with access to secrets name: CI on: pull_request_target: # Runs in base repo context with secrets! branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} # Checks out fork code! - run: make build # Fork's Makefile executes with secrets access env: DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
# Separate workflows: untrusted build + trusted deploy name: CI on: pull_request: # No secret access for PR builds branches: [main] permissions: contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # Checks out merge commit (safe) - run: npm ci && npm test # No secrets needed for build/test - uses: actions/upload-artifact@v4 with: name: build-output path: dist/
PBAC(파이프라인 기반 액세스 제어)는 파이프라인이 액세스할 수 있는 리소스(클라우드 계정, Kubernetes 클러스터, 데이터베이스, 내부 서비스)를 제어합니다. PBAC가 충분하지 않다는 것은 파이프라인이 필요한 것보다 더 광범위한 액세스 권한을 가지며 최소 권한 원칙을 위반한다는 것을 의미합니다.
과도한 권한이 부여된 파이프라인을 활용하여 의도한 범위를 훨씬 넘어서는 리소스에 액세스할 수 있습니다. 사소한 서비스에 대해 손상된 빌드 파이프라인을 사용하여 프로덕션 데이터베이스에 액세스하거나, 인프라를 수정하거나, 다른 환경으로 전환할 수 있습니다.
# Pipeline with admin-level cloud credentials deploy: stage: deploy script: - aws configure set aws_access_key_id $AWS_ACCESS_KEY - aws configure set aws_secret_access_key $AWS_SECRET_KEY # This key has AdministratorAccess policy! - aws s3 sync dist/ s3://my-bucket/ - aws ecs update-service --cluster prod --service myapp # Same credentials could access ANY AWS resource
name: Deploy on: push: branches: [main] permissions: id-token: write # For OIDC contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456:role/deploy-s3-only # Scoped role: only s3:PutObject on specific bucket aws-region: us-east-1 - run: aws s3 sync dist/ s3://my-bucket/
CI/CD 파이프라인은 API 키, 클라우드 토큰, 레지스트리 비밀번호, SSH 키, 서명 인증서 등 다양한 자격 증명을 처리합니다. 열악한 자격 증명 위생에는 파이프라인 파일의 비밀 하드코딩, 로그에 비밀 인쇄, 암호화되지 않은 비밀 저장소 사용, 자격 증명 절대 교체 등이 포함됩니다.
유출된 CI/CD 자격 증명은 가장 일반적인 초기 액세스 벡터 중 하나입니다. 빌드 로그에 노출되거나 리포지토리에 커밋되거나 암호화 없이 저장된 비밀은 공격자에게 프로덕션 시스템, 클라우드 계정 및 아티팩트 레지스트리에 직접 액세스할 수 있는 권한을 제공합니다.
#!/bin/bash # Secrets hardcoded and leaked in logs export DOCKER_PASSWORD="MyS3cret!" # Hardcoded! echo "Logging in with $DOCKER_PASSWORD" # Printed to logs! docker login -u admin -p "$DOCKER_PASSWORD" registry.example.com docker push registry.example.com/myapp:latest # AWS credentials in environment — visible in process listing export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCY" aws s3 cp artifact.zip s3://releases/
name: Publish on: push: tags: ['v*'] jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: docker/login-action@v3 # Handles credentials securely with: registry: registry.example.com username: ${{ secrets.DOCKER_USER }} # Masked in logs password: ${{ secrets.DOCKER_TOKEN }} # Short-lived token - run: | docker build -t registry.example.com/myapp:${{ github.ref_name }} . docker push registry.example.com/myapp:${{ github.ref_name }}
CI/CD 시스템(Jenkins, GitLab, GitHub Actions 러너)은 안전하지 않은 기본 구성으로 실행되는 경우가 많습니다. 여기에는 오래된 소프트웨어 버전, 노출된 관리 인터페이스, 비활성화된 보안 기능, 과도한 네트워크 액세스 및 프로젝트 전체에서 공유되는 자체 호스팅 실행기가 포함됩니다.
잘못 구성된 CI/CD 인프라를 악용하여 환경 구축에 대한 무단 액세스 권한을 얻거나, 비밀을 가로채거나, 내부 네트워크로 전환할 수 있습니다. 공유된 자체 호스팅 실행기는 하나의 손상된 워크플로가 다른 워크플로에 영향을 미치는 프로젝트 간 공격을 허용합니다.
// Jenkins with insecure configuration // - Script console enabled without auth // - Agent-to-controller access unrestricted // - Outdated plugins with known CVEs pipeline { agent any // Runs on any available agent — no isolation stages { stage('Build') { steps { // Running as root on shared agent sh 'whoami' // root sh 'docker build -t myapp .' } } } }
pipeline { agent { kubernetes { // Ephemeral, isolated pod per build yaml """ apiVersion: v1 kind: Pod spec: securityContext: runAsNonRoot: true runAsUser: 1000 containers: - name: builder image: builder:1.2.3 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true """ } } stages { stage('Build') { steps { container('builder') { sh 'make build' } } } } }
CI/CD 파이프라인은 코드 품질 도구, 보안 스캐너, 알림 시스템, 배포 플랫폼 등 타사 서비스와 통합되는 경우가 많습니다. 이러한 통합에는 액세스 토큰 및 권한이 부여되어 신뢰 체인이 생성됩니다. 관리되지 않는 사용은 타사 서비스가 액세스하거나 수행할 수 있는 작업에 대한 가시성이 없음을 의미합니다.
Codecov 위반과 같은 손상된 타사 서비스는 소스 코드, 비밀 및 빌드 아티팩트에 액세스할 수 있습니다. 거버넌스 없이 팀은 알 수 없는 서비스에 과도한 권한을 부여하여 공급망에 보이지 않는 공격 벡터를 만들 수 있습니다.
name: CI on: push jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test # Unknown third-party action with full repo access - uses: random-org/code-analysis@main # Unpinned, unvetted! with: token: ${{ secrets.GITHUB_TOKEN }} # Full token access! # Uploading coverage to external service with repo token - run: | bash <(curl -s https://example.com/uploader.sh) # Remote script!
name: CI on: push permissions: contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm test # Vetted action pinned to SHA - uses: github/codeql-action/analyze@8a470fddafa5cbc14 # Pinned SHA # Upload via official CLI tool, not remote scripts - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 with: token: ${{ secrets.CODECOV_TOKEN }} # Scoped token fail_ci_if_error: true
빌드 아티팩트(컨테이너 이미지, 바이너리, 패키지)는 CI/CD 파이프라인을 통해 프로덕션으로 흐릅니다. 무결성 검증이 없으면 빌드 중, 전송 중, 아티팩트 레지스트리 또는 배포 시 어느 시점에서든 아티팩트가 변조될 수 있습니다. 이는 코드에서 프로덕션까지 신뢰 체인을 깨뜨립니다.
변조된 아티팩트에는 백도어, 악성 코드 또는 수정된 논리가 포함될 수 있습니다. 서명 및 확인 없이는 빌드 후 아티팩트가 수정되었는지 여부를 감지할 방법이 없습니다. 공격자는 레지스트리의 합법적인 이미지를 교체하거나 전송 중인 아티팩트를 가로챌 수 있습니다.
#!/bin/bash # Build and deploy without any integrity checks docker build -t myregistry.com/app:latest . docker push myregistry.com/app:latest # On deployment side — no verification docker pull myregistry.com/app:latest # Could be tampered! docker run myregistry.com/app:latest # Tag is mutable!
name: Build and Sign on: push: tags: ['v*'] permissions: id-token: write packages: write jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: | # Build with immutable tag (SHA) docker build -t myregistry.com/app:${{ github.sha }} . docker push myregistry.com/app:${{ github.sha }} - uses: sigstore/cosign-installer@v3 - run: | # Sign image with keyless signing (Sigstore) cosign sign myregistry.com/app:${{ github.sha }} # Generate and attach SBOM syft myregistry.com/app:${{ github.sha }} -o spdx-json > sbom.json cosign attest --predicate sbom.json myregistry.com/app:${{ github.sha }}
CI/CD 환경은 파이프라인 실행, 구성 변경, 비밀 액세스, 배포 활동 등 중요한 보안 이벤트를 생성합니다. 포괄적인 로깅 및 모니터링이 없으면 파이프라인의 악의적인 활동이 감지되지 않고 사고 대응이 심각하게 방해받습니다.
CI/CD 활동에 대한 가시성이 없으면 공격자는 경고를 트리거하지 않고도 파이프라인을 수정하고, 비밀을 유출하고, 아티팩트를 변조할 수 있습니다. 감사 추적이 부족하면 위반의 범위와 영향을 판단하는 것이 불가능합니다.
#!/bin/bash # Pipeline with no logging or audit trail echo "Starting deployment..." kubectl apply -f deploy.yaml echo "Done." # No record of: who triggered this, what changed, # which image was deployed, what secrets were accessed # Build logs expire after 30 days with no archival
name: Audited Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Log deployment metadata run: | echo "=== Deployment Audit Log ===" echo "Triggered by: ${{ github.actor }}" echo "Commit: ${{ github.sha }}" echo "Ref: ${{ github.ref }}" echo "Workflow: ${{ github.workflow }}" echo "Run ID: ${{ github.run_id }}" - name: Deploy with audit run: | kubectl apply -f deploy.yaml 2>&1 | tee deploy.log # Send audit event to SIEM curl -X POST "${{ secrets.SIEM_WEBHOOK }}" \ -d '{"event":"deploy","actor":"${{ github.actor }}","sha":"${{ github.sha }}"}'
| ID | 취약점 | 심각성 | 주요 완화 |
|---|---|---|---|
| CICD-SEC-1 | 불충분한 흐름 제어 메커니즘 | Critical | 지점 보호, 필수 승인, 환경 게이트 |
| CICD-SEC-2 | 부적절한 ID 및 액세스 관리 | Critical | 최소 권한 토큰, OIDC, MFA, 자격 증명 순환 |
| CICD-SEC-3 | 종속성 체인 남용 | Critical | 잠금 파일, 해시 확인, 개인 레지스트리, SCA |
| CICD-SEC-4 | 중독된 파이프라인 실행(PPE) | Critical | 별도의 빌드/배포, 불변 파이프라인 정의, 포크 제어 |
| CICD-SEC-5 | PBAC가 부족함 | High | OIDC 페더레이션, 범위가 지정된 IAM 역할, 환경 분리 |
| CICD-SEC-6 | 불충분한 자격 증명 위생 | Critical | 비밀관리, 로그마스킹, 순환, 비밀스캐닝 |
| CICD-SEC-7 | 안전하지 않은 시스템 구성 | High | 임시 실행자, 루트가 아닌 사용자, 플러그인 업데이트, 네트워크 격리 |
| CICD-SEC-8 | 제3자 서비스의 통제되지 않은 사용 | High | 서비스 인벤토리, SHA 고정, 원격 스크립트 없음 |
| CICD-SEC-9 | 부적절한 아티팩트 무결성 검증 | High | 공동 서명, 승인 관리, SLSA 출처 |
| CICD-SEC-10 | 불충분한 로깅 및 가시성 | Medium | 감사 로깅, SIEM 통합, 경고, 로그 보존 |