PostgreSQL


After gaining the initial foothold, there’s been many sources that suggested the presence of PostgreSQL running in the target system.

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 | {}
  1. Role Name: pg_write_server_files
  2. Attributes: The role cannot be used for login (Cannot login).
  3. 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