当应用是通过用户上传的XML文件或POST请求进行数据的传输,并且应用没有禁止XML引用外部实体,也没有过滤用户提交的XML数据,那么就会产生XML外部实体注入漏洞。
XXE 漏洞在owasp2021中位置:
A05:2021 – Security Misconfiguration CWE-611 Improper Restriction of XML External Entity Reference(XXE)
防护
使用语言中推荐的禁用外部实体的方法
这里以 Java 语言为例子说明。
使用XML库的Java应用程序特别容易受到XXE攻击,因为大多数Java XML解析器的默认设置是启用XXE。为了安全地使用这些解析器,必须在使用的解析器中显式禁用XXE。下面描述如何在最常用的Java XML解析器中禁用XXE。
01.DocumentBuilderFactory
javax.xml.parsers.DocumentBuilderFactory
`DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();``String FEATURE = null;``FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";``dbf.setFeature(FEATURE, true);`` ``FEATURE = "http://xml.org/sax/features/external-general-entities";``dbf.setFeature(FEATURE, false);`` ``FEATURE = "http://xml.org/sax/features/external-parameter-entities";``dbf.setFeature(FEATURE, false);`` ``FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";``dbf.setFeature(FEATURE, false);`` ``dbf.setXIncludeAware(false);``dbf.setExpandEntityReferences(false);`
02.Dom4j
org.dom4j.io.SAXReader
`saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);``saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false);``saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);`
`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);``builder.setExpandEntities(false);``Document doc = builder.build(new File(fileName));`
javax.xml.stream.XMLInputFactory
`// This disables DTDs entirely for that factory``xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);``// This causes XMLStreamException to be thrown if external DTDs are accessed.``xmlInputFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");``// disable external entities``xmlInputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false);`
org.xml.sax.XMLReader
`XMLReader reader = XMLReaderFactory.createXMLReader();``reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);``// This may not be strictly required as DTDs shouldn't be allowed at all, per previous line.``reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);``reader.setFeature("http://xml.org/sax/features/external-general-entities", false);``reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);`
RASP防护
上面的修复方式在具体实施时存在缺陷:
如果读取xml的代码在第三方包中(或者依赖框架),一般很难直接修改,必须pr框架/包提供者来修复;
这里以dom4j为例子来说RASP如何修复XX漏洞
`package com.example.controller;`` ``import org.dom4j.Document;``import org.dom4j.Element;``import org.dom4j.io.SAXReader;``import org.springframework.http.ResponseEntity;``import org.springframework.web.bind.annotation.PostMapping;``import org.springframework.web.bind.annotation.RequestMapping;``import org.springframework.web.bind.annotation.RestController;`` ``import java.io.ByteArrayInputStream;`` ``@RestController``@RequestMapping("/xxe/dom4j")``public class Dom4jController {` ` @PostMapping("/post/dom4j.do")` `public ResponseEntity<String> documentBuilder1(String xml, int fix) throws Exception {` `return ResponseEntity.ok(dom4j(xml, fix));` `}`` ` `public static String dom4j(String xml, int fix) throws Exception {` `SAXReader saxReader = new SAXReader();` `// 启用修复方式` `if (fix == 1) {` `saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);` `saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false);` `saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);` `saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);` `}` `ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xml.getBytes());` `Document document = saxReader.read(byteArrayInputStream);` `Element element = document.getRootElement();` `return element.getText();` `}``}`` `
dom4j版本
`<dependency>` `<groupId>org.dom4j</groupId>` `<artifactId>dom4j</artifactId>` `<version>2.0.0</version>``</dependency>`
POC
`curl --location --request POST 'http://localhost:8080/xxe/dom4j/post/dom4j.do' \``--header 'Content-Type: application/x-www-form-urlencoded' \``--data-urlencode 'xml=<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>' \``--data-urlencode 'fix=0'`
读取 /etc/passwd
文本内容
返回结果:
`##``# User Database``#` `# Note that this file is consulted directly only when the system is running``# in single-user mode. At other times this information is provided by``# Open Directory.``#``# See the opendirectoryd(8) man page for additional information about``# Open Directory.``##``nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false``root:*:0:0:System Administrator:/var/root:/bin/sh``daemon:*:1:1:System Services:/var/root:/usr/bin/false``...`` `
`/**` `* read重载方法最终调用 read(InputSource)` `*` `* @see org.dom4j.io.SAXReader#read(InputSource)` `*/` `public void closeDom4jXXE() {` `final String className = "org.dom4j.io.SAXReader";` `final String methdName = "read";` `new EventWatchBuilder(moduleEventWatcher)` `.onClass(className)` `.includeBootstrap()` `.onBehavior(methdName)` `.withParameterTypes("org.xml.sax.InputSource")` `.onWatch(new AdviceListener() {` `@Override` `public void before(Advice advice) throws Throwable {` `if (!enableCheck) {` `return;` `}` `SAXReader saxReader = (SAXReader) advice.getTarget();` `saxReader.setFeature(FEATURE_DEFAULTS_1, true);` `saxReader.setFeature(FEATURE_DEFAULTS_2, false);` `saxReader.setFeature(FEATURE_DEFAULTS_3, false);` `}`` ` `@Override` `protected void afterThrowing(Advice advice) throws Throwable {` `requestInfoThreadLocal.remove();` `}` `});` `}`` `
需要注意的是,JRASP 对Dom4j的hook类做了进一步的优化,hook了更加底层的方法(如下所示)。
public Document read(InputSource in) throws DocumentException
(经过debug和实际dump字节码发现,open-rasp 存在重复插桩、防护逻辑执行两次的问题。相关issue:https://github.com/baidu/openrasp/issues/396)
选取hook类一般是十分慎重,必须了解hook类的功能,选取类的一般性原则如下:
能防护漏洞;
选取更底层方法,避免被被绕过;
在保证功能的基础上hook类尽量少;
目前JRASP已经具备上面5类常用XML解析器的漏洞防护插件。
----------------------------------------------------------------------------
XXE漏洞案例:
https://www.jrasp.com/case/CVE-2018-15531.html
https://www.jrasp.com/case/CVE-2018-1259.html