Announcement

How To Protect PostgreSQL Against Automated Attacks

Remote access is one of the common and more easily rectified situations that can lead to the exploit of a PostgreSQL database. However, many exploits are automated and specifically designed to look for common errors in configuration. These programs scan networks to discover servers, independent of the nature of the content.

This article will show you how to mitigate the specific risk posed by allowing remote connections to database which is an important step to look in first, but servers can be compromised in other ways too.

For this particular guide, we'll use two Ubuntu machines, one for the database host and one as the client that will be connecting to the host remotely. Each one should have a sudo user and the firewall enabled.Ubuntu 16.04 PostgreSQL Database Machine:
If you haven't install PostgreSQL yet, you can do so with the following commands:sudo apt-get updatesudo apt-get install postgresql postgresql-contrib

Ubuntu 16.04 Client Machine:

In order to demonstrate and test allowing remote connections, we'll use the PostgreSQL client, psql. To install it, use the following commands:

sudo apt-get update

sudo apt-get install postgresql-client

When you are done with prerequisites above, you're ready to move to next step.

Understanding the Default Configuration

When PostgreSQL is installed from the Ubuntu packages, by default it is restricted to listening on localhost. This default can be changed by overriding the listen_addresses in the postgresql.conf file, but the default prevents the server from automatically listening on a public interface.

In addition, the pg_hba.conf file only allows connections from Unix/Linux domain sockets and the local loopback address for the server, so it wouldn’t accept connections from external hosts:

replace

# Put your actual configuration here
# ----------------------------------
#
# If you want to allow non-local connections, you need to add more
# "host" records. In that case you will also need to make PostgreSQL
# listen on a non-local interface via the listen_addresses
# configuration parameter, or via the -i or -h command line switches.
# DO NOT DISABLE!
# If you change this first entry you will need to make sure that the
# database superuser can access the database using some other method.
# Noninteractive access to all databases is required during automatic
# maintenance (custom daily cronjobs, replication, and similar tasks).
#
# Database administrative login by Unix domain socket
local all postgres peer
# TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all peer
# IPv4 local connections:
host all all 127.0.0.1/32 md5
# IPv6 local connections:
host all all ::1/128 md5

These defaults meet the objective of not listening on a public interface. If we leave them intact and keep our firewall up, we're done!

If you need to connect from a remote host, we’ll cover how to override the defaults as well as the immediate steps you can take to protect the server in the next section.

Configuring Remote Connections

For a production setup and before we start working with sensitive data, ideally we'll have PostgreSQL traffic encrypted with SSL in transit, secured behind an external firewall, or protected by a virtual private network (VPN). As we work toward that, we can take the somewhat less complicated step of enabling a firewall on our database server and restricting access to the hosts that need it.

Adding a User and Database

We'll begin by adding a user and database that will allow us to test our work. To do so, we'll use the PostgreSQL client, psql, to connect as the administrative user postgres. By passing the -i option to sudo we'll run the postgres user's login shell, which ensures that we load options from the .profile or other login-specific resources. -u species the postgres user:

sudo -i -u postgres psql

Next, we'll create a user with a password. Be sure to use secure password in place of the example highlighted below:

postgres=# CREATE USER peterWITH PASSWORD 'password';

When the user is successfully created, we should receive the following output:

Output

CREATE ROLE

Note: Since PostgreSQL 8.1, ROLES and USERS are synonymous. By convention, a role that has a password is still called a USER, while a role that does not is called a ROLE, so sometimes we will see ROLE in output where we might expect to see USER.

Next, we'll create a database and grant full access to our new user. Best practices recommend that we grant users only the access that they need and only on the resources where they should have them, so depending on the use case, it may be appropriate to restrict a user's access even more.

postgres=# CREATE DATABASE peterdb OWNER peter;

When the database is created successfully, we should receive confirmation:

Output

CREATE DATABASE

Now, that we've created a user and database, we'll exit the monitor

postgres=# \q

After pressing ENTER, we'll be at the command prompt and ready to continue.

Configuring UFW

Before we start our configuration, let's verify firewall's status:

sudo ufw status

Note: If the output indicates that the firewall is inactive we can activate it with:

sudo ufw enable

Once it's enabled, rerunning the status command, sudo ufw status will show the current rules. If necessary, be sure to allow SSH.

sudo ufw allow OpenSSH

Unless we made changes to the prerequisites, the output should show that only OpenSSH is allowed:

Output

Status: active

To Action From

-- ------ ----

OpenSSH ALLOW Anywhere

OpenSSH (v6) ALLOW Anywhere (v6)

Now that we've checked the firewall status, we will allow access to the PostgreSQL port and restrict it to the host or hosts we want to allow.

The command below will add the rule for the PostgreSQL default port, which is 5432. If you've changed that port, be sure to update it in the command below. Make sure that you've used the IP address of the server that needs access. If need be, re-run this command to add each client IP address that needs access:

sudo ufw allow from client_ip_address to any port 5432

To double-check the rule, we can run ufw status again:

sudo ufw status

Output

To Action From

-- ------ ----

OpenSSH ALLOW Anywhere

5432 ALLOW client_ip_address

OpenSSH (v6) ALLOW Anywhere (v6)

With this firewall rule in place, we'll now configure PostgreSQL to listen on its public IP address. This requires a combination of two settings, an entry for the connecting host in pg_hba.conf and configuration of the listen_addresses in postgresql.conf.

Configuring the Allowed Hosts

We'll start by adding the host entry in pg_hba.conf. If you have a different version of PostgreSQL installed, be sure to substitute it in the path below:

sudo nano /etc/postgresql/9.5/main/pg_hba.conf

We'll place the host lines under the comment block that describes how to allow non-local connections. We'll also include a line with the public address of the database server so we can quickly test that our firewall is configured correctly. Be sure to substitute the hostname or IP address of your machines in the example below.

Excerpt from pg_hba.conf

# If you want to allow non-local connections, you need to add more
# "host" records. In that case you will also need to make PostgreSQL
# listen on a non-local interface via the listen_addresses
# configuration parameter, or via the -i or -h command line switches.
host peterdb peterclient_ip_address/32 md5

Before we save our changes, let's focus on each of the values in this line in case you want to change some of the options:

host The first parameter, host, establishes that a TCP/IP connection will be used.

database peterdb The second column indicates which database/s the host can connect to. More than one database can be added by separating the names with commas.

user peter indicates the user that is allowed to make the connection. As with the database column, multiple users can be specified, separated by commas.

address The address specifies the client machine address or addresses and may contain a hostname, IP address range or other special key words. In the example above, we've allowed just the single IP address of our client.

auth-method Finally, the auth-method, md5 indicates a double-MD5-hashed password will be supplied for authentication. You'll need to do nothing more than supply the password that was created for the user connecting.

Once you're done, save and exit the file.

Configuring the Listening Address

Next we'll set the listen address in the postgresql.conf file:

sudo nano /etc/postgresql/9.5/main/postgresql.conf

Find the listen_addresses line and below it, define your listen addresses, being sure to substitute the hostname or IP address of your database host. You may want to double-check that you're using the public IP of the database server, not the connecting client:

Now that we've restarted the daemon, we're ready to test.Testing From Ubuntu Client Machine

Finally, let's test that we can connect from our client machine. To do this, we'll use psql with -U to specify the user, -h to specify the client's IP address, and -d to specify the database, since we've tightened our security so that the peter can only connect to a single database.

psql -U peter -h postgres_host_ip -d peterdb

If everything is configured correctly, you should receive the following prompt:

Output

Password for user peter:

Enter the password you set earlier when you added the user peter in the PostgreSQL monitor.

If you arrive at the following prompt, you're successfully connected:

[secondary_label]

peterdb=>

This confirms that we can get through the firewall and connect to the database. We'll exit now:

peterdb=# \q

Since we've confirmed our configuration, we'll finish by cleaning up.

Removing the Test Database and User

Back on the host once we've finished testing the connection, we can use the following commands to delete the database and the user as well.

sudo -i -u postgres psql

To delete the database:

postgres=# DROP DATABASE peterdb;

The action is confirmed by the following output:

Output

DROP DATABASE

To delete the user:

postgres=# DROP USER peter;

The success is confirmed by:

Output

DROP ROLE

We’ll finish our cleanup by removing the host entry for the peterdb database from pg_hba.conf file since we no longer need it:

sudo nano /etc/postgresql/9.5/main/pg_hba.conf

Line to remove from `pg_hba.conf`

host peterdb peter client_ip_address/32 md5

For the change to take effect, we’ll save and exit, then restart the database server:

sudo systemctl restart postgresl

To be sure it restarted successfully, we’ll check the status:

sudo systemctl status postgres

If we see “Active: active” we’ll know the restart succeeded.

At this point, we can move forward with configuring the application or service on the client that needs to connect remotely.

Conclusion

We've taken essential steps to prevent advertising our PostgreSQL installation by configuring the server's firewall to allow connections only from hosts that require access and by configuring PostgreSQL to accept connections only from those hosts. This mitigates the risk of certain kinds of attacks.

How To Protect PostgreSQL Against Automated Attacks
Reviewed by Mr. WhiteSays
on
January 22, 2017
Rating: 5