如何限制XXE解析?


在我的上一篇文章中,我们明白了如何配置XML解析器来实现全面禁用XXE声明和扩展。这是一个简单但严格的解决方案,可能很难在您的项目中实现。所以今天,我们将讨论如何精确地限制XXE。同样,我们的示例代码将主要使用Java代码,同时也参考其他语言的代码。

限制外部连接到授权协议

Java JAXP API 1.5版添加了新的属性来限制对外部内容的访问。在本文中,我们将只关注限制XXE,从而限制访问外部DTD属性。但限制对其他外部资源(如模式)的访问对于降低其他XML漏洞的风险也具有必要性。

必须使用授权协议列表定义以下属性:

Factory setProperty(xmlcontents.ACCESS_EXTERNAL_DTD,“http”);

在本例中,只允许连接至http资源,防止使用文件URI方案使用XXEs过滤包括敏感内容的文件,但这仍然不足以有效避免漏洞,因为敏感内容可以从网络可访问的资源中被检索出来。稍后我们将会看到,此功能通常用做第一个过滤器,并与实体解析器配合使用。

通过将此功能设置为空字符串,可以安全地完全禁用XXE:

Factory setProperty(xmlcontents.ACCESS_EXTERNAL_DTD,”);

注:我们已经了解到了一些XML处理器具备的功能,比如ApacheXerces,依然不支持这些JAXP1.5属性。

对于libxml数据库,限制外部连接的最快捷设置是libxml_NONET功能,它禁止在检索外部资源时使用网络。以下是一个PHP示例:

$doc=simplexml_load_string($xml,“simplexmlement”,LIBXML_NONET);

使用自定义解析程序解析实体

SAX(XML的简单API,最初是仅用Java语言表示的API)解析器基于事件驱动的API。对于在XML文档中找到的每个实体引用,都会调用EntityResolver接口实现的resolveEntity回调函数。在默认的情况下,内置Java解析器将会尝试访问XML文档中定义的几乎所有外部内容。

因此,为了保护SAX应用程序的安全,应该禁用XXE声明或引用扩展,就像我们在本讨论内容的第二篇文章所述内容一样,或者根据应用程序的需要使用自定义解析器。

使用自定义解析器的常见用例有:

  • 使用资源的缓存版本,而非从网络获取资源。

  • 将URI资源的方案(如http://)替换为更合适的方案(https://)。

  • 将相对URI资源转换为绝对URI资源。

  • 只对安全且经过验证的实体进行授权。

自定义解析器由EntityResolver接口实现组成,该实现应使用SAX处理器的setEntityResolver方法注册。

例如,我们在开源项目中看到过很多防止XXE漏洞的有效解决方案,就是为任何实体关联一个空字符串作为内容,从而能够完全禁用实体解析。与我们之前讨论的解决方案的不同之处在于,这里允许在XML文件中使用XXE声明,但它们的解析被禁用,因此提供了一种非阻塞且安全的方式来解析XML文件:

builder.setEntityResolver(new EntityResolver() {
   @Override
   public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
     return new InputSource(new StringReader(""));
   }
 });

请注意,将null注册为实体冲突解决程序相当于使用默认且不安全的冲突解决程序,因此这样做可能没有很好的理由:

builder.setEntityResolver(null);

在自定义解析器中,当返回null时,解析器的默认行为是获取外部内容:

builder.setEntityResolver(new EntityResolver() {
   @Override
   public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
     return null;
   }
 });

因此,如上所述的已得到许可的实体解析程序依然可能导致XXE漏洞。这就是我们前面讨论的属性要发挥的作用所在。entityResolver对systemId以logo.png结尾的实体使用自定义解析,否则使用默认行为。由于通过将ACCESS_EXTERNAL_DTD属性设置为空字符串已在第一行修改了默认行为,因此实体解析器是安全的。

builder.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
builder.setEntityResolver(new EntityResolver() {
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
    if (systemId.endsWith("logo.png")) {           
      InputStream in = classLoader.getResourceAsStream("com/package/logo.png");
      return new InputSource(new StringReader(Base64.getEncoder().encodeToString(IOUtils.toByteArray(in))));
    }
 
    return null;
  }
});

其他语言中的其他数据库也提供了类似的功能,允许自定义外部实体的解析,比如PHP的libxml和libxml_set_external_entity_loader函数:

libxml_set_external_entity_loader(
   function ($public, $system, $context) {
       if(str_ends_with($system, "logo.png")) {
           return fopen("./logo.png", "r");
       }
 
       return null;
   }
);
libxml的主要区别在于,在自定义解析器中返回null不会解析实体,并会导致出现错误。

使用目录解析实体

实体到其他实体的映射通常也使用XML目录来完成。

XML目录是包含外部实体标识符到URI的映射的XML文件。

<?xml version="1.0"?>
"urn:oasis:names:tc:entity:xmlns:xml:catalog">
 "https://www.externalwebsite.com/logos/logo.png" uri="logo.png"/>

XML处理器在目录中为解析XML文件时找到的每个实体执行查找。如果没有匹配项,则会发出异常提示信息,从而提供一种安全的方法来防止XXE漏洞。

在java中,可以通过调用setEntityResolver方法使用目录,如下所示:

URL catalogUrl = classLoader.getResource(catalogFile);
CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogUrl.toURI());
 
builder.setEntityResolver(cr);
 

有关XML漏洞的更多信息

尽管XXE是解析XML文件时需要注意的危险性最高的漏洞,但也可能会出现其他问题,例如拒绝服务攻击或使用xinclude或XSLT文件I/O元素获取外部内容。我们将很快发布一组规则来进一步加强您的XML解析器功能。敬请关注!

作者简介:

ERIC THEROND 安全研究人员