mcpField
S
supply-chain-attack-recon

supply-chain-attack-recon

github.com/uphiagoセキュリティ1.3k40 回インストール8 週間前に更新
リポジトリ

supply-chain-attack-recon について

認可された外部セキュリティ評価向けのスキルです。GitHub、依存関係メタデータ、CI ワークフロー、コンテナイメージ、公開レジストリのサプライチェーン上の露出を調べ、悪用可能性の証拠とテスト範囲を重視します。

主な機能

  1. 01公開 GitHub の成果物調査
  2. 02内部パッケージ名の確認
  3. 03CI ワークフロー注入のレビュー
  4. 04コンテナと SBOM の露出確認
  5. 05深刻度と証拠の指針

活用シーン

  1. 01公開リポジトリの依存関係混乱リスク評価
  2. 02GitHub Actions の注入経路レビュー
  3. 03公開イメージや SBOM の機密情報確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/uphiago/recon-skills --skill supply-chain-attack-recon

インストール後、Claude Code で/supply-chain-attack-reconを入力して読み込まれたか確認します。

詳細

使用するタイミング

次の場合に使用します:

  • 対象に公開 GitHub 組織がある(OSINT で特定)
  • JS バンドルに内部向けと思われるパッケージ名(@target-internal/...、target-utils、target-shared)が含まれている
  • ビルドログ、SBOM、または package-lock.json ファイルに公開アクセスできる
  • 対象が一部公開されている CI/CD(GitHub Actions、GitLab CI、Bitrise)を使用している
  • Docker Hub/GHCR/Quay 上の Docker イメージが対象の組織に属している
  • 検出結果に、内部レジストリ URL を含む npmrc/pip.conf/gradle.properties がある
  • .github/workflows/*.yml ファイルが内部ツールを参照している

次の場合は使用しないでください:

  • 内部ネットワークのアーティファクトレジストリ(外部境界の対象外)
  • 明示的な許可なしに、タイポスクワッティング/依存関係混同パッケージを実際に公開する場合
  • 上流のオープンソースプロジェクトを侵害する場合(影響範囲が非常に大きく、許可なしではほとんどの法域で違法です)

サプライチェーン攻撃対象領域のマップ

Target Org
├── Public GitHub Org → workflow files → secrets exfil opportunities
├── Internal package names in JS/Android bundles → dependency confusion
├── Docker images on public registries → secrets in layers, RCE on pull
├── SBOM / artifact metadata → exact dep versions for known-vuln chaining
├── npmrc / pip.conf in repos → internal registry URL disclosure
├── External package dependencies → typosquat name candidates
└── Build/release pipelines → injection if pull_request_target etc.

ステップ 1 — GitHub 組織の特定

TARGET="<brand>"  # set to target brand name

# Direct guesses
for guess in $TARGET "${TARGET}-tech" "${TARGET}corp" "${TARGET}-io" "${TARGET}-eng"; do
  curl --max-time 30 --connect-timeout 10 -sI "https://github.com/$guess" | grep -E "HTTP|status" | head -1
done

# Via WHOIS / email-domain → GitHub search
gh search users --owner-affiliations=organization --query "$TARGET" --limit 10

# Via employees → reverse from social media + GitHub profile
# Many employees list their employer org on their GitHub profile

ステップ 2 — 公開リポジトリで機密性の高いアーティファクトを列挙

ORG="targetorg"

# List public repos
gh repo list "$ORG" --limit 100 --json name,description,visibility,defaultBranchRef

# Look for high-signal repo names
gh repo list "$ORG" --limit 100 --json name | jq -r '.[].name' | grep -iE "internal|infra|deploy|config|secret|setup|sdk|api"

# Clone all (small org) or selectively
gh repo clone "$ORG/$repo_name"

ステップ 3 — 内部パッケージ名の特定

JS バンドルから
# JS bundles are the easiest source of internal npm names
curl --max-time 30 --connect-timeout 10 -sk https://target.com/main.js | grep -oE '@[a-z-]+/[a-z-]+' | sort -u
curl --max-time 30 --connect-timeout 10 -sk https://target.com/main.js | grep -oE 'require\("[^"]+"\)' | sort -u

# Look for scoped names that are NOT public on npm
for pkg in @target/utils @target-internal/api @companybrand/sdk; do
  status=$(curl --max-time 30 --connect-timeout 10 -sI "https://registry.npmjs.org/$pkg" | head -1 | awk '{print $2}')
  echo "  $pkg → $status"
  # 404 → name unclaimed on public npm → DEPENDENCY-CONFUSION CANDIDATE
done
GitHub リポジトリの package.json ファイルから
# Public repos with package.json that reference internal scopes
for repo in $(gh repo list "$ORG" --limit 50 --json name --jq '.[].name'); do
  pkg=$(gh api "repos/$ORG/$repo/contents/package.json" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null)
  echo "$pkg" | jq -r '.dependencies // {} | keys[]' 2>/dev/null | grep -E '^@[a-z-]+/'
done | sort -u
Python プロジェクトから
# Internal pip package names
for repo in $(gh repo list "$ORG" --limit 50 --json name --jq '.[].name'); do
  gh api "repos/$ORG/$repo/contents/requirements.txt" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null
done | sort -u | grep -vE '^(requests|django|flask|numpy|pandas|...common)'

ステップ 4 — 依存関係混同の脆弱性チェック

特定した内部向けと思われるパッケージ名ごとに:

NAME="@target-internal/utils"   # example

# npm check
curl --max-time 30 --connect-timeout 10 -sI "https://registry.npmjs.org/$NAME" | head -1
# 404 → name is registerable → DEPENDENCY-CONFUSION POSSIBLE

# pypi check (no scopes, just name)
NAME="target_utils"
curl --max-time 30 --connect-timeout 10 -sI "https://pypi.org/project/$NAME/" | head -1
# 404 → name is registerable

# rubygems
curl --max-time 30 --connect-timeout 10 -sI "https://rubygems.org/api/v1/gems/$NAME.json" | head -1

# Go modules — slightly different, since module names are URLs
# Check if module path is reachable
curl --max-time 30 --connect-timeout 10 -sI "https://proxy.golang.org/github.com/$ORG/$NAME/@latest" | head -1

深刻度の評価: 名前が未登録だからといって、悪用可能とは限りません。次のいずれかを示す証拠も必要です:

  1. 対象のビルドシステムが公開レジストリから名前を解決する(内部レジストリのみではない)
  2. または、対象のパッケージマネージャーが安全でない設定になっている(例:@scope:registry= のマッピングがない .npmrc)
  3. または、そのパッケージが対象のビルドでインストールされる(単に使用されていないコードから参照されているだけでなく、実際に package.json に記載されている)

裏付けとなる状況証拠がないレジストリの 404 は、情報提供のみです。


ステップ 5 — タイポスクワッティング候補(外部依存関係の周辺)

対象が使用する外部の公開依存関係ごとに:

# Common typosquat patterns:
# Original: "react-router-dom"
# Typos: 
#   "react-router-doms" (extra s)
#   "react-routter-dom" (double t)
#   "react-rotuer-dom" (transposed)
#   "react--router-dom" (double dash)
#   "react-router-dorn" (m→rn)
#   "reactrouterdom" (no dashes)

# Generate candidates
python3 -c "
import sys
name='react-router-dom'
for i in range(len(name)):
    print(name[:i] + name[i+1:])   # delete
    if i < len(name)-1:
        print(name[:i] + name[i+1] + name[i] + name[i+2:])  # transpose
"

# Check which candidates are UNCLAIMED on the registry
for candidate in ...; do
  status=$(curl --max-time 30 --connect-timeout 10 -sI "https://registry.npmjs.org/$candidate" | head -1 | awk '{print $2}')
  [ "$status" = "404" ] && echo "  UNCLAIMED: $candidate"
done

⚠ 外部攻撃に関する注意: 公開レジストリにタイポスクワッティングパッケージを公開することは、より広範なエコシステムに対する攻撃です。明示的な書面による承認があり、対象範囲が明確になっていない限り、絶対に実行しないでください。契約範囲外のユーザーにも影響する可能性があり、違法となる場合があります。


ステップ 6 — GitHub Actions ワークフローへのインジェクションのスキャン

.github/workflows/ がある公開リポジトリごとに:

for repo in $(gh repo list "$ORG" --limit 50 --json name --jq '.[].name'); do
  workflows=$(gh api "repos/$ORG/$repo/contents/.github/workflows" --jq '.[].name' 2>/dev/null)
  for wf in $workflows; do
    content=$(gh api "repos/$ORG/$repo/contents/.github/workflows/$wf" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null)
    echo "=== $repo/$wf ==="
    
    # High-risk patterns:
    # 1. pull_request_target (runs with secrets on PR from forks)
    echo "$content" | grep -E 'pull_request_target'
    
    # 2. Untrusted context interpolation
    echo "$content" | grep -E '\$\{\{[^}]*github\.(event|head_ref|pull_request)[^}]*\}\}'
    
    # 3. ${{ github.event.* }} into shell run blocks
    echo "$content" | grep -B1 -A2 'run:' | grep -E '\$\{\{ ?github\.event\.'
    
    # 4. checkout of PR head with elevated perms
    echo "$content" | grep -E 'ref:.*pull_request|head_ref'
    
    # 5. Self-hosted runner without isolation
    echo "$content" | grep -E 'runs-on:.*self-hosted'

    # 6. Unpinned third-party actions — mutable tag (@v1, @main) vs pinned (@<40-char sha>)
    #    Mutable tags can be repointed by a compromised action repo (see case #9, tj-actions/changed-files).
    echo "$content" | grep -E 'uses: *[^ ]+/[^ ]+@(v?[0-9]+([.][0-9]+)*|main|master|latest)\b' | grep -v '@[0-9a-f]\{40\}'
  done
done
検出すべきインジェクションパターン(深刻度の目安)
パターン深刻度
pull_request_target + ref: pull_request.head.sha を指定した actions/checkout + リポジトリシークレットの使用重大 — 組織のシークレットを使ってランナー上で RCE が可能
${{ github.event.pull_request.title }} をシェルに埋め込む重大 — PR タイトル経由のスクリプトインジェクション
コミット SHA ではなく、変更可能なタグ(uses: org/repo@v1 / @main)に固定されたサードパーティアクション高 — 付け替え可能なサプライチェーン攻撃経路(事例 #9 を参照)
公開リポジトリのワークフローから到達可能なセルフホストランナー高 — 攻撃者が持続的に侵入拠点として利用可能
トークンを使って gh を実行する、Issue コメントをトリガーとするワークフロー高
対象が制御する URL からダウンロードするワークフロー中

ステップ 7 — Docker/コンテナイメージレジストリの調査

# Docker Hub
curl --max-time 30 --connect-timeout 10 -s "https://hub.docker.com/v2/repositories/$ORG/?page_size=100" | jq -r '.results[].name'

# GHCR (GitHub Container Registry) — public images visible in repo packages tab
gh api "users/$ORG/packages?package_type=container" 2>/dev/null
gh api "orgs/$ORG/packages?package_type=container" 2>/dev/null

# For each image, list tags
for img in image1 image2; do
  curl --max-time 30 --connect-timeout 10 -s "https://hub.docker.com/v2/repositories/$ORG/$img/tags?page_size=20" | jq -r '.results[].name'
done

# Pull and inspect layers
docker pull "$ORG/$img:latest"
docker history --no-trunc "$ORG/$img:latest"

# Mine layers for secrets
docker save "$ORG/$img:latest" -o /tmp/image.tar
mkdir -p /tmp/img && tar -xf /tmp/image.tar -C /tmp/img
find /tmp/img -name "*.tar*" -exec tar -xf {} -C /tmp/img/extracted \;
# Then run gitleaks / trufflehog over extracted filesystem
trufflehog filesystem /tmp/img/extracted --no-update

ステップ 8 — SBOM/アーティファクトメタデータの漏えい

# Look for SBOMs published as releases (SPDX, CycloneDX format)
gh api "repos/$ORG/$REPO/releases" --jq '.[] | .assets[] | select(.name | test("sbom|cyclonedx|spdx"; "i")) | .browser_download_url'

# JSON dependency lockfiles in releases
gh api "repos/$ORG/$REPO/releases" --jq '.[] | .assets[] | select(.name | test("lock|deps"; "i")) | .browser_download_url'

# Exact-version-pinned deps → known-CVE chaining
# Compare versions to nuclei nvd templates or osv.dev for known vulns
curl --max-time 30 --connect-timeout 10 -s "https://api.osv.dev/v1/query" -d '{"package": {"name": "lodash", "ecosystem": "npm"}, "version": "4.17.10"}'

ステップ 9 — 内部レジストリ URL の漏えい

# .npmrc patterns
grep -r "registry=" .                                            # in cloned repos
grep -r "_authToken=" .                                          # leaked npm token!
grep -r "@.*registry=" .                                          # scoped registry

# pip config
grep -r "extra-index-url" .
grep -r "index-url" .

# Gradle / Maven
grep -rE "(mavenCentral|maven\s*\{)" .
grep -r "url.*\(.*nexus" .

# Each leaked internal URL is intel — flag the URL itself even if not directly exploitable

ステップ 10 — npm/PyPI における組織の存在確認

# Some orgs maintain a public npm scope mirroring their brand
curl --max-time 30 --connect-timeout 10 -s "https://registry.npmjs.org/-/v1/search?text=scope:$ORG&size=50" | jq '.objects[].package.name'

# Public PyPI presence
curl --max-time 30 --connect-timeout 10 -s "https://pypi.org/simple/" | grep "$ORG" | head -20

# Check if scope is taken — if it's NOT, an attacker could register
# (relevant for any internal package using that scope)
curl --max-time 30 --connect-timeout 10 -sI "https://registry.npmjs.org/-/org/$ORG"

ツール

ツール目的
trufflehogファイルシステム/git/docker のシークレットスキャン
gitleaksGit 履歴のシークレットスキャン
dependency-confusion (Confused)npm スコープ/PyPI のチェック
packjパッケージのリスクスコア(PyPI/npm/RubyGems)
Lift / Snyk vuln-dbパッケージバージョンから既知の CVE を検索
actionlintGitHub Actions 静的解析ツール
OSSGadgetMicrosoft のパッケージメタデータツールキット
semgrep + サプライチェーンルールワークフローへのインジェクション検出
osv-scannerバージョンと既知の脆弱性の照合

深刻度評価のガイダンス

発見事項深刻度
内部パッケージ名があり、スコープマッピングがなく、公開 npm で未取得で、ビルドで実際に使用中Critical — 依存関係の混同による RCE
内部パッケージ名があり、.npmrc にスコープマッピングがあるものの、_authToken が漏えいCritical — レジストリへ直接プッシュ可能
Pull_request_target ワークフローでシークレットが公開され、PR によって制御されたコード実行が可能Critical — 組織全体のトークン窃取
レイヤーにシークレットが漏えいした Docker イメージHigh(シークレットによって異なります)
内部レジストリの URL が開示(認証情報はなし)Low — 情報開示のみ
タイポスクワット候補を特定(未公開)Informational — 注意喚起事項
公開組織に、取得可能な未使用名が 1000 個以上あるInformational — 衛生管理

アンチパターン

  • 明示的な承認を得て、スコープを明確にした署名付きの許可がない限り、タイポスクワット/依存関係の混同を狙うパッケージを公開しないでください — エンゲージメントの対象外のユーザーにも影響します
  • 個別の明確な許可がない限り、テストの一環としてクライアントのリポジトリに PR を提出しないでください — ワークフローへのインジェクションを検証する PoC が必要な場合もありますが、CI/CD や他の開発者に影響します
  • タイポスクワット候補を探すために npm/PyPI 全体をスクレイピングしないでください — 無責任で、ノイズも増やします
  • 「名前が未取得」であることと「依存関係の混同が悪用可能」であることを混同しないでください — ビルドシステムが重要です。適切なスコープマッピングを使用して攻撃を防いでいる組織は数多くあります
  • GitHub Actions のセルフホストランナーに触れないでください — クライアントのネットワーク内にあり、外部スコープの対象外である可能性があります
  • 大容量の Docker イメージを確認せずに取得しないでください — イメージの容量は 5~50GB に達することがあります。まずタグを確認してください

報告対象となる発見事項の条件

サプライチェーンに関する発見事項には、以下のすべてが必要です。

  1. 具体的な名前/パス — 正確な内部パッケージ名、正確なワークフローファイルのパス、正確なイメージタグ
  2. 脆弱性の仕組み — 依存関係の混同/タイポスクワット/インジェクションなど
  3. 悪用可能性を示す証拠 — ビルド/インストールで攻撃者のペイロードが実際に使用される証明(「名前が未取得」だけでは不十分です)
  4. 深刻度 — 影響範囲に応じて適切に評価(開発者 1 人か、全開発者か、パッケージの全ユーザーか)
  5. 推奨事項 — 具体的であること(例:「未使用でも、未取得の名前 @target-internal/utils を npm で自身のものとして登録し、.npmrc の scope:registry マッピングを設定する」)

隣接するスキルとの連携

  • apk-redteam-pipeline — APK からも内部パッケージ名が判明します(逆コンパイルした build.gradle から見つけられます)
  • cloud-iam-deep — CI/CD のシークレットはクラウド認証情報であることがよくあります。このスキルで見つけ、あちらのスキルで検証します
  • hunt-cloud-misconfig — CI/CD パイプラインの設定ミス(Jenkins/GitLab Runner)と重複します
  • m365-entra-attack — Azure DevOps のパイプラインは Entra の攻撃対象領域の一部です
  • redteam-report-template — サプライチェーンの発見事項では、影響範囲(1 つのリポジトリか、エコシステム全体か)をより明確に示す必要があります
  • mid-engagement-ir-detection — 公開 npm で名前を登録してもクライアント内部では何も発生しませんが、公開操作は必ず目立ち、監査記録が残ります

外部のみという境界の確認

このスキルは明確に外部を対象としており、対象はすべて公開レジストリ/公開 GitHub です。エンゲージメントにクライアントの内部アーティファクトレジストリ(内部の Nexus、JFrog、Sonatype)が含まれる場合、それは内部インフラであり、feedback_skill_boundaries に従ってスコープ対象外です。内部レジストリの URL の露出は発見事項として報告し、列挙は試みないでください。


実例

  • Alex Birsan 2021 — 依存関係の混同に関する初期の研究。Apple/Microsoft/PayPal/Yelp などから、総額 13 万ドル以上のバグバウンティを獲得
  • ua-parser-js 2021 — メンテナーの認証情報の窃取による npm パッケージの侵害
  • node-ipc 2022 — メンテナーが持ち込んだ、サプライチェーンを狙う悪意ある更新
  • 3CX 2023 — X_TRADER → 3CX → 顧客へと連鎖したサプライチェーン攻撃
  • XZ Utils 2024 — 上流 OSS を狙った、数年にわたるソーシャルエンジニアリングによるサプライチェーン攻撃

攻撃が効果的だった理由と、より早い段階でどのような危険信号があったのかを知るために、これらはどれも読む価値があります。


公開された事例の一覧(引用)

上記の偵察対象領域に対応する、公開情報が豊富な12件の事例です。各項目には、攻撃名、年、攻撃の流れ、根本原因、影響、参考資料、偵察スキルへの教訓を記載しています。

1. SolarWinds Orion / SUNBURST(CISA AA20-352A、2020年12月)
  • 攻撃の流れ: APT29(UNC2452 / Cozy Bear)はSolarWindsのビルドパイプラインに侵入し、SUNBURSTバックドアをSolarWinds.Orion.Core.BusinessLayer.dllに挿入しました。このトロイの木馬化されたDLLにはSolarWindsの正規の証明書でコード署名が行われ、2020年3月から6月にかけて、通常の自動更新チャネルを通じて約18,000社の顧客に配布されました。
  • 根本原因: ビルド環境の侵害です。攻撃者はコンパイルの途中でソースを改変し、署名基盤はソースの完全性を検証せずにビルド成果物を信頼していました。
  • 影響: 約18,000の組織がバックドアを受け取り、そのうち約100組織(米国財務省、商務省、国土安全保障省、司法省、Microsoft、FireEye/Mandiantを含む)が第2段階のTEARDROP/BEACONペイロードを受け取りました。SolarWindsは、直接的な対応費用が4,000万ドルを超えたと報告しました。また、集団訴訟の和解金は2,600万ドルでした。
  • 参考資料:
  • 偵察への教訓: 標的が自社のCIから署名済みバイナリを配布している場合、偵察で確認すべきなのはビルド環境自体にアクセスできるかどうかです。公開状態のJenkins/GitLab CIコンソール、外部からアクセス可能なTeamCityエージェント、ソースパスが漏えいするビルド成果物を探します。コード署名証明書と侵害されたビルドが組み合わさると、信頼の連鎖を止めることはできません。
2. 3CX VoIPソフトフォンのサプライチェーン攻撃(CVE-2023-29059、2023年3月)
  • 攻撃の流れ: 北朝鮮に関与するとされるLazarusのサブグループ(UNC4736 / Labyrinth Chollima)が、WindowsとmacOSの両方で3CX DesktopApp(Electronベースのソフトフォン)をトロイの木馬化しました。最初の侵入経路は、それ以前に起きたサプライチェーン攻撃でした。従業員がバックドアを仕込まれたTrading TechnologiesのX_TRADERをインストールしており、これは公開された初の連鎖型サプライチェーン侵害でした(あるサプライチェーン攻撃の被害者が、別の攻撃の侵入経路になります)。
  • 根本原因: 開発用ワークステーションの侵害から3CXのソース/ビルドパイプラインへのアクセスにつながり、悪意のあるffmpeg.dllとd3dcompiler_47.dllが署名済みインストーラーに含まれて配布されました。
  • 影響: 約600,000の組織が3CXを使用しており、数万件のトロイの木馬化されたクライアントがダウンロードされました。Lazarusは暗号資産関連企業や取引企業に対して、選択的に第2段階のペイロードを起動しました。
  • 参考資料:
  • 偵察への教訓: 連鎖型サプライチェーン攻撃は現実に起きています。標的のベンダーだけでなく、そのベンダーのベンダーも重要です。偵察で「この組織はエンジニアのノートPCにどのソフトウェアをインストールしているか」を調べる際は、それぞれのソフトウェアがサプライチェーン攻撃の標的になり得ることを念頭に置きます。Electronアプリ(署名済みのJSバンドル)は、特によく使われる侵入経路です。
3. MOVEit Transfer の大規模悪用(CVE-2023-34362、2023年5月~7月)
  • 攻撃の流れ: Cl0p ランサムウェアのアフィリエイト(FIN11/Lace Tempest)が Progress MOVEit Transfer の認証不要な SQLi を発見し、LEMURLOOT Web シェルを展開しました。2023年5月31日にパッチが公開される前の約2週間にわたり、インターネットからアクセス可能なすべてのインスタンスからファイルを窃取しました。
  • 根本原因: moveitisapi/moveitisapi.dll における認証前の SQLi → 任意の SQL 実行 → xp_cmdshell 相当の経路を使った Web シェルの書き込み。典型的な単一 CVE の大規模悪用であり、ビルドパイプラインへの攻撃ではなく、出荷済みコードに起因するサプライチェーンの障害です。
  • 影響: 約2,700の組織で侵害が確認され、約9,500万人分の個人情報(PII)が漏えいしました(BBC、Shell、BA、米国エネルギー省、ルイジアナ州OMV、オレゴン州DMVなど)。損失総額は推定150億ドル超です。
  • 参考資料:
  • 偵察での要点: ベンダー製のファイル転送製品(MOVEit、Accellion FTA、GoAnywhere MFT、Cleo Harmony)には、「インターネット境界にあり、あらゆる組織のデータを保持し、Windows 上で動作する」という共通パターンがあります。偵察の初期段階で、HTML タイトルやファビコンのハッシュを使って必ず製品を特定してください。境界製品の単一 CVE で、顧客基盤全体が影響を受ける可能性があります。
4. Codecov bash uploader の侵害(2021年4月)
  • 攻撃の流れ: 攻撃者は、イメージ作成フローにおける認証情報の取り扱いミスを利用して Codecov の Docker イメージビルドプロセスにアクセスし、Bash Uploader スクリプト(https://codecov.io/bash)を改ざんして、環境変数を第三者の IP アドレスに窃取しました。この改ざんは2021年1月31日から2021年4月1日までの2か月間継続し、SHA-256 の不一致に気づいた顧客が検知しました。
  • 根本原因: Docker イメージのビルドで認証情報が漏えいし、配信される bash スクリプトの改ざんが可能になりました。顧客側では整合性検証(署名済みハッシュの固定)が行われていませんでした。
  • 影響: 2か月間にわたり、curl --max-time 30 --connect-timeout 10 -s https://codecov.io/bash | bash を実行した世界中のあらゆる CI 実行から環境変数が窃取されました。下流での被害が確認された組織は、HashiCorp(GPG キーをローテーション)、Twilio、Rapid7(ソースコードの一部が露出)、Mercari、Confluent、Atlassian です。
  • 参考資料:
  • 偵察での要点: 公開 CI ワークフローにある「curl-bash-install」パターンは、この偵察スキルにとって有益な調査対象です。.github/workflows/ で curl ... | bash、wget ... | sh、iwr ... | iex を検索してください。シェルに渡される第三者 URL は、いずれもサプライチェーンへの影響を広げる可能性があります。ワークフローで SHA を固定すればリスクを軽減できます。固定がない場合は、指摘事項となります。
5. ua-parser-js npm の乗っ取り(2021年10月)
  • 攻撃の流れ: 攻撃者はメンテナーの npm アカウントをフィッシング/認証情報の総当たり攻撃で侵害し、ua-parser-js の 0.7.29、0.8.0、1.0.0 を公開しました(週間ダウンロード数は約700万件で、Facebook、Microsoft、Amazon、IBMにも間接的に影響しました)。悪意のあるバージョンは、暗号資産マイナーと Windows のパスワード窃取ツール(Jason credential stealer)をダウンロードする preinstall フックを実行しました。
  • 根本原因: メンテナーの npm アカウントでは2FAが有効になっておらず、認証情報も脆弱でした。当時、npmは影響力の大きい公開者に2FAを義務付けていませんでした。
  • 影響: パッケージは削除されるまで約4時間公開されていましたが、その間に数万件インストールされました。CISAは緊急警告を発行しました。CISAがnpmパッケージの侵害について警告したのは、これが初めてでした。
  • 参考資料:
  • 偵察の要点: パッケージのダウンロード数を基準に、標的が利用する npm/PyPI の上位30名のメンテナーを特定し、各アカウントで2FAが有効になっているか確認します(npmでは、組織メンバーについては npm profile get で確認でき、レジストリ API からも一部公開されています)。偵察結果の例:「この4名のメンテナーは、合計X回インストールされているパッケージを管理しており、公開レジストリのデータによると2FAが有効になっていません。」
6. event-stream npm パッケージ(2018年11月)
  • 攻撃の流れ: 元のメンテナーである Dominic Tarr(そのモジュールをもう使用していません)は、保守を申し出た「right9ctrl」という新しいコントリビューターに event-stream(週間ダウンロード数は約200万件)を引き継ぎました。新しいメンテナーは flatmap-stream を依存関係に追加し、その後、Copay ビットコインウォレットのビルドを標的とするペイロードを含む flatmap-stream の更新を公開しました。これにより、Copay ユーザーのウォレットシードから BTC/BCH が盗まれました。
  • 根本原因: メンテナー交代を狙ったソーシャルエンジニアリングです。重要なパッケージを引き継ぐ新しいコントリビューターをレビューする仕組みがありませんでした。悪意のある依存関係が発動したのは、event-stream が Copay ウォレットにバンドルされた場合のみでした(ビルドコンテキストを標的とする攻撃)。
  • 影響: Copay ウォレットのユーザーからキーが盗まれましたが、金銭的被害の正確な額は公表されていません。この事件を受け、npm全体で2FAの導入が推進され、「人気パッケージには追加のレビューが必要」というポリシーが導入されました。
  • 参考資料:
  • 偵察の要点: 標的が依存するパッケージについて、npm view <pkg> maintainers を実行し、最近のメンテナー変更を確認します。ダウンロード数が10万件を超えるパッケージで、過去90日以内にメンテナーの変更があった場合は注意が必要です。また、ビルドコンテキストを標的とするペイロード(特定のアプリにバンドルされた場合のみ発動するもの)は検出が非常に困難で、静的スキャナーでは見落とされます。
7. PHP Git サーバー侵害(2021 年 3 月)
  • 攻撃の流れ: 攻撃者は、git.php.net 上の公式 php-src Git リポジトリに、Rasmus Lerdorf と Nikita Popov の署名を偽装した悪意のあるコミットを 2 件プッシュしました。これらのコミットは、User-Agentt HTTP ヘッダー(t が 2 つ)からコードを実行する Zend バックドアを追加していました。
  • 根本原因: セルフホスト型 Git サーバー(Gitolite ベースの git.php.net)に、認証情報/認証の欠陥がありました。ユーザーデータベースの漏えいにより、パスワードが平文で保存されていた可能性があります。このインシデントの後、PHP チームは GitHub を正式なソースに移行しました。
  • 影響: バックドアを含むコミットは数時間以内に発見され、リリースに含まれることはありませんでした。しかし、「セルフホスト型の信頼できる唯一の情報源=単一障害点」の典型的な事例です。
  • 参考資料:
  • 偵察での教訓: セルフホスト型 Git(Gitea、Gitolite、Phabricator、Bitbucket Server)を稼働させている対象は、GitHub でホストされている対象よりリスクが高くなります。偵察では Git サーバーソフトウェアを特定し、デフォルトの認証情報を確認するとともに、予期しない IP アドレスからの SSH キーベースのプッシュ(コミットのメタデータで確認可能)を監視する必要があります。
8. Log4Shell(CVE-2021-44228、2021 年 12 月)
  • 攻撃の流れ: 厳密にはサプライチェーン攻撃そのものではありませんが、「依存関係ツリーの中身を把握できていない」ことを象徴する典型的な事例です。Apache Log4j 2.x の JNDI ルックアップ機能により、ログに記録されるあらゆる文字列に含まれる ${jndi:ldap://attacker/...} を介して、リモートコード実行が可能でした。Log4j は何千もの Java アプリに間接的な依存関係として取り込まれていたため、数億のシステムが脆弱でした。
  • 根本原因: 安全でない設定がデフォルトの機能(MessageLookup による置換)が 2013 年にリリースされ、深くネストした間接的な依存関係によって、依存関係の把握とパッチ適用がほぼ不可能になっていました。
  • 影響: CISA 長官 Jen Easterly の言葉を借りれば、「過去 10 年で最も深刻な脆弱性」です。あらゆる主要クラウド、あらゆる Apache 製品、あらゆる Java エンタープライズスタックが影響を受けました。Conti や Khonsari ランサムウェア、国家の支援を受けた攻撃者による大規模な悪用が続いています。
  • 参考資料:
  • 偵察での教訓: ここでの答えは SBOM です。偵察スキルのステップ 8(SBOM の調査)が役立ちます。公開されているリリース成果物から SPDX/CycloneDX を取得すれば、間接的な依存関係のバージョンを正確に把握でき、それを OSV/NVD に照らして既知の CVE と対応付けられます。ほとんどの組織は、間接的な依存関係の深さを過小評価しています。
9. tj-actions/changed-files GitHub Action の侵害(CVE-2025-30066、2025年3月)
  • 攻撃の流れ: 攻撃者は、約23,000のリポジトリで使用されていた tj-actions/changed-files GitHub Action を侵害し、バージョンタグ v1~v45 のすべてを悪意のあるコミットを指すように変更しました。挿入されたコードは printenv を実行して CI のシークレットをダンプし、GitHub Actions のログに出力しました。公開リポジトリでは、読み取りアクセス権がある人なら誰でも閲覧できる状態でした。
  • 根本原因: GitHub Actions で変更可能なタグ参照が使われていました。uses: tj-actions/changed-files@v35 は実行時に解決されるため、リポジトリを侵害した攻撃者は古いタグの参照先を変更できます。ほとんどの利用者はコミット SHA(@<sha>)に固定していませんでした。
  • 影響: 約23,000のリポジトリが影響を受け、CISA の KEV に追加されました。数千件のシークレット(AWS キー、npm トークン、Docker Hub の認証情報)が公開された Action ログに流出しました。Coinbase、Cloudflare など複数の下流インシデントも、この侵害にたどり着きました。
  • 参考資料:
  • 調査上の要点: 現在、最も効果の高い調査ベクトルです。公開リポジトリで uses: <org>/<repo>@v\d+(変更可能なタグ)と uses: <org>/<repo>@<sha>(固定された参照)を grep してください。固定されていないサードパーティ製 Action はすべて、サプライチェーンのリスクになります。このスキルのステップ 6 では、変更可能なタグの使用を明示的に警告してください。
10. PyPI のタイポスクワッティング(colourama、python3-dateutil、jeIlyfish など)
  • 攻撃の流れ: 攻撃者は、人気パッケージと見た目や綴りが似た名前の PyPI パッケージを登録します。たとえば、colorama に似せた colourama、python-dateutil に似せた python3-dateutil、jellyfish の L を大文字の I に置き換えた jeIlyfish です。これらにはそれぞれ、SSH キー、GPG キー、GitHub トークンを外部に流出させる、または ~/.bitcoin/wallet.dat を狙う暗号資産窃取マルウェアをインストールする setup.py のインストール後フックが含まれていました。
  • 根本原因: PyPI では見た目が紛らわしい名前を登録でき、pip は名前を完全一致で解決します。新しいパッケージの公開に人手による審査の関門がありません。
  • 影響: 各キャンペーンは通常、削除されるまでのインストール数は 10,000 未満でしたが、jeIlyfish は 1 年間(2018年12月~2019年12月)存続しました。累計では、Snyk/Phylum/Sonatype/ReversingLabs が毎年数十件のキャンペーンを記録しています。
  • 参考資料:
  • 調査上の要点: このスキルのステップ 5(タイポスクワッティング候補の生成)が、ここに直接当てはまります。外部調査では、タイポスクワッティング候補の名前を一覧にするだけにしてください。明示的な許可がない限り、登録してはいけません。成果物は「主要な依存関係のタイポスクワッティング候補 17 件が現在未登録です。防御策として登録することを推奨します」です。
11. Alex Birsanによる依存関係の混乱に関する開示(2021年2月)
  • 流れ: Birsanは、Apple、Microsoft、PayPal、Shopify、Uber、Tesla、Yelpなど約35社について、漏えいしたpackage.jsonファイル(archive.orgで公開キャッシュされたもの、誤って公開されたGitHubリポジトリ、JSバンドル)から社内npmスコープ名を抽出しました。これらの社内名と同じ名前で、より高いsemverのパッケージを公開npm/PyPI/RubyGemsに公開しました。その結果、ほとんどの企業のビルドシステムが社内パッケージより公開パッケージを優先して解決し、テレメトリーのみを送信する彼のペイロードを実行しました。
  • 根本原因: パッケージマネージャー(npm、pip、gem)は、デフォルトで「レジストリにかかわらず、最も高いバージョンを選ぶ」仕様です。社内パッケージ名が外部ソースに漏えいしていました。スコープとレジストリの対応を強制する仕組みがありませんでした。
  • 影響: バグ報奨金は13万ドル超(2021年に複数のプログラムを通じて単独の研究者が獲得した金額として、判明している中で最高額)。「依存関係の混乱」という攻撃手法の一分野が生まれました。npm/PyPI/Microsoft Azure Artifactsはいずれも緩和策を発表しました。
  • 参考資料:
  • 偵察に関する教訓: これは、このスキルのステップ3とステップ4を裏付ける基礎的な出典です。JSバンドルを通じた社内スコープの発見は、偵察の典型的な手法です。Birsanの深刻度評価にも注目してください。ほとんどの標的では「名前が未登録である」だけで十分でした。ビルドが、公開npmにフォールバックする設定でnpm installを実行していたためです。ただし、このスキルの深刻度表が正しく指摘しているように、これはすべての環境に当てはまるわけではありません。
12. XZ Utils(CVE-2024-3094、2024年3月)
  • 流れ: 「Jia Tan」(JiaT75)は、2年以上にわたりソーシャルエンジニアリングを行い、xz-utils(ほぼすべてのLinuxディストリビューションで使われる上流のOSS圧縮ライブラリ)のメンテナーから信頼を得ました。共同メンテナーの権限を得た後、テスト用フィクスチャを装って、liblzmaのビルドプロセスに多段階のバックドアを埋め込みました。このバックドアは、OpenSSHのsystemd-notify連携を介してSSH認証を乗っ取るものでした。
  • 根本原因: 単独メンテナーのOSS疲弊と、国家レベルの忍耐力(Operation J/APTの関与が疑われています)。主要ディストリビューションに出荷される前にバックドアが発見されました(短期間含まれていたのはFedora RawhideとDebian unstableのみ)。ベンチマーク中のSSHに500ミリ秒の遅延があることに、Andres Freundが気づいたためです。
  • 影響: 大規模展開の前に発見された、危機一髪の事例です。「単独メンテナーが支える重要OSS」のリスクについて、業界全体で再評価が行われるきっかけとなりました。CISA、NIST、OpenSSFはいずれも事後検証を発表しました。
  • 参考資料:
  • 偵察に関する教訓: 外部から偵察するうえで最も難しい事例です。メンテナーを何年もかけてソーシャルエンジニアリングしても、外部から確認できる兆候はほとんど残りません。しかし、GitHubのコミット履歴の分析(重要なライブラリでコミット権限を得た新規コントリビューター、難読化されたテスト用フィクスチャを追加するコミット、リリース時にのみ実行されるビルドの変更)こそが、Andres Freundが実質的に行ったことです。このスキルのステップ2(公開リポジトリの列挙)は、「履歴の少ないアカウントへの高い信頼を伴う権限付与を監視する」ことにまで拡張できます。
網羅性マップ:事例 → リコンスキルのステップ
スキルのステップ基準となる事例
Step 1 — GitHub 組織の調査Birsan 2021、XZ 2024
Step 2 — 公開リポジトリの成果物のマイニングCodecov 2021、XZ 2024、PHP 2021
Step 3 — 内部パッケージ名の特定Birsan 2021
Step 4 — 依存関係の混同の確認Birsan 2021、ua-parser-js 2021
Step 5 — タイポスクワッティング候補PyPI colourama/jeIlyfish、event-stream 2018
Step 6 — GitHub Actions ワークフローへのインジェクションtj-actions/changed-files 2025、Codecov 2021
Step 7 — Docker/コンテナレジストリのマイニングCodecov 2021、3CX 2023
Step 8 — SBOM/成果物のメタデータLog4Shell 2021、MOVEit 2023
Step 9 — 内部レジストリ URL の漏えいBirsan 2021、SolarWinds 2020
Step 10 — npm/PyPI 組織の存在確認ua-parser-js 2021、event-stream 2018
12 件すべてに共通するパターン
  • コード署名があっても安全とは限りません — SolarWinds、3CX、ua-parser-js はいずれも、正規の署名が付いた悪意あるコードを配布しました。
  • 変更可能な参照へのピン留めが、繰り返し発生する失敗要因です — curl | bash(Codecov)、@v35 アクションタグ(tj-actions)、^1.0.0 のセマンティックバージョニング(Birsan、event-stream)。
  • npm/PyPI エコシステムでは、技術的な CVE よりメンテナーアカウントの侵害が多く発生しています — 12 件中 6 件です。
  • サプライチェーンの連鎖は今や一般的です — 3CX は X_TRADER から、Codecov は HashiCorp を経由して HashiCorp の下流ユーザーに影響しました。対象のベンダーのベンダーも、概念上はスコープ内と想定してください。
  • CI ランナーは最も価値の高い足がかりです — 攻撃者のコードが CI ランナー上で実行されたすべての事例で、クラウド/GitHub/シークレットが大量に取得されました。

検証

  1. 依存関係スキャン — ツールが利用可能であることを確認します。
    which npm-audit 2>/dev/null || which npm 2>/dev/null && echo "PASS: npm available" || echo "NOTE: npm not installed"
    pip3 show pip-audit 2>/dev/null | grep -q "Name:" && echo "PASS: pip-audit installed" || echo "NOTE: pip-audit not installed"
    

すべてのテストで、サプライチェーンへの対応状況を確認します。


注意点

  • サードパーティの依存関係がすべてスコープ内だと思い込むこと — バグ報奨金プログラムのスコープを確認してください。サードパーティサービス(Zendesk、Intercom、statuspage)は通常、スコープ外です。
  • CVE のない依存関係のバージョン — ライブラリのバージョンが分かっても、該当する悪用可能な CVE がなければ、情報提供にとどまります。
  • 証拠のない npm/pip パッケージのタイポスクワッティング — タイポスクワッティングされたパッケージの存在を示しても、対象がそれを使っている証拠にはなりません。package.json/requirements.txt による証拠が必要です。
  • CI/CD サプライチェーンとランタイムサプライチェーン — ビルド時の依存関係の汚染とランタイム依存関係の汚染では、影響が異なります。区別してください。
  • ベンダーのメール侵害をサプライチェーン攻撃とみなすこと — 悪意ある更新を送るためにベンダーのメールを侵害する行為は、コード依存関係への攻撃とは異なります。どちらもサプライチェーン攻撃ですが、TTP は異なります。

関連スキルとチェーン

  • hunt-rce — 依存関係の混同攻撃は、パッケージをインストールするあらゆるランナーで RCE につながります。CI ランナーは最も価値の高い標的です。チェーンの起点:公開 JS バンドル/SBOM/Docker イメージから内部パッケージ名が漏えい → 同じ名前で、より高いバージョンの悪意あるパッケージを公開 npm/PyPI に登録 → CI ランナーで次の npm install/pip install が実行され、preinstall フックで攻撃者のコードが動作 → hunt-rce で侵入後の活動(環境変数の抽出により AWS キー、GitHub PAT、Slack トークンを取得) → CI プレーンの乗っ取り。
  • cloud-iam-deep — CI ランナーには IAM 認証情報があります。そこでサプライチェーンを介した RCE に成功すれば、認証情報を大量に窃取できます。チェーンの起点:GitHub Actions ランナーで悪意あるパッケージが実行される → 環境変数から $AWS_ACCESS_KEY_ID/$GITHUB_TOKEN を読み取る → cloud-iam-deep で列挙 → IAM 権限昇格チェーン → 本番環境のクラウドプレーンにアクセス。
  • offensive-osint — 偵察の手法は大きく重複します。SBOM、JS バンドル、GitHub 組織の列挙、Docker レジストリのタグは、どちらにも関係します。チェーンの起点:offensive-osint による GitHub 組織の偵察で、CI ワークフローから参照される内部パッケージ名を特定 → supply-chain-attack-recon で公開 npm/PyPI と照合し、タイプミススクワッティング/依存関係の混同の候補を探す。
  • hunt-cloud-misconfig — コンテナレジストリ(Docker Hub、GHCR、ECR public)では、非公開イメージが誤って公開されていることがよくあります。チェーンの起点:SBOM の調査で internal-tools-v2:latest の参照を発見 → Docker Hub で誤って公開されたミラーがないか確認 → hunt-cloud-misconfig でレジストリを列挙 → イメージを取得 → レイヤーに埋め込まれたシークレットを抽出。
  • triage-validation + redteam-report-template — サプライチェーンの偵察はスコープ内ですが、実際のパッケージ公開は外部攻撃にあたり、明示的な書面での承認が必要です。チェーンの起点:偵察のみで候補リストを作成 → triage-validation の 7-Question Gate に通す(特に「公開せずに影響を実証できるか?」を確認) → redteam-report-template を使い、「依存関係の混同候補の一覧+再現手順」として報告します。クライアントから書面で承認を得ていない限り、公開パッケージを使った PoC として報告してはいけません。

コメント