XXE学习
XXE
XXE全称XML External Entity InjectionXML在引入外部实体的时候完成注入攻击称为XXE。
前置知识
XML语言
XML是一种用来传输和存储数据的可扩展标记语言。
XML用于传输和数据存储。HTML用于显示数据
语法规则
1 | 1.所有的XML元素都必须有一个关闭标签 e.g. <note> </note> |
xml结构
1 | XML 文档声明,在文档的第一行 |
根据 DOM,XML 文档中的每个成分都是一个节点。
DOM 是这样规定的:
- 整个文档是一个文档节点
- 每个 XML 标签是一个元素节点
- 包含在 XML 元素中的文本是文本节点
- 每一个 XML 属性是一个属性节点
- 注释属于注释节点
*
2005 *,元素节点,拥有一个值为 “2005” 的文本节点。”2005” 不是 元素的值!
XML DTD
1、文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。
2、DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。
第二条是重点,也是XXE漏洞产生的原因,DTD可以定义外部实体并引用
内部声明DTD
若DTD要在XML文档中使用,他需要包含在·DOCTYPE声明中
1 | <!--xml声明--> |
- DOCTYPE声明语法
<!DOCTYPE 根元素 [元素声明]>
- 实体引用:
<!ENTITY 实体名 "实体值">
- 一个实体由三部分构成: 一个和号 (&), 一个实体名称, 以及一个分号 ( ; )。
- 声明 name 的值为 playwin ,下面引用 &name;xml会自动解析为他的值,如果有的话,否则报错
外部声明DTD
声明外部实体(外部实体不是xml本身已定义的实体)
语法
1 | <!--可以使用协议读取外部数据--> |
案例
1 |
|
- 外部声明实体外部实体用来引用外部资源,有两个关键字
SYSTEM
和PUBLIC
两个,表示实体来自本地计算机还是公共计算机- 根据不同环境不同协议读取外部数据
实体的嵌套
1 | <!ENTITY %实体名称 "值"> <!--内部--> |
实例:
1 | <!DOCTYPE foo [<!ELEMENT foo ANY > |
DOCTYPE前要紧跟!
而里面引用的外部实体evil.dtd的内容:
1 | <!ENTITY evil SYSTEM "file://文件路径" > |
利用方式
前提:
Content-Type: application/xml
相关函数
file_get_contents()
:把整个文件读入一个字符串中。
libxml_disable_entity_loader(false)
:意思就是不禁止外部实体加载
loadXML( string $source, int $options = 0 )
: $source:此参数保存包含 XML 文档的字符串。$options:此参数保存 libxml 选项常量的按位或。返回值:此函数在成功时返回 TRUE,在失败时返回 FALSE。如果静态调用此函数,则返回 DOMDocument,失败时返回 FALSE。
LIBXML_NOENT: 将 XML 中的实体引用 替换 成对应的值
LIBXML_DTDLOAD: 加载 DOCTYPE 中的 DTD 文件
libxml选项常量
名称 描述 PHP LIBXML_COMPACT 设置小型节点分配优化。会改善应用程序的性能。 5 LIBXML_DTDATTR 设置默认 DTD 属性。 5 LIBXML_DTDLOAD 加载外部子集。 5 LIBXML_DTDVALID 通过 DTD 进行验证。 5 LIBXML_NOBLANKS 删除空节点。 5 LIBXML_NOCDATA 把 CDATA 设置为文本节点。 5 LIBXML_NOEMPTYTAG 更改空标签(比如
改为
)。仅在 DOMDocument->save() 和 DOMDocument->saveXML() 函数中可用。5 LIBXML_NOENT 替代实体。 5 LIBXML_NOERROR 不显示错误报告。 5 LIBXML_NONET 在加载文档时停止网络访问。 5 LIBXML_NOWARNING 不显示警告报告。 5 LIBXML_NOXMLDECL 在保存文档时,撤销 XML 声明。 5 LIBXML_NSCLEAN 删除额外的命名空间声明。 5 LIBXML_XINCLUDE 使用 XInclude 置换。 5 LIBXML_ERR_ERROR 获得可恢复的错误。 5 LIBXML_ERR_FATAL 获得致命的错误。 5 LIBXML_ERR_NONE 获得无错误。 5 LIBXML_ERR_WARNING 获得简单警告。 5 LIBXML_VERSION 获得 Libxml 版本(例如:20605 或 20617)Get libxml version (e.g. 20605 or 20617) 5 LIBXML_DOTTED_VERSION 获得有点号的 Libxml 版本(例如:2.6.5 或 2.6.17)。 5
利用file协议读取本地文件
1 |
|
php伪协议读取源码
1 |
|
不回显漏洞信息外带信息
test.xml(靶场需要传输的xml文件)
1 |
e.dtd
1 | <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file://读取文件路径"> |
在内部DTD里,参数实体引用只能和元素同级而不能直接出现在元素声明内部,否则解析器会报错
在 外部DTD 文件中,参数实体的声明才能引用其他实体
%remote调用e.dtd
%int加载了file,%file获取了flag文件内容
%send将数据发送到靶机上
%不允许出现在 Entity的value中,所以需要将%进行Unicode编码为 % 或者 %(转化成Unicode编码有两种形式,以&#后接十进制数字,&#x后接十六进制数字)
使用本地php_xxe靶场,发送以下
1 | <?xml version="1.0" encoding="utf-8"?> |
操作过程:
在自己的vps或云服务器上先搭建站点(使用宝塔面板搭建php站点,端口任意,可以是默认80,域名写:公网ip:端口)
登录云服务器 腾讯云下目录为 /www/wwwroot/公网ip 在里面vim一个e.dtd,输入上述payload
注意选择监听端口,选择比较偏的,没有服务运行的,比如2333,然后要在云服务器控制台和宝塔都放行该端口,防火墙可关可不关
云服务器执行监听2333端口:
nc -lvvp 2333
本机靶场上发送构造的xml文件,云服务器端会回显file参数实体内容,即
php://filter/read=convert.base64-encode/resource=file://读取文件路径
⚠️踩过的坑:在日志中查看回显,服务器日志(80端口),但携带flag的http请求是访问3344端口的
报错输出
环境:
- libxml<=2.8(2.9以后默认不使用外部实体)
- 开启了报错
- 无回显
payload1
1 | <?xml version="1.0" ?> |
nonexistent可替换成任意根目录下不存在的文件夹名称,这也是报错的原因:读取了不存在文件
file参数实体内容即为想读取的文件内容
'
:'
%
:%
&
:&
payload2
1 | <?xml version="1.0" ?> |
DDos
1 |
|
该攻击通过创建一项递归的 XML 定义,在内存中生成十亿个”abc”字符串,从而导致 DDoS 攻击。原理为:构造恶意的XML实体文件耗尽可用内存,因为许多XML解析器在解析XML文档时倾向于将它的整个结构保留在内存中,解析非常慢,造成了拒绝服务器攻击。
.PHP expect RCE
由于 PHP 的 expect 并不是默认安装扩展,如果安装了这个expect 扩展我们就能直接利用 XXE 进行 RCE
示例代码:
1 | <!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]> |
HTTP 内网主机探测
探测脚本
1 | import requests |
要进行内网探测我们还需要做一些准备工作,我们需要先利用 file 协议读取我们作为支点服务器的网络配置文件,看一下有没有内网,以及网段大概是什么样子。我们可以尝试读取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 文件
HTTP 内网主机端口扫描
利用burpsuite辅助
1 | <?xml version="1.0" encoding="utf-8"?> |
端口号部分用bp爆破
至此,我们已经有能力对整个网段进行了一个全面的探测,并能得到内网服务器的一些信息了,如果内网的服务器有漏洞,并且恰好利用方式在服务器支持的协议的范围内的话,我们就能直接利用 XXE 打击内网服务器甚至能直接 getshell(比如有些 内网的未授权 redis 或者有些通过 http get 请求就能直接getshell 的 比如 strus2)
内网盲注
这里有一个小技巧,当我们使用 libxml 读取文件内容的时候,文件不能过大,如果太大就会报错,于是我们就需要使用 php
过滤器的一个压缩的方法压缩:echo file_get_contents(“php://filter/zlib.deflate/convert.base64-encode/resource=/etc/passwd”);
解压:echo file_get_contents(“php://filter/read=convert.base64-decode/zlib.inflate/resource=/tmp/1”);
我们考虑内网有没有东西,我们读取
1 | /proc/net/arp |
WAF绕过
针对的WAF
1.成熟的waf——使用自己的解析器预处理XML文档的WAFs。
2.基于正则表达式。仅搜索数据中的特定子字符串或正则表达式的WAFS。
额外空格
由于XXE通常在XML文档的开头,所以比较省事儿的WAF可以避免处理整个文档,而只解析它的开头。但是,XML格式允许在格式化标记属性时使用任意数量的空格,因此攻击者可以在<?xml?>
或<!DOCTYPE>
中插入额外的空格,从而绕过此类WAF。
格式无效(链接到未知实体)
为了安全起见,一些WAF通常不会读取链接文件的内容,但外部资源链接还可以存在于声明<!DOCTYPE>中。这意味着未读取文件内容的WAF将不会读取文档中实体的声明,这样在后续正文中使用外部链接中的实体,就会被waf的xml解析器认为是未知实体,导致错误
罕见编码
一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)、UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码。在这种编码的帮助下,使用正则表达式可以很容易地绕过WAF,因为在这种类型的WAF中,正则表达式通常仅配置为单字符集。
改变编码可以利用vscode的按其他编码保存
例题:[CSAWQual 2019]Web_Unagi
在一个文档中使用两种类型的编码
当包含编码属性的标记引用与文档首字节决定的字符集不同时,一些解析器更改编码,使文件的开头有一组字符,其余的是另一组编码。也就是说,不同的解析器可能在不同的时间转换编码。Java解析器(javax.xml.parsers)在结束后严格地更改字符集,而libxml2解析器可以在执行“编码”属性的值之后或在处理之前或之后切换编码。
只有在根本不处理这些文件时,比较成熟的WAF才能可靠地防止这些文件中的攻击。我们还必须记住,有许多同义词编码,例如UTF-32BE和UCS-4BE。此外,有些编码可能不同,但从编码文档初始部分 <?xml?>
的角度来看,它们是兼容的。例如,看似UTF-8的文档可能包含字符串<?xml version=”1.0” encoding=”windows-1251”?>
。
这里有一些例子。为了简明扼要,我们不把XXE放在文档里。
libxml2解析器将文档视为有效,但是,javax.xml.parsers set中的Java引擎认为它无效:
控制文档的编码(例如)——文档的第一个字节带有可选的BOM(字节顺序标记)。
XXE防御
方案一:使用语言中推荐的禁用外部实体的方法
PHP:
1 | libxml_disable_entity_loader(true); |
JAVA:
1 | DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance(); |
java:https://xz.aliyun.com/t/10774#toc-11
https://xz.aliyun.com/t/3357#toc-20
Python:
1 | from lxml import etree |
尝试改用defusedxml 是一个纯 Python 软件包,它修改了所有标准库 XML 解析器的子类,可以防止任何潜在的恶意操作。 对于解析不受信任的XML数据的任何服务器代码,建议使用此程序包。
方案二:手动黑名单过滤(不推荐)
过滤关键词:
1 | <!DOCTYPE、<!ENTITY SYSTEM、PUBLIC |
- 标题: XXE学习
- 作者: Sl0th
- 创建于 : 2022-04-22 00:02:32
- 更新于 : 2024-11-11 18:23:06
- 链接: http://sl0th.top/2022/04/22/XXE学习/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。