Beyond Escaping - SQL Injection Lessons from the Treasury Breach

Beyond Escaping - SQL Injection Lessons from the Treasury Breach

On December 30, 2024, the U.S. Treasury Department disclosed a breach by Chinese state-sponsored hackers from the APT group. They hit workstations, including Janet Yellen’s, accessing fewer than 50 files on her device. The attack didn’t come through Treasury’s code—it exploited BeyondTrust , their Privileged Access Management (PAM) tool for remote support. A stolen API key let them escalate privileges.

But the real entry point?

Two vulnerabilities in BeyondTrust’s Remote Support SaaS platform:

  • CVE-2024-12356 (CVSS 9.8): A critical command injection vulnerability
  • CVE-2024-12686 (Medium severity): Another command injection flaw

Both let unauthenticated attackers run OS commands remotely.

BeyondTrust’s post-mortem, with help from Rapid7 , revealed something unexpected: a bug in their database setup that had been sitting there for a while. It wasn’t just a random glitch—it exposed a fundamental flaw in Postgres, one of the most widely used databases out there. For those of us who’ve been in the database trenches, this hit close to home because it challenged a core assumption we’ve leaned on for ages:

“Always escape your inputs, and you’ll be safe from SQL injection.”

Turns out, even with proper escaping, there was still a way in.

We all know the drill: take user input, escape special characters like quotes, and you’re golden.

Consider a simple query:

 SELECT * FROM users WHERE username = 'admin' AND password = 'I'm Batman';.        

But if an attacker slips in something like:

I'm Batman' OR 1=1; --        

, that single quote breaks out of the password check, and since 1=1 is always true, they’re logged in as admin without the real password. That’s why we escape inputs—to neutralize those special characters and make them safe. BeyondTrust was doing that, stripping out dangerous characters to harden their queries.

So how did the attackers still get through?

The answer lies in two sneaky bytes

0xC0 and 0x27

To understand this, we need to dig into Postgres’s source code—bear with me, it’s worth it

BeyondTrust used PQescapeString, a standard function to safely escape user input. You pass your string through it, and it handles the heavy lifting. But under the hood, it calls into Postgres’s native functions, and that’s where things get hairy. One key function, pg_utf_mblen, determines how many bytes make up a character in UTF-8 encoding. Think about it: a letter like A is one byte, but an emoji or non-English character might take multiple bytes. Postgres needs to handle this correctly when escaping strings.

Here’s the catch:

  • If the first byte is 0xC0, Postgres assumes it’s the start of a two-byte UTF-8 sequence and sets the length to 2. That length gets passed to another function that copies bytes from the source to the target, trusting pg_utf_mblen’s output. But it doesn’t validate if those bytes are actually a legit UTF-8 character
  • So, if an attacker sets the second byte to 0x27—a single quote—Postgres sees 0xC0 and thinks, “Cool, two-byte sequence,” then copies both bytes, including that unescaped single quote, straight into the query. Boom—SQL injection, even with escaping in place.

In BeyondTrust’s case, their Remote Support software had a component that took user input and fed it to a PHP script. The script used PQescapeString to sanitize it, but attackers tricked it with those two bytes. The resulting unsafe string got passed to psql, Postgres’s command-line tool, which has a feature to execute system commands. Game over! They turned a database flaw into a full-on breach, pivoting from BeyondTrust’s systems into Treasury’s infrastructure.

BeyondTrust patched their cloud environments by December 14, 2024, and announced the vulnerabilities two days later, with fixes rolled out to customers. The true scope came into focus in January 2025, when Rapid7’s analysis tied it to that Postgres bug—a flaw that could’ve affected millions of systems worldwide. BeyondTrust’s fix was straightforward: restrict input to letters and numbers, no special characters or funky bytes. Postgres also released patches for supported versions (if you’re on 13 or higher).

But this wasn’t just a one-off—it was a supply chain attack. Treasury didn’t mess up their own code; they trusted a third-party tool with a weak spot.

So, why does this matter?

Because escaping inputs isn’t just a checkbox-it’s a mindset. Parameterized queries are our best defense;

Example:

// UNSAFE: String concatenation
String query = "SELECT * FROM users WHERE username = '" + username + "'";

// SAFE: Parameterized query
PreparedStatement stmt = connection.prepareStatement("SELECT * FROM users WHERE username = ?");
stmt.setString(1, username)        

They block injection cold.

Even so, this Postgres flaw shows that our strongest assumptions can have cracks. In the Treasury case, a single API key and a database bug turned a PAM tool into a backdoor. For us, skipping best practices like proper sanitization—or assuming the tools we use are bulletproof—could make us the weak link in someone else’s chain.


Here’s the kicker: this isn’t new. A 2006 Postgres forum thread (link) flagged the same UTF-8 escaping issue with PQescapeString. Invalid sequences could slip through—19 years later, it hit the Treasury.

Ever seen escaping fall apart? Got a trick to stop SQL injection? This one’s stuck with me—security hinges on what we don’t assume.

Resources:



To view or add a comment, sign in

Others also viewed

Explore topics