前言 在本篇文章中我将分享如何在
PostgreSQL
堆叠注入场景中通过
CREATE FUNCTION
关键字来实现命令执行的思路。 简要信息如下:
- CVE:N/A
- CVSS:4.1 (AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:L)
- 适用环境:Windows、Linux、Unix
在新版的
PostgreSQL
中:
- 12.3
- 11.8
- 10.13
- 9.6.18
- 9.5.22
其数据库超管用户被限制只允许从默认目录读取后缀为
.dll
或
.so
动态库文件,举例如下:
- Windows:C:\Program Files\PostgreSQL\11\lib
- Unix/Linux:/var/lib/postgresql/11/lib
此外,默认情况下
NETWORK_SERVICE
与
postgres
这两个系统用户对该目录均无写入权限。但是经过鉴权的数据库超管用户可以通过调用
lo_import
函数将文件写入
pg_largeobject
系统表,再更新对应的数据内容将原本的内容替换为我们的恶意代码(通常是反弹个shell),随后通过
lo_export
函数转储数据至动态库目录,最终生成我们的恶意动态库文件。
CREATE FUNCTION
的另一个骚操作就是可以接收指定目录来遍历其动态库中的相关函数。因此只要已鉴权用户可以将动态库文件写入对应的存储目录,然后通过
CREATE FUNCTION
的目录遍历特性来加载动态库文件就可以实现命令执行。 利用流程
1. 通过 lo_import
获得一个 OID
lo_import
OID
pg_largeobject
系统表保存了那些标记为
large object(大对象)
的数据。每个
大对象
都会关联其被创建成功时分配的
OID
标志。此后
大对象
都分解成足够小的数据块并关联
pageno
字段来存储
pg_largeobject
里。每页的数据定义为
LOBLKSIZE(目前是BLCKSZ/4或者通常是 2K 字节)
。
该过程需使用
lo_import
函数,举例如下:
在Windwos场景下这里我们还可以使用
UNC路径
,如果使用该项技术则可跳过3.3,但我希望兼容Unix/Windows平台,所有没有使用。
2. 基于 OID
来进行数据替换
OID
现在,我们基于
OID
的值来替换
pg_largeobject
表中的数据,将其对应内容替换为我们的恶意代码。这些
恶意代码
最终需要基于目标数据库的完整版本来进行编译还要匹配对应的系统架构。对于超过2048字节大小的文件,
pg_largeobject
表需使用
pageno
字段来将文件分割成大小为2048字节大小的数据块。分割示例如下:
通过使用PostgreSQL中的
object identifier types
,可以跳过第1阶段(并且仅对第2阶段执行单个语句执行),但是我没有时间确认这一点。
3. 使用 lo_export
函数生成恶意动态库
lo_export
现在我们可以通过
lo_export
来转储之前变相导入的数据来生恶意的动态库文件。不过这一步不能指定目录,因为这样做会触发
PostgreSQL
内置的安全检查,而且就算能绕过该检查,
NETWORK_SERVICE
(Unix/Linux场景下为
postgres
)帐户也存在路径限制,搞不定搞不定。
4. 基于恶意动态库文件创建函数
我在以往的研究中提到过,可以使用绝对路径(包括
UNC
)来加载基于
Postgresql 9.x
的扩展从而执行系统命令。
@zerosum0x0
师傅也有相关的 操作笔记 。不过那个时候对系统用户的文件操作权限并没有那么多的限制。
如今几年过去了,PostgreSQL官方决定禁用
CREATE FUNCTION
时使用绝对路径导致现在这种技术已经失效了。不过现在我们可以很方便地从对应的默认动态库目录中遍历并加载我们转储的恶意动态库文件,举例如下:
4. 反弹shell
成功创建
connect_back
函数后,直接通过
select
指令来调用:
问题现状
ZDI团队
咨询过PostgreSQL官方对该问题的意见,但无后文,后来我得知官方并不打算修复此问题,因为官方认为该问题属于正常系统功能而不是漏洞。 造个轮子 代码如下,将生成poc.sql文件,以超管用户在数据库上逐步执行,或将poc.sql中的sql命令逐个通过注入点执行:
跑一下展示下效果:
译文声明
译文仅供参考,具体内容表达以及含义原文为准。
戳“阅读原文”查看更多内容