文章分类:PHP漏洞利用分析
翻译来源:https://www.synacktiv.com/en/publications/elfinder-the-story-of-a-repwning.html
最近我们发现了elFinder软件中的路径遍历问题,该漏洞被分配了CVE-2022-26960。虽然此漏洞类型比较常见,但其发现的故事却非常有趣。在这里需要继续阅读以了解详细信息。
elFinder软件的这个严重的漏洞问题一直存在,在2019年,我们公司Synacktiv已经参与了一些漏洞的研究,Thomas Chauchefoin披露了一个该产品的命令注入问题。
因此,在我们的安全测试中,它经常是我们测试的重点之一,有时我们会发现一些意外的漏洞。
几个月前,我们公司在常规的入侵渗透测试过程中,使用目标应用程序的wordlists对目标的根目录进行fuzz测试,测试结果结果引起了我们的兴趣。
000017052: 301 9 L 28 W 321 Ch "elfinder"
在访问我们发现的目录后,我们发现了一个 elFinder Web管理器的部署。点击几下后,发生了令人惊异的事情:版本居然老版本2.1.12,版本非常古老!(2016年的版本)。
Thomas在2019年发现的CVE问题中是关于影响了2.1.48之前的所有版本,所以我们的2.1.12版本也是可以使用的,所以我们应该进入方向从命令注入走向RCE,接下来,如果我们阅读了有关CVE-2019-9194的报告,发现了一些微妙的细节:
其中$volume->resize()的实现可以在elFinder/php/elFinderVolumeDriver.class.php中找到,并且会执行各种操作确保调整大小可以不会显式禁用。
在我们的例子中,禁用了调整大小的功能,防止了攻击利用。然而,在这样一个旧版本中,那么就会因为未升级的带有的其他问题。但是事实上这样的问题是存在多个的。
CVE-2018-9109可以发现允许读取任意文件的路径遍历,也可以删除它,对我们的生产环境不太好。
所以,你可以在CVE-2021-32682和CVE-2021-23394中发现SonarSource的报告,并且漏洞发现者仍然是Thomas Chauchefoin。如果你还没有度过他的两篇文章的话,我建议你去看看。其中的TL和DR是一个有趣的漏洞列表,https://www.synacktiv.com/en/publications/elfinder-the-story-of-a-repwning.html#references,可以造成下面非常严重的影响:
事实上,如果我们的产品不具备软件更新的条件,就做一流服务器的加固:
此时我们没有了一个容易利用的方法
提示:如果你曾经怀疑投资是必需的,这会导致你变得错误。
接下来发生的事就像一个大骗局,像一个漏洞抢劫,可能也是意外的好运。
如果你不熟悉我们所讨论的软件,他的特点是上传和下载文件,解压他们,预览诸如此类的功能。Web文件管理器是一个非常常见的Web应用程序,常常用于帮助用户上传、下载、编辑、删除和查看他们在Web服务器上存储的文件。然而,这些应用程序经常被安全研究人员和黑客用来寻找安全漏洞,因为它们可以直接操作Web服务器上的文件系统,从而可能导致危险的攻击行为。
Web文件管理器的安全漏洞案例研究:
让我们想象我们在2010年开发一个软件,这个软件没有被广泛的审查。让我们看看我们面对的一个处理文件路径的软件,为了完成这个功能,想象我们想要测试路径的穿越问题。第一种一般的行为是用长的../字符提供给应用,然后尝试到达/etc/passwd 用这种方式。
如果我们提供的绕过方式不起作用,添加一些二次编码,编码关键词字符等等。
但是如果使用这种方式还是不成功呢?不在一个6年的老版本中。没有一个软件被有能力的人复查和检查过。
事实就是我们所尝试的。
等等?结果真的绕过了?
我们发现我们在一种情况,这种情况下我们有一个意外看到的漏洞,并且没有办法,关于为什么是在这里,如此长得时间都没有人知道?然而,如果我们分析我们的paylaod,出现的是这个应用程序可能在处理../并且尝试删除他们。这种情况常在CTF中出现。
在我们的幸运纸上,让我们查找字符串".."。
$ find . -name "*.php" -exec grep -HF ".." {} \; ./elFinderVolumeDriver.class.php: * System Root path (Unix like: '/', Windows: '\', 'C:\' or 'D:\'...) ./elFinderVolumeDriver.class.php: $name = preg_replace('/^(.*?)(\..*)?$/', '$1_'.$files[$name]++.'$2', $name); ./elFinderVolumeDriver.class.php: $name != '.' && $name != '..' && $this->rmTmb($this->stat($p)); ./elFinderVolumeDriver.class.php: if (substr($path, 0, 3) === '..' . $separator) { ./elFinderVolumeDriver.class.php: // normalize `/../` ./elFinderVolumeDriver.class.php: if ($file === '.' || $file === '..') { ./elFinderVolumeDriver.class.php: if ($file !== '.' && $file !== '..') { ./elFinderVolumeDriver.class.php: if ($entry !== "." && $entry !== "..") { ./elFinderVolumeDriver.class.php: * Ususaly used for images, but can be realize for video etc... ./elFinderVolumeFTP.class.php: if (count($info) < 9 || $info[8] == '.' || $info[8] == '..') { ./elFinderVolumeFTP.class.php: if (($comp != '..') ./elFinderVolumeFTP.class.php: || ($new_comps && (end($new_comps) == '..'))) { ./elFinderVolumeFTP.class.php: if ($name && $name !== '.' && $name !== '..' && substr(strtolower($info[0]), 0, 1) === 'd') { ./elFinderVolumeFTP.class.php: * Ususaly used for images, but can be realize for video etc... ./elFinderVolumeFTP.class.php: if ($name !== '.' && $name !== '..' && (!$targets || isset($targets[$name]))) { ./elFinderVolumeFTP.class.php: $excludes = array(".",".."); ./elFinderVolumeLocalFileSystem.class.php: if (($comp != '..') ./elFinderVolumeLocalFileSystem.class.php: || ($new_comps && (end($new_comps) == '..'))) { ./elFinderVolumeLocalFileSystem.class.php: * Usualy used for images, but can be realize for video etc... ./elFinderVolumeDropbox.class.php: * Ususaly used for images, but can be realize for video etc... ./elFinderVolumeMySQL.class.php: * Usualy used for images, but can be realize for video etc...
这里可以发现出现了很多结果。甚至更少的是如果我们目标是elFinderVolumeFTP
, elFinderVolumeDropbox
和 elFinderVolumeMySQL
,很可能没有什么起作用对我们的问题。所有的这些结果,一个代码评论可能捕捉这个眼睛。
./elFinderVolumeDriver.class.php: // normalize
/../
跟着路径导致的getFullPath函数,从elFinderVolumeDriver类中发现。这个抽象类实现了一个通用组件,可以被使用,使用drivers调用,抽象文件从安坐,为不同的源类型。我们说我们仅仅考虑本地文件例子,所以让我们不用为此烦恼了。
/** * Resolve relative / (Unix-like)absolute path * * @param string $path target path * @param string $base base path * @return string */ protected function getFullPath($path, $base) { [...] // normalize `/../` $normreg = '#('.$sepquoted.')[^'.$sepquoted.']+'.$sepquoted.'\.\.'.$sepquoted.'#'; // '#(/)[^\/]+/\.\./#' while(preg_match($normreg, $path)) { $path = preg_replace($normreg, '$1', $path, 1); } [...] return $path; }
我们跳过函数中不相关的部分,然后获取问题的核心,以及我们正在寻找的模式。我们看见事实上正则表达式习惯使用“normalize /../”。我们可以询问:你好,什么能导致错误?但是我们也知道了答案。
由于代码评论。我们知道模式被寻找到#(/)[^\/]+/\.\./#
,并且匹配到所有的都被一个“/.”替换,这个过程是被一直重复知道没有任何被匹配。
但是这个模式是错误的,这个//../序号永远不会被匹配,允许路径穿越到我们高亮的地方。
让我们启动IDE的go to refer转到引用功能,验证该函数是为文件行为调用链的部分。
elFinderVolumeDriver 调用第10个函数 stat 超过50次,包括一个调用file(我们调用API的行为名字)。大多数的这些函数都是控制器对于行为,这意味着我们的问题可能有一个更广阔的影响,在读取任意文件方面。
在我们参与的时候,发现已经足够获取RCE。我们的目标是运行Laravel示例,然后我们泄漏cookie值,你知道记过。
下一步的问题是:这个问题在目前最新的release中存在吗?现在的问题是从我们测试那个版本到最新的版本之间已经有50个小版本发布了。
$ git diff --ignore-cr-at-eol --ignore-space-at-eol -w --ignore-blank-lines 2.1.12 2.1.60 -- php/elFinderVolumeDriver.class.php [...] - protected function getFullPath($path, $base) { + protected function getFullPath($path, $base) + { $separator = $this->separator; $systemroot = $this->systemRoot; + $base = (string)$base; - if ($base[0] === $separator && strpos($base, 0, strlen($systemroot)) !== $systemroot) { + if ($base[0] === $separator && substr($base, 0, strlen($systemroot)) !== $systemroot) { $base = $systemroot . substr($base, 1); } + if ($base !== $systemroot) { + $base = rtrim($base, $separator); + } // 'Here' if ($path === '' || $path === '.' . $separator) return $base; @@ -5259,6 +6807,9 @@ abstract class elFinderVolumeDriver { while (preg_match($normreg, $path)) { $path = preg_replace($normreg, '$1', $path, 1); } + if ($path !== $systemroot) { + $path = rtrim($path, $separator); + }
使用diff工具在2.1.12和所谓的发布最新版本比较。没有发现在有漏洞的函数getFullPath函数有什么不同的改变。看起来似乎没有对应的补丁,但是在开香槟庆祝之前,我们要使用payload有效的攻击最新的版本验证漏洞是否攻击成功。
等等,我们发现了什么?
所以我们的漏洞函数并没有打补丁,可是我们的攻击payload没有起任何作用。当然,所以一定有另一个改变在软件中一定预防了直接的目录穿越。
由SonarSource[2]报告的漏洞中,有两个与路径遍历相关。elFinderVolumeLocalFileSystem
的 _joinPath
方法是造成这个问题的根本原因。它在没有任何检查的情况下连接了两个路径,并使用下面的方式进行了修补:
_joinPath
函数的目录遍历补丁:
虽然该函数之前只是返回两个路径的连接,没有进一步的验证或清理,但新版本补丁包含了一些安全措施。特别是如果 _joinPath
函数是 file 操作的调用链的一部分,那么对 realpath
的调用可能会导致我们的问题。
elFinderVolumeLocaleFileSystem::_abspath
。elFinderVolumeDriver::abspathCE
。elFinderVolumeDriver::decode
。elFinderVolumeDriver
的 decode
方法实际上是用于解码 target 类型的参数(也就是从 l1_AAAA
参数中提取实际文件指针)。因此,在大多数action controllers中都会调用它,包括用于 file*调用的控制器,却无法绕过 decode
的调用。
但是,当我们我们追溯调用链到 _joinPath
并停在 elFinderVolumeLocaleFileSystem::_abspath
,似乎并不总是会调用 _joinPath
函数。
protected function _abspath($path) { if ($path === DIRECTORY_SEPARATOR) { return $this->root; } else { if (strpos($path, $this->systemRoot) === 0) { return $path; } else if (DIRECTORY_SEPARATOR !== '/' && preg_match('/^[a-zA-Z]:' . preg_quote(DIRECTORY_SEPARATOR, '/') . '/', $path)) { return $path; } else { return $this->_joinPath($this->root, $path); } } }
事实上,只有在查询的路径是相对路径时才会调用_joinPath
函数。但是在使用绝对路径直接返回了$path,所以应该足以在最新版本上利用此漏洞。
当然,仅仅将绝对路径(如/etc/passwd
)直接传递给应用程序是肯定不能成功利用该漏洞的。
对于“路径是绝对路径时会发生什么”这个问题的答案实际上在调用getFullPath
函数时的调用链中。如果回顾一下这个调用链,会发现其中经过一个名为_inpath
的函数。这个函数可以防止路径遍历漏洞。
protected function _stat($path) { $stat = array(); if (!file_exists($path) && !is_link($path)) { return $stat; } //Verifies the given path is the root or is inside the root. Prevents directory traveral. if (!$this->_inpath($path, $this->root)) { return $stat; }
正如注释中所解释的那样,_inpath
函数被用于验证请求的路径是否在配置的Web根目录内。即使我们知道这部分软件在处理点点斜杠时会出现问题,但是/etc/passwd不可能在root之内被考虑。所以要想实现这一点,提供的路径需要以配置的根路径作为前缀。
当然,这并非完全不可能。如果我们构建一个路径,确切地以该前缀开头,并利用双斜线技巧在它之后注入未经过滤的../字符序列,我们就应该能够通过所有的检查和防御函数。
漏洞成功利用!
如果您已经阅读到了这篇文章的这个部分,那么您可能已经知道,elFinder软件中存在一个路径遍历漏洞。其影响程度很大程度上取决于它所部署的上下文。至少可以进行任意文件读取,有时还足以实现远程代码执行。
正如我们之前所说,我们绕过的安全函数在多个action controllers中被使用。它应该能够调用像下面这样的动作:
如果有合适的权限,这些功能就等同于读取、写入和浏览服务器的文件系统。
elFinder还有其他的控制防御机制,特别是文件扩展名的控制,可以防止写入任意的PHP脚本等。然而,这并不是在主机上执行代码所必需的。你只需替换authorized_keys文件、crontab文件,等等。
最后,利用这个问题的最新版本需要知道elFinder根目录的文件系统路径。这个路径可能会被猜测(如/var/www/html?/srv/php?),通过暴力破解或通过另一个漏洞泄露,这部分留给读者自己去练习了。
在2.1.59之前的所有版本中并非都需要这样做。但在那些版本中,您可能也可以利用Sonar的Thomas报告的RCE漏洞。有可能您跟我们一样幸运...
Date | Action |
---|---|
2022-03-08 | 漏洞提交 |
2022-03-09 | 修补程序 |
2022-03-14 | CVE-2022-26960 发布 |
2022-03-14 | elFinder 2.1.61 修复版本发布 |
CVE-2022-26960是关于elFinder的一个安全漏洞,该漏洞可能会被攻击者利用进行路径遍历并读取、写入、浏览配置文件根目录外的文件。
[1] elFinder命令注入 < 2.1.48 https://www.synacktiv.com/ressources/advisories/elFinder_2.1.47_Command_Injection.pdf
[2] elFinder -Web文件管理器漏洞案例的研究 https://blog.sonarsource.com/elfinder-case-study-of-web-file-manager-vulnerabilities