pgAdmin命令执行漏洞的三年

前几天看到pgAdmin发布了新漏洞,《CVE-2024-3116 – Remote Code Execution Vulnerability in pgAdmin – PostgreSQL Tools (<=8.4): Detailed Analysis Report》。看了这个漏洞利用的过程,我总感觉有所不对,我也在『代码审计』知识星球里发表了相关疑问。随后,我研究了一下这个漏洞的来龙去脉,简单写下这篇短文。

pgAdmin命令执行漏洞的三年

CVE-2022-4223:pgAdmin <= 6.16 未授权命令执行漏洞

最新的CVE-2024-3116漏洞,其利用到了/misc/validate_binary_path这个API。这个API出现问题不是第一次了,最远的追溯到几年前CVE-2022-4223这个未授权命令执行漏洞。

CVE-2022-4223是一个非常简单的漏洞,我甚至觉得有可能是一个后门。

pgAdmin支持在后台设置psql、pg_dump、pg_restore等PostgreSQL数据库工具所在的基础路径,在保存设置前,用户可以执行validate_binary_path这个API来检查路径是否合法。

检查的过程就是,用户传入路径,然后pgAdmin分别给这些路径拼接上“psql”、“pg_dump”等可执行文件名,并执行psql --version命令。如果返回结果是正常,则说明文件存在,用户路径合法;如果返回结果出错,则说明文件不存在,用户传入的路径不合法。

validate_binary_pathAPI代码如下:

pgAdmin命令执行漏洞的三年

可见,这里用户传入的utility_path拼接上可执行文件名后,直接拼接进subprocess.getoutput()方法执行。这是一个非常简单的命令注入漏洞。

而且这个API没有增加@login_required修饰器,任意用户均可调用这个函数,导致了未授权命令执行漏洞。

漏洞复现的过程可以参考Vulhub中的环境:https://github.com/vulhub/vulhub/tree/master/pgadmin/CVE-2022-4223

pgAdmin命令执行漏洞的三年

CVE-2023-5002:pgAdmin <= 7.6 后台命令执行漏洞

官方对于CVE-2022-4223漏洞,做了如下两个处理:

  • validate_binary_path函数增加@login_required装饰器,限制未授权的用户访问相关接口
  • 使用os.path.exists()检查用户传入的路径是否有效

@login_required装饰器是由Flask-Login这个第三方库提供的能力,并不存在绕过的问题,所以这个API后续就没法再无授权的情况下利用了。

对于使用os.path.exists()检查用户传入的路径是否存在,我们可以想到下面两个绕过方法:

  • 使用../跳转到一个存在的目录,如:/path/to/exist/folder/a";id;/../
  • 通过文件上传等功能创建一个文件名中包含Payload的文件

第一个问题其实是一个经典问题,在『代码审计』星球创建之初就讨论过:https://t.zsxq.com/19Cfkp0b1。当时虽然是使用PHP的做演示,但实际上答案在Python中是一致的:判断文件存在时,Windows会将路径normalize后再判断,Linux会逐级判断文件夹是否存在

所以,表现出来的结果就是,下面代码在Windows环境下会返回True:

pgAdmin命令执行漏洞的三年

在Linux环境下会返回False:

pgAdmin命令执行漏洞的三年

所以对于CVE-2022-4223的补丁,在Windows环境下我们可以直接绕过os.path.exists()继续注入命令,但Linux下需要想想其他方法。

pgAdmin后台提供了一个文件管理器,在菜单的“Tools -> Storage Manager”中:

pgAdmin命令执行漏洞的三年

这个文件管理器具有上传、删除、创建目录等功能,用户上传的文件均位于其个人storage目录下,默认情况下这个基础目录是固定的。这个绝对目录可以通过上传文件来获取,随便上传一个文件,即可获取到完整路径,比如我这里是/var/lib/pgadmin/storage/vulhub_example.com

pgAdmin命令执行漏洞的三年

然后创建或上传一个新文件,文件名是我们的payload:";id;#

pgAdmin命令执行漏洞的三年

最后拼接出的绝对路径为/var/lib/pgadmin/storage/vulhub_example.com/";id;#,这个文件存在可以绕过os.path.exists(),并传入subprocess.getoutput()执行的完整命令是:

"/var/lib/pgadmin/storage/vulhub_example.com/";id;#" --version

在设置中填写完整路径,并点击右侧的“validate”:

pgAdmin命令执行漏洞的三年

命令被成功执行:

pgAdmin命令执行漏洞的三年

这个漏洞的完整复现过程和环境也可以在Vulhub上找到:https://github.com/vulhub/vulhub/tree/master/pgadmin/CVE-2023-5002

CVE-2024-3116:pgAdmin <= 8.4 后台远程命令执行漏洞

那么说回到本文开头的漏洞,CVE-2024-3116漏洞其实仍然是之前漏洞没有修复完成导致的。对于CVE-2023-5002,pgAdmin官方的修复方法还算比较正常,就是将subprocess.getoutput()改成subprocess.run(),且shell=False

pgAdmin命令执行漏洞的三年

此时用户控制的就是subprocess.run()中命令列表的文件名位置,虽然无法继续进行命令注入了,但因为这里仍然执行了用户输入的文件,如果我们上传一个文件名是“psql”的可执行文件,是否可以仍然执行任意代码?

本文开头说的文章就是这个思路:

pgAdmin命令执行漏洞的三年

首先在本地编写一个恶意的C程序,并使用上一章中提到的方法将其上传到个人目录下,并改名为psql。

接着在validate_binary_pathAPI中传入这个psql文件的绝对路径,进入subprocess.run()下执行,完成漏洞利用。

但这里没有提到的重要一点是,可执行权限。在Linux下,如果一个文件没有执行权限,是不能直接被执行的。所以,上述文章中给出的那个运行在Linux下的C程序(使用curl来验证漏洞是否存在),是肯定无法执行的,不知道作者是如何复现漏洞的。

当然,在部分Windows环境下没有非常严格的文件权限机制,上传一个psql.exe文件即可被执行。所以,这个漏洞其实只能影响Windows环境。

由于CVE-2024-3116这个漏洞的利用条件比较苛刻,我也没有制作Vulhub环境来复现。后来原文的作者好像也意识到了他文章中的问题,于是发布了新的文章《CVE-2024-3116 – Remote Code Execution Vulnerability in pgAdmin – PostgreSQL Tools (<=8.4): Detailed Analysis Report》,将其中Linux相关的内容改成了Windows:

pgAdmin命令执行漏洞的三年

有兴趣的同学可以参考他的文章做复现。

总结

最后总结一下,这个文章分析了pgAdmin在同一个API上跨越了三年的三个漏洞(CVE-2022-4223、CVE-2023-5002、CVE-2024-3116)。其中影响最大的也还是第一个未授权命令注入漏洞,可以让攻击者在无需任何权限的情况下在pgAdmin中执行任意命令。

pgAdmin命令执行漏洞的三年

喜欢这篇文章,点个在看再走吧~

加入「代码审计」,学习更多安全知识。一次付费,终身学习免续费

pgAdmin命令执行漏洞的三年

原文始发于微信公众号(代码审计):pgAdmin命令执行漏洞的三年

版权声明:admin 发表于 2024年4月7日 上午10:01。
转载请注明:pgAdmin命令执行漏洞的三年 | CTF导航

相关文章