前几天看到pgAdmin发布了新漏洞,《CVE-2024-3116 – Remote Code Execution Vulnerability in pgAdmin – PostgreSQL Tools (<=8.4): Detailed Analysis Report》。看了这个漏洞利用的过程,我总感觉有所不对,我也在『代码审计』知识星球里发表了相关疑问。随后,我研究了一下这个漏洞的来龙去脉,简单写下这篇短文。
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_path
API代码如下:
可见,这里用户传入的utility_path
拼接上可执行文件名后,直接拼接进subprocess.getoutput()
方法执行。这是一个非常简单的命令注入漏洞。
而且这个API没有增加@login_required
修饰器,任意用户均可调用这个函数,导致了未授权命令执行漏洞。
漏洞复现的过程可以参考Vulhub中的环境:https://github.com/vulhub/vulhub/tree/master/pgadmin/CVE-2022-4223
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:
在Linux环境下会返回False:
所以对于CVE-2022-4223的补丁,在Windows环境下我们可以直接绕过os.path.exists()
继续注入命令,但Linux下需要想想其他方法。
pgAdmin后台提供了一个文件管理器,在菜单的“Tools -> Storage Manager”中:
这个文件管理器具有上传、删除、创建目录等功能,用户上传的文件均位于其个人storage目录下,默认情况下这个基础目录是固定的。这个绝对目录可以通过上传文件来获取,随便上传一个文件,即可获取到完整路径,比如我这里是/var/lib/pgadmin/storage/vulhub_example.com
:
然后创建或上传一个新文件,文件名是我们的payload:";id;#
:
最后拼接出的绝对路径为/var/lib/pgadmin/storage/vulhub_example.com/";id;#
,这个文件存在可以绕过os.path.exists()
,并传入subprocess.getoutput()
执行的完整命令是:
"/var/lib/pgadmin/storage/vulhub_example.com/";id;#" --version
在设置中填写完整路径,并点击右侧的“validate”:
命令被成功执行:
这个漏洞的完整复现过程和环境也可以在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
:
此时用户控制的就是subprocess.run()
中命令列表的文件名位置,虽然无法继续进行命令注入了,但因为这里仍然执行了用户输入的文件,如果我们上传一个文件名是“psql”的可执行文件,是否可以仍然执行任意代码?
本文开头说的文章就是这个思路:
首先在本地编写一个恶意的C程序,并使用上一章中提到的方法将其上传到个人目录下,并改名为psql。
接着在validate_binary_path
API中传入这个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在同一个API上跨越了三年的三个漏洞(CVE-2022-4223、CVE-2023-5002、CVE-2024-3116)。其中影响最大的也还是第一个未授权命令注入漏洞,可以让攻击者在无需任何权限的情况下在pgAdmin中执行任意命令。
喜欢这篇文章,点个在看再走吧~
加入「代码审计」,学习更多安全知识。一次付费,终身学习免续费。
原文始发于微信公众号(代码审计):pgAdmin命令执行漏洞的三年