Correct me if I'm wrong, but those all look like sql exploits. Don't those already require some sort of authenticated remote access. They are security issues, but calling them remote code exploitation make them seem more dangerous than they are.
These seem more like privilege escalation, you have access through a database account and use that to gain full access to the whole server. So an issue for multi-tenant databases mostly.
You can do authentication and authorization inside sql. For example you could have something like
CREATE ROLE initial LOGIN NOINHERIT NOCREATEDB NOCREATEROLE NOSUPERUSER;
And then you run SQL to validate any credentials and then check the database for any permissions, ending with
SET LOCAL ROLE someuser;
It is uncommon but some people appreciate the simplicity of making the application layer do as little work as possible while pushing work into the database. It’s the logical conclusion after years of telling developers about the N+1 query problem.
As an interesting thought experiment, if you do something like this and give your end users a full sql connection almost by definition you can't have a sql injection vulnerability, the database itself limits what access the clients have.
Now as to if this is actually a good idea and the database internal security is robust enough to handle full access, Well I have my doubts, It is a complicated poorly tested interface and results as disclosed by the article in question are probably not surprising.
I do run a toy application, where the experiment was to see if I could push all authentication and access control into the database, It is actually sort of neat not needing an auth layer in the application, but attacks like this were half expected.
Why use SQL for authentication and authorization? I understand that the point of SQL is so that the query optimizer can create an efficient execution plan, but there is no need for that when it comes to configuring auth, right?
I think at my day job it's used so that a bunch of things can connect to the production database to siphon off data for BI/do ancillary tasks (owned by other teams) without messing anything up, while the main business application that actually writes to the database has the ability to do pretty much everything.
ppl host open Postgres instances, one I can think of is https://postpass.geofabrik.de/ where you can query OpenStreetMap data in a Postgres instance exposed over plain HTTP API
So it's a little worse than a "it rather involved being on the other side of the airtight hatchway" "bug"; instead it's a "the call is coming from inside the house" bug.
Long, meandering article, mixed human/AI writing probably, but the exploit is neat: Create a prepared statement on a table, change the table, then run the prepared statement (which should have been updated but wasn't).
I would say ACE, there is code executing out of the proscribed execution environment, (the rdbms). but it's not remote(needs existing authenticated access to the rdbms), so going with Arbitrary Code Execution.
Isn't that only true if "millions of businesses" choose to hang their PG port out on the Internet with "allow 0/0"? In the spirit of being constructive, https://www.shodan.io/search?query=postgresql shows 516,000 which is definitely not none but pretty far from millions and based on skimming the results pretty far from the businesses part, too
I would consider it a more useful lateral movement vector inside an existing org than anything else. Lots and lots of old postgress with weak acls inside networks.
So apparently, we got an RCE in every layer of the entire LAMP stack in like a month? Yes, M is not Postgres here, but I guess MySQL/MariaDB will follow soon.
The age isn't the surprising part. What's interesting is that the bug lived in a code path
that needs specific privileges or an extension to reach, which is exactly the kind of path
fuzzing harnesses skip because they target the parser and the wire protocol. Most of these
"decades-old" findings sit in reachable-but-unfuzzed middle layers, not in the hot paths.
Wow, in my mind this is beyond “code smell” and into code reek!
According to the article, Postgres has an internal Datum that might be a pointer or not a pointer. Okay, this is already a bit risky.
Datum comes along with a small-integer type id. That id is stored separately, presumably for efficiency? Maybe you have lots of Datums and one id and you do this for efficiency. Okay, as long as there is a clear story for why the id stays correctly bound to the datum. The “internal” thing where a bogus AST will cause type confusion makes me think the story here isn’t great.
But then I learn that the type id is something that the code looks up in a system table in the database. I’m speechless. This is the kind of thing that can’t be represented in any safe language because the indication of whether a value is a pointer isn’t even in application memory.
Eww. Maybe the AIs can exhaustively enumerate every code path that might get it wrong and rule them all out in a particular release. Maybe there’s even a Lean program that can verify it. But this is still the kind of design that is wrong by construction and is only okay if every single detail avoids the giant foot-cannon.
> This is the kind of thing that can’t be represented in any safe language because the indication of whether a value is a pointer isn’t even in application memory.
Welcome to the wonderful world of high performance low level optimization.
Storing type ids everywhere would double the size small fields and quadruple tiny ones like booleans. It’s also not necessary for most persisted data as you know the columns for a table.
Smaller representation makes reading and writing faster, and it also makes more data fit in memory.
Great. So you store a type id separately. But you make that typeid define how to safely read (maybe not interpret, but still read) the data without needing to cross-reference to anything else.
I’ve written plenty of high performance code. You can have safety and high performance at the same timetime.
> Great. So you store a type id separately. But you make that typeid define how to safely read (maybe not interpret, but still read) the data without needing to cross-reference to anything else.
Correct me if I'm wrong, but those all look like sql exploits. Don't those already require some sort of authenticated remote access. They are security issues, but calling them remote code exploitation make them seem more dangerous than they are.
These seem more like privilege escalation, you have access through a database account and use that to gain full access to the whole server. So an issue for multi-tenant databases mostly.
You can do authentication and authorization inside sql. For example you could have something like
And then you run SQL to validate any credentials and then check the database for any permissions, ending with It is uncommon but some people appreciate the simplicity of making the application layer do as little work as possible while pushing work into the database. It’s the logical conclusion after years of telling developers about the N+1 query problem.As an interesting thought experiment, if you do something like this and give your end users a full sql connection almost by definition you can't have a sql injection vulnerability, the database itself limits what access the clients have.
Now as to if this is actually a good idea and the database internal security is robust enough to handle full access, Well I have my doubts, It is a complicated poorly tested interface and results as disclosed by the article in question are probably not surprising.
I do run a toy application, where the experiment was to see if I could push all authentication and access control into the database, It is actually sort of neat not needing an auth layer in the application, but attacks like this were half expected.
see also: the schemaverse. A multiplayer space game where the game engine is postgres. https://github.com/Abstrct/Schemaverse
Why use SQL for authentication and authorization? I understand that the point of SQL is so that the query optimizer can create an efficient execution plan, but there is no need for that when it comes to configuring auth, right?
I think at my day job it's used so that a bunch of things can connect to the production database to siphon off data for BI/do ancillary tasks (owned by other teams) without messing anything up, while the main business application that actually writes to the database has the ability to do pretty much everything.
It can severely simplify a lot of patterns like row level security and other things inside the database that need tigher access controls.
that requires additional privelages plus login.. I agree with the pp, sensationalism
who's pp?
priginal poster.
What's that?
It's like original o[y]ster, but with a shift left.
This is a remote code execution attack - but authenticated, rather than unauthenticated.
Remote does not mean unauthenticated, it means "not local" - as in, over the network.
ppl host open Postgres instances, one I can think of is https://postpass.geofabrik.de/ where you can query OpenStreetMap data in a Postgres instance exposed over plain HTTP API
Wow, that is... I would have never believed in such a thing.
It's RCE even if it's not pre-auth.
So it's a little worse than a "it rather involved being on the other side of the airtight hatchway" "bug"; instead it's a "the call is coming from inside the house" bug.
Long, meandering article, mixed human/AI writing probably, but the exploit is neat: Create a prepared statement on a table, change the table, then run the prepared statement (which should have been updated but wasn't).
I would call this more of a privilege escalation attack than RCE.
I would say ACE, there is code executing out of the proscribed execution environment, (the rdbms). but it's not remote(needs existing authenticated access to the rdbms), so going with Arbitrary Code Execution.
And does that code run on the RDBMS client, or the RDBMS server? Is the code provided by the RDBMS client, or the RDBMS server?
Amazing write-up. A great look at DB internals and your thought process.
Thanks for actually sharing your path to the zero-day. The context and story helps
Is this exploitable? If it is, this is very serious and if this is an unpatched disclosure millions of Businesses around the world will be affected.
Yes, once you have logged in on a primary.
Isn't that only true if "millions of businesses" choose to hang their PG port out on the Internet with "allow 0/0"? In the spirit of being constructive, https://www.shodan.io/search?query=postgresql shows 516,000 which is definitely not none but pretty far from millions and based on skimming the results pretty far from the businesses part, too
I would consider it a more useful lateral movement vector inside an existing org than anything else. Lots and lots of old postgress with weak acls inside networks.
I've had people tell me that they trust postres RLS as their security boundary and that it's fine to expose.
Eh, any sensible one would have DB only accessible to app that uses it
So apparently, we got an RCE in every layer of the entire LAMP stack in like a month? Yes, M is not Postgres here, but I guess MySQL/MariaDB will follow soon.
This comment is how I found out about Apache's RCE.
haha brilliant
It's L and A and M and P, but the letter common to all of them is C. The bills are coming due.
The age isn't the surprising part. What's interesting is that the bug lived in a code path that needs specific privileges or an extension to reach, which is exactly the kind of path fuzzing harnesses skip because they target the parser and the wire protocol. Most of these "decades-old" findings sit in reachable-but-unfuzzed middle layers, not in the hot paths.
this bug is reachable from a lowest privileged roles. not a superuser.
Wow, in my mind this is beyond “code smell” and into code reek!
According to the article, Postgres has an internal Datum that might be a pointer or not a pointer. Okay, this is already a bit risky.
Datum comes along with a small-integer type id. That id is stored separately, presumably for efficiency? Maybe you have lots of Datums and one id and you do this for efficiency. Okay, as long as there is a clear story for why the id stays correctly bound to the datum. The “internal” thing where a bogus AST will cause type confusion makes me think the story here isn’t great.
But then I learn that the type id is something that the code looks up in a system table in the database. I’m speechless. This is the kind of thing that can’t be represented in any safe language because the indication of whether a value is a pointer isn’t even in application memory.
Eww. Maybe the AIs can exhaustively enumerate every code path that might get it wrong and rule them all out in a particular release. Maybe there’s even a Lean program that can verify it. But this is still the kind of design that is wrong by construction and is only okay if every single detail avoids the giant foot-cannon.
its an abstraction in the software architecture
> This is the kind of thing that can’t be represented in any safe language because the indication of whether a value is a pointer isn’t even in application memory.
Welcome to the wonderful world of high performance low level optimization.
Storing type ids everywhere would double the size small fields and quadruple tiny ones like booleans. It’s also not necessary for most persisted data as you know the columns for a table.
Smaller representation makes reading and writing faster, and it also makes more data fit in memory.
Great. So you store a type id separately. But you make that typeid define how to safely read (maybe not interpret, but still read) the data without needing to cross-reference to anything else.
I’ve written plenty of high performance code. You can have safety and high performance at the same timetime.
> Great. So you store a type id separately. But you make that typeid define how to safely read (maybe not interpret, but still read) the data without needing to cross-reference to anything else.
Yes, that's how it works in Postgres