XML(eXtensible Markup Language,可扩展标记语言)是一种用于存储和传输数据的标记语言。它被设计用来结构化、存储和传输信息,同时保持人类可读和机器可读的特性。它被设计为具有自我描述性,并且是W3C的推荐标准。作为数据交换的标准格式,在Web服务、配置文件、数据存储等领域广泛应用。然而,正是其强大的可扩展性设计,隐藏着一个危险的攻击向量——XML外部实体注入(XXE)。
XML的核心特点:
XML的应用场景
要清楚XXE漏洞,首先需要了解的就是XML文档
XML文档必须包含一个根元素,该元素是所有其他元素的父元素。XML文档由声明、元素、属性、文本等组成,<?xml version="1.0" encoding="UTF-8"?>必须出现在文档开头,XML版本(通常是1.0或1.1),比如下面的一个常规的xml文档格式:
<?xml version="1.0" encoding="UTF-8"?> <note> <to>aaa</to> <from>bbbb</from> <heading>email</heading> <body>this is a email</body> </note>
第一行是XML声明,定义了XML版本和编码。接下来是根元素<note>,每个XML文档必须有且仅有一个根元素。根元素包含4个子元素。
XML元素
XML元素由开始标签、结束标签和两者之间的内容组成(<标签名>内容</标签名>)。元素可以包含其他元素、文本或两者混合。元素也可以为空。比如如下示例:
<book>
<title>XML example</title>
<author>monsterL</author>
<chapter>
<title>description</title>
</chapter>
</book>XML属性
属性提供有关元素的额外信息。属性在开始标签中定义,以名称-值对的形式出现(<元素名 属性名="属性值">),同一个元素不能有相同名称的属性。比如:
<book name="web安全" pageSize="22">
<title>XML example</title>
<author>monsterL</author>
<chapter>
<title>description</title>
</chapter>
</book>使用属性和元素的比较:
<!-- 使用属性 -->
<book title="XML example" author="monsterL" pageSize="999">
<chapter title="description"/>
</book>
<!-- 使用子元素 -->
<book>
<title>XML example</title>
<author>monsterL</author>
<pageSize>999</pageSize>
<chapter>
<title>description</title>
</chapter>
</book>XML命名空间
由于XML元素名称是用户定义的,当两个不同的文档使用相同的元素名时,可能会发生冲突。XML命名空间通过使用URI来避免元素名冲突。
<root xmlns:h="http://www.w3.org/TR/html4/"
xmlns:f="http://www.w3schools.com/furniture">
<h:table>
<h:tr>
<h:td>Apples</h:td>
<h:td>Bananas</h:td>
</h:tr>
</h:table>
<f:table>
<f:name>potato</f:name>
</f:table>
</root>文本内容
元素可以包含文本内容,<tag>这里是文本内容</tag>
注释
<!-- 这是XML注释 -->,注释不能出现在声明之前
CDATA区段-了解
包含不需要解析的文本,如脚本代码或包含大量特殊字符的内容。语法如下:
<script><![CDATA[
function test() {
if (a < b && c > d) {
alert("Hello");
}
}
]]></script>CDATA 声明:<![CDATA[ 和 ]]> 用于包裹 JavaScript 代码,防止浏览器将 < 和 > 误认为 HTML 标签。
以上为XML的基本格式,那么什么又是实体呢。
如果xml文本中特殊字符需要转义,那么转义之后的内容就称之为实体。如下转换规则:它是 HTML/XML 实体编码的一种形式,以下可以看作是内部定义好的实体。
在 HTML/XML 中,实体是一种特殊字符的替代表示方式,用于避免直接使用字符本身可能引发的解析错误或歧义
实体编码的常见用途:在HTML/XML 内容中使用:在标签或属性中嵌入 < 时必须使用 <,例如:
<p>这是一个段落,包含一个标签:<div></p>
其作用是防止浏览器将 <误认为标签开始符,导致解析错误。比如将其在浏览器解析出来如下:这也是防止XSS攻击的防御手段之一。

既然有xml中存在内部定义好的实体,那也可以存在自己定义的实体。
实体编码:根据内部的实体格式,如果将字符串转换为 &name; 或 &#number; 格式的字符串,称之为实体,需要确保内容在不同上下文中正确解析--有点类似于代码中的变量的作用。
实体的定义:以&符号开头,允许在文档中定义可重用的内容。实体分为内部实体和外部实体,其定义格式:以<!ENTITY开头 :通过<!ENTITY>声明的实体,称为自定义实体(Custom Entities),允许开发者在 XML 文档中定义自己的变量或占位符,而如果该自定义实体使用 SYSTEM 或 PUBLIC 关键字引用外部资源(如文件或 URL)则称之为外部实体(External Entities)。
<!ENTITY 内部实体 "固定内容"> <!ENTITY 外部实体 SYSTEM "外部资源URI">
内部实体示例:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE note [ <!ENTITY writer "monsterL"> ]> <note> <author>&writer;</author> </note>
这里解析就会得到<author>monsterL</author>类似变量的作用
外部实体示例:多用于资源的引用
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE note [ <!ENTITY file SYSTEM "file:///tmp/note.txt"> ]> <note> <content>&file;</content> </note>
这里解析xml之后将得到 <content>note中的内容</content>
关键区别:外部实体可以加载本地文件系统或远程资源内容,这也是XXE攻击的核心。
DTD用于定义XML文档的结构。它定义了一组规则,用来确保XML文档的一致性。
<?xml version="1.0"?> <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> ]> <note> <to>aaa</to> <from>bbb</from> <heading>email</heading> <body>this is a email!</body> </note>
1-7行表示定义了需要使用到的标签名,后面就只能使用这些标签,9-10就是对定义的标签的使用。
XML Schema(XSD)
XML Schema是DTD的替代者,它使用XML语法,功能更强大。如下示例定义:
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="note">
<xs:complexType>
<xs:sequence>
<xs:element name="to" type="xs:string"/>
<xs:element name="from" type="xs:string"/>
<xs:element name="heading" type="xs:string"/>
<xs:element name="body" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>其实这有点像DTD+XML属性的结合使用,在定义的时候,同时定义了其对应的属性。
理论结束,现在开始实践教学(请问神乐哪里最可爱吖!不好意思走错片场了)
先来搭建环境吧:
新建一个java项目,导入相关的依赖:pom.xml--这里可以看到XML的实际应用,配置文件。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>使用tomcat启动web应用。

XML解析器
既然有xml格式的内容,那么就应该有对应的解析器:常见的XML解析方式有两种:DOM和SAX。
DOM:将整个XML文档加载到内存,形成一棵树,可以方便地遍历和修改,但耗内存。
SAX:事件驱动,逐行读取,内存消耗小,但无法随机访问。
新建一个servlet---DomXMLServlet.java代码如下:
package org.example;
import org.w3c.dom.Document;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.PrintWriter;
/**
* XML解析Servlet - 提供基于DOM的XML解析服务
* 支持GET请求展示测试页面,POST请求解析XML内容
*/
@WebServlet("/parse-xml")
public class DomXMLServlet extends HttpServlet {
/**
* 处理POST请求 - 接收并解析XML内容
* @param request HTTP请求对象,包含待解析的XML数据
* @param response HTTP响应对象,返回解析结果
* @throws IOException IO异常
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// 设置响应内容类型为文本格式,支持中文编码
response.setContentType("text/plain;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
// 创建DocumentBuilderFactory实例用于配置XML解析器
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 创建DocumentBuilder实例用于解析XML文档
DocumentBuilder builder = factory.newDocumentBuilder();
// 从HTTP请求读取XML内容
Document doc = builder.parse(request.getInputStream());
// 输出解析内容
String textContent = doc.getDocumentElement().getTextContent();
out.println("解析结果: " + textContent);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
out.println("解析失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 处理GET请求 - 返回XML解析测试页面
* @param request HTTP请求对象
* @param response HTTP响应对象
* @throws IOException IO异常
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html><head><title>XML解析测试</title></head><body>");
out.println("<h2>XML解析服务</h2>");
out.println("<p>使用以下表单发送XML:</p>");
out.println("<textarea id='xmlInput' rows='10' cols='50'><root>测试XML内容</root></textarea><br>");
out.println("<button onclick='sendXML()'>解析XML</button>");
out.println("<div id='result'></div>");
out.println("<script>");
out.println("function sendXML() {");
out.println(" var xmlContent = document.getElementById('xmlInput').value;");
out.println(" fetch('parse-xml', {");
out.println(" method: 'POST',");
out.println(" headers: { 'Content-Type': 'text/xml' },");
out.println(" body: xmlContent");
out.println(" }).then(response => response.text())");
out.println(" .then(text => document.getElementById('result').innerText = text)");
out.println(" .catch(err => document.getElementById('result').innerText = '错误:' + err)");
out.println("}");
out.println("</script>");
out.println("</body></html>");
}
}代码说明:
使用tomcat启动当前应用,访问这个servlet:http://localhost:8000/xxe//parse-xml