Fix critical privilege escalation via function/operator overloading and search_path hijacking - #29
Open
mdisec wants to merge 2 commits into
Open
Conversation
PostgreSQL replaces the caller's search_path while running an extension script, setting it to the extension's target schema. For a relocatable extension installed the usual way that schema is public, so any object a low-privileged user can create there is visible to the script, which runs as the superuser performing CREATE EXTENSION or ALTER EXTENSION. pg_catalog is searched implicitly ahead of the rest of the path, so a planted function with a signature identical to a catalog one loses. An exact-typed overload competing against a polymorphic catalog function does not: function resolution prefers the exact match before it ever considers schema order. A user with CREATE on public can therefore define public.unnest(text[]) and have it called in place of pg_catalog.unnest(anyarray), executing an arbitrary body as superuser while the install completes without error. ip4r--2.4.sql was exploitable on any install against PostgreSQL 11 or later via the UNNEST(ARRAY[...]) in the operator family fixups. The 2.2--2.4 update script was exploitable through UNNEST over its oid[] and text[] locals, and through the polymorphic array operators || and <>. The 2.0--2.1 and 2.1--2.2 scripts were exploitable through array_to_string over an oid[]. Qualify the function calls, replace the two vulnerable operators with OPERATOR(pg_catalog.<>) and pg_catalog.array_append(), and qualify the catalog table references while here. This completes the sweep started in b07bf1d, which qualified the format() calls only. Left unqualified references such as current_setting() and pg_get_indexdef() are not hijackable, since their catalog signatures are exact and pg_catalog wins ties, but they are qualified too so the scripts can be audited by inspection rather than by case analysis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit qualified function calls and the two polymorphic array operators, but missed a second instance of the same hazard. Where a catalog column of type oid is compared against a ::regclass or ::regtype constant, pg_catalog has no operator for that argument pair, so a planted public.=(oid,regclass) is the only exact match and wins outright. Unlike the unknown-literal comparisons elsewhere in these scripts -- name = 'x', "char" <> 'i' and so on, where the catalog's own operator takes the tie -- there is nothing for it to tie against. The reachable case is the dependency scan in ip4r--2.2--2.4.sql. With a dependent hash index present, which is the situation the whole update script exists to handle, a planted =(oid,regclass) is evaluated and its body runs as the superuser performing ALTER EXTENSION. Confirmed by escalating a low-privileged role to superuser on 18.4. Use OPERATOR(pg_catalog.=) and OPERATOR(pg_catalog.<>) there. The IN (...::regprocedure) lists are not capturable -- an IN list of two or more elements becomes = ANY over an array and resolves to oid = oid -- but that depends on subtle resolution behaviour, so compare oid to oid explicitly and remove the question. The row comparison against ::regtype in ip4r--2.0--2.1.sql is likewise not an escalation route, because a planted operator outside a btree family makes the row comparison fail to resolve, but that is an unauthenticated denial of service on the update, so cast there as well. Also restore the continuation-line alignment disturbed by the previous commit, where qualifying a call shifted the opening element right but left the following lines behind. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hey,
I have found this vulnerability during a security research for a managed-Postgres provider and I actually successfully exploited this to escalate priv to rolsuper.
The bug is like if an untrusted user can create objects in a schema, they can plant exact-typed function or operator overloads, such as
unnest(text[])or=(oid, regclass). Later PostgreSQL may select these instead of thepg_catalogimplementations, allowing attacker-controlled SQL to execute as the superuser installing or upgradingip4rextension.I used claude to prepare a fix for this bug, please feel free to make changes on the PR and do necessary changed you want.