一步一步教你漏洞挖掘之python Flask框架pgadmin4 组合RCE漏洞链完整分析
引言
pgadmin4是一款为PostgreSQL设计的可靠和全面的数据库设计和管理软件, 它允许您连接到特定的数据库,创建表和运行各种从简单到复杂的SQL语句。
2021年初看到有大佬发了个关于pgadmin4的RCE漏洞。当时很感兴趣跟踪调试了下,发现这个漏洞挺有意思,通过一系列的组合模式,最终构建了一条完整的RCE漏洞链条。整个分析过程相对比较复杂,下面将完整分析过程分享给大家。
环境安装
可以通过docker拉取进行安装,也可以手动下载后自行安装,既然想完整研究该漏洞,选择手动安装方式。因为使用的是python2 ,所以这里安装最后支持python2的版本v4.21:
分析代码结构发现系统是基于Flask框架进行构建的。在启动前可以修改配置文件,将服务器IP修改为`0.0.0.0`,这样启动后就可以外部访问了,启动输入邮箱和密码`test@123.com/123456`。访问首页:
远程调试
利用pycharm进行远程调试。配置sftp:
配置python解析器:
尝试1:认证缺陷
正常的登录认证信息为`test@123.com/123456`,但是在黑盒测试过程中发现输入`1/123456`也可以登录成功。深入了解一下登录后账号验证的逻辑。在Flask项目登录代码处下断点:
跟下来,最后通过`flask_security`的`forms.py`中的函数`validate()`进行验证:
其中的`self.email.data`就是提交的参数1。继续跟进`get_user`:
参数`identifier`对应的就是`email`的值。当为数字或者`uuid`时,调用如下查询过程:
rv = self.user_model.query.get(identifier)if rv is not None: return rv
也就是说`flask_security`存在逻辑缺陷,可以在不知道邮箱地址的情况下,通过主键`id`对密码进行爆破。这就导致了我们只要知道密码,可以通过自增爆破`id`实现认证绕过。如果结合一定实际数据资料,很容易构造密码字典进行爆破。
尝试2:路径穿越尝试(失败)
认证后在"新增服务器"功能中,发现有一个证书模块,通过`select file`界面点击刷新会列出目录文件:
数据包如下:
尝试路径穿越:
分析代码,对应的路由映射如下:
主要由位于`Filemanger`中的`getfolder`函数处理:
最终会进入`list_filesystem`函数:
函数`check_access_permission`如下:
`check_access_permission`函数会将`path`参数值与资源目录合并,并使用`os.path.abspath`函数获取真实路径,最终被还原的真实路径必须要包含资源路径,这样无论我们修改任何跨目录格式最终都不会和原有资源路径匹配造成异常抛出:
所以这里无法进行路径穿越。
尝试3:路径穿越尝试(成功)
发现`select file`界面还有一个文件上传的功能:
选择一个文件上传,发现回显的包中的路径中包含了用户名信息:
定位`add`函数:
查看`self.dir`的来源:
程序中使用`os.path.join`将存储目录与`username`进行拼接生成新的目录。我们知道`os.path.join`函数存在一个逻辑问题:只要最后一个参数为`/`开头就会忽略之前所有参数然后返回路径:
这样可以尝试创建一个新用户(需要管理员权限,也就是`id=1`),`username`改为`/`,既可以遍历到根目录,又可以通过`check_access_permission`的检查。分析添加用户操作:
发现前端是无法编辑用户名的,发送数据包如下:
传递的参数中没有用户名的相关信息,定位后端处理代码:
函数`create_user`:
如果我们传入`username`参数,同时确保`auth_source`的值不是`internal`,那么就给`usr`变量加入`username`。回到`create`函数:
可以控制`username`变量并写入数据库:
现在我们登出,并用刚才注册的新用户登录系统,回到证书模块的`select file`界面:
实现了任意文件遍历。
尝试4:任意文件下载
看下`Filemanager`类的定义:
相当于通过`mode=getfolder`实现了目录遍历,可以尝试进一步实现文件下载。先关注下上面请求中的URL:
分析后发现这个数字来源于点击的时候发送的请求:
分析代码:
函数`create_new_transaction`:
根据`dialog_type`的取值赋予不同的操作权限,关注下`storage_dialog`,具有`download`权限。所以改下发送的数据包:
尝试5:sqlite反序列化漏洞
pgAdmin采用sqlite数据库存储认证信息:
数据库路径为`/var/lib/pgadmin/pgadmin4.db`,实际部署环境中可利用上面的目录遍历来寻找数据库路径。
通过上面的分析,具备下载`pgadmin4.db`的条件,也可以上传`pgadmin4.db`。那么可以考虑在读写数据库代码中寻找一个反序列化操作的点,然后下载`pgadmin4.db`,修改对应数据表字段值,然后上传恶意`pgadmin4.db`并触发数据库读写操作,这样可以达到RCE的目的。
首先找一个存在反序列化过程的数据库操作点,通过搜索分析(pycharm搜索有点问题,可能漏掉一些点,建议用sublime):
先看下函数`_retrieve_process`的调用情况:
上面的请求就可以触发反序列化操作。下载`/var/lib/pgadmin/pgadmin4.db`:
`process`数据表定义:
手动添加一条记录:
构造一个反弹shell的payload:
上传文件:
访问如下接口触发RCE:
执行`payload.py`需要注意编译环境(windows和Linux操作是不一样的),否则可能异常:
主要原因是由于windows和Linux生成的序列化数据头不一样,从而导致异常。
总结
上面分析了多个漏洞或缺陷,主要包括:
- 认证机制缺陷:这个主要是Flask_security本身的问题,导致无需知道注册邮箱地址,完成认证信息爆破
- 路径穿越漏洞:充分利用python语言一些特性,比如os.path.join在路径拼接的特性,引发了路径穿越的风险
- 反序列化漏洞:sqlite数据库是文件型数据库,通常可考虑替换配合反序列化漏洞出发操作
组合起来构造了一条完整RCE漏洞链条,整个过程比较绕,但是认真分析下来感觉挺有意思。
