elFinder Web文件管理器漏洞CVE-2021-32682 2021-23394

elFinder Web文件管理器漏洞CVE-2021-32682 2021-23394

应用程序与文件系统的交互始终是高度安全敏感的,因为微小的功能错误很容易成为可利用漏洞的来源。这种观察在 Web 文件管理器的情况下尤其正确,其作用是复制完整文件系统的功能并以透明的方式将其公开给客户端的浏览器。

elFinder 是一种流行的 Web 文件管理器,常用于 CMS 和框架,例如 WordPress 插件 ( wp-file-manager ) 或 Symfony 包,以允许对本地和远程文件进行轻松操作。过去,elFinder 一直是针对不安全配置或实际代码漏洞的活跃攻击的一部分。因此,elFinder 以安全的默认配置发布,以防止攻击者进行任何恶意使用。

作为我们对广泛部署的开源项目的定期评估的一部分,我们在 elFinder 中发现了多个新的代码漏洞。在以下 Web 文件管理器中常见代码漏洞的案例研究中,我们描述了五个不同的漏洞链,并演示了如何利用它们来控制底层服务器及其数据。我们还将讨论供应商后来实施的一些补丁,以展示如何在您自己的代码中防止它们。

影响

elFinder Web文件管理器漏洞CVE-2021-32682 2021-23394

我们在开发分支上工作,提交f9c906d。调查结果也在 2.1.57 版中得到证实;都影响默认配置(除非本文另有说明)并且不需要事先认证。

正如我们提到的,利用这些漏洞可以让攻击者在安装了 elFinder 的服务器上执行任意 PHP 代码,最终导致其受到攻击。 

我们在这篇博文中讨论的发现(均分配给 CVE-2021-32682)并成功利用以获取代码执行的结果是: 

  • 删除任意文件
  • 移动任意文件
  • 上传 PHP 文件
  • 参数注入
  • 竞争条件

所有这些错误类别在向用户公开文件系统的软件中非常常见,并且可能会影响广泛的产品,而不仅仅是 elFinder。 

elFinder 发布了 2.1.59 版以解决我们负责任地披露的所有错误。毫无疑问,这些漏洞也将被广泛利用,因为针对旧版本的漏洞利用已经公开发布,并且连接器文件名是尝试破坏网站时要查找的路径编译的一部分。因此,我们强烈建议所有用户立即将 elFinder 升级到最新版本。

技术细节

elFinder 带有一个用 PHP 编写的后端(也称为连接器)和一个用 HTML 和 JavaScript 编写的前端。该连接器是调度前端代码到右侧后端代码的操作来实现文件系统功能主要脚本。连接器可以配置为禁止危险操作,将上传限制为特定的 MIME 类型:默认安装中有两种不同的类型。我们在所谓的“最小”连接器中检测到漏洞。它只允许图像和纯文本上传,并且 FTP 是唯一支持的远程虚拟文件系统:这可能是最安全的,也是最有可能部署的。 

为了更好地理解我们将用来展示我们的发现的代码片段,我们将首先描述 elFinder 的路由是如何工作的。与许多现代 PHP 应用程序一样,连接器(例如connector.minimal.php)是唯一的入口点。它声明配置指令和闭包,然后实例化elFinder(核心)和elFinderConnector(elFinder和传输通道之间的接口,这里是 HTTP)。 

属性elFinder::$commands包含每个有效的操作和预期的参数:

php/elFinder.class.php

protected $commands = array(
  'abort' => array('id' => true),
  'archive' => array('targets' => true, 'type' => true, 'mimes' => false, 'name' => false),
  'callback' => array('node' => true, 'json' => false, 'bind' => false, 'done' => false),
  'chmod' => array('targets' => true, 'mode' => true),
  'dim' => array('target' => true, 'substitute' => false),
  'duplicate' => array('targets' => true, 'suffix' => false),
  // [...]

用户可以通过PATH_INFO、GET或POST提供具有所需命令参数的cmd参数来调用这些命令中的任何一个。在每个命令处理程序中,使用$args访问参数。

为了允许远程文件系统(FTP、Dropbox 等)与本地文件系统一起使用,elFinder 实现了一个文件系统抽象层(elFinderVolumeDriver),所有驱动程序都在其上构建。然后通过它们的卷名(例如t1_是垃圾,l1_是默认本地卷)和它们名称的 URL 安全 Base64引用文件。 

让我们首先深入研究一个任意文件删除错误链,它由两个不同的问题组成。

删除任意文件

PHP 核心不提供运行后台线程或执行同步和进程间通信的有效方法。elFinder 试图通过大量使用临时文件和请求后挂钩来平衡这一点。例如,用户可以通过调用同名方法来中止正在进行的操作:

php/elFinder.class.php

protected function abort($args = array())
{
  if (!elFinder::$connectionFlagsPath || $_SERVER['REQUEST_METHOD'] === 'HEAD') {
    return;
  }

  $flagFile = elFinder::$connectionFlagsPath . DIRECTORY_SEPARATOR . 'elfreq%s';
  if (!empty($args['makeFile'])) { 
    self::$abortCheckFile = sprintf($flagFile, $args['makeFile']); // <-- [1]
    touch(self::$abortCheckFile);
    $GLOBALS['elFinderTempFiles'][self::$abortCheckFile] = true;
    return;
  }

  $file = !empty($args['id']) ? sprintf($flagFile, $args['id']) : self::$abortCheckFile; // <-- [2]
  $file && is_file($file) && unlink($file);
}

此处,[1]和[2]处存在代码漏洞:用户控制的参数在没有事先检查的情况下连接成完整路径。对于[1],它最终可以创建一个具有完全可控名称的空文件,而在[2] 中,它可以用于删除任意文件。两个错误的 SonarCloud 问题都可用:[1][2]

有一个问题:由[1]生成的文件名将以elfreq为前缀。在路径遍历攻击中,如果路径中的任何前任不存在或不是目录,POSIX 系统将无法进行路径解析。例如,解析/tmp/i_do_not_exist/../或/tmp/i_am_a_file/../将分别以ENOENT和ENOTDIR失败。这个先决条件使得利用这两个漏洞不可能按原样进行,并且需要另一个错误,例如创建任意目录的能力。

然后攻击者可以查看命令mkdir并发现允许这种确切行为的原语。这是它的顶级处理程序,在它通过文件系统抽象层之前:

php/elFinder.class.php

function mkdir($args)
{
  $target = $args['target'];
  $name = $args['name'];
  $dirs = $args['dirs'];
            // [...]
  if (($volume = $this->volume($target)) == false) {
    return array('error' => $this->error(self::ERROR_MKDIR, $name, self::ERROR_TRGDIR_NOT_FOUND, '#' . $target));
  }
    // [...]
  return ($dir = $volume->mkdir($target, $name)) == false
            ? array('error' => $this->error(self::ERROR_MKDIR, $name, $volume->error()))
            : array('added' => array($dir));
    }
}

elFinderVolumeDriver 中存在一个通用实现来处理应该创建的卷和路径。它将使用文件系统上的卷绝对路径作为第一个参数和目标名称作为第二个参数调用[1] 中特定于卷的实现: 

php/elFinderVolumeDriver.class.php

public function mkdir($dsthash, $name)
{
  // [...]
  $path = $this->decode($dsthash);
  // [...]
  $dst = $this->joinPathCE($path, $name);
  // v--- [1]
  $mkpath = $this->convEncOut($this->_mkdir($this->convEncIn($path),      $this->convEncIn($name)));
    if ($mkpath) {
        $this->clearstatcache();
        $this->updateSubdirsCache($path, true);
        $this->updateSubdirsCache($mkpath, false);
    }

    return $mkpath ? $this->stat($mkpath) : false;
}

它的定义如下:

php/elFinderVolumeLocalFileSystem.class.php

protected function _joinPath($dir, $name)
{
  return rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name;
}

protected function _mkdir($path, $name)
{
  $path = $this->_joinPath($path, $name);

  if (mkdir($path)) {
    chmod($path, $this->options['dirMode']);
    return $path;
  }
 
  return false;
}

elFinderVolumeLocalFileSystem::_joinPath()只是将两个值连接起来,导致路径遍历漏洞。这提供了在本地文件系统上创建任意空文件夹的原语。虽然本身不​​是漏洞,但它允许利用上述行为。 

还值得注意的是rm命令中存在完整路径公开,公开给定文件在本地文件系统上的绝对路径:

php/elFinderVolumeDriver.class.php

protected function remove($path, $force = false)
{
  $stat = $this->stat($path);

  if (empty($stat)) {
    return $this->setError(elFinder::ERROR_RM, $path, elFinder::ERROR_FILE_NOT_FOUND);
  }

此漏洞的影响非常依赖于环境:它可能与其他 elFinder 错误链接,用于触发其他应用程序中的有趣行为(例如删除 WordPress 的wp-config.php文件以获得代码执行)或用于影响现有的安全措施(例如删除.htaccess文件)。

此漏洞已通过提高执行elFinderVolumeLocalFileSystem :: _ joinPath()断言,最终的路不会是基地之一的外面。还添加了跨代码库对basename() 的几次调用作为强化措施。

移动任意文件

相同的elFinderVolumeLocalFileSystem::_joinPath()方法用于其他操作,例如rename:它结合了卷基本目录和用户提供的目标名称。因此,它很容易受到我们刚刚描述的错误的影响。 

以下代码段是elFinderVolumeLocalFileSystem::rename()的实际实现,在执行所有负责解码路径并确保允许目标扩展名的代码之后:

php/elFinderVolumeLocalFileSystem.class.php

protected function _move($source, $targetDir, $name)
{
  $mtime = filemtime($source);
  $target = $this->_joinPath($targetDir, $name);
  if ($ret = rename($source, $target) ? $target : false) {
    isset($this->options['keepTimestamp']['move']) && $mtime && touch($target, $mtime);
  }
  return $ret;
}

虽然目标扩展名仍然受到 MIME 检查的严格限制,但对于未经身份验证的攻击者来说,根据环境,通过覆盖authorized_keys、composer.json等文件,该原语足以在服务器上执行命令。这个错误已经被修复用相同的补丁,因为我们讨论之前的错误。

上传 PHP 文件

对于大多数 PHP 应用程序,elFinder 面临的最大威胁是攻击者能够将 PHP 脚本上传到服务器,因为没有任何东西(除了相当坚固的 Web 服务器配置)可以阻止他们直接访问它以执行其内容。维护人员最初试图通过制作一个将危险 MIME 类型与相关扩展相关联的阻止列表来防御这种情况:

php/elFinderVolumeDriver.class.php

'staticMineMap' => array(
  'php:*' => 'text/x-php',
  'pht:*' => 'text/x-php',
  'php3:*' => 'text/x-php',
  'php4:*' => 'text/x-php',
  'php5:*' => 'text/x-php',
  'php7:*' => 'text/x-php',
  'phtml:*' => 'text/x-php',
  // [...]

在我们的测试环境(Ubuntu 20.10 上的 Apache HTTP 2.4.46-1ubuntu1)中,默认配置声明.phar文件应该被视为application/x-httpd-php ( [1] ) 并被解释:

$ cat /etc/apache2/mods-available/php7.4.conf
<FilesMatch ".+\.ph(ar|p|tml)$">         
    SetHandler application/x-httpd-php  # <-- [1]
</FilesMatch>                           
<FilesMatch ".+\.phps$">
    SetHandler application/x-httpd-php-source
    # Deny access to raw php sources by default
    # To re-enable it's recommended to enable access to the files
    # only in specific virtual host or directory
    Require all denied
</FilesMatch>
# Deny access to files without filename (e.g. '.php')
<FilesMatch "^\.ph(ar|p|ps|tml)$">
    Require all denied
</FilesMatch>
// [...]

在 Debian 的稳定版本中也观察到了这种配置。尽管对文件内容执行了另一轮 MIME 类型检测,但由于 PHP 解释器允许在已解释文件中的任何位置使用语句(例如,<?php可以放置在一些虚拟数据之后),因此可以轻松绕过这一点。

修复很简单:它宣称.phar文件与MIME相关的文本/ X-PHP,这是默认不允许的。 

参数注入

在使 elFinder 如此强大的默认功能中,用户可以选择多个文件并使用zip、rar和7z等外部工具对其进行存档。此功能在名为archive的操作下公开:

php/elFinder.class.php

public function archive($args)
{
  $targets = isset($args['targets']) && is_array($args['targets']) ? $args['targets'] : array();
  $name = isset($args['name']) ? $args['name'] : '';

  if (($volume = $this->volume($targets[0])) == false) {
    return $this->error(self::ERROR_ARCHIVE, self::ERROR_TRGDIR_NOT_FOUND);
  }

  foreach ($targets as $target) {
    $this->itemLock($target);
  }

  return ($file = $volume->archive($targets, $args['type'], $name))
        ? array('added' => array($file))
        : array('error' => $this->error(self::ERROR_ARCHIVE, $volume->error()));
}

请注意,即使禁止上传,用户也可以通过对现有文件调用存档命令来创建存档。该实现特定于正在使用的虚拟文件系统。我们将只关注默认的,因为它是由elFinderVolumeLocalFileSystem继承的,它制作完整的命令行 ( [1] ) 并使用默认 shell ( [2] )执行它:

php/elFinderVolumeLocalFileSystem.class.php

protected function makeArchive($dir, $files, $name, $arc)
{
// [...]
    $cwd = getcwd();
    if (chdir($dir)) {
      foreach ($files as $i => $file) {
        $files[$i] = '.' . DIRECTORY_SEPARATOR . basename($file);
      }
      $files = array_map('escapeshellarg', $files);

      $cmd = $arc['cmd'] . ' ' . $arc['argc'] . ' ' . escapeshellarg($name) . ' ' . implode(' ', $files); // <-- [1]
      $this->procExec($cmd, $o, $c);                // <-- [2]
// [...]

这里,$name的值来自用户控制的参数$_GET[‘name’]。虽然使用escapeshellarg()正确转义以防止使用命令替换序列,但程序将尝试将此值解析为标志 ( –foo=bar ),然后作为位置参数。还值得注意的是,在选择 ZIP 存档器的情况下,用户的值后缀为.zip。

命令zip实现了完整性测试功能 ( -T ),可与-TT一起使用以指定要运行的测试命令。在目前的情况下,它为攻击者提供了一种使用此参数注入执行任意命令的方法。

为了能够利用此漏洞,攻击者需要创建一个虚拟文件(例如a.txt),将其存档以创建a.zip,然后使用名称以原始文件和存档为目标调用存档操作像-TmTT=”$(id>out.txt)foooo”。

生成的命令行将是zip -r9 -q ‘-TmTT=”$(id>out.txt)foooo”.zip’ ‘./a.zip’ ‘./a.txt’,从而执行id并记录其标准输出到out.txt — 此文件将与 elFinder 界面中的其他文档一起使用。

当需要修复这个错误时,zip不是很友好。通常基于 POSIX 的方法–(请参阅我们之前关于 Composer 中参数注入的深入解释的文章)不能在此处应用,因为zip将退出并出现以下错误:

zip error: Invalid command arguments (can't use -- before archive name)

然后维护人员决定在存档名称前加上./以防止任何参数注入的风险。他们还决定在同一个补丁中强化对其他存档器(7z、rar等)的调用。 

隔离和竞争条件

让我们来看看我们对这个案例研究的最后发现。由于无法上传档案,因此无法在默认配置中利用隔离功能中的此漏洞;由于其设计,该功能可能会导致未来的安全问题。 

隔离背后的基本原理是档案可能包含不需要的文件(主要是 PHP 脚本),如果没有首先运行安全检查(例如,使用 MIME 验证),则不应在当前文件夹中提取这些文件。因此,elFinder 选择将档案提取到名为.quarantine的文件夹中,放置在files/文件夹下,并且  elFinderVolumeLocalFileSystem::_extract()为每个档案提取生成一个随机目录名称(在[1] 处):

php/elFinderVolumeLocalFileSystem.class.php

protected function _extract($path, $arc)
{
  if ($this->quarantine) {
    $dir = $this->quarantine . DIRECTORY_SEPARATOR . md5(basename($path) . mt_rand()); // <-- [1]
    $archive = (isset($arc['toSpec']) || $arc['cmd'] === 'phpfunction') ? '' : $dir . DIRECTORY_SEPARATOR . basename($path);
// [...]

这可以通过strace或inotify套件动态确认,例如这里有一个包含 PHP 文件的存档:

$ inotifywait -m -r .
./ CREATE,ISDIR efbf975ccbac8727f434574610a0f1b6
./ OPEN,ISDIR efbf975ccbac8727f434574610a0f1b6
]...[
./efbf975ccbac8727f434574610a0f1b6/ ATTRIB,ISDIR
./efbf975ccbac8727f434574610a0f1b6/ CREATE win.php
./efbf975ccbac8727f434574610a0f1b6/ OPEN win.php
./efbf975ccbac8727f434574610a0f1b6/ MODIFY win.php
./efbf975ccbac8727f434574610a0f1b6/ ATTRIB win.php
./efbf975ccbac8727f434574610a0f1b6/ CLOSE_WRITE,CLOSE win.php
./efbf975ccbac8727f434574610a0f1b6/ ATTRIB win.php
[...]
./efbf975ccbac8727f434574610a0f1b6/ DELETE win.php
[...]
./efbf975ccbac8727f434574610a0f1b6/ DELETE_SELF

这个轨迹可以理解为:

  • 创建了一个名为efbf975ccbac8727f434574610a0f1b6的文件夹,
  • 命名的文件win.php是内创建efbf975ccbac8727f434574610a0f1b6,
  • 数据写入win.php,
  • win.php被删除,
  • efbf975ccbac8727f434574610a0f1b6被删除。

如果服务器被配置为列出目录,这个行为很容易被利用,因为可以在 MIME 验证步骤和删除它们之前访问危险文件(例如.php)。然而,如果无法以这种方式找到随机目录名称,那么竞争条件窗口太小而无法考虑涉及暴力破解的攻击。 

攻击者可能会发现复制操作可用于内部文件夹,如.quarantine,并复制任何文件而不管其内容。虽然它本身是一个无害的功能错误,但它可以与隔离功能链接在一起,以在删除之前复制包含我们提取的存档的文件夹。复制的文件夹然后在界面中可见,并允许攻击者绕过随机名称来访问恶意脚本,最终允许执行任意代码。

作为修复,维护人员决定将.quarantine文件夹移到files/之外。该elFinderVolumeLocalFileSystem抽象层是不知道这个文件夹的东西之外,防止任何意外的行动.quarantine。

时间轴

日期行动
2021-03-22向维护者报告这 5 个问题
2021-06-10维护者承认我们所有的发现
2021-06-13elFinder 2.1.59 发布,修复了我们报告的bug
2021-06-13分配了 CVE-2021-32682 和 CVE-2021-23394

总结

在本案例研究中,我们研究了 Web 文件管理器中常见的关键代码漏洞。我们在当时可用的最新版 elFinder 中展示了我们在现实世界中的一些发现,包括它们的潜在影响以及供应商如何修复它们。它使我们能够证明无害的错误通常可以组合在一起以获得任意代码执行。我们认为记录和报告这些漏洞以打破未来的错误链并降低类似问题的风险非常重要。

我们还了解到处理路径并不容易,应该采取额外的措施:在“低级”函数中执行额外的检查,自信地使用basename()和dirname()(并知道它们的限制!)并始终验证用户控制的数据。此类错误在 Web 文件管理器中非常见,您在使用它们时应始终牢记此类错误。

虽然我们不打算发布针对这些漏洞的任何漏洞利用,但我们仍然希望让您注意这样一个事实,即任意代码执行很容易被证明,并且攻击者复制它不会有太多麻烦。我们敦促您立即升级到 elFinder 2.1.59。我们还建议对连接器实施强访问控制(例如基本访问身份验证)。 

最后,我们要感谢 elFinder 的维护者认可我们的建议并及时和专业地修复这些漏洞。

相关博客文章

from

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用*标注