sast-xss について
コードベースのセキュリティ評価向けに、XSS を検出するスキルです。出力先を特定し、入力からの汚染を段階的に追跡して、一般的な安全パターンを考慮しながら結果をまとめます。
主な機能
- 01HTML、DOM、スクリプト実行の出力先を特定
- 02入力から出力先への汚染を分割して追跡
- 03エスケープやサニタイズの対策を確認
- 04結果を統合し、誤検出を除外
活用シーン
- 01Web アプリの反射型・格納型 XSS の調査
- 02フロントエンドコードの DOM ベース XSS の確認
- 03テンプレートやフレームワークの危険な HTML 出力の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ utkusen/ sast-skills --skill sast-xssインストール後、Claude Code で/sast-xssを入力して読み込まれたか確認します。
詳細
クロスサイトスクリプティング(XSS)の検出
コードベース内のクロスサイトスクリプティングの脆弱性を見つけるため、対象を絞ったセキュリティ評価を実施します。このスキルでは、サブエージェントを使った3段階のアプローチを採用します。recon(シンク箇所の特定)、batched verify(シンクを最大3件ずつ並列バッチに分けて汚染を追跡)、merge(バッチ結果を1つのレポートに統合)。
前提条件:sast/architecture.md が存在している必要があります。存在しない場合は、先に分析スキルを実行してください。
XSSとは
XSSは、ユーザーが提供した入力が適切にエスケープまたはサニタイズされずに、WebページのHTML、JavaScript、またはDOMに組み込まれることで発生します。これにより、攻撃者が被害者のブラウザーに任意のスクリプトを注入して実行できるようになり、セッションハイジャック、認証情報の窃取、ページの改ざん、マルウェアの配布につながります。
基本的なパターン:エスケープもサニタイズもされていないユーザー入力が、HTML/JSの出力シンクに到達する。
XSSの種類
- 反射型XSS:ユーザー入力がHTTPレスポンスに即座に反映されます(例:検索語句がページのHTMLに直接出力される)。
- 格納型XSS:ユーザー入力が永続ストレージ(データベース、ファイル)に保存され、後に他のユーザー向けのHTMLに出力されます。
- DOMベースXSS:クライアント側のJavaScriptが攻撃者に制御されたソース(
location.search、location.hash、document.cookie)から値を読み取り、サーバーを介さずに危険なDOMシンク(innerHTML、eval、document.write)に書き込みます。
XSS とは
サーバーサイドの HTML シンク — エスケープせずにユーザーデータを HTML レスポンスにレンダリングすること:
- Python/Jinja2:
{{ var | safe }}、{% autoescape off %}...{{ var }}...{% endautoescape %} - Python/Django:
mark_safe(var)、エスケープされていない入力を%sで使うformat_html(...)、テンプレート内の{{ var | safe }} - Python/Flask:
Markup(var)、render_template_string(f"...{var}...") - PHP:
htmlspecialchars()を使わないecho $var、print $var、<?= $var ?> - Ruby/Rails:
raw(var)、var.html_safe、<%= raw var %>、.html_safeを使うcontent_tag - Java/JSP:
<c:out>またはfn:escapeXml()を使わない<%= var %>、${var} - Java/Thymeleaf:
th:utext="${var}"(エスケープなし)、[(${var})] - Go/html-template の誤用:自動エスケープを回避するために
template.HTML(var)、template.JS(var)、template.URL(var)を使用する - C#/Razor:
@Html.Raw(var)、MvcHtmlString.Create(var) - Node.js/EJS:
<%- var %>(エスケープなし)。<%= var %>は安全 - Node.js/Handlebars:
{{{ var }}}(三重波括弧、エスケープなし) - Node.js/Pug:
!{var}(エスケープなし) - Express:
res.send("<html>..." + var + "...")、res.write("<p>" + var + "</p>")
クライアントサイドの DOM シンク — JavaScript でユーザー制御データを安全でない方法で DOM に書き込むこと:
element.innerHTML = varelement.outerHTML = vardocument.write(var)、document.writeln(var)element.insertAdjacentHTML('beforeend', var)- jQuery:
$(element).html(var)、$(element).append(var)(var に HTML が含まれる場合)、$('<div>' + var + '</div>') - React:
dangerouslySetInnerHTML={{ __html: var }} - Angular:
[innerHTML]="var"、bypassSecurityTrustHtml(var)、bypassSecurityTrustScript(var)、bypassSecurityTrustUrl(var) - Vue:
v-html="var"
JavaScript 実行シンク — ユーザー制御データをコードとして評価すること:
eval(var)setTimeout(var, delay)/setInterval(var, delay)(varが文字列の場合)new Function(var)()element.setAttribute('onclick', var)、element.setAttribute('href', 'javascript:' + var)location.href = var、location.replace(var)、location.assign(var)(var がユーザー制御されており、javascript:...を指定できる場合)element.src = var、element.action = var(javascript:URI によるスクリプトインジェクション)scriptElement.text = var、scriptElement.textContent = var
DOM ベースのソース — クライアントサイド JavaScript が読み取る攻撃者制御の入力:
location.search(URL クエリー文字列)location.hash(URL フラグメント)location.hrefdocument.referrerdocument.URL、document.documentURIdocument.cookiepostMessageイベントデータ(event.data)window.namelocalStorage.getItem(...)、sessionStorage.getItem(...)(URL または postMessage から値が設定される場合)
XSS ではないもの
以下を XSS として報告しないでください:
- CSRF:ユーザーになりすましてリクエストを偽造すること — 別の脆弱性クラスです
- XSS を介した SQLi:XSS ベクトルを通じて SQL をインジェクションすること — 主な検出対象は SQL インジェクションそのものです
- クリックジャッキング:ページを iframe に埋め込むこと — 異なる脆弱性クラスです
- ヘッダーインジェクション:HTTP レスポンスヘッダーに改行をインジェクションすること — 別のクラスです(HTTP レスポンス分割)
- 安全なテンプレート出力:Jinja2/Django/Twig/Blade/Handlebars の二重波括弧構文で自動エスケープが有効な
{{ var }}— これらは安全です textContent/innerText:プレーンテキストのみを書き込み、HTML の解析は行われません — 安全です
XSS を防ぐパターン
以下のパターンが見られる場合、そのコードは脆弱ではない可能性が高いです:
1. コンテキストに応じた自動エスケープ(ほとんどのテンプレートエンジンのデフォルト)
# Jinja2 / Django (auto-escape on by default)
{{ var }} # HTML-escaped → safe
# EJS
<%= var %> # HTML-escaped → safe
# Handlebars
{{ var }} # HTML-escaped → safe
# Pug
= var # HTML-escaped → safe
# Thymeleaf
th:text="${var}" # HTML-escaped → safe
# Razor (C#)
@var # HTML-encoded → safe
2. 出力前の明示的なエスケープ
// PHP
echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8');
# Rails
<%= h(var) %>
<%= ERB::Util.html_escape(var) %>
// JSP with JSTL
<c:out value="${var}"/>
// or fn:escapeXml()
${fn:escapeXml(var)}
// html/template — auto-escapes by context (HTML, JS, URL, CSS)
{{.Var}} // safe inside html/template
3. 安全なプロパティを使った DOM 操作
element.textContent = userInput; // plain text, no HTML parsing — safe
element.innerText = userInput; // plain text — safe
4. 許可リスト方式の HTML ライブラリによるサニタイズ
// DOMPurify
element.innerHTML = DOMPurify.sanitize(userInput);
// sanitize-html with strict config
const clean = sanitizeHtml(userInput, { allowedTags: [], allowedAttributes: {} });
5. React / Angular / Vue の自動エスケープ
// React JSX — auto-escaped
return <div>{userInput}</div>;
<div>{{ userInput }}</div>
<div>{{ userInput }}</div>
脆弱な例と安全な例
Python — Flask / Jinja2
# VULNERABLE: Markup() bypasses Jinja2 auto-escaping
@app.route('/greet')
def greet():
name = request.args.get('name', '')
return render_template_string(f"<h1>Hello, {name}!</h1>") # raw f-string, no template escaping
# VULNERABLE: mark_safe equivalent
@app.route('/profile')
def profile():
bio = request.args.get('bio', '')
return render_template('profile.html', bio=Markup(bio)) # Markup() marks it as safe, bypassing escaping
# SECURE: use template with auto-escaping (never pass Markup around user input)
@app.route('/greet')
def greet():
name = request.args.get('name', '')
return render_template('greet.html', name=name) # template: {{ name }} — auto-escaped
Python — Django
# VULNERABLE: mark_safe() with user input
def user_bio(request):
bio = request.GET.get('bio', '')
safe_bio = mark_safe(bio) # user input bypasses Django's auto-escaping
return render(request, 'bio.html', {'bio': safe_bio})
# SECURE: pass raw string; template handles escaping
def user_bio(request):
bio = request.GET.get('bio', '')
return render(request, 'bio.html', {'bio': bio}) # template: {{ bio }} — auto-escaped
PHP
// VULNERABLE: echo without escaping
function showUsername($username) {
echo "<p>Welcome, " . $username . "</p>";
}
// SECURE: htmlspecialchars
function showUsername($username) {
echo "<p>Welcome, " . htmlspecialchars($username, ENT_QUOTES, 'UTF-8') . "</p>";
}
Node.js — Express(文字列連結)
// VULNERABLE: user input concatenated into HTML response
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Results for: ${query}</h1>`);
});
// SECURE: use a template engine with auto-escaping, or escape manually
const escapeHtml = require('escape-html');
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Results for: ${escapeHtml(query)}</h1>`);
});
Node.js / EJS
<div><%- userInput %></div>
<div><%= userInput %></div>
Node.js / Handlebars
<div>{{{ userInput }}}</div>
<div>{{ userInput }}</div>
JavaScript — DOM Sinks
// VULNERABLE: innerHTML with URL fragment
const name = location.hash.substring(1);
document.getElementById('greeting').innerHTML = 'Hello, ' + name;
// SECURE: textContent
const name = location.hash.substring(1);
document.getElementById('greeting').textContent = 'Hello, ' + name;
// VULNERABLE: eval with postMessage data
window.addEventListener('message', (event) => {
eval(event.data);
});
// SECURE: parse and validate; never eval postMessage data
window.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
// handle data safely
});
React
// VULNERABLE: dangerouslySetInnerHTML with user input
function Comment({ content }) {
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
// SECURE: render as text (auto-escaped by React)
function Comment({ content }) {
return <div>{content}</div>;
}
Angular
// VULNERABLE: bypassing Angular's DomSanitizer
constructor(private sanitizer: DomSanitizer) {}
getUserHtml(input: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(input); // unsafe if input is user-controlled
}
<div [innerHTML]="userInput"></div>
<div>{{ userInput }}</div>
Ruby on Rails
<%# VULNERABLE: raw() or html_safe with user input %>
<%= raw(@user.bio) %>
<%= @user.bio.html_safe %>
<%# SECURE: default ERB escaping %>
<%= @user.bio %>
Java — JSP
<%-- VULNERABLE: scriptlet echo --%>
<p>Hello, <%= request.getParameter("name") %></p>
<%-- VULNERABLE: EL without c:out --%>
<p>Hello, ${param.name}</p>
<%-- SECURE: c:out escaping --%>
<p>Hello, <c:out value="${param.name}"/></p>
Go — html/template と text/template
// VULNERABLE: using text/template (no HTML escaping)
import "text/template"
tmpl := template.Must(template.New("").Parse("<h1>Hello, {{.Name}}!</h1>"))
tmpl.Execute(w, data)
// VULNERABLE: using template.HTML() cast to bypass escaping
import "html/template"
name := template.HTML(r.URL.Query().Get("name")) // bypasses auto-escaping
// SECURE: html/template with plain string value
import "html/template"
tmpl := template.Must(template.New("").Parse("<h1>Hello, {{.Name}}!</h1>"))
tmpl.Execute(w, data) // .Name is a plain string — auto-escaped
実行
このスキルはサブエージェントを使って3つのフェーズで実行します。sast/architecture.md の内容を、すべてのサブエージェントにコンテキストとして渡してください。
フェーズ1:XSS シンク箇所の特定
以下の指示を使ってサブエージェントを起動します。
目標:スクリプトインジェクションを許す可能性のある方法で、データが HTML、JavaScript、または DOM に出力されるコードベース内のすべての箇所を見つけてください。エスケープされていない出力、明示的に安全とマークされた出力、危険な DOM プロパティへの代入、JavaScript の実行シンクをすべて対象とします。結果を
sast/xss-recon.mdに記録してください。コンテキスト:プロジェクトのアーキテクチャ概要が提供されます。これを使って、フロントエンドスタック、テンプレートエンジン、サーバーサイドレンダリングフレームワーク、クライアントサイドの JavaScript パターンを把握してください。
検索対象 — 脆弱なシンクのパターン:
危険な出力シンクに渡される動的変数はすべて検出してください。その変数がユーザー制御可能かどうかは、まだ確認しません。それはフェーズ 2 で行います。
1. サーバーサイドテンプレートのエスケープされない出力:
- Jinja2/Django:
{{ var | safe }}、{% autoescape off %}、Markup(var)、mark_safe(var)、ユーザー制御可能なフォーマット引数を直接使うformat_html(...)- EJS:
<%- var %>- Handlebars/Mustache:
{{{ var }}}- Pug:
!{var}- Thymeleaf:
th:utext="${var}"、[(${var})]- Twig:
{{ var | raw }}- Blade (Laravel):
{!! $var !!}- Rails ERB:
raw(var)、var.html_safe、<%= raw var %>- PHP:
htmlspecialchars()を使わないecho $var、print $var、<?= $var ?>- Go:
template.HTML(var)、template.JS(var)、template.URL(var)、HTML 出力へのtext/templateの使用- C#/Razor:
@Html.Raw(var)、MvcHtmlString.Create(var)2. サーバーサイドコードでの HTML 文字列の直接構築:
- HTML レスポンスを構築する文字列連結または文字列補間:
res.send("<p>" + var + "</p>")、f"<h1>{var}</h1>"、"<div>" + var + "</div>"- Flask の
render_template_string(f"...{var}...")3. クライアントサイドの DOM シンク:
element.innerHTML = varelement.outerHTML = vardocument.write(var)、document.writeln(var)element.insertAdjacentHTML(position, var)- jQuery:
$(el).html(var)、$(el).append(var)、$('<tag>' + var + '</tag>')、$.parseHTML(var)を DOM に渡す場合- React:
dangerouslySetInnerHTML={{ __html: var }}- Angular:
[innerHTML]="var"、bypassSecurityTrustHtml(var)、bypassSecurityTrustScript(var)、bypassSecurityTrustUrl(var)、bypassSecurityTrustStyle(var)、bypassSecurityTrustResourceUrl(var)- Vue:
v-html="var"4. JavaScript 実行シンク:
eval(var)setTimeout(var, ...)/setInterval(var, ...)(varが関数参照ではなく文字列変数の場合)new Function(var)()scriptElement.text = var、scriptElement.textContent = varelement.setAttribute('onclick', var)、element.setAttribute('href', 'javascript:' + var)などのイベントハンドラー属性への代入javascript:URI が実行される可能性のある URL ベースのシンク:location.href = var、location.replace(var)、element.src = var、element.action = var5. DOM ベースの XSS パターン — クライアントサイドコードが攻撃者制御可能なソースから値を読み取り、上記のシンクに渡す場合:
- 読み取り元:
location.search、location.hash、location.href、document.referrer、document.URL、document.cookie、window.name、postMessageハンドラー(event.data)、URLSearchParams- その後、エスケープせずに HTML または JS のシンクに渡す場合
対象外(これらは安全な出力パターンのため、検出しないでください):
- 自動エスケープされるテンプレート出力:Jinja2(自動エスケープ有効)の
{{ var }}、EJS の<%= var %>、Handlebars の二重波括弧{{ var }}、Razor の@var、Thymeleaf のth:textelement.textContent = varおよびelement.innerText = var— HTML として解析されないため安全- React JSX の
{var}— 自動エスケープされる- Angular の
{{ var }}補間 — 自動エスケープされる- Vue の
{{ var }}補間 — 自動エスケープされる- innerHTML への代入をラップする
DOMPurify.sanitize(var)— 通常は安全(設定を確認すること)- 出力をラップする
sanitize-html、xss、または同様の許可リスト型サニタイザーライブラリ出力形式 —
sast/xss-recon.mdに記録してください:# XSS Recon: [Project Name] ## Summary HTML/JS/DOM にデータが確実なエスケープなしで出力される箇所が [N] 件見つかりました。 ## Sink Sites ### 1. [説明的な名前 — 例:「検索結果ハンドラーでの innerHTML 代入」] - **File**: `path/to/file.ext` (lines X-Y) - **Function / endpoint / component**: [関数名、ルート、またはコンポーネント] - **Sink type**: [サーバーサイドテンプレート / HTML 文字列連結 / DOM innerHTML / eval / JS 実行シンク / DOM ベースのソースからシンクへの受け渡し] - **Sink call**: [使用されている正確な API またはプロパティ — 例:`innerHTML`、`mark_safe()`、`<%- %>`] - **Interpolated variable(s)**: `var_name` — [簡単なメモ。例:「出所不明」または「ユーザープロフィールのフィールドと思われる」] - **XSS type**: [反射型 / 格納型 / DOM ベース — 現時点での推定] - **Code snippet**:[脆弱なシンクのコード]
[箇所ごとに繰り返す]
フェーズ 1 の後:先に進む前に候補を確認する
フェーズ 1 が完了したら、sast/xss-recon.md を読みます。偵察で シンク箇所が 0 件 と判明した場合(概要に「Found 0」と記載されている、または「Sink Sites」セクションが空、あるいは存在しない場合)、フェーズ 2 とフェーズ 3 は完全にスキップします。代わりに、以下の内容を sast/xss-results.md に書き込み、終了します(書き込み後に sast/xss-recon.md を削除してもかまいません)。
# XSS Analysis Results
No vulnerabilities found.
フェーズ 1 でシンク箇所が 1 件以上見つかった場合に限り、フェーズ 2 に進みます。
フェーズ 2:検証 — ユーザー入力からシンクまでを追跡する(一括処理)
フェーズ 1 が完了したら、sast/xss-recon.md を読み、シンク箇所を 最大 3 件ずつのバッチに分けます。バッチごとにサブエージェントを 1 つずつ並列で起動します。各サブエージェントは割り当てられたシンクのみを対象に汚染を追跡し、結果を専用のバッチファイルに書き込みます。
バッチ分割の手順(サブエージェントではなく、オーケストレーターであるあなたが実行します):
sast/xss-recon.mdを読み、番号付きのシンクセクション(### 1.、### 2. など)の数を数えます。- 最大 3 件ずつのバッチに分けます。たとえば、シンクが 8 件なら、3 つのバッチ(1-3、4-6、7-8)に分けます。
- 各バッチについて、偵察ファイルから該当するシンクセクションの全文を抽出します。
- すべてのバッチサブエージェントを 並列で 起動し、それぞれに割り当てたシンクのみを渡します。
- 各サブエージェントは
sast/xss-batch-N.mdに書き込みます。N は 1 から始まるバッチ番号です。 sast/architecture.mdからプロジェクトの主要な言語/フレームワークを特定し、「脆弱な例と安全な例」セクションから 該当する例のみ を選びます。たとえば、プロジェクトが Express API を使用する React の場合は、関連する Node.js と React の例を含めます。以下の[TECH-STACK EXAMPLES]の指定箇所に、選んだ例を各サブエージェントへの指示に含めます。
各バッチサブエージェントには、以下の指示を与えます(バッチに応じて値を置き換えてください)。
目標:割り当てられた各 XSS シンク箇所について、ユーザー指定の値が出力変数に到達するかどうかを判断します。結果を
sast/xss-batch-[N].mdに記述してください。割り当てられたシンク箇所(調査フェーズより):
[割り当てられたシンクのセクション全文を、元の番号を維持してここに貼り付けてください]
コンテキスト:プロジェクトのアーキテクチャ概要が提供されます。これを使って、リクエストのエントリーポイント、データフロー、ミドルウェア、クライアント側のデータソースを把握してください。
各シンク箇所について、補間される変数をその起点まで遡って追跡してください:
探すべきユーザー制御のソース:
- HTTP リクエストのソース(サーバー側):
- クエリパラメーター:
request.GET.get(...)、req.query.x、params[:x]、$_GET['x']、c.Query("x")、r.URL.Query().Get("x")- パスパラメーター:
request.path_params['id']、req.params.id、params[:id]、$_GET['id']- リクエストボディ/フォームフィールド:
request.POST.get(...)、req.body.x、request.form.get(...)、$_POST['x']- HTTP ヘッダー:
request.headers.get(...)、req.headers['x']、$_SERVER['HTTP_X_CUSTOM']- Cookie:
request.COOKIES.get(...)、req.cookies.x、$_COOKIE['x']- ファイルアップロードのファイル名または内容:
request.files['x'].filename- 攻撃者が制御できる DOM ソース(クライアント側/DOM ベース XSS):
location.search、location.hash、location.href、document.referrer、document.URLwindow.name、document.cookiepostMessageイベント:window.addEventListener('message', (e) => { ... e.data ... })location.searchから取得したURLSearchParamsの値- URL または postMessage から書き込まれた
localStorage/sessionStorageの値- 保存済み入力(二次的な入力) — 変数は永続ストレージ(データベース、ファイル、キャッシュ)から読み込まれますが、保存された値の元はユーザー入力です:
- 書き込み経路を確認してください。このフィールドはどこに保存されましたか?保存時にユーザーが指定した値でしたか?
- 保存時にエスケープまたはサニタイズされましたか?(注:保存時の HTML エスケープは脆弱な方法です。二重エンコードされたり、別の場所で除去されたりする可能性があります)
- 検証済みまたは安全に保存された値であっても、保存型 XSS は依然として脆弱性です。読み戻し後、レンダリング前にエスケープされるかどうかを追跡してください
- サーバー側の値/ハードコードされた値 — 変数が設定、環境変数、ハードコードされた定数、またはユーザーの影響を受けないサーバー側ロジックに由来する場合、この箇所は悪用できません。
各シンク箇所について、悪用を防ぐ緩和策も確認してください:
- シンクの直前で、出力が安全な関数によって明示的にエスケープされていますか?(
htmlspecialchars()、escapeHtml()、h()、fn:escapeXml())- 厳格な許可リスト設定でサニタイズライブラリが適用されていますか?(
DOMPurify.sanitize(input)— 設定でスクリプトが除去されるか確認してください)- HTTP レスポンスの
Content-Typeはapplication/jsonまたはtext/plainに設定されていますか(HTML としてレンダリングされません)?- インラインスクリプトをブロックする Content Security Policy ヘッダーはありますか?(CSP は影響を軽減しますが、完全な対策ではありません)
- WAF または、想定される形式(数値 ID など)を厳格に許可する入力検証はありますか?
このプロジェクトの技術スタックにおける脆弱/安全な例:
[TECH-STACK EXAMPLES]
分類:
- 脆弱:ユーザー入力が有効なエスケープまたはサニタイズなしでシンクに到達することが確認できる。
- 脆弱の可能性が高い:ユーザー入力がシンクに到達すると考えられる(間接的なフロー/保存型フロー)、または緩和策が不十分(CSP のみ、WAF のみ、一部のサニタイズ、不完全な許可リスト)。
- 脆弱ではない:変数がユーザーの影響を受けないサーバー側の値である、またはシンクの直前で適切なコンテキストに応じたエスケープが適用されている。
- 手動レビューが必要:変数の起点を確信を持って特定できない(不透明なヘルパー、複雑な条件分岐、外部ライブラリ、サービス間のデータフローなど)。
出力形式 —
sast/xss-batch-[N].mdに記述してください:# XSS Batch [N] の結果 ## 検出結果 ### [脆弱] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数/コンポーネント**:[ルート、関数、またはコンポーネント名] - **XSS の種類**:[反射型/保存型/DOM ベース] - **問題**:[例:「HTTP クエリパラメーター `q` がエスケープされずに直接 innerHTML に渡される」] - **汚染トレース**:[ソースからシンクまでを順に記述 — 例:「req.query.q → query → `<h1>${query}</h1>` → res.send()」] - **影響**:[攻撃者が実行できること — セッションハイジャック、認証情報の窃取、キー入力の記録、ページの改ざん、悪意のあるサイトへのリダイレクトなど] - **修正方法**:[具体的な修正 — 適切な関数でエスケープする、textContent に切り替える、自動エスケープされるテンプレート構文を使う、DOMPurify を適用する] - **動的テスト**:[検出結果を確認する curl コマンドまたはブラウザー用ペイロード。 正確なパラメーター、ペイロード、確認すべき内容を示してください。 例:curl "https://app.example.com/search?q=" または:https://app.example.com/#
にアクセスし、アラートボックスが表示されるか確認します]
### [脆弱の可能性が高い] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数/コンポーネント**:[ルート、関数、またはコンポーネント名] - **XSS の種類**:[反射型/保存型/DOM ベース] - **問題**:[例:「保存されたユーザーの自己紹介が innerHTML 経由でレンダリングされる可能性が高い。書き込み経路からユーザー入力であることを確認済み」] - **汚染トレース**:[不確かな箇所を明示した、可能な範囲でのトレース] - **懸念事項**:[依然としてリスクがある理由 — 例:「サニタイズライブラリは存在するが、スクリプト実行可能なタグを許可する設定になっている」] - **修正方法**:[具体的な修正] - **動的テスト**:[試行するペイロード]
### [脆弱ではない] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数/コンポーネント**:[ルート、関数、またはコンポーネント名] - **理由**:[例:「出力が echo の前に htmlspecialchars() でラップされている」または「変数はハードコードされたサーバー定数である」] ### [手動レビューが必要] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数/コンポーネント**:[ルート、関数、またはコンポーネント名] - **不確実な点**:[変数の起点またはエスケープ状況を特定できない理由] - **提案**:[手動で追跡する内容 — 例:「utils.js の `buildProfileHtml()` を追跡し、戻り値の起点を確認する」]
フェーズ 3:統合 — バッチ結果の集約
すべてのフェーズ 2 のバッチサブエージェントが完了したら、すべての sast/xss-batch-*.md ファイルを読み込み、1 つの sast/xss-results.md に統合します。これはオーケストレーターであるあなたが直接行います。サブエージェントは必要ありません。
統合手順:
- すべての
sast/xss-batch-1.md、sast/xss-batch-2.md、... ファイルを読み込みます。 - 各バッチファイルからすべての検出結果を集め、元の分類とすべての詳細フィールドを維持したまま、1 つのリストにまとめます。
- エグゼクティブサマリー用に、すべてのバッチの合計を集計します(分析したシンクサイトの総数は偵察結果の数と一致し、分類ごとの件数は各バッチの合計と一致します)。
- 次の形式で、統合したレポートを
sast/xss-results.mdに書き込みます。
# XSS Analysis Results: [Project Name]
## Executive Summary
- Sink sites analyzed: [total across all batches]
- Vulnerable: [N]
- Likely Vulnerable: [N]
- Not Vulnerable: [N]
- Needs Manual Review: [N]
## Findings
[All findings from all batches, grouped by classification:
VULNERABLE first, then LIKELY VULNERABLE, then NEEDS MANUAL REVIEW, then NOT VULNERABLE.
Preserve every field from the batch results exactly as written.]
sast/xss-results.mdを書き込んだ後、中間バッチファイル(sast/xss-batch-*.md)をすべて削除します。
重要な注意事項
sast/architecture.mdを読み込み、その内容をコンテキストとしてすべてのサブエージェントに渡します。- フェーズ 2 はフェーズ 1 の完了後に実行する必要があります。フェーズ 2 は偵察結果に依存します。
- フェーズ 3 は、フェーズ 2 のすべてのバッチ完了後に実行する必要があります。フェーズ 3 はすべてのバッチ出力に依存します。
- バッチサイズはサブエージェント 1 つあたりシンクサイト 3 件です。シンクが合計 1~3 件の場合は、サブエージェントを 1 つ使用します。10 件の場合は、サブエージェントを 4 つ使用します(3+3+3+1)。
- すべてのバッチサブエージェントは並列で起動します。順番に実行しないでください。
- 各バッチサブエージェントに渡すのは、偵察ファイル全体ではなく、割り当てられたシンクのテキストのみです。これにより、各サブエージェントのコンテキストを小さく保ち、対象に集中させられます。
- フェーズ 1 は構造の分析に限定します。HTML/JS/DOM シンクに渡される動的変数は、その出所にかかわらずすべて報告します。フェーズ 1 ではユーザー入力の追跡は試みません。それはフェーズ 2 の作業です。
- フェーズ 2 は汚染分析に限定します。フェーズ 1 で見つかった各シンクについて、変数をその出所まで追跡します。有効なエスケープ処理がないままユーザーが制御できるソースに由来する場合、そのサイトは実際の脆弱性です。
- コンテキストが重要です。同じ変数でも、ある出力コンテキストでは安全(エスケープ処理された HTML 本文)で、別のコンテキストでは危険(JavaScript 文字列リテラル、URL 属性、イベントハンドラー属性)になる場合があります。正確なレンダリングコンテキストを確認してください。
- 独自のサニタイズ処理(自作の正規表現による除去、
<script>のブラックリスト化など)では不十分です。「脆弱性の可能性あり」と判定してください。使用が許容されるのは、厳格な設定の DOMPurify、または同等の許可リスト型ライブラリのみです。 - 格納型 XSS は見落としやすいため、書き込み経路を追跡して、そのフィールドにユーザー入力が使われていることを確認し、さらに読み取り/レンダリング経路にエスケープ処理がないことを個別に確認します。脆弱性を悪用可能とするには、両方の条件を満たす必要があります。
- DOM ベースの XSS は、クライアント側 JavaScript 内だけで発生します。
location.*、document.referrer、event.dataなど、攻撃者が制御できるプロパティが、サーバーを経由せず DOM シンクに流れ込んでいないかを確認します。 - CSP ヘッダーは XSS の悪用可能性を下げますが、修正ではありません。根本的なインジェクション箇所は引き続き報告してください。
- 判断に迷う場合は、「脆弱性なし」ではなく「手動レビューが必要」に分類してください。セキュリティ評価では、偽陰性のほうが偽陽性より重大です。
- Angular の
DomSanitizer.bypassSecurityTrust*メソッドは常に疑わしいものです。引数がハードコードされた定数ではない場合は、必ず報告してください。 - JavaScript の実行シンク(
eval、文字列引数を使うsetTimeout)では、一見無害なデータ(エラーメッセージ、ID など)でも、攻撃者がその値に影響を与えられるなら危険な場合があります。 - 中間ファイルを整理します。最終的な
sast/xss-results.mdを書き込んだ後、sast/xss-recon.mdとすべてのsast/xss-batch-*.mdファイルを削除します。