XXE漏洞的本质,是把XML解析器的"信任"变成了攻击者的武器。2008年,安全研究员Gregory Duchemin在Black Hat上展示了一个案例:某银行系统的SOAP接口在解析用户提交的XML时,直接返回了服务器上的/etc/passwd文件内容。问题出在一行看似无害的DTD声明上——解析器不仅读了文件,还把它嵌进了响应里。
XML实体分两种:内部实体和外部实体。内部实体就像变量,<!ENTITY writer "Donald Duck.">,引用时用&writer;展开。外部实体则通过SYSTEM关键字指向一个URI,可以是文件路径,也可以是URL。当解析器处理<!ENTITY xxe SYSTEM "file:///etc/passwd">时,它真的会去读那个文件。如果应用程序把解析结果返回给用户,攻击者就拿到了文件内容。
这听起来像是XML设计时的疏忽,但更准确地说,是功能被滥用了。DTD本来是为了定义文档结构,外部实体是为了复用内容,结果成了SSRF和文件读取的跳板。PHP的libxml在5.4之前默认开启实体替换,Java的DocumentBuilderFactory默认也允许外部实体。很多开发者根本不知道这些默认值意味着什么。
实际攻击中,XXE的利用方式比想象中灵活。盲注场景下,攻击者用带外通道(OOB)把数据发到自己的服务器:<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">,再通过参数实体把内容拼进URL请求。服务器日志里就留下了base64编码的文件内容。2019年,某知名社交平台的API就因为这个漏洞泄露了内部配置文件,攻击者通过一个上传头像的XML接口读到了AWS密钥。
防御方法不复杂,但容易被忽略。最直接的是禁用外部实体。Java里设置factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true),PHP里用libxml_disable_entity_loader(true)。如果业务必须用DTD,那就限制协议,只允许http/https,禁止file、ftp、gopher。有些团队用JSON替代XML,但JSON解析器也有自己的问题,比如深度嵌套导致的DoS。
我见过一个案例,开发人员以为用了HTTPS就安全了,结果XXE照样读本地文件。加密传输和解析器配置是两码事。另一个常见误区是认为只有POST请求有风险,实际上GET请求里的XML参数一样会被解析。WAF规则能拦一部分,但编码绕过、参数实体嵌套这些技巧很容易绕过基于正则的检测。
XML在SOAP、RSS、SVG、Office文档里无处不在。SVG文件本质就是XML,上传头像时如果服务端用XML解析器处理SVG,XXE就可能触发。Word文档的docx格式也是XML打包的,某些在线预览功能会中招。攻击面比大多数人想的要宽。
修复XXE不需要高深技术,关键是意识到XML解析器默认不设防。检查代码里所有用到DocumentBuilder、SAXParser、XMLReader的地方,把外部实体关掉。如果用了第三方库,查一下它的XML解析配置。安全扫描工具能发现一部分,但盲注和OOB场景往往需要手动测试。别等数据泄露了才想起那行DTD声明。